channel.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. package channel
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "net/url"
  7. "strconv"
  8. "strings"
  9. "time"
  10. cdm "go-common/app/interface/main/app-card/model"
  11. cardm "go-common/app/interface/main/app-card/model/card"
  12. "go-common/app/interface/main/app-card/model/card/operate"
  13. "go-common/app/interface/main/app-channel/model"
  14. "go-common/app/interface/main/app-channel/model/card"
  15. "go-common/app/interface/main/app-channel/model/channel"
  16. "go-common/app/interface/main/app-channel/model/tab"
  17. tag "go-common/app/interface/main/tag/model"
  18. locmdl "go-common/app/service/main/location/model"
  19. "go-common/library/log"
  20. "go-common/library/net/metadata"
  21. "go-common/library/sync/errgroup"
  22. "github.com/dgryski/go-farm"
  23. )
  24. const (
  25. _initRegionKey = "region_key_%d_%v"
  26. _initlanguage = "hans"
  27. _initVersion = "region_version"
  28. _regionRepeat = "r_%d_%d"
  29. _maxAtten = 10 //展示最多10个我的订阅
  30. )
  31. var (
  32. _tabList = []*channel.TabList{
  33. &channel.TabList{
  34. Name: "推荐",
  35. URI: "bilibili://pegasus/channel/feed/%d",
  36. TabID: "multiple",
  37. },
  38. &channel.TabList{
  39. Name: "话题",
  40. URI: "bilibili://following/topic_detail?id=%d&name=%s",
  41. TabID: "topic",
  42. },
  43. }
  44. )
  45. // Tab channel tab
  46. func (s *Service) Tab(c context.Context, tid, mid int64, tname string, plat int8) (res *channel.Tab, err error) {
  47. var (
  48. t *tag.ChannelDetail
  49. )
  50. if t, err = s.tg.ChannelDetail(c, mid, tid, tname, s.isOverseas(plat)); err != nil || t == nil {
  51. log.Error("s.tag.ChannelDetail(%d, %d, %v) error(%v)", mid, tid, tname, err)
  52. return
  53. }
  54. res = &channel.Tab{}
  55. res.SimilarTagChange(t)
  56. res.TabList = s.tablist(t)
  57. return
  58. }
  59. //SubscribeAdd subscribe add
  60. func (s *Service) SubscribeAdd(c context.Context, mid, id int64, now time.Time) (err error) {
  61. if err = s.tg.SubscribeAdd(c, mid, id, now); err != nil {
  62. log.Error("s.tg.SubscribeAdd(%d,%d) error(%v)", mid, id, err)
  63. return
  64. }
  65. return
  66. }
  67. //SubscribeCancel subscribe channel
  68. func (s *Service) SubscribeCancel(c context.Context, mid, id int64, now time.Time) (err error) {
  69. if err = s.tg.SubscribeCancel(c, mid, id, now); err != nil {
  70. log.Error("s.tg.SubscribeCancel(%d,%d) error(%v)", mid, id, err)
  71. return
  72. }
  73. return
  74. }
  75. // SubscribeUpdate subscribe update
  76. func (s *Service) SubscribeUpdate(c context.Context, mid int64, ids string) (err error) {
  77. if err = s.tg.SubscribeUpdate(c, mid, ids); err != nil {
  78. log.Error("s.tg.SubscribeUpdate(%d,%s) error(%v)", mid, ids, err)
  79. return
  80. }
  81. return
  82. }
  83. // List 频道tab页
  84. func (s *Service) List(c context.Context, mid int64, plat int8, build, limit int, ver, mobiApp, device, lang string) (res *channel.List, err error) {
  85. var (
  86. rec, atten []*channel.Channel
  87. top, bottom []*channel.Region
  88. max = 3
  89. )
  90. g, _ := errgroup.WithContext(c)
  91. //获取推荐的三个频道
  92. g.Go(func() (err error) {
  93. rec, err = s.Recommend(c, mid, plat)
  94. if err != nil {
  95. log.Error("%+v", err)
  96. err = nil
  97. }
  98. return
  99. })
  100. //获取我的订阅
  101. if mid > 0 {
  102. g.Go(func() (err error) {
  103. atten, err = s.Subscribe(c, mid, limit)
  104. if err != nil {
  105. log.Error("%+v", err)
  106. err = nil
  107. }
  108. return
  109. })
  110. }
  111. //获取分区
  112. g.Go(func() (err error) {
  113. top, bottom, _, err = s.RegionList(c, plat, build, mobiApp, device, lang)
  114. if err != nil {
  115. log.Error("%+v", err)
  116. err = nil
  117. }
  118. return
  119. })
  120. g.Wait()
  121. if tl := len(rec); tl < max {
  122. if last := max - tl; len(atten) > last {
  123. rec = append(rec, atten[:last]...)
  124. } else {
  125. rec = append(rec, atten...)
  126. }
  127. } else {
  128. rec = rec[:max]
  129. }
  130. res = &channel.List{
  131. RegionTop: top,
  132. RegionBottom: bottom,
  133. }
  134. if isAudit := s.auditList(mobiApp, plat, build); !isAudit {
  135. res.RecChannel = rec
  136. res.AttenChannel = atten
  137. }
  138. res.Ver = s.hash(res)
  139. return
  140. }
  141. // Recommend 推荐
  142. func (s *Service) Recommend(c context.Context, mid int64, plat int8) (res []*channel.Channel, err error) {
  143. list, err := s.tg.Discover(c, mid, s.isOverseas(plat))
  144. if err != nil {
  145. log.Error("%+v", err)
  146. return
  147. }
  148. for _, chann := range list {
  149. item := &channel.Channel{
  150. ID: chann.ID,
  151. Name: chann.Name,
  152. Cover: chann.Cover,
  153. IsAtten: chann.Attention,
  154. Atten: chann.Sub,
  155. }
  156. res = append(res, item)
  157. }
  158. return
  159. }
  160. //Subscribe 我订阅的tag(老) standard放前面用户自定义custom放后面
  161. func (s *Service) Subscribe(c context.Context, mid int64, limit int) (res []*channel.Channel, err error) {
  162. var (
  163. tinfo []*tag.TagInfo
  164. )
  165. list, err := s.tg.Subscribe(c, mid)
  166. if err != nil {
  167. log.Error("%+v", err)
  168. return
  169. }
  170. tinfo = list.Standard
  171. tinfo = append(tinfo, list.Custom...)
  172. for _, chann := range tinfo {
  173. item := &channel.Channel{
  174. ID: chann.ID,
  175. Name: chann.Name,
  176. Cover: chann.Cover,
  177. Atten: chann.Sub,
  178. IsAtten: chann.Attention,
  179. Content: chann.Content,
  180. }
  181. res = append(res, item)
  182. }
  183. if len(res) > limit && limit > 0 {
  184. res = res[:limit]
  185. } else if len(res) == 0 {
  186. res = []*channel.Channel{}
  187. }
  188. return
  189. }
  190. // Discover 发现频道页(推荐走recommend接口,有分类的揍list接口)
  191. func (s *Service) Discover(c context.Context, id, mid int64, plat int8) (res []*channel.Channel, err error) {
  192. var (
  193. list []*tag.Channel
  194. )
  195. if id > 0 {
  196. list, err = s.tg.ListByCategory(c, id, mid, s.isOverseas(plat))
  197. if err != nil {
  198. log.Error("%+v", err)
  199. return
  200. }
  201. } else {
  202. list, err = s.tg.Recommend(c, mid, s.isOverseas(plat))
  203. if err != nil {
  204. log.Error("%+v", err)
  205. return
  206. }
  207. }
  208. if len(list) == 0 {
  209. res = []*channel.Channel{}
  210. return
  211. }
  212. for _, chann := range list {
  213. item := &channel.Channel{
  214. ID: chann.ID,
  215. Name: chann.Name,
  216. Cover: chann.Cover,
  217. Atten: chann.Sub,
  218. IsAtten: chann.Attention,
  219. Content: chann.Content,
  220. }
  221. res = append(res, item)
  222. }
  223. return
  224. }
  225. // Category 频道分类
  226. func (s *Service) Category(c context.Context, plat int8) (res []*channel.Category, err error) {
  227. category, err := s.tg.Category(c, s.isOverseas(plat))
  228. if err != nil {
  229. log.Error("%+v", err)
  230. return
  231. }
  232. res = append(res, &channel.Category{
  233. ID: 0,
  234. Name: "推荐",
  235. })
  236. for _, cat := range category {
  237. item := &channel.Category{
  238. ID: cat.ID,
  239. Name: cat.Name,
  240. }
  241. res = append(res, item)
  242. }
  243. return
  244. }
  245. // RegionList 分区信息
  246. func (s *Service) RegionList(c context.Context, plat int8, build int, mobiApp, device, lang string) (regionTop, regionBottom, regions []*channel.Region, err error) {
  247. var (
  248. hantlanguage = "hant"
  249. )
  250. if ok := model.IsOverseas(plat); ok && lang != _initlanguage && lang != hantlanguage {
  251. lang = hantlanguage
  252. } else if lang == "" {
  253. lang = _initlanguage
  254. }
  255. var (
  256. rs = s.cachelist[fmt.Sprintf(_initRegionKey, plat, lang)]
  257. // maxTop = 8
  258. ridtmp = map[string]struct{}{}
  259. pids []string
  260. auths map[string]*locmdl.Auth
  261. ip = metadata.String(c, metadata.RemoteIP)
  262. )
  263. regionTop = []*channel.Region{}
  264. regionBottom = []*channel.Region{}
  265. regions = []*channel.Region{}
  266. for _, rtmp := range rs {
  267. if rtmp.ReID != 0 { //过滤二级分区
  268. continue
  269. }
  270. if rtmp.Area != "" {
  271. pids = append(pids, rtmp.Area)
  272. }
  273. }
  274. if len(pids) > 0 {
  275. auths, _ = s.loc.AuthPIDs(c, strings.Join(pids, ","), ip)
  276. }
  277. LOOP:
  278. for _, rtmp := range rs {
  279. r := &channel.Region{}
  280. *r = *rtmp
  281. if r.ReID != 0 { //过滤二级分区
  282. continue
  283. }
  284. var tmpl, limitshow bool
  285. if limit, ok := s.limitCache[r.ID]; ok {
  286. for i, l := range s.limitCache[r.ID] {
  287. if i+1 <= len(limit)-1 {
  288. if ((l.Condition == "gt" && limit[i+1].Condition == "lt") && (l.Build < limit[i+1].Build)) ||
  289. ((l.Condition == "lt" && limit[i+1].Condition == "gt") && (l.Build > limit[i+1].Build)) {
  290. if (l.Condition == "gt" && limit[i+1].Condition == "lt") &&
  291. (build > l.Build && build < limit[i+1].Build) {
  292. break
  293. } else if (l.Condition == "lt" && limit[i+1].Condition == "gt") &&
  294. (build < l.Build && build > limit[i+1].Build) {
  295. break
  296. } else {
  297. tmpl = true
  298. continue
  299. }
  300. }
  301. }
  302. if tmpl {
  303. if i == len(limit)-1 {
  304. limitshow = true
  305. break
  306. // continue LOOP
  307. }
  308. tmpl = false
  309. continue
  310. }
  311. if model.InvalidBuild(build, l.Build, l.Condition) {
  312. limitshow = true
  313. continue
  314. // continue LOOP
  315. } else {
  316. limitshow = false
  317. break
  318. }
  319. }
  320. }
  321. if limitshow {
  322. continue LOOP
  323. }
  324. if r.RID == 65539 {
  325. if model.IsIOS(plat) {
  326. r.URI = fmt.Sprintf("%s?from=category", r.URI)
  327. } else {
  328. r.URI = fmt.Sprintf("%s?sourceFrom=541", r.URI)
  329. }
  330. }
  331. if auth, ok := auths[r.Area]; ok && auth.Play == locmdl.Forbidden {
  332. log.Warn("s.invalid area(%v) ip(%v) error(%v)", r.Area, ip, err)
  333. continue
  334. }
  335. if isAudit := s.auditRegion(mobiApp, plat, build, r.RID); isAudit {
  336. continue
  337. }
  338. config, ok := s.configCache[r.ID]
  339. if !ok {
  340. continue
  341. }
  342. key := fmt.Sprintf(_regionRepeat, r.RID, r.ReID)
  343. if _, ok := ridtmp[key]; !ok {
  344. ridtmp[key] = struct{}{}
  345. } else {
  346. continue
  347. }
  348. for _, conf := range config {
  349. if conf.ScenesID == 1 /*&& len(regionTop) < maxTop*/ {
  350. regionTop = append(regionTop, r)
  351. regions = append(regions, r)
  352. } else if conf.ScenesID == 0 {
  353. regionBottom = append(regionBottom, r)
  354. regions = append(regions, r)
  355. }
  356. }
  357. }
  358. return
  359. }
  360. func (s *Service) hash(v *channel.List) string {
  361. bs, err := json.Marshal(v)
  362. if err != nil {
  363. log.Error("json.Marshal error(%v)", err)
  364. return _initVersion
  365. }
  366. return strconv.FormatUint(farm.Hash64(bs), 10)
  367. }
  368. func (s *Service) loadRegionlist() {
  369. res, err := s.rg.AllList(context.TODO())
  370. if err != nil {
  371. log.Error("s.dao.All error(%v)", err)
  372. return
  373. }
  374. tmp := map[string][]*channel.Region{}
  375. for _, v := range res {
  376. key := fmt.Sprintf(_initRegionKey, v.Plat, v.Language)
  377. tmp[key] = append(tmp[key], v)
  378. }
  379. if len(tmp) > 0 {
  380. s.cachelist = tmp
  381. }
  382. log.Info("region list cacheproc success")
  383. limit, err := s.rg.Limit(context.TODO())
  384. if err != nil {
  385. log.Error("s.dao.limit error(%v)", err)
  386. return
  387. }
  388. s.limitCache = limit
  389. log.Info("region limit cacheproc success")
  390. config, err := s.rg.Config(context.TODO())
  391. if err != nil {
  392. log.Error("s.dao.Config error(%v)", err)
  393. return
  394. }
  395. s.configCache = config
  396. log.Info("region config cacheproc success")
  397. }
  398. // Square 频道广场页
  399. func (s *Service) Square(c context.Context, mid int64, plat int8, build int, loginEvent int32, mobiApp, device, lang, buvid string) (res *channel.Square, err error) {
  400. res = new(channel.Square)
  401. var (
  402. squ *tag.ChannelSquare
  403. regions []*channel.Region
  404. oidNum = 2
  405. )
  406. isAudit := s.auditList(mobiApp, plat, build)
  407. eg := errgroup.Group{}
  408. //获取分区
  409. eg.Go(func() (err error) {
  410. _, _, regions, err = s.RegionList(c, plat, build, mobiApp, device, lang)
  411. if err != nil {
  412. log.Error("%+v", err)
  413. err = nil
  414. }
  415. res.Region = regions
  416. return
  417. })
  418. if !isAudit {
  419. //获取推荐频道
  420. eg.Go(func() (err error) {
  421. var (
  422. oids []int64
  423. tagm = map[int64]*tag.Tag{}
  424. chanOids = map[int64][]*channel.ChanOids{}
  425. channelCards = map[int64][]*card.Card{}
  426. initCardPlatKey = "card_platkey_%d_%d"
  427. )
  428. squ, err = s.tg.Square(c, mid, s.c.SquareCount, oidNum, build, loginEvent, plat, buvid, s.isOverseas(plat))
  429. if err != nil {
  430. log.Error("%+v", err)
  431. err = nil
  432. }
  433. for _, rec := range squ.Channels {
  434. cards, ok := s.cardCache[rec.ID]
  435. if !ok {
  436. continue
  437. }
  438. LOOP:
  439. for _, c := range cards {
  440. key := fmt.Sprintf(initCardPlatKey, plat, c.ID)
  441. cardPlat, ok := s.cardPlatCache[key]
  442. if !ok {
  443. continue
  444. }
  445. if c.Type != model.GotoAv {
  446. continue
  447. }
  448. for _, l := range cardPlat {
  449. if model.InvalidBuild(build, l.Build, l.Condition) {
  450. continue LOOP
  451. }
  452. }
  453. channelCards[c.ChannelID] = append(channelCards[c.ChannelID], c)
  454. }
  455. }
  456. for channelID, recOid := range squ.Oids {
  457. oids = append(oids, recOid...)
  458. if cards, ok := channelCards[channelID]; ok {
  459. for _, c := range cards {
  460. if c.Type == model.GotoAv {
  461. chanOids[channelID] = append(chanOids[channelID], &channel.ChanOids{Oid: c.Value, FromType: _fTypeOperation})
  462. oids = append(oids, c.Value)
  463. }
  464. }
  465. }
  466. for _, tmpOid := range recOid {
  467. chanOids[channelID] = append(chanOids[channelID], &channel.ChanOids{Oid: tmpOid, FromType: _fTypeRecommend})
  468. }
  469. }
  470. am, err := s.arc.Archives(c, oids)
  471. if err != nil {
  472. return
  473. }
  474. for _, rec := range squ.Channels {
  475. var cardItem []*operate.Card
  476. tagm[rec.ID] = &tag.Tag{
  477. ID: rec.ID,
  478. Name: rec.Name,
  479. Cover: rec.Cover,
  480. Content: rec.ShortContent,
  481. Type: int8(rec.Type),
  482. State: int8(rec.State),
  483. IsAtten: int8(rec.Attention),
  484. }
  485. tagm[rec.ID].Count.Atten = int(rec.Sub)
  486. for _, oidItem := range chanOids[rec.ID] {
  487. if len(cardItem) >= 2 {
  488. break
  489. }
  490. if _, ok := am[oidItem.Oid]; !ok {
  491. continue
  492. }
  493. cardItem = append(cardItem, &operate.Card{ID: oidItem.Oid, FromType: oidItem.FromType})
  494. }
  495. if len(cardItem) < 2 {
  496. continue
  497. }
  498. var (
  499. h = cardm.Handle(plat, cdm.CardGt("channel_square"), "channel_square", cdm.ColumnSvrSingle, nil, tagm, nil, nil, nil)
  500. )
  501. if h == nil {
  502. continue
  503. }
  504. op := &operate.Card{
  505. ID: rec.ID,
  506. Items: cardItem,
  507. Plat: plat,
  508. Param: strconv.FormatInt(rec.ID, 10),
  509. }
  510. h.From(am, op)
  511. if h.Get() != nil && h.Get().Right {
  512. res.Square = append(res.Square, h)
  513. }
  514. }
  515. return
  516. })
  517. }
  518. eg.Wait()
  519. return
  520. }
  521. // Mysub 我订阅的tag(新) standard放前面用户自定义custom放后面
  522. func (s *Service) Mysub(c context.Context, mid int64, limit int) (res *channel.Mysub, err error) {
  523. var (
  524. tinfo []*tag.TagInfo
  525. subChannel []*channel.Channel
  526. )
  527. res = new(channel.Mysub)
  528. list, err := s.tg.Subscribe(c, mid)
  529. if err != nil {
  530. log.Error("%+v", err)
  531. return
  532. }
  533. tinfo = list.Standard
  534. tinfo = append(tinfo, list.Custom...)
  535. if len(tinfo) > 0 {
  536. for _, chann := range tinfo {
  537. subChannel = append(subChannel, &channel.Channel{
  538. ID: chann.ID,
  539. Name: chann.Name,
  540. Cover: chann.Cover,
  541. Atten: chann.Sub,
  542. IsAtten: chann.Attention,
  543. Content: chann.Content,
  544. })
  545. }
  546. if len(subChannel) > limit && limit > 0 {
  547. subChannel = subChannel[:limit]
  548. }
  549. }
  550. res.List = subChannel
  551. res.DisplayCount = _maxAtten
  552. return
  553. }
  554. func (s *Service) isOverseas(plat int8) (res int32) {
  555. if ok := model.IsOverseas(plat); ok {
  556. res = 1
  557. } else {
  558. res = 0
  559. }
  560. return
  561. }
  562. func (s *Service) tablist(t *tag.ChannelDetail) (res []*channel.TabList) {
  563. res = s.defaultTab(t)
  564. var (
  565. mpos []int
  566. tmpmenus = map[int]*tab.Menu{}
  567. menus = s.menuCache[t.Tag.ID]
  568. menusTabIDs = map[int64]struct{}{}
  569. )
  570. if len(menus) == 0 {
  571. return
  572. }
  573. for _, m := range menus {
  574. tmpmenus[m.Priority] = m
  575. mpos = append(mpos, m.Priority)
  576. }
  577. for _, pos := range mpos {
  578. var (
  579. tmpm *tab.Menu
  580. ok bool
  581. )
  582. if tmpm, ok = tmpmenus[pos]; !ok || pos == 0 {
  583. continue
  584. }
  585. if _, ok := menusTabIDs[tmpm.TabID]; !ok {
  586. menusTabIDs[tmpm.TabID] = struct{}{}
  587. } else {
  588. continue
  589. }
  590. tl := &channel.TabList{}
  591. tl.TabListChange(tmpm)
  592. if len(res) < pos {
  593. res = append(res, tl)
  594. continue
  595. }
  596. res = append(res[:pos-1], append([]*channel.TabList{tl}, res[pos-1:]...)...)
  597. }
  598. return
  599. }
  600. func (s *Service) defaultTab(t *tag.ChannelDetail) (res []*channel.TabList) {
  601. for _, tmp := range _tabList {
  602. r := &channel.TabList{}
  603. *r = *tmp
  604. switch tmp.TabID {
  605. case "multiple":
  606. r.URI = fmt.Sprintf(r.URI, t.Tag.ID)
  607. case "topic":
  608. r.URI = fmt.Sprintf(r.URI, t.Tag.ID, url.QueryEscape(t.Tag.Name))
  609. }
  610. res = append(res, r)
  611. }
  612. return
  613. }