| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package sender
- import (
- "context"
- "fmt"
- "net/http"
- "net/url"
- "yunion.io/x/jsonutils"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/httputils"
- api "yunion.io/x/onecloud/pkg/apis/notify"
- "yunion.io/x/onecloud/pkg/monitor/notifydrivers/feishu"
- "yunion.io/x/onecloud/pkg/notify/models"
- )
- type SFeishuSender struct {
- config map[string]api.SNotifyConfigContent
- }
- func (self *SFeishuSender) GetSenderType() string {
- return api.FEISHU
- }
- const ApiSendMessageForFeishuByOpenId = feishu.ApiRobotSendMessage + "?receive_id_type=open_id"
- // 发送飞书消息
- func (feishuSender *SFeishuSender) Send(ctx context.Context, args api.SendParams) error {
- // 发送通知消息体
- body := map[string]interface{}{
- "open_id": args.Receivers.Contact,
- "msg_type": "text",
- "content": map[string]interface{}{
- "text": args.Message,
- },
- } // 添加bearer token请求头
- header := http.Header{}
- header.Add("Authorization", fmt.Sprintf("Bearer %s", models.ConfigMap[fmt.Sprintf("%s-%s", api.FEISHU, args.DomainId)].Content.AccessToken))
- rep, err := sendRequest(ctx, ApiSendMessageForFeishuByOpenId, httputils.POST, header, nil, jsonutils.Marshal(body))
- if err == nil {
- return nil
- }
- if rep == nil {
- return errors.Wrap(err, "feishu.sendRequest")
- }
- // 发送通知失败的情况
- code, err := rep.GetString("code")
- if err != nil {
- return err
- }
- switch code {
- case "99991663": //token过期
- err = feishuSender.GetAccessToken(ctx, fmt.Sprintf("%s-%s", api.FEISHU, args.DomainId))
- if err != nil {
- return errors.Wrap(err, "tenant token invalid && getToken err")
- }
- header.Set("Authorization", fmt.Sprintf("Bearer %s", models.ConfigMap[fmt.Sprintf("%s-%s", api.FEISHU, args.DomainId)].Content.AccessToken))
- _, err = sendRequest(ctx, ApiSendMessageForFeishuByOpenId, httputils.POST, header, nil, jsonutils.Marshal(body))
- if err == nil {
- return nil
- }
- case "99991672": // 未开放发送消息通知的权限
- err = errors.Wrap(ErrIncompleteConfig, err.Error())
- }
- return err
- }
- // 校验appId与appSecret
- func (feishuSender *SFeishuSender) ValidateConfig(ctx context.Context, config api.NotifyConfig) (string, error) {
- rep, err := feishuSender.getAccessToken(config.AppId, config.AppSecret)
- if err == nil {
- return "", nil
- }
- var msg string
- switch rep.Code {
- case 10003:
- msg = "invalid AppId"
- case 10014:
- msg = "invalid AppSecret"
- }
- return msg, err
- }
- // 根据用户手机号获取用户的open_id
- func (feishuSender *SFeishuSender) ContactByMobile(ctx context.Context, mobile, domainId string) (string, error) {
- body := jsonutils.NewDict()
- body.Set("mobiles", jsonutils.NewArray(jsonutils.NewString(mobile)))
- header := http.Header{}
- // 考虑到获取用户id需求较少,可通过直接更新token来避免token失效
- err := feishuSender.GetAccessToken(ctx, domainId)
- if err != nil {
- return "", errors.Wrap(err, "GetAccessToken")
- }
- params := url.Values{}
- params.Set("mobiles", mobile)
- header.Set("Authorization", fmt.Sprintf("Bearer %s", models.ConfigMap[fmt.Sprintf("%s-%s", api.FEISHU, domainId)].Content.AccessToken))
- resp, err := sendRequest(ctx, ApiFetchUserID, httputils.GET, header, params, body)
- if err != nil {
- return "", err
- }
- mobileNotExist, _ := resp.GetArray("data", "mobiles_not_exist")
- if len(mobileNotExist) != 0 {
- return "", errors.Wrapf(errors.ErrNotFound, "no such user whose mobile is %s", mobile)
- }
- list, err := resp.GetArray("data", "mobile_users", mobile)
- if err != nil {
- return "", errors.Wrap(err, "jsonutils.JSONObject.GetArray")
- }
- // len(list) must be positive
- userId, err := list[0].GetString("open_id")
- if err != nil {
- return "", errors.Wrapf(err, "user result:%v", resp)
- }
- return userId, nil
- }
- func (feishuSender *SFeishuSender) IsPersonal() bool {
- return true
- }
- func (feishuSender *SFeishuSender) IsRobot() bool {
- return false
- }
- func (feishuSender *SFeishuSender) IsValid() bool {
- return len(feishuSender.config) > 0
- }
- func (feishuSender *SFeishuSender) IsPullType() bool {
- return true
- }
- func (feishuSender *SFeishuSender) IsSystemConfigContactType() bool {
- return true
- }
- func (feishuSender *SFeishuSender) RegisterConfig(config models.SConfig) {
- models.ConfigMap[fmt.Sprintf("%s-%s", config.Type, config.DomainId)] = config
- }
- // 获取token
- func (feishuSender *SFeishuSender) GetAccessToken(ctx context.Context, domainId string) error {
- key := fmt.Sprintf("%s-%s", api.FEISHU, domainId)
- if _, ok := models.ConfigMap[key]; !ok {
- return errors.Wrapf(errors.ErrNotSupported, "contact-type:%s,domain_id:%s is missing config", api.FEISHU, domainId)
- }
- appId, appSecret := models.ConfigMap[key].Content.AppId, models.ConfigMap[key].Content.AppSecret
- resp, err := feishuSender.getAccessToken(appId, appSecret)
- if err != nil {
- return errors.Wrap(err, "get accessToken")
- }
- models.ConfigMap[key].Content.AccessToken = resp.TenantAccessToken
- return nil
- }
- func (feishuSender *SFeishuSender) getAccessToken(appId, appSecret string) (*feishu.TenantAccesstokenResp, error) {
- resp, err := feishu.GetTenantAccessTokenInternal(appId, appSecret)
- if err != nil {
- return resp, errors.Wrap(err, "feishu.GetTenantAccessTokenInternal")
- }
- return resp, nil
- }
- func init() {
- models.Register(&SFeishuSender{
- config: map[string]api.SNotifyConfigContent{},
- })
- }
|