| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- // 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 notifyclient
- import (
- "context"
- "fmt"
- "html/template"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "sync"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/onecloud/pkg/cloudcommon/consts"
- "yunion.io/x/onecloud/pkg/i18n"
- "yunion.io/x/onecloud/pkg/mcclient"
- "yunion.io/x/onecloud/pkg/mcclient/auth"
- "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
- npk "yunion.io/x/onecloud/pkg/mcclient/modules/notify"
- )
- var (
- templatesTable map[string]*template.Template
- templatesTableLock *sync.Mutex
- notifyclientI18nTable = i18n.Table{}
- AdminSessionGenerator SAdminSessionGenerator = getAdminSesion
- UserLangFetcher SUserLangFetcher = getUserLang
- topicWithTemplateSet = &sync.Map{}
- checkTemplates bool
- )
- type SAdminSessionGenerator func(ctx context.Context, region string) (*mcclient.ClientSession, error)
- type SUserLangFetcher func(uids []string) (map[string]string, error)
- func getAdminSesion(ctx context.Context, region string) (*mcclient.ClientSession, error) {
- return auth.GetAdminSession(ctx, region), nil
- }
- func getUserLang(uids []string) (map[string]string, error) {
- s, err := AdminSessionGenerator(context.Background(), consts.GetRegion())
- if err != nil {
- return nil, err
- }
- uidLang := make(map[string]string)
- if len(uids) > 0 {
- params := jsonutils.NewDict()
- params.Set("filter", jsonutils.NewString(fmt.Sprintf("id.in(%s)", strings.Join(uids, ","))))
- params.Set("details", jsonutils.JSONFalse)
- params.Set("scope", jsonutils.NewString("system"))
- params.Set("system", jsonutils.JSONTrue)
- ret, err := identity.UsersV3.List(s, params)
- if err != nil {
- return nil, err
- }
- for i := range ret.Data {
- id, _ := ret.Data[i].GetString("id")
- langStr, _ := ret.Data[i].GetString("lang")
- uidLang[id] = langStr
- }
- }
- return uidLang, nil
- }
- func getRobotLang(robots []string) (map[string]string, error) {
- s, err := AdminSessionGenerator(context.Background(), consts.GetRegion())
- if err != nil {
- return nil, err
- }
- robotLang := make(map[string]string)
- if len(robots) > 0 {
- params := jsonutils.NewDict()
- params.Set("filter", jsonutils.NewString(fmt.Sprintf("id.in(%s)", strings.Join(robots, ","))))
- params.Set("scope", jsonutils.NewString("system"))
- ret, err := npk.NotifyRobot.List(s, params)
- if err != nil {
- return nil, err
- }
- for i := range ret.Data {
- id, _ := ret.Data[i].GetString("id")
- langStr, _ := ret.Data[i].GetString("lang")
- robotLang[id] = langStr
- }
- }
- return robotLang, nil
- }
- func init() {
- templatesTableLock = &sync.Mutex{}
- templatesTable = make(map[string]*template.Template)
- notifyclientI18nTable.Set(suffix, i18n.NewTableEntry().EN("en").CN("cn"))
- templatesTableLock = &sync.Mutex{}
- templatesTable = make(map[string]*template.Template)
- }
- func hasTemplateOfTopic(topic string) bool {
- if checkTemplates {
- _, ok := topicWithTemplateSet.Load(topic)
- return ok
- }
- path := filepath.Join(consts.NotifyTemplateDir, consts.GetServiceType(), "content@cn")
- fileInfoList, err := ioutil.ReadDir(path)
- if err != nil {
- if os.IsNotExist(err) {
- checkTemplates = true
- return false
- }
- log.Errorf("unable to read dir %s", path)
- return false
- }
- for i := range fileInfoList {
- topicWithTemplateSet.Store(fileInfoList[i].Name(), nil)
- }
- checkTemplates = true
- _, ok := topicWithTemplateSet.Load(topic)
- return ok
- }
- func getTemplateString(suffix string, topic string, contType string, channel npk.TNotifyChannel) ([]byte, error) {
- contType = contType + "@" + suffix
- if len(channel) > 0 {
- path := filepath.Join(consts.NotifyTemplateDir, consts.GetServiceType(), contType, fmt.Sprintf("%s.%s", topic, string(channel)))
- cont, err := ioutil.ReadFile(path)
- if err == nil {
- return cont, nil
- }
- }
- path := filepath.Join(consts.NotifyTemplateDir, consts.GetServiceType(), contType, topic)
- return ioutil.ReadFile(path)
- }
- func getTemplate(langSuffix string, topic string, contType string, channel npk.TNotifyChannel, templateFuncs template.FuncMap) (*template.Template, error) {
- key := fmt.Sprintf("%s.%s.%s@%s", topic, contType, channel, langSuffix)
- templatesTableLock.Lock()
- defer templatesTableLock.Unlock()
- if _, ok := templatesTable[key]; !ok {
- cont, err := getTemplateString(langSuffix, topic, contType, channel)
- if err != nil {
- return nil, err
- }
- tmp := template.New(key)
- funcMap := template.FuncMap{
- "unescaped": unescaped,
- }
- // 合并自定义模板函数,并将 langSuffix 绑定到函数中
- for k, v := range templateFuncs {
- // 检查是否是 LangSuffixFunc 类型(需要 langSuffix 的函数)
- if langFunc, ok := v.(LangSuffixFunc); ok {
- // 创建一个闭包,将 langSuffix 绑定到函数中
- funcMap[k] = func(data interface{}) string {
- return langFunc.Call(langSuffix, data)
- }
- } else {
- // 否则直接使用原函数(标准模板函数)
- funcMap[k] = v
- }
- }
- tmp.Funcs(funcMap)
- tmp, err = tmp.Parse(string(cont))
- if err != nil {
- return nil, err
- }
- templatesTable[key] = tmp
- }
- return templatesTable[key], nil
- }
- func getContent(langSuffix string, topic string, contType string, channel npk.TNotifyChannel, data jsonutils.JSONObject, templateFuncs template.FuncMap) (string, error) {
- if channel == npk.NotifyByWebhook && !hasTemplateOfTopic(topic) {
- return "", nil
- }
- tmpl, err := getTemplate(langSuffix, topic, contType, channel, templateFuncs)
- if err != nil {
- return "", errors.Wrapf(err, "getTemplate %s %s %s %s %s", langSuffix, topic, contType, channel, data)
- }
- buf := strings.Builder{}
- err = tmpl.Execute(&buf, data.Interface())
- if err != nil {
- return "", errors.Wrapf(err, "tmpl.Execute %s %s %s %s %s", langSuffix, topic, contType, channel, data)
- }
- // log.Debugf("notify.getContent %s %s %s %s", topic, contType, data, buf.String())
- return buf.String(), nil
- }
- func unescaped(str string) template.HTML {
- return template.HTML(str)
- }
- // LangSuffixFunc 是需要 langSuffix 参数的模板函数接口
- // 实现此接口的函数可以在 getTemplate 中接收 langSuffix 参数
- type LangSuffixFunc interface {
- Call(langSuffix string, data interface{}) string
- }
- const (
- suffix = "suffix"
- )
|