template.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 notifyclient
  15. import (
  16. "context"
  17. "fmt"
  18. "html/template"
  19. "io/ioutil"
  20. "os"
  21. "path/filepath"
  22. "strings"
  23. "sync"
  24. "yunion.io/x/jsonutils"
  25. "yunion.io/x/log"
  26. "yunion.io/x/pkg/errors"
  27. "yunion.io/x/onecloud/pkg/cloudcommon/consts"
  28. "yunion.io/x/onecloud/pkg/i18n"
  29. "yunion.io/x/onecloud/pkg/mcclient"
  30. "yunion.io/x/onecloud/pkg/mcclient/auth"
  31. "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
  32. npk "yunion.io/x/onecloud/pkg/mcclient/modules/notify"
  33. )
  34. var (
  35. templatesTable map[string]*template.Template
  36. templatesTableLock *sync.Mutex
  37. notifyclientI18nTable = i18n.Table{}
  38. AdminSessionGenerator SAdminSessionGenerator = getAdminSesion
  39. UserLangFetcher SUserLangFetcher = getUserLang
  40. topicWithTemplateSet = &sync.Map{}
  41. checkTemplates bool
  42. )
  43. type SAdminSessionGenerator func(ctx context.Context, region string) (*mcclient.ClientSession, error)
  44. type SUserLangFetcher func(uids []string) (map[string]string, error)
  45. func getAdminSesion(ctx context.Context, region string) (*mcclient.ClientSession, error) {
  46. return auth.GetAdminSession(ctx, region), nil
  47. }
  48. func getUserLang(uids []string) (map[string]string, error) {
  49. s, err := AdminSessionGenerator(context.Background(), consts.GetRegion())
  50. if err != nil {
  51. return nil, err
  52. }
  53. uidLang := make(map[string]string)
  54. if len(uids) > 0 {
  55. params := jsonutils.NewDict()
  56. params.Set("filter", jsonutils.NewString(fmt.Sprintf("id.in(%s)", strings.Join(uids, ","))))
  57. params.Set("details", jsonutils.JSONFalse)
  58. params.Set("scope", jsonutils.NewString("system"))
  59. params.Set("system", jsonutils.JSONTrue)
  60. ret, err := identity.UsersV3.List(s, params)
  61. if err != nil {
  62. return nil, err
  63. }
  64. for i := range ret.Data {
  65. id, _ := ret.Data[i].GetString("id")
  66. langStr, _ := ret.Data[i].GetString("lang")
  67. uidLang[id] = langStr
  68. }
  69. }
  70. return uidLang, nil
  71. }
  72. func getRobotLang(robots []string) (map[string]string, error) {
  73. s, err := AdminSessionGenerator(context.Background(), consts.GetRegion())
  74. if err != nil {
  75. return nil, err
  76. }
  77. robotLang := make(map[string]string)
  78. if len(robots) > 0 {
  79. params := jsonutils.NewDict()
  80. params.Set("filter", jsonutils.NewString(fmt.Sprintf("id.in(%s)", strings.Join(robots, ","))))
  81. params.Set("scope", jsonutils.NewString("system"))
  82. ret, err := npk.NotifyRobot.List(s, params)
  83. if err != nil {
  84. return nil, err
  85. }
  86. for i := range ret.Data {
  87. id, _ := ret.Data[i].GetString("id")
  88. langStr, _ := ret.Data[i].GetString("lang")
  89. robotLang[id] = langStr
  90. }
  91. }
  92. return robotLang, nil
  93. }
  94. func init() {
  95. templatesTableLock = &sync.Mutex{}
  96. templatesTable = make(map[string]*template.Template)
  97. notifyclientI18nTable.Set(suffix, i18n.NewTableEntry().EN("en").CN("cn"))
  98. templatesTableLock = &sync.Mutex{}
  99. templatesTable = make(map[string]*template.Template)
  100. }
  101. func hasTemplateOfTopic(topic string) bool {
  102. if checkTemplates {
  103. _, ok := topicWithTemplateSet.Load(topic)
  104. return ok
  105. }
  106. path := filepath.Join(consts.NotifyTemplateDir, consts.GetServiceType(), "content@cn")
  107. fileInfoList, err := ioutil.ReadDir(path)
  108. if err != nil {
  109. if os.IsNotExist(err) {
  110. checkTemplates = true
  111. return false
  112. }
  113. log.Errorf("unable to read dir %s", path)
  114. return false
  115. }
  116. for i := range fileInfoList {
  117. topicWithTemplateSet.Store(fileInfoList[i].Name(), nil)
  118. }
  119. checkTemplates = true
  120. _, ok := topicWithTemplateSet.Load(topic)
  121. return ok
  122. }
  123. func getTemplateString(suffix string, topic string, contType string, channel npk.TNotifyChannel) ([]byte, error) {
  124. contType = contType + "@" + suffix
  125. if len(channel) > 0 {
  126. path := filepath.Join(consts.NotifyTemplateDir, consts.GetServiceType(), contType, fmt.Sprintf("%s.%s", topic, string(channel)))
  127. cont, err := ioutil.ReadFile(path)
  128. if err == nil {
  129. return cont, nil
  130. }
  131. }
  132. path := filepath.Join(consts.NotifyTemplateDir, consts.GetServiceType(), contType, topic)
  133. return ioutil.ReadFile(path)
  134. }
  135. func getTemplate(langSuffix string, topic string, contType string, channel npk.TNotifyChannel, templateFuncs template.FuncMap) (*template.Template, error) {
  136. key := fmt.Sprintf("%s.%s.%s@%s", topic, contType, channel, langSuffix)
  137. templatesTableLock.Lock()
  138. defer templatesTableLock.Unlock()
  139. if _, ok := templatesTable[key]; !ok {
  140. cont, err := getTemplateString(langSuffix, topic, contType, channel)
  141. if err != nil {
  142. return nil, err
  143. }
  144. tmp := template.New(key)
  145. funcMap := template.FuncMap{
  146. "unescaped": unescaped,
  147. }
  148. // 合并自定义模板函数,并将 langSuffix 绑定到函数中
  149. for k, v := range templateFuncs {
  150. // 检查是否是 LangSuffixFunc 类型(需要 langSuffix 的函数)
  151. if langFunc, ok := v.(LangSuffixFunc); ok {
  152. // 创建一个闭包,将 langSuffix 绑定到函数中
  153. funcMap[k] = func(data interface{}) string {
  154. return langFunc.Call(langSuffix, data)
  155. }
  156. } else {
  157. // 否则直接使用原函数(标准模板函数)
  158. funcMap[k] = v
  159. }
  160. }
  161. tmp.Funcs(funcMap)
  162. tmp, err = tmp.Parse(string(cont))
  163. if err != nil {
  164. return nil, err
  165. }
  166. templatesTable[key] = tmp
  167. }
  168. return templatesTable[key], nil
  169. }
  170. func getContent(langSuffix string, topic string, contType string, channel npk.TNotifyChannel, data jsonutils.JSONObject, templateFuncs template.FuncMap) (string, error) {
  171. if channel == npk.NotifyByWebhook && !hasTemplateOfTopic(topic) {
  172. return "", nil
  173. }
  174. tmpl, err := getTemplate(langSuffix, topic, contType, channel, templateFuncs)
  175. if err != nil {
  176. return "", errors.Wrapf(err, "getTemplate %s %s %s %s %s", langSuffix, topic, contType, channel, data)
  177. }
  178. buf := strings.Builder{}
  179. err = tmpl.Execute(&buf, data.Interface())
  180. if err != nil {
  181. return "", errors.Wrapf(err, "tmpl.Execute %s %s %s %s %s", langSuffix, topic, contType, channel, data)
  182. }
  183. // log.Debugf("notify.getContent %s %s %s %s", topic, contType, data, buf.String())
  184. return buf.String(), nil
  185. }
  186. func unescaped(str string) template.HTML {
  187. return template.HTML(str)
  188. }
  189. // LangSuffixFunc 是需要 langSuffix 参数的模板函数接口
  190. // 实现此接口的函数可以在 getTemplate 中接收 langSuffix 参数
  191. type LangSuffixFunc interface {
  192. Call(langSuffix string, data interface{}) string
  193. }
  194. const (
  195. suffix = "suffix"
  196. )