notification.go 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package models
  15. import (
  16. "context"
  17. "database/sql"
  18. "fmt"
  19. "html"
  20. "html/template"
  21. "strings"
  22. "time"
  23. "yunion.io/x/jsonutils"
  24. "yunion.io/x/log"
  25. "yunion.io/x/pkg/errors"
  26. "yunion.io/x/pkg/util/rbacscope"
  27. "yunion.io/x/pkg/util/sets"
  28. "yunion.io/x/pkg/utils"
  29. "yunion.io/x/sqlchemy"
  30. api "yunion.io/x/onecloud/pkg/apis/notify"
  31. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  32. "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
  33. "yunion.io/x/onecloud/pkg/cloudcommon/validators"
  34. "yunion.io/x/onecloud/pkg/httperrors"
  35. "yunion.io/x/onecloud/pkg/mcclient"
  36. "yunion.io/x/onecloud/pkg/mcclient/auth"
  37. modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
  38. "yunion.io/x/onecloud/pkg/notify/options"
  39. "yunion.io/x/onecloud/pkg/util/logclient"
  40. "yunion.io/x/onecloud/pkg/util/stringutils2"
  41. )
  42. type SNotificationManager struct {
  43. db.SStatusStandaloneResourceBaseManager
  44. }
  45. var NotificationManager *SNotificationManager
  46. func init() {
  47. NotificationManager = &SNotificationManager{
  48. SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
  49. SNotification{},
  50. "notifications_tbl",
  51. "notification",
  52. "notifications",
  53. ),
  54. }
  55. NotificationManager.SetVirtualObject(NotificationManager)
  56. NotificationManager.TableSpec().AddIndex(false, "deleted", "contact_type", "topic_type")
  57. }
  58. // 站内信
  59. type SNotification struct {
  60. db.SStatusStandaloneResourceBase
  61. ContactType string `width:"128" nullable:"true" create:"optional" list:"user" get:"user"`
  62. // swagger:ignore
  63. Topic string `width:"128" nullable:"true" create:"required" list:"user" get:"user"`
  64. Priority string `width:"16" nullable:"true" create:"optional" list:"user" get:"user"`
  65. // swagger:ignore
  66. Message string `create:"required"`
  67. // swagger:ignore
  68. TopicType string `json:"topic_type" width:"20" nullable:"true" update:"user" list:"user"`
  69. // swagger:ignore
  70. TopicId string `width:"128" nullable:"true" list:"user" get:"user"`
  71. ReceivedAt time.Time `nullable:"true" list:"user" get:"user"`
  72. EventId string `width:"128" nullable:"true"`
  73. SendTimes int
  74. }
  75. const (
  76. SendByContact = "send_by_contact"
  77. )
  78. func (nm *SNotificationManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.NotificationCreateInput) (api.NotificationCreateInput, error) {
  79. cTypes := []string{}
  80. if len(input.Contacts) > 0 && !userCred.HasSystemAdminPrivilege() {
  81. return input, httperrors.NewForbiddenError("only admin can send notification by contact")
  82. }
  83. // check robot
  84. robots := []string{}
  85. for i := range input.Robots {
  86. _robot, err := validators.ValidateModel(ctx, userCred, RobotManager, &input.Robots[i])
  87. if err != nil && !input.IgnoreNonexistentReceiver {
  88. return input, err
  89. }
  90. if _robot != nil {
  91. robot := _robot.(*SRobot)
  92. if !utils.IsInStringArray(robot.GetId(), robots) {
  93. robots = append(robots, robot.GetId())
  94. }
  95. if !utils.IsInStringArray(robot.Type, cTypes) {
  96. cTypes = append(cTypes, robot.Type)
  97. }
  98. }
  99. }
  100. input.Robots = robots
  101. // check receivers
  102. receivers, err := ReceiverManager.FetchByIdOrNames(ctx, input.Receivers...)
  103. if err != nil {
  104. return input, errors.Wrap(err, "ReceiverManager.FetchByIDs")
  105. }
  106. idSet := sets.NewString()
  107. nameSet := sets.NewString()
  108. for i := range receivers {
  109. idSet.Insert(receivers[i].Id)
  110. nameSet.Insert(receivers[i].Name)
  111. }
  112. for _, re := range input.Receivers {
  113. if idSet.Has(re) || nameSet.Has(re) {
  114. continue
  115. }
  116. if input.ContactType == api.WEBCONSOLE {
  117. input.Contacts = append(input.Contacts, re)
  118. }
  119. if !input.IgnoreNonexistentReceiver {
  120. return input, httperrors.NewInputParameterError("no such receiver whose uid is %q", re)
  121. }
  122. }
  123. input.Receivers = idSet.UnsortedList()
  124. if len(input.Receivers)+len(input.Contacts)+len(input.Robots) == 0 {
  125. return input, httperrors.NewInputParameterError("no valid receiver or contact")
  126. }
  127. input.ContactType = strings.Join(cTypes, ",")
  128. nowStr := time.Now().Format("2006-01-02 15:04:05")
  129. if len(input.Priority) == 0 {
  130. input.Priority = api.NOTIFICATION_PRIORITY_NORMAL
  131. }
  132. // hack
  133. length := 10
  134. topicRunes := []rune(input.Topic)
  135. if len(topicRunes) < 10 {
  136. length = len(topicRunes)
  137. }
  138. name := fmt.Sprintf("%s-%s-%s", string(topicRunes[:length]), input.ContactType, nowStr)
  139. input.Name, err = db.GenerateName(ctx, nm, ownerId, name)
  140. if err != nil {
  141. return input, errors.Wrapf(err, "unable to generate name for %s", name)
  142. }
  143. return input, nil
  144. }
  145. func (n *SNotification) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
  146. n.ReceivedAt = time.Now()
  147. n.Id = db.DefaultUUIDGenerator()
  148. var input api.NotificationCreateInput
  149. err := data.Unmarshal(&input)
  150. if err != nil {
  151. return err
  152. }
  153. for i := range input.Receivers {
  154. _, err := ReceiverNotificationManager.Create(ctx, userCred, input.Receivers[i], 0, n.Id)
  155. if err != nil {
  156. return errors.Wrap(err, "ReceiverNotificationManager.Create")
  157. }
  158. }
  159. for i := range input.Contacts {
  160. _, err := ReceiverNotificationManager.CreateContact(ctx, userCred, input.Contacts[i], n.Id)
  161. if err != nil {
  162. return errors.Wrap(err, "ReceiverNotificationManager.CreateContact")
  163. }
  164. }
  165. for i := range input.Robots {
  166. _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, input.Robots[i], 0, n.Id)
  167. if err != nil {
  168. return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot")
  169. }
  170. }
  171. return nil
  172. }
  173. func (n *SNotification) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
  174. n.SStatusStandaloneResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
  175. n.SetStatus(ctx, userCred, api.NOTIFICATION_STATUS_RECEIVED, "")
  176. task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
  177. if err != nil {
  178. n.SetStatus(ctx, userCred, api.NOTIFICATION_STATUS_FAILED, "NewTask")
  179. return
  180. }
  181. task.ScheduleRun(nil)
  182. }
  183. // TODO: support project and domain
  184. func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.NotificationManagerEventNotifyInput) (api.NotificationManagerEventNotifyOutput, error) {
  185. var output api.NotificationManagerEventNotifyOutput
  186. // contact type
  187. contactTypes := input.ContactTypes
  188. cts, err := ConfigManager.allContactType(userCred.GetProjectDomainId())
  189. if err != nil {
  190. return output, errors.Wrap(err, "unable to fetch allContactType")
  191. }
  192. if len(contactTypes) == 0 {
  193. contactTypes = append(contactTypes, cts...)
  194. }
  195. topic, err := TopicManager.TopicByEvent(input.Event)
  196. if err != nil {
  197. return output, errors.Wrapf(err, "TopicByEvent")
  198. }
  199. receiverIds := make(map[string]uint32)
  200. receiverIds1, err := SubscriberManager.getReceiversSent(ctx, topic.Id, input.ProjectDomainId, input.ProjectId)
  201. if err != nil {
  202. return output, errors.Wrap(err, "unable to get receive")
  203. }
  204. for k, v := range receiverIds1 {
  205. receiverIds[k] = v
  206. }
  207. // robot
  208. robots := make(map[string]uint32)
  209. _robots, err := SubscriberManager.robot(topic.Id, input.ProjectDomainId, input.ProjectId)
  210. if err != nil {
  211. if errors.Cause(err) != errors.ErrNotFound {
  212. return output, errors.Wrapf(err, "unable fetch robot of subscription %q", topic.Id)
  213. }
  214. } else {
  215. for robot, groupTime := range _robots {
  216. robots[robot] = groupTime
  217. }
  218. }
  219. for _, id := range input.RobotIds {
  220. robots[id] = 0
  221. }
  222. var webhookRobots []string
  223. realRobot := make(map[string]uint32)
  224. if len(robots) > 0 {
  225. robotList := []string{}
  226. for robot := range robots {
  227. robotList = append(robotList, robot)
  228. }
  229. rs, err := RobotManager.FetchByIdOrNames(ctx, robotList...)
  230. if err != nil {
  231. return output, errors.Wrap(err, "unable to get robots")
  232. }
  233. webhookRobots = make([]string, 0, 1)
  234. for i := range rs {
  235. if rs[i].Type == api.ROBOT_TYPE_WEBHOOK {
  236. webhookRobots = append(webhookRobots, rs[i].Id)
  237. } else {
  238. realRobot[rs[i].Id] = robots[rs[i].Id]
  239. }
  240. }
  241. }
  242. message := jsonutils.Marshal(input.ResourceDetails).String()
  243. for _, receiver := range input.ReceiverIds {
  244. // receiverIds = append(receiverIds, api.SReceiverWithGroupTimes{ReceiverId: receiver})
  245. if _, ok := receiverIds[receiver]; !ok {
  246. receiverIds[receiver] = 0
  247. }
  248. }
  249. receiverIdList := []string{}
  250. for k := range receiverIds {
  251. receiverIdList = append(receiverIdList, k)
  252. }
  253. receivers, err := ReceiverManager.FetchByIdOrNames(ctx, receiverIdList...)
  254. if err != nil {
  255. return output, errors.Wrap(err, "fetch receiver")
  256. }
  257. webconsoleContacts := sets.NewString()
  258. idSet := sets.NewString()
  259. for i := range receivers {
  260. idSet.Insert(receivers[i].Id)
  261. }
  262. for re := range receiverIds {
  263. webconsoleContacts.Insert(re)
  264. }
  265. realReceiverIds := make(map[string]uint32)
  266. for _, id := range receiverIdList {
  267. realReceiverIds[id] = receiverIds[id]
  268. }
  269. // create event
  270. event, err := EventManager.CreateEvent(ctx, input.Event, topic.Id, message, string(input.Action), input.ResourceType, input.AdvanceDays)
  271. if err != nil {
  272. return output, errors.Wrap(err, "unable to create Event")
  273. }
  274. if nm.needWebconsole([]STopic{*topic}) {
  275. // webconsole
  276. err = nm.create(ctx, userCred, api.WEBCONSOLE, realReceiverIds, webconsoleContacts.UnsortedList(), input.Priority, event.GetId(), topic.GetId(), topic.Type)
  277. if err != nil {
  278. output.FailedList = append(output.FailedList, api.FailedElem{
  279. ContactType: api.WEBCONSOLE,
  280. Reason: err.Error(),
  281. })
  282. }
  283. }
  284. // normal contact type
  285. for _, ct := range contactTypes {
  286. if ct == api.MOBILE {
  287. continue
  288. }
  289. err := nm.create(ctx, userCred, ct, realReceiverIds, nil, input.Priority, event.GetId(), topic.GetId(), topic.Type)
  290. if err != nil {
  291. log.Errorf("unable to create notification for %s: %v", ct, err)
  292. output.FailedList = append(output.FailedList, api.FailedElem{
  293. ContactType: ct,
  294. Reason: err.Error(),
  295. })
  296. }
  297. }
  298. err = nm.createWithWebhookRobots(ctx, userCred, webhookRobots, input.Priority, event.GetId(), topic.Type)
  299. if err != nil {
  300. log.Errorf("unable to create notification for webhook robots: %v", err)
  301. output.FailedList = append(output.FailedList, api.FailedElem{
  302. ContactType: api.WEBHOOK,
  303. Reason: err.Error(),
  304. })
  305. }
  306. // robot
  307. err = nm.createWithRobots(ctx, userCred, realRobot, input.Priority, event.GetId(), topic.Type)
  308. if err != nil {
  309. log.Errorf("unable to create notification for robots: %v", err)
  310. output.FailedList = append(output.FailedList, api.FailedElem{
  311. ContactType: api.ROBOT,
  312. Reason: err.Error(),
  313. })
  314. }
  315. return output, nil
  316. }
  317. func (nm *SNotificationManager) PerformContactNotify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.NotificationManagerContactNotifyInput) (api.NotificationManagerEventNotifyOutput, error) {
  318. var output api.NotificationManagerEventNotifyOutput
  319. params := api.SendParams{
  320. Title: input.Subject,
  321. Message: input.Body,
  322. EmailMsg: api.SEmailMessage{
  323. Body: input.Body,
  324. },
  325. DomainId: userCred.GetDomainId(),
  326. }
  327. // 机器人订阅
  328. if len(input.RobotIds) > 0 {
  329. robots := []SRobot{}
  330. q := RobotManager.Query().In("id", input.RobotIds)
  331. err := db.FetchModelObjects(RobotManager, q, &robots)
  332. if err != nil {
  333. output.FailedList = append(output.FailedList, api.FailedElem{ContactType: "robot", Reason: errors.Wrapf(err, "unable to fetch robots:%s", jsonutils.Marshal(input.RobotIds).String()).Error()})
  334. return output, errors.Wrapf(err, "unable to fetch robots:%s", jsonutils.Marshal(input.RobotIds).String())
  335. }
  336. for _, robot := range robots {
  337. go func(ctx context.Context, userCred mcclient.TokenCredential, robot SRobot, params api.SendParams) {
  338. params.Header = robot.Header
  339. params.Body = robot.Body
  340. params.MsgKey = robot.MsgKey
  341. params.SecretKey = robot.SecretKey
  342. params.Receivers = api.SNotifyReceiver{
  343. Contact: robot.Address,
  344. }
  345. driver := GetDriver(fmt.Sprintf("%s-robot", robot.Type))
  346. err = driver.Send(ctx, params)
  347. if err != nil {
  348. logclient.AddSimpleActionLog(&robot, "contact send", err, userCred, false)
  349. }
  350. }(ctx, userCred, robot, params)
  351. }
  352. }
  353. // 传入接受人id声明map保证唯一
  354. receivermap := map[string]struct{}{}
  355. for _, receiverId := range input.ReceiverIds {
  356. receivermap[receiverId] = struct{}{}
  357. }
  358. // 存在角色接受人
  359. if len(input.RoleIds) > 0 {
  360. s := auth.GetAdminSession(ctx, options.Options.Region)
  361. query := jsonutils.NewDict()
  362. query.Set("roles", jsonutils.NewStringArray(input.RoleIds))
  363. query.Set("effective", jsonutils.JSONTrue)
  364. listRet, err := modules.RoleAssignments.List(s, query)
  365. if err != nil {
  366. return output, errors.Wrap(err, "unable to list RoleAssignments")
  367. }
  368. userList := []struct {
  369. User struct {
  370. Id string `json:"id"`
  371. } `json:"user"`
  372. }{}
  373. jsonutils.Update(&userList, listRet.Data)
  374. for _, user := range userList {
  375. receivermap[user.User.Id] = struct{}{}
  376. }
  377. }
  378. // 声明接受人数组
  379. receiverIds := []string{}
  380. // 输入接受人与角色去重
  381. for receiverId := range receivermap {
  382. receiverIds = append(receiverIds, receiverId)
  383. }
  384. // 接受人ID存在的情况下
  385. if len(receiverIds) > 0 {
  386. receivers, err := ReceiverManager.FetchByIDs(ctx, receiverIds...)
  387. if err != nil {
  388. return output, errors.Wrap(err, "FetchByIDs")
  389. }
  390. // 对于每个接受人根据通知渠道逐一发送
  391. for _, receiver := range receivers {
  392. // 用户没有启用的情况
  393. if receiver.Enabled.IsNone() {
  394. continue
  395. }
  396. // 获取启用的通知渠道
  397. enabledContactTypes, err := receiver.GetEnabledContactTypes()
  398. if err != nil {
  399. continue
  400. }
  401. for _, contactType := range input.ContactTypes {
  402. // 通知渠道没有启用
  403. if !utils.IsInStringArray(contactType, enabledContactTypes) {
  404. continue
  405. }
  406. // 发送
  407. go func(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiver SReceiver, params api.SendParams) {
  408. contact, _ := receiver.GetContact(contactType)
  409. params.Receivers = api.SNotifyReceiver{Contact: contact}
  410. driver := GetDriver(contactType)
  411. err = driver.Send(ctx, params)
  412. if err != nil {
  413. logclient.AddSimpleActionLog(&receiver, "contact send", err, userCred, false)
  414. }
  415. }(ctx, userCred, contactType, receiver, params)
  416. }
  417. }
  418. }
  419. return output, nil
  420. }
  421. func (nm *SNotificationManager) needWebconsole(topics []STopic) bool {
  422. for i := range topics {
  423. if topics[i].WebconsoleDisable.IsFalse() || topics[i].WebconsoleDisable.IsNone() {
  424. return true
  425. }
  426. }
  427. return false
  428. }
  429. func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.TokenCredential, contactType string, receiverIds map[string]uint32, contacts []string, priority, eventId, topicId string, topicType string) error {
  430. if len(receiverIds)+len(contacts) == 0 {
  431. return nil
  432. }
  433. n := &SNotification{
  434. ContactType: contactType,
  435. Priority: priority,
  436. ReceivedAt: time.Now(),
  437. EventId: eventId,
  438. TopicType: topicType,
  439. TopicId: topicId,
  440. }
  441. n.Id = db.DefaultUUIDGenerator()
  442. err := nm.TableSpec().Insert(ctx, n)
  443. if err != nil {
  444. return errors.Wrap(err, "unable to insert Notification")
  445. }
  446. for receiver := range receiverIds {
  447. _, err := ReceiverNotificationManager.Create(ctx, userCred, receiver, receiverIds[receiver], n.Id)
  448. if err != nil {
  449. return errors.Wrap(err, "ReceiverNotificationManager.Create")
  450. }
  451. }
  452. for i := range contacts {
  453. _, err := ReceiverNotificationManager.CreateContact(ctx, userCred, contacts[i], n.Id)
  454. if err != nil {
  455. return errors.Wrap(err, "ReceiverNotificationManager.CreateContact")
  456. }
  457. }
  458. n.SetModelManager(nm, n)
  459. task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
  460. if err != nil {
  461. return errors.Wrapf(err, "NewTask")
  462. }
  463. return task.ScheduleRun(nil)
  464. }
  465. func (nm *SNotificationManager) createWithWebhookRobots(ctx context.Context, userCred mcclient.TokenCredential, webhookRobotIds []string, priority, eventId string, topicType string) error {
  466. if len(webhookRobotIds) == 0 {
  467. return nil
  468. }
  469. n := &SNotification{
  470. ContactType: api.WEBHOOK,
  471. Priority: priority,
  472. ReceivedAt: time.Now(),
  473. EventId: eventId,
  474. TopicType: topicType,
  475. }
  476. n.Id = db.DefaultUUIDGenerator()
  477. for i := range webhookRobotIds {
  478. _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, webhookRobotIds[i], 0, n.Id)
  479. if err != nil {
  480. return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot")
  481. }
  482. }
  483. err := nm.TableSpec().Insert(ctx, n)
  484. if err != nil {
  485. return errors.Wrap(err, "unable to insert Notification")
  486. }
  487. n.SetModelManager(nm, n)
  488. task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
  489. if err != nil {
  490. return errors.Wrapf(err, "NewTask")
  491. }
  492. return task.ScheduleRun(nil)
  493. }
  494. func (nm *SNotificationManager) createWithRobots(ctx context.Context, userCred mcclient.TokenCredential, robotIds map[string]uint32, priority, eventId string, topicType string) error {
  495. if len(robotIds) == 0 {
  496. return nil
  497. }
  498. n := &SNotification{
  499. ContactType: api.ROBOT,
  500. Priority: priority,
  501. ReceivedAt: time.Now(),
  502. EventId: eventId,
  503. TopicType: topicType,
  504. }
  505. n.Id = db.DefaultUUIDGenerator()
  506. for i := range robotIds {
  507. _, err := ReceiverNotificationManager.CreateRobot(ctx, userCred, i, robotIds[i], n.Id)
  508. if err != nil {
  509. return errors.Wrap(err, "ReceiverNotificationManager.CreateRobot")
  510. }
  511. }
  512. err := nm.TableSpec().Insert(ctx, n)
  513. if err != nil {
  514. return errors.Wrap(err, "unable to insert Notification")
  515. }
  516. n.SetModelManager(nm, n)
  517. task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
  518. if err != nil {
  519. log.Errorf("NotificationSendTask newTask error %v", err)
  520. } else {
  521. task.ScheduleRun(nil)
  522. }
  523. return nil
  524. }
  525. func (n *SNotification) Create(ctx context.Context, userCred mcclient.TokenCredential, receiverIds map[string]uint32, contacts []string) error {
  526. if len(receiverIds)+len(contacts) == 0 {
  527. return nil
  528. }
  529. n.Id = db.DefaultUUIDGenerator()
  530. err := NotificationManager.TableSpec().Insert(ctx, n)
  531. if err != nil {
  532. return errors.Wrap(err, "unable to insert Notification")
  533. }
  534. for receiver := range receiverIds {
  535. _, err := ReceiverNotificationManager.Create(ctx, userCred, receiver, receiverIds[receiver], n.Id)
  536. if err != nil {
  537. return errors.Wrap(err, "ReceiverNotificationManager.Create")
  538. }
  539. }
  540. for i := range contacts {
  541. _, err := ReceiverNotificationManager.CreateContact(ctx, userCred, contacts[i], n.Id)
  542. if err != nil {
  543. return errors.Wrap(err, "ReceiverNotificationManager.CreateContact")
  544. }
  545. }
  546. task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
  547. if err != nil {
  548. log.Errorf("NotificationSendTask newTask error %v", err)
  549. } else {
  550. task.ScheduleRun(nil)
  551. }
  552. return nil
  553. }
  554. func (nm *SNotificationManager) FetchCustomizeColumns(
  555. ctx context.Context,
  556. userCred mcclient.TokenCredential,
  557. query jsonutils.JSONObject,
  558. objs []interface{},
  559. fields stringutils2.SSortedStrings,
  560. isList bool,
  561. ) []api.NotificationDetails {
  562. rows := make([]api.NotificationDetails, len(objs))
  563. resRows := nm.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
  564. var err error
  565. notifications := make([]*SNotification, len(objs))
  566. for i := range notifications {
  567. notifications[i] = objs[i].(*SNotification)
  568. }
  569. for i := range rows {
  570. rows[i], err = notifications[i].getMoreDetails(ctx, userCred, query, rows[i])
  571. if err != nil {
  572. log.Errorf("Notification.getMoreDetails: %v", err)
  573. }
  574. rows[i].StatusStandaloneResourceDetails = resRows[i]
  575. }
  576. return rows
  577. }
  578. func (n *SNotification) ReceiverNotificationsNotOK() ([]SReceiverNotification, error) {
  579. rnq := ReceiverNotificationManager.Query().Equals("notification_id", n.Id).NotEquals("status", api.RECEIVER_NOTIFICATION_OK)
  580. rns := make([]SReceiverNotification, 0, 1)
  581. err := db.FetchModelObjects(ReceiverNotificationManager, rnq, &rns)
  582. if err == sql.ErrNoRows {
  583. return []SReceiverNotification{}, nil
  584. }
  585. if err != nil {
  586. return nil, err
  587. }
  588. return rns, nil
  589. }
  590. func (n *SNotification) receiveDetails(userCred mcclient.TokenCredential, scope string) ([]api.ReceiveDetail, error) {
  591. RQ := ReceiverManager.Query("id", "name")
  592. q := ReceiverNotificationManager.Query("receiver_id", "notification_id", "receiver_type", "contact", "send_at", "send_by", "status", "failed_reason").Equals("notification_id", n.Id).IsNotEmpty("receiver_id").IsNullOrEmpty("contact")
  593. s := rbacscope.TRbacScope(scope)
  594. switch s {
  595. case rbacscope.ScopeSystem:
  596. subRQ := RQ.SubQuery()
  597. q.AppendField(subRQ.Field("name", "receiver_name"))
  598. q = q.LeftJoin(subRQ, sqlchemy.OR(sqlchemy.Equals(q.Field("receiver_id"), subRQ.Field("id")), sqlchemy.Equals(q.Field("contact"), subRQ.Field("id"))))
  599. case rbacscope.ScopeDomain:
  600. subRQ := RQ.Equals("domain_id", userCred.GetDomainId()).SubQuery()
  601. q.AppendField(subRQ.Field("name", "receiver_name"))
  602. q = q.Join(subRQ, sqlchemy.OR(sqlchemy.Equals(q.Field("receiver_id"), subRQ.Field("id")), sqlchemy.Equals(q.Field("contact"), subRQ.Field("id"))))
  603. default:
  604. subRQ := RQ.Equals("id", userCred.GetUserId()).SubQuery()
  605. q.AppendField(subRQ.Field("name", "receiver_name"))
  606. q = q.Join(subRQ, sqlchemy.OR(sqlchemy.Equals(q.Field("receiver_id"), subRQ.Field("id")), sqlchemy.Equals(q.Field("contact"), subRQ.Field("id"))))
  607. }
  608. ret := make([]api.ReceiveDetail, 0, 2)
  609. err := q.All(&ret)
  610. if err != nil && errors.Cause(err) != sql.ErrNoRows {
  611. log.Errorf("SQuery.All: %v", err)
  612. return nil, err
  613. }
  614. return ret, nil
  615. }
  616. func (n *SNotification) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, out api.NotificationDetails) (api.NotificationDetails, error) {
  617. // get title adn content
  618. lang := getLangSuffix(ctx)
  619. nn, err := n.Notification(false)
  620. if err != nil {
  621. return out, err
  622. }
  623. p, err := n.GetTemplate(ctx, n.TopicId, lang, nn)
  624. if err != nil {
  625. return out, err
  626. }
  627. out.Title = p.Title
  628. out.Content = p.Message
  629. scope, _ := query.GetString("scope")
  630. // get receive details
  631. out.ReceiveDetails, err = n.receiveDetails(userCred, scope)
  632. if err != nil {
  633. return out, err
  634. }
  635. return out, nil
  636. }
  637. func (n *SNotification) Notification(robotUseTemplate bool) (api.SsNotification, error) {
  638. if n.EventId == "" {
  639. return api.SsNotification{
  640. ContactType: n.ContactType,
  641. Topic: n.Topic,
  642. Message: n.Message,
  643. }, nil
  644. }
  645. event, err := EventManager.GetEvent(n.EventId)
  646. if err != nil {
  647. return api.SsNotification{}, err
  648. }
  649. e, _ := parseEvent(event.Event)
  650. return api.SsNotification{
  651. ContactType: n.ContactType,
  652. Topic: n.Topic,
  653. Message: event.Message,
  654. Event: e,
  655. AdvanceDays: event.AdvanceDays,
  656. RobotUseTemplate: robotUseTemplate,
  657. }, nil
  658. }
  659. func (nm *SNotificationManager) ResourceScope() rbacscope.TRbacScope {
  660. return rbacscope.ScopeUser
  661. }
  662. func (nm *SNotificationManager) NamespaceScope() rbacscope.TRbacScope {
  663. return rbacscope.ScopeSystem
  664. }
  665. func (nm *SNotificationManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) {
  666. return db.FetchUserInfo(ctx, data)
  667. }
  668. func (nm *SNotificationManager) FilterByOwner(ctx context.Context, q *sqlchemy.SQuery, man db.FilterByOwnerProvider, userCred mcclient.TokenCredential, owner mcclient.IIdentityProvider, scope rbacscope.TRbacScope) *sqlchemy.SQuery {
  669. if owner == nil {
  670. return q
  671. }
  672. switch scope {
  673. case rbacscope.ScopeDomain:
  674. subRq := ReceiverManager.Query("id").Equals("domain_id", owner.GetDomainId()).SubQuery()
  675. RNq := ReceiverNotificationManager.Query("notification_id", "receiver_id")
  676. subRNq := RNq.Join(subRq, sqlchemy.OR(
  677. sqlchemy.Equals(RNq.Field("receiver_id"), subRq.Field("id")),
  678. sqlchemy.Equals(RNq.Field("contact"), subRq.Field("id")),
  679. )).SubQuery()
  680. q = q.Join(subRNq, sqlchemy.Equals(q.Field("id"), subRNq.Field("notification_id")))
  681. case rbacscope.ScopeProject, rbacscope.ScopeUser:
  682. sq := ReceiverNotificationManager.Query("notification_id")
  683. subq := sq.Filter(sqlchemy.OR(
  684. sqlchemy.Equals(sq.Field("receiver_id"), owner.GetUserId()),
  685. sqlchemy.Equals(sq.Field("contact"), owner.GetUserId()),
  686. )).SubQuery()
  687. q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("notification_id")))
  688. }
  689. return q
  690. }
  691. func (n *SNotification) AddOne() error {
  692. _, err := db.Update(n, func() error {
  693. n.SendTimes += 1
  694. return nil
  695. })
  696. return err
  697. }
  698. func (nm *SNotificationManager) InitializeData() error {
  699. return dataCleaning(nm.TableSpec().Name())
  700. }
  701. func dataCleaning(tableName string) error {
  702. now := time.Now()
  703. monthsDaysAgo := now.AddDate(0, -1, 0).Format("2006-01-02 15:04:05")
  704. sqlStr := fmt.Sprintf(
  705. "delete from %s where deleted = 0 and created_at < '%s'",
  706. tableName,
  707. monthsDaysAgo,
  708. )
  709. q := sqlchemy.NewRawQuery(sqlStr)
  710. rows, err := q.Rows()
  711. if err != nil {
  712. return errors.Wrapf(err, "unable to delete expired data in %q", tableName)
  713. }
  714. defer rows.Close()
  715. log.Infof("delete expired data in %q successfully", tableName)
  716. return nil
  717. }
  718. // 通知消息列表
  719. func (nm *SNotificationManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.NotificationListInput) (*sqlchemy.SQuery, error) {
  720. q, err := nm.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneResourceListInput)
  721. if err != nil {
  722. return nil, err
  723. }
  724. if len(input.ContactType) > 0 {
  725. q = q.Equals("contact_type", input.ContactType)
  726. }
  727. if len(input.ReceiverId) > 0 {
  728. subq := ReceiverNotificationManager.Query("notification_id").Equals("receiver_id", input.ReceiverId).SubQuery()
  729. q = q.Join(subq, sqlchemy.Equals(q.Field("id"), subq.Field("notification_id")))
  730. }
  731. if len(input.Tag) > 0 {
  732. q = q.Equals("tag", input.Tag)
  733. }
  734. if len(input.TopicType) > 0 {
  735. q = q.Equals("topic_type", input.TopicType)
  736. }
  737. return q, nil
  738. }
  739. func (nm *SNotificationManager) ReSend(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
  740. timeLimit := time.Now().Add(-time.Duration(options.Options.ReSendScope) * time.Second * 2).Format("2006-01-02 15:04:05")
  741. q := nm.Query().GT("created_at", timeLimit).In("status", []string{api.NOTIFICATION_STATUS_FAILED, api.NOTIFICATION_STATUS_PART_OK}).LT("send_times", options.Options.MaxSendTimes)
  742. ns := make([]SNotification, 0, 2)
  743. err := db.FetchModelObjects(nm, q, &ns)
  744. if err != nil {
  745. log.Errorf("fail to FetchModelObjects: %v", err)
  746. return
  747. }
  748. log.Infof("need to resend total %d notifications", len(ns))
  749. for i := range ns {
  750. task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", &ns[i], userCred, nil, "", "")
  751. if err != nil {
  752. log.Errorf("NotificationSendTask newTask error %v", err)
  753. } else {
  754. task.ScheduleRun(nil)
  755. }
  756. }
  757. }
  758. func (n *SNotification) GetNotOKReceivers() ([]SReceiver, error) {
  759. ret := []SReceiver{}
  760. q := ReceiverManager.Query().IsTrue("enabled")
  761. sq := ReceiverNotificationManager.Query().Equals("notification_id", n.Id).NotEquals("status", api.RECEIVER_NOTIFICATION_OK).Equals("receiver_type", api.RECEIVER_TYPE_USER).SubQuery()
  762. q = q.Join(sq, sqlchemy.Equals(q.Field("id"), sq.Field("receiver_id")))
  763. err := db.FetchModelObjects(ReceiverManager, q, &ret)
  764. return ret, err
  765. }
  766. func (n *SNotification) TaskInsert() error {
  767. return NotificationManager.TableSpec().Insert(context.Background(), n)
  768. }
  769. // 获取消息文案
  770. func (n *SNotification) GetTemplate(ctx context.Context, topicId, lang string, no api.SsNotification) (api.SendParams, error) {
  771. if len(n.EventId) == 0 || n.ContactType == api.MOBILE {
  772. return TemplateManager.FillWithTemplate(ctx, lang, no)
  773. }
  774. out, event := api.SendParams{}, no.Event
  775. topicModel, err := TopicManager.FetchById(topicId)
  776. if err != nil {
  777. return out, errors.Wrapf(err, "get topic by id")
  778. }
  779. topic := topicModel.(*STopic)
  780. groupKeys := []string{}
  781. if topic.GroupKeys != nil {
  782. groupKeys = *topic.GroupKeys
  783. }
  784. rtStr, aStr, resultStr := event.ResourceType(), string(event.Action()), string(event.Result())
  785. msgObj, err := jsonutils.ParseString(no.Message)
  786. if err != nil {
  787. return out, errors.Wrapf(err, "unable to parse json from %q", no.Message)
  788. }
  789. msg := msgObj.(*jsonutils.JSONDict)
  790. if !msg.Contains("brand") {
  791. if info, _ := TemplateManager.GetCompanyInfo(ctx); len(info.Name) > 0 {
  792. msg.Set("brand", jsonutils.NewString(info.Name))
  793. }
  794. }
  795. webhookMsg := jsonutils.NewDict()
  796. webhookMsg.Set("resource_type", jsonutils.NewString(rtStr))
  797. webhookMsg.Set("action", jsonutils.NewString(aStr))
  798. webhookMsg.Set("result", jsonutils.NewString(resultStr))
  799. webhookMsg.Set("resource_details", msg)
  800. if (no.ContactType == api.WEBHOOK || no.ContactType == api.WEBHOOK_ROBOT) && !no.RobotUseTemplate {
  801. return api.SendParams{
  802. Title: no.Event.StringWithDeli("_"),
  803. Message: webhookMsg.String(),
  804. }, nil
  805. }
  806. for _, key := range groupKeys {
  807. keyValue, _ := msg.GetString(key)
  808. if len(keyValue) > 0 {
  809. out.GroupKey += keyValue
  810. }
  811. }
  812. if lang == "" {
  813. lang = getLangSuffix(ctx)
  814. }
  815. // 文案关键字翻译
  816. tag := languageTag(lang)
  817. rtDis := notifyclientI18nTable.LookupByLang(tag, rtStr)
  818. if len(rtDis) == 0 {
  819. rtDis = rtStr
  820. }
  821. aDis := notifyclientI18nTable.LookupByLang(tag, aStr)
  822. if len(aDis) == 0 {
  823. aDis = aStr
  824. }
  825. resultDis := notifyclientI18nTable.LookupByLang(tag, resultStr)
  826. if len(resultDis) == 0 {
  827. resultDis = resultStr
  828. }
  829. templateParams := webhookMsg
  830. templateParams.Set("advance_days", jsonutils.NewInt(int64(no.AdvanceDays)))
  831. templateParams.Set("resource_type_display", jsonutils.NewString(rtDis))
  832. templateParams.Set("action_display", jsonutils.NewString(aDis))
  833. templateParams.Set("result_display", jsonutils.NewString(resultDis))
  834. var stemplateTitle *template.Template
  835. var stemplateContent *template.Template
  836. failedReason := []error{}
  837. switch lang {
  838. case api.TEMPLATE_LANG_CN:
  839. stemplateTitle, err = template.New("template").Parse(topic.TitleCn)
  840. if err != nil {
  841. stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
  842. failedReason = append(failedReason, errors.Wrapf(err, "parse title cn %s", topic.TitleCn))
  843. }
  844. stemplateContent, err = template.New("template").Parse(topic.ContentCn)
  845. if err != nil {
  846. stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
  847. failedReason = append(failedReason, errors.Wrapf(err, "parse content cn %s", topic.ContentCn))
  848. }
  849. case api.TEMPLATE_LANG_EN:
  850. stemplateTitle, err = template.New("template").Parse(topic.TitleEn)
  851. if err != nil {
  852. stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_EN)
  853. failedReason = append(failedReason, errors.Wrapf(err, "parse title en %s", topic.TitleEn))
  854. }
  855. stemplateContent, err = template.New("template").Parse(topic.ContentEn)
  856. if err != nil {
  857. stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
  858. failedReason = append(failedReason, errors.Wrapf(err, "parse content en %s", topic.ContentEn))
  859. }
  860. default:
  861. failedReason = append(failedReason, errors.Errorf("empty lang"))
  862. stemplateTitle, err = template.New("template").Parse(topic.TitleEn)
  863. if err != nil {
  864. stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_EN)
  865. failedReason = append(failedReason, errors.Wrapf(err, "parse topic en %s", topic.TitleEn))
  866. }
  867. stemplateContent, err = template.New("template").Parse(topic.ContentEn)
  868. if err != nil {
  869. stemplateTitle, _ = template.New("template").Parse(api.COMMON_TITLE_CN)
  870. failedReason = append(failedReason, errors.Wrapf(err, "parse content en: %s", topic.ContentEn))
  871. }
  872. }
  873. if len(failedReason) > 0 {
  874. return out, errors.NewAggregate(failedReason)
  875. }
  876. tmpTitle := strings.Builder{}
  877. tmpContent := strings.Builder{}
  878. err = stemplateTitle.Execute(&tmpTitle, templateParams.Interface())
  879. if err != nil {
  880. failedReason = append(failedReason, errors.Errorf("unable to stemplateTitle.Execute:%s", err.Error()))
  881. }
  882. err = stemplateContent.Execute(&tmpContent, templateParams.Interface())
  883. if err != nil {
  884. failedReason = append(failedReason, errors.Errorf("unable to stemplateContent.Execute:%s", err.Error()))
  885. }
  886. out.Title = html.UnescapeString(tmpTitle.String())
  887. out.Message = html.UnescapeString(tmpContent.String())
  888. return out, errors.NewAggregate(failedReason)
  889. }