push.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. package dao
  2. import (
  3. "context"
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. "sync/atomic"
  8. "time"
  9. "unicode"
  10. "go-common/app/service/main/push/dao/apns2"
  11. "go-common/app/service/main/push/dao/fcm"
  12. "go-common/app/service/main/push/dao/huawei"
  13. "go-common/app/service/main/push/dao/jpush"
  14. "go-common/app/service/main/push/dao/mi"
  15. "go-common/app/service/main/push/dao/oppo"
  16. "go-common/app/service/main/push/model"
  17. "go-common/library/log"
  18. )
  19. func fmtRoundIndex(appid int64, platform int) string {
  20. return fmt.Sprintf("%d_%d", appid, platform)
  21. }
  22. func (d *Dao) roundIndex(appid int64, platform int) (int, error) {
  23. i := fmtRoundIndex(appid, platform)
  24. l := d.clientsLen[i]
  25. if l == 0 {
  26. log.Error("no client app(%d) platform(%d)", appid, platform)
  27. PromError("push:no client")
  28. return 0, errNoClinets
  29. }
  30. n := atomic.AddUint32(d.clientsIndex[i], 1)
  31. if n%uint32(l) == 0 { // 把第一个client预留出来,做一些其它类型请求工作
  32. n = atomic.AddUint32(d.clientsIndex[i], 1)
  33. }
  34. return int(n % uint32(l)), nil
  35. }
  36. func logPushError(task string, platform int, tokens []string) {
  37. for _, t := range tokens {
  38. log.Error("push error, task(%s) platfrom(%d) token(%s)", task, platform, t)
  39. }
  40. }
  41. func buildAPNS(info *model.PushInfo, item *model.PushItem) *apns2.Payload {
  42. var aps apns2.Aps
  43. if info.PassThrough == model.SwitchOn {
  44. aps = apns2.Aps{
  45. ContentAvailable: 1, // 必带字段,让程序处于后台时也可以获取到推送内容
  46. }
  47. } else {
  48. aps = apns2.Aps{
  49. Alert: apns2.Alert{
  50. Title: info.Title,
  51. Body: info.Summary,
  52. },
  53. Badge: 0,
  54. MutableContent: 1,
  55. }
  56. if info.Sound == model.SwitchOn {
  57. aps.Sound = "default" // 默认提示音
  58. }
  59. }
  60. scheme := model.Scheme(info.LinkType, info.LinkValue, item.Platform, item.Build)
  61. return &apns2.Payload{Aps: aps, URL: scheme, TaskID: info.TaskID, Token: item.Token, Image: info.ImageURL}
  62. }
  63. // PushIPhone .
  64. func (d *Dao) PushIPhone(c context.Context, info *model.PushInfo, item *model.PushItem) (res *model.HTTPResponse, err error) {
  65. var (
  66. index int
  67. response *apns2.Response
  68. )
  69. if index, err = d.roundIndex(info.APPID, item.Platform); err != nil {
  70. return
  71. }
  72. if response, err = d.clientsIPhone[info.APPID][index].Push(item.Token, buildAPNS(info, item), int64(info.ExpireTime)); err != nil {
  73. log.Error("push iPhone task(%s) mid(%d) token(%s) error", info.TaskID, item.Mid, item.Token)
  74. PromError("push: 推送iPhone")
  75. return
  76. }
  77. if response == nil {
  78. return
  79. }
  80. res = &model.HTTPResponse{Code: response.StatusCode, Msg: response.Reason}
  81. log.Info("push iPhone task(%s) mid(%d) token(%s) success", info.TaskID, item.Mid, item.Token)
  82. return
  83. }
  84. // PushIPad .
  85. func (d *Dao) PushIPad(c context.Context, info *model.PushInfo, item *model.PushItem) (res *model.HTTPResponse, err error) {
  86. var (
  87. index int
  88. response *apns2.Response
  89. )
  90. if index, err = d.roundIndex(info.APPID, item.Platform); err != nil {
  91. return
  92. }
  93. if response, err = d.clientsIPad[info.APPID][index].Push(item.Token, buildAPNS(info, item), int64(info.ExpireTime)); err != nil {
  94. log.Error("push iPad task(%s) mid(%d) token(%s) error", info.TaskID, item.Mid, item.Token)
  95. PromError("push:推送iPad")
  96. return
  97. }
  98. if response == nil {
  99. return
  100. }
  101. res = &model.HTTPResponse{Code: response.StatusCode, Msg: response.Reason}
  102. log.Info("push iPad task(%s) mid(%d) token(%s) success", info.TaskID, item.Mid, item.Token)
  103. return
  104. }
  105. // PushMi .
  106. func (d *Dao) PushMi(c context.Context, info *model.PushInfo, scheme, tokens string) (res *model.HTTPResponse, err error) {
  107. res = &model.HTTPResponse{}
  108. var index int
  109. if index, err = d.roundIndex(info.APPID, model.PlatformXiaomi); err != nil {
  110. return
  111. }
  112. passThrough := mi.NotPassThrough
  113. if info.PassThrough == model.SwitchOn {
  114. passThrough = mi.PassThrough
  115. }
  116. xmm := &mi.XMMessage{
  117. Payload: scheme,
  118. RestrictedPackageName: d.clientsMi[info.APPID][0].Package,
  119. PassThrough: passThrough,
  120. Title: info.Title,
  121. Description: info.Summary,
  122. NotifyType: mi.NotifyTypeDefaultNone,
  123. TaskID: info.TaskID,
  124. }
  125. xmm.SetRegID(tokens)
  126. xmm.SetNotifyID(info.TaskID)
  127. xmm.SetTimeToLive(int64(info.ExpireTime))
  128. xmm.SetCallbackParam(strconv.FormatInt(info.APPID, 10))
  129. if info.Sound == model.SwitchOn {
  130. xmm.SetNotifyType(mi.NotifyTypeDefaultSound)
  131. }
  132. if info.Vibration == model.SwitchOn {
  133. xmm.SetNotifyType(mi.NotifyTypeDefaultVibration)
  134. }
  135. if info.Sound == model.SwitchOn && info.Vibration == model.SwitchOn {
  136. xmm.SetNotifyType(mi.NotifyTypeDefaultAll)
  137. }
  138. var response *mi.Response
  139. if response, err = d.clientsMi[info.APPID][index].Push(xmm); err != nil {
  140. log.Error("push mi task(%s) resp(%+v) error(%v)", info.TaskID, response, err)
  141. logPushError(info.TaskID, model.PlatformXiaomi, strings.Split(tokens, ","))
  142. PromError("push:推送Xiaomi")
  143. return
  144. }
  145. res.Code = response.Code
  146. res.Msg = response.Reason
  147. if response.Code == mi.ResultCodeOk {
  148. res.Code = model.HTTPCodeOk
  149. res.Msg = response.Info
  150. }
  151. log.Info("push mi task(%s) tokens(%d) result(%+v) traceid(%s) success", info.TaskID, len(tokens), res, response.TraceID)
  152. return
  153. }
  154. // PushMiByMids .
  155. func (d *Dao) PushMiByMids(c context.Context, info *model.PushInfo, scheme, mids string) (res *model.HTTPResponse, err error) {
  156. if d.clientMiByMids[info.APPID] == nil {
  157. return
  158. }
  159. res = &model.HTTPResponse{}
  160. passThrough := mi.NotPassThrough
  161. if info.PassThrough == model.SwitchOn {
  162. passThrough = mi.PassThrough
  163. }
  164. xmm := &mi.XMMessage{
  165. Payload: scheme,
  166. RestrictedPackageName: d.clientMiByMids[info.APPID].Package,
  167. PassThrough: passThrough,
  168. Title: info.Title,
  169. Description: info.Summary,
  170. NotifyType: mi.NotifyTypeDefaultNone,
  171. TaskID: info.TaskID,
  172. }
  173. xmm.SetUserAccount(mids)
  174. xmm.SetNotifyID(info.TaskID)
  175. xmm.SetTimeToLive(int64(info.ExpireTime))
  176. xmm.SetCallbackParam(strconv.FormatInt(info.APPID, 10))
  177. if info.Sound == model.SwitchOn {
  178. xmm.SetNotifyType(mi.NotifyTypeDefaultSound)
  179. }
  180. if info.Vibration == model.SwitchOn {
  181. xmm.SetNotifyType(mi.NotifyTypeDefaultVibration)
  182. }
  183. if info.Sound == model.SwitchOn && info.Vibration == model.SwitchOn {
  184. xmm.SetNotifyType(mi.NotifyTypeDefaultAll)
  185. }
  186. var response *mi.Response
  187. if response, err = d.clientMiByMids[info.APPID].Push(xmm); err != nil {
  188. log.Error("d.PushMi(%s,%s,%s) error(%v)", info.TaskID, scheme, mids, err)
  189. PromError("push:推送miByMids")
  190. return
  191. }
  192. res.Code = response.Code
  193. res.Msg = response.Reason
  194. if response.Code == mi.ResultCodeOk {
  195. res.Code = model.HTTPCodeOk
  196. res.Msg = response.Info
  197. }
  198. return
  199. }
  200. // PushHuawei push huawei notifications.
  201. func (d *Dao) PushHuawei(c context.Context, info *model.PushInfo, scheme string, tokens []string) (res *huawei.Response, err error) {
  202. var index int
  203. if index, err = d.roundIndex(info.APPID, model.PlatformHuawei); err != nil {
  204. return
  205. }
  206. payload := huawei.NewMessage().SetTitle(info.Title).SetContent(info.Summary).SetCustomize("task_id", info.TaskID).SetCustomize("scheme", scheme).SetBiTag(info.TaskID).SetIcon(info.ImageURL)
  207. if info.PassThrough == model.SwitchOn {
  208. payload.SetMsgType(huawei.MsgTypePassthrough)
  209. }
  210. expire := time.Unix(int64(info.ExpireTime), 0)
  211. if res, err = d.clientsHuawei[info.APPID][index].Push(payload, tokens, expire); err != nil {
  212. if err == huawei.ErrLimit {
  213. return
  214. }
  215. log.Error("push huawei task(%s) resp(%+v) tokens(%v) error(%v)", info.TaskID, res, tokens, err)
  216. logPushError(info.TaskID, model.PlatformHuawei, tokens)
  217. return
  218. }
  219. log.Info("push huawei task(%s) tokens(%d) result(%+v) success", info.TaskID, len(tokens), res)
  220. return
  221. }
  222. // OppoMessage saves oppo message content.
  223. func (d *Dao) OppoMessage(c context.Context, info *model.PushInfo, m *oppo.Message) (res *oppo.Response, err error) {
  224. var index int
  225. if index, err = d.roundIndex(info.APPID, model.PlatformOppo); err != nil {
  226. return
  227. }
  228. if res, err = d.clientsOppo[info.APPID][index].Message(m); err != nil {
  229. log.Error("save oppo message task(%s) result(%+v) error(%v)", info.TaskID, res, err)
  230. return
  231. }
  232. log.Info("save oppo message task(%s) result(%+v) success", info.TaskID, res)
  233. return
  234. }
  235. // PushOppo push oppo notifications.
  236. func (d *Dao) PushOppo(c context.Context, info *model.PushInfo, msgID string, tokens []string) (res *oppo.Response, err error) {
  237. var index int
  238. if index, err = d.roundIndex(info.APPID, model.PlatformOppo); err != nil {
  239. return
  240. }
  241. if res, err = d.clientsOppo[info.APPID][index].Push(msgID, tokens); err != nil {
  242. log.Error("push oppo task(%s) resp(%+v) error(%v)", info.TaskID, res, err)
  243. logPushError(info.TaskID, model.PlatformOppo, tokens)
  244. return
  245. }
  246. log.Info("push oppo task(%s) tokens(%d) result(%+v) success", info.TaskID, len(tokens), res)
  247. return
  248. }
  249. // PushOppoOne push oppo notifications.
  250. func (d *Dao) PushOppoOne(c context.Context, info *model.PushInfo, m *oppo.Message, token string) (res *oppo.Response, err error) {
  251. var index int
  252. if index, err = d.roundIndex(info.APPID, model.PlatformOppo); err != nil {
  253. return
  254. }
  255. if res, err = d.clientsOppo[info.APPID][index].PushOne(m, token); err != nil {
  256. log.Error("push oppo one task(%s) token(%s) result(%+v) error(%v)", info.TaskID, token, res, err)
  257. return
  258. }
  259. log.Info("push oppo one task(%s) token(%s) result(%+v) success", info.TaskID, token, res)
  260. return
  261. }
  262. // PushJpush push huawei notifications.
  263. func (d *Dao) PushJpush(c context.Context, info *model.PushInfo, scheme string, tokens []string) (res *jpush.PushResponse, err error) {
  264. var (
  265. index int
  266. ad jpush.Audience
  267. notice jpush.Notice
  268. plat = jpush.NewPlatform(jpush.PlatformAndroid)
  269. payload = jpush.NewPayload()
  270. cbr = jpush.NewCallbackReq()
  271. an = &jpush.AndroidNotice{
  272. Title: info.Title,
  273. Alert: info.Summary,
  274. AlertType: jpush.AndroidAlertTypeNone,
  275. Extras: map[string]interface{}{
  276. "task_id": info.TaskID,
  277. "scheme": scheme,
  278. },
  279. }
  280. )
  281. if index, err = d.roundIndex(info.APPID, model.PlatformJpush); err != nil {
  282. return
  283. }
  284. if info.Sound == model.SwitchOn {
  285. an.AlertType |= jpush.AndroidAlertTypeSound
  286. }
  287. if info.Vibration == model.SwitchOn {
  288. an.AlertType |= jpush.AndroidAlertTypeVibrate
  289. }
  290. if info.Sound == model.SwitchOn && info.Vibration == model.SwitchOn {
  291. an.AlertType = jpush.AndroidAlertTypeAll
  292. }
  293. ad.SetID(tokens)
  294. notice.SetAndroidNotice(an)
  295. payload.SetPlatform(plat)
  296. payload.SetAudience(&ad)
  297. payload.SetNotice(&notice)
  298. payload.Options.SetTimelive(int(int64(info.ExpireTime) - time.Now().Unix()))
  299. payload.Options.SetReturnInvalidToken(true)
  300. cbr.SetParam(map[string]string{"task": info.TaskID, "appid": strconv.FormatInt(info.APPID, 10)})
  301. payload.SetCallbackReq(cbr)
  302. if res, err = d.clientsJpush[info.APPID][index].Push(payload); err != nil {
  303. logPushError(info.TaskID, model.PlatformJpush, tokens)
  304. log.Error("push jpush task(%s) tokens(%d) result(%+v) error(%v)", info.TaskID, len(tokens), res, err)
  305. return
  306. }
  307. log.Info("push jpush task(%s) tokens(%d) result(%+v) success", info.TaskID, len(tokens), res)
  308. return
  309. }
  310. // PushFCM .
  311. func (d *Dao) PushFCM(ctx context.Context, info *model.PushInfo, scheme string, tokens []string) (res *fcm.Response, err error) {
  312. var index int
  313. if index, err = d.roundIndex(info.APPID, model.PlatformFCM); err != nil {
  314. return
  315. }
  316. message := fcm.Message{
  317. Data: map[string]string{
  318. "task_id": info.TaskID,
  319. "scheme": scheme,
  320. },
  321. RegistrationIDs: tokens,
  322. Priority: fcm.PriorityHigh,
  323. DelayWhileIdle: true,
  324. Notification: fcm.Notification{
  325. Title: info.Title,
  326. Body: info.Summary,
  327. ClickAction: "com.bilibili.app.in.com.bilibili.push.FCM_MESSAGE",
  328. },
  329. TimeToLive: int(int64(info.ExpireTime) - time.Now().Unix()),
  330. CollapseKey: strings.TrimFunc(info.TaskID, func(r rune) bool {
  331. return !unicode.IsNumber(r)
  332. }), // 应客户端要求,task_id 保证值转成 int 传到客户端
  333. Android: fcm.Android{Priority: fcm.PriorityHigh},
  334. }
  335. if res, err = d.clientsFCM[info.APPID][index].Send(&message); err != nil {
  336. log.Error("push fcm task(%s) tokens(%d) result(%+v) error(%v)", info.TaskID, len(tokens), res)
  337. PromError("push: 推送fcm")
  338. return
  339. }
  340. log.Info("push fcm task(%s) tokens(%d) result(%+v) error(%v)", info.TaskID, len(tokens), res)
  341. return
  342. }