workwx.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  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 sender
  15. import (
  16. "context"
  17. "fmt"
  18. "net/url"
  19. "strings"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/util/httputils"
  25. api "yunion.io/x/onecloud/pkg/apis/notify"
  26. "yunion.io/x/onecloud/pkg/notify/models"
  27. )
  28. type SWorkwxSender struct {
  29. config map[string]api.SNotifyConfigContent
  30. }
  31. func (workwxSender *SWorkwxSender) GetSenderType() string {
  32. return api.WORKWX
  33. }
  34. func (workwxSender *SWorkwxSender) Send(ctx context.Context, args api.SendParams) error {
  35. body := map[string]interface{}{
  36. "agentid": models.ConfigMap[fmt.Sprintf("%s-%s", api.WORKWX, args.DomainId)].Content.AgentId,
  37. "msgtype": "markdown",
  38. "markdown": map[string]interface{}{
  39. "content": fmt.Sprintf("# %s\n\n%s", args.Title, args.Message),
  40. },
  41. "touser": args.Receivers.Contact,
  42. }
  43. err := workwxSender.GetAccessToken(ctx, args.DomainId)
  44. if err != nil {
  45. return errors.Wrap(err, "GetAccessToken")
  46. }
  47. resp, err := workwxSender.sendMessageWithToken(ctx, ApiWorkwxSendMessage, fmt.Sprintf("%s-%s", api.WORKWX, args.DomainId), jsonutils.Marshal(body))
  48. if err != nil {
  49. return errors.Wrap(err, "sendMessageWithToken")
  50. }
  51. result := api.SWorkwxSendMessageResp{}
  52. resp.Unmarshal(&result)
  53. if result.ErrCode == 0 {
  54. return nil
  55. }
  56. return errors.Errorf("%s", resp.String())
  57. }
  58. func (workwxSender *SWorkwxSender) ValidateConfig(ctx context.Context, config api.NotifyConfig) (string, error) {
  59. // 校验accesstoken
  60. _, _, err := workwxSender.getAccessToken(ctx, config.CorpId, config.Secret)
  61. if err != nil {
  62. switch {
  63. case strings.Contains(err.Error(), "40013"):
  64. return "invalid corpid", nil
  65. case strings.Contains(err.Error(), "40001"):
  66. return "invalid corpsecret", nil
  67. }
  68. return "", err
  69. }
  70. return "", nil
  71. }
  72. func (workwxSender *SWorkwxSender) ContactByMobile(ctx context.Context, mobile, domainId string) (string, error) {
  73. err := workwxSender.GetAccessToken(ctx, domainId)
  74. if err != nil {
  75. return "", err
  76. }
  77. body := jsonutils.Marshal(map[string]interface{}{
  78. "mobile": mobile,
  79. })
  80. res, err := workwxSender.sendMessageWithToken(ctx, ApiWorkwxGetUserByMobile, fmt.Sprintf("%s-%s", api.WORKWX, domainId), jsonutils.Marshal(body))
  81. if err != nil {
  82. return "", errors.Wrap(err, "get user by mobile")
  83. }
  84. userId, err := res.GetString("userid")
  85. if err != nil {
  86. return "", errors.Wrapf(err, "user result:%v", res)
  87. }
  88. return userId, nil
  89. }
  90. func (workwxSender *SWorkwxSender) IsPersonal() bool {
  91. return true
  92. }
  93. func (workwxSender *SWorkwxSender) IsRobot() bool {
  94. return false
  95. }
  96. func (workwxSender *SWorkwxSender) IsValid() bool {
  97. return len(workwxSender.config) > 0
  98. }
  99. func (workwxSender *SWorkwxSender) IsPullType() bool {
  100. return true
  101. }
  102. func (workwxSender *SWorkwxSender) IsSystemConfigContactType() bool {
  103. return true
  104. }
  105. func (workwxSender *SWorkwxSender) RegisterConfig(config models.SConfig) {
  106. models.ConfigMap[fmt.Sprintf("%s-%s", config.Type, config.DomainId)] = config
  107. }
  108. func (workwxSender *SWorkwxSender) GetAccessToken(ctx context.Context, domainId string) error {
  109. key := fmt.Sprintf("%s-%s", api.WORKWX, domainId)
  110. if _, ok := models.ConfigMap[key]; !ok {
  111. return errors.Wrapf(errors.ErrNotSupported, "contact-type:%s,domain_id:%s is missing config", api.WORKWX, domainId)
  112. }
  113. if len(models.ConfigMap[key].Content.AccessToken) > 0 && models.ConfigMap[key].Content.AccessTokenExpireTime.After(time.Now()) {
  114. log.Debugf("workwx access token is valid %s expire time %s", key, models.ConfigMap[key].Content.AccessTokenExpireTime.Format(time.RFC3339))
  115. return nil
  116. }
  117. corpId, secret := models.ConfigMap[key].Content.CorpId, models.ConfigMap[key].Content.Secret
  118. token, expireTime, err := workwxSender.getAccessToken(ctx, corpId, secret)
  119. if err != nil {
  120. return errors.Wrap(err, "workwx getAccessToken")
  121. }
  122. models.ConfigMap[key].Content.AccessToken = token
  123. models.ConfigMap[key].Content.AccessTokenExpireTime = expireTime
  124. log.Debugf("workwx access token is valid %s expire time %s", key, models.ConfigMap[key].Content.AccessTokenExpireTime.Format(time.RFC3339))
  125. return nil
  126. }
  127. func (workwxSender *SWorkwxSender) getAccessToken(ctx context.Context, corpId, secret string) (string, time.Time, error) {
  128. params := url.Values{}
  129. params.Set("corpid", corpId)
  130. params.Set("corpsecret", secret)
  131. res, err := sendRequest(ctx, ApiWorkwxGetToken, httputils.GET, nil, params, nil)
  132. if err != nil {
  133. return "", time.Time{}, errors.Wrap(err, "get workwx token")
  134. }
  135. info := struct {
  136. AccessToken string `json:"access_token"`
  137. ExpiresIn int `json:"expires_in"`
  138. }{}
  139. err = res.Unmarshal(&info)
  140. if err != nil {
  141. return "", time.Time{}, errors.Wrap(err, "get workwx token")
  142. }
  143. if len(info.AccessToken) == 0 {
  144. return "", time.Time{}, errors.Wrapf(errors.ErrNotFound, "get workwx token %s", res.String())
  145. }
  146. expireTime := time.Now().Add(time.Duration(info.ExpiresIn) * time.Second)
  147. return info.AccessToken, expireTime, nil
  148. }
  149. func (workwxSender *SWorkwxSender) sendMessageWithToken(ctx context.Context, uri, key string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  150. params := url.Values{}
  151. if _, ok := models.ConfigMap[key]; !ok {
  152. return nil, errors.Wrapf(errors.ErrNotSupported, "contact-type:%s,domain_id:%s is missing config", strings.Split(key, "-")[0], strings.Split(key, "-")[1])
  153. }
  154. params.Set("access_token", models.ConfigMap[key].Content.AccessToken)
  155. return sendRequest(ctx, uri, httputils.POST, nil, params, jsonutils.Marshal(body))
  156. }
  157. func init() {
  158. models.Register(&SWorkwxSender{
  159. config: map[string]api.SNotifyConfigContent{},
  160. })
  161. }