content_moderation_email.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. package service
  2. import (
  3. "fmt"
  4. "html"
  5. "strings"
  6. "time"
  7. )
  8. func buildContentModerationViolationEmailBody(siteName string, log *ContentModerationLog, cfg *ContentModerationConfig) string {
  9. if log == nil {
  10. return ""
  11. }
  12. userName := strings.TrimSpace(log.UserEmail)
  13. if userName == "" && log.UserID != nil {
  14. userName = fmt.Sprintf("UID %d", *log.UserID)
  15. }
  16. threshold := cfg.BanThreshold
  17. if threshold <= 0 {
  18. threshold = defaultContentModerationBanThreshold
  19. }
  20. statusBlock := ""
  21. if log.AutoBanned {
  22. statusBlock = `<div style="margin-top:24px;padding:18px 20px;border-radius:10px;background:#ff3b30;color:#fff;font-size:18px;font-weight:700;text-align:center;line-height:1.6;">账户当前处于封禁状态,所有 API 请求将被拒绝</div>`
  23. }
  24. return fmt.Sprintf(`<!doctype html>
  25. <html>
  26. <body style="margin:0;padding:0;background:#f5f6fb;color:#222;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">
  27. <div style="max-width:680px;margin:0 auto;padding:32px 20px;">
  28. <div style="height:8px;background:#ef4444;border-radius:14px 14px 0 0;"></div>
  29. <div style="background:#fff;border-radius:0 0 14px 14px;padding:40px 48px;box-shadow:0 8px 28px rgba(15,23,42,.08);">
  30. <div style="letter-spacing:4px;color:#999;font-size:14px;text-transform:uppercase;">Risk Control / 风控提醒</div>
  31. <h1 style="margin:20px 0 28px;font-size:30px;line-height:1.25;">账户触发内容审计规则</h1>
  32. <p style="font-size:17px;line-height:1.9;margin:0 0 24px;">尊敬的用户 <strong>%s</strong>,您的 API 请求在内容审计中触发平台风控策略。详情如下。</p>
  33. <div style="background:#fff1f2;border:1px solid #fecdd3;border-radius:12px;padding:22px 28px;margin:28px 0;">
  34. <h2 style="margin:0 0 18px;color:#b91c1c;font-size:18px;">触发详情</h2>
  35. <table style="width:100%%;border-collapse:collapse;font-size:16px;">
  36. <tr><td style="padding:12px 0;color:#888;border-bottom:1px solid #fee2e2;">触发时间</td><td style="padding:12px 0;border-bottom:1px solid #fee2e2;">%s</td></tr>
  37. <tr><td style="padding:12px 0;color:#888;border-bottom:1px solid #fee2e2;">触发来源</td><td style="padding:12px 0;border-bottom:1px solid #fee2e2;">内容审核</td></tr>
  38. <tr><td style="padding:12px 0;color:#888;border-bottom:1px solid #fee2e2;">所属分组</td><td style="padding:12px 0;border-bottom:1px solid #fee2e2;">%s</td></tr>
  39. <tr><td style="padding:12px 0;color:#888;border-bottom:1px solid #fee2e2;">命中类别</td><td style="padding:12px 0;border-bottom:1px solid #fee2e2;">%s / %.3f</td></tr>
  40. <tr><td style="padding:12px 0;color:#888;">累计触发次数</td><td style="padding:12px 0;color:#dc2626;font-weight:700;">%d 次(阈值 %d)</td></tr>
  41. </table>
  42. </div>
  43. %s
  44. <p style="font-size:14px;line-height:1.8;color:#777;margin-top:28px;">此邮件由 %s 自动发送,请勿回复。</p>
  45. </div>
  46. </div>
  47. </body>
  48. </html>`,
  49. html.EscapeString(userName),
  50. html.EscapeString(time.Now().Format("2006-01-02 15:04:05")),
  51. html.EscapeString(defaultContentModerationString(log.GroupName, "-")),
  52. html.EscapeString(defaultContentModerationString(log.HighestCategory, "-")),
  53. log.HighestScore,
  54. log.ViolationCount,
  55. threshold,
  56. statusBlock,
  57. html.EscapeString(siteName),
  58. )
  59. }
  60. func buildContentModerationAccountDisabledEmailBody(siteName string, log *ContentModerationLog, cfg *ContentModerationConfig) string {
  61. if log == nil {
  62. return ""
  63. }
  64. userName := strings.TrimSpace(log.UserEmail)
  65. if userName == "" && log.UserID != nil {
  66. userName = fmt.Sprintf("UID %d", *log.UserID)
  67. }
  68. threshold := cfg.BanThreshold
  69. if threshold <= 0 {
  70. threshold = defaultContentModerationBanThreshold
  71. }
  72. return fmt.Sprintf(`<!doctype html>
  73. <html>
  74. <body style="margin:0;padding:0;background:#f5f6fb;color:#222;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">
  75. <div style="max-width:680px;margin:0 auto;padding:32px 20px;">
  76. <div style="height:8px;background:#ef4444;border-radius:14px 14px 0 0;"></div>
  77. <div style="background:#fff;border-radius:0 0 14px 14px;padding:40px 48px;box-shadow:0 8px 28px rgba(15,23,42,.08);">
  78. <div style="letter-spacing:4px;color:#999;font-size:14px;text-transform:uppercase;">Risk Control / 账户封禁</div>
  79. <h1 style="margin:20px 0 28px;font-size:30px;line-height:1.25;">账户已被自动禁用</h1>
  80. <p style="font-size:17px;line-height:1.9;margin:0 0 24px;">尊敬的用户 <strong>%s</strong>,您的账户在计数周期内多次触发平台风控策略,系统已自动禁用该账户。详情如下。</p>
  81. <div style="background:#fff1f2;border:1px solid #fecdd3;border-radius:12px;padding:22px 28px;margin:28px 0;">
  82. <h2 style="margin:0 0 18px;color:#b91c1c;font-size:18px;">封禁详情</h2>
  83. <table style="width:100%%;border-collapse:collapse;font-size:16px;">
  84. <tr><td style="padding:12px 0;color:#888;border-bottom:1px solid #fee2e2;">封禁时间</td><td style="padding:12px 0;border-bottom:1px solid #fee2e2;">%s</td></tr>
  85. <tr><td style="padding:12px 0;color:#888;border-bottom:1px solid #fee2e2;">触发来源</td><td style="padding:12px 0;border-bottom:1px solid #fee2e2;">内容审核</td></tr>
  86. <tr><td style="padding:12px 0;color:#888;border-bottom:1px solid #fee2e2;">所属分组</td><td style="padding:12px 0;border-bottom:1px solid #fee2e2;">%s</td></tr>
  87. <tr><td style="padding:12px 0;color:#888;border-bottom:1px solid #fee2e2;">命中类别</td><td style="padding:12px 0;border-bottom:1px solid #fee2e2;">%s / %.3f</td></tr>
  88. <tr><td style="padding:12px 0;color:#888;">累计触发次数</td><td style="padding:12px 0;color:#dc2626;font-weight:700;">%d 次(阈值 %d)</td></tr>
  89. </table>
  90. </div>
  91. <div style="margin-top:24px;padding:18px 20px;border-radius:10px;background:#ff3b30;color:#fff;font-size:18px;font-weight:700;text-align:center;line-height:1.6;">账户当前处于封禁状态,所有 API 请求将被拒绝</div>
  92. <p style="font-size:15px;line-height:1.8;color:#666;margin-top:24px;">如需申诉或恢复账号,请联系平台管理员处理。</p>
  93. <p style="font-size:14px;line-height:1.8;color:#777;margin-top:28px;">此邮件由 %s 自动发送,请勿回复。</p>
  94. </div>
  95. </div>
  96. </body>
  97. </html>`,
  98. html.EscapeString(userName),
  99. html.EscapeString(time.Now().Format("2006-01-02 15:04:05")),
  100. html.EscapeString(defaultContentModerationString(log.GroupName, "-")),
  101. html.EscapeString(defaultContentModerationString(log.HighestCategory, "-")),
  102. log.HighestScore,
  103. log.ViolationCount,
  104. threshold,
  105. html.EscapeString(siteName),
  106. )
  107. }
  108. func defaultContentModerationString(value string, fallback string) string {
  109. if strings.TrimSpace(value) == "" {
  110. return fallback
  111. }
  112. return strings.TrimSpace(value)
  113. }
  114. // buildCyberPolicyNoticeEmailBody 是 cyber_policy 通知邮件的内置兜底正文,
  115. // 当 notification email 模板渲染失败时使用(与 sendViolationEmail 的兜底同理)。
  116. func buildCyberPolicyNoticeEmailBody(siteName string, log *ContentModerationLog) string {
  117. if log == nil {
  118. return ""
  119. }
  120. userName := strings.TrimSpace(log.UserEmail)
  121. if userName == "" && log.UserID != nil {
  122. userName = fmt.Sprintf("UID %d", *log.UserID)
  123. }
  124. return fmt.Sprintf(`<!doctype html>
  125. <html><body style="margin:0;padding:0;background:#f5f6fb;color:#222;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif;">
  126. <div style="max-width:680px;margin:0 auto;padding:32px 20px;">
  127. <div style="height:8px;background:#ef4444;border-radius:14px 14px 0 0;"></div>
  128. <div style="background:#fff;border-radius:0 0 14px 14px;padding:40px 48px;box-shadow:0 8px 28px rgba(15,23,42,.08);">
  129. <div style="letter-spacing:4px;color:#999;font-size:14px;text-transform:uppercase;">Risk Control / 网络安全策略</div>
  130. <h1 style="margin:20px 0 28px;font-size:30px;line-height:1.25;">请求被网络安全策略拦截</h1>
  131. <p style="font-size:17px;line-height:1.9;margin:0 0 24px;">尊敬的用户 <strong>%s</strong>,您的请求被上游网络安全策略(cyber policy)拦截。</p>
  132. <div style="background:#fff1f2;border:1px solid #fecdd3;border-radius:12px;padding:22px 28px;margin:28px 0;">
  133. <table style="width:100%%;border-collapse:collapse;font-size:16px;">
  134. <tr><td style="padding:12px 0;color:#888;border-bottom:1px solid #fee2e2;">触发时间</td><td style="padding:12px 0;border-bottom:1px solid #fee2e2;">%s</td></tr>
  135. <tr><td style="padding:12px 0;color:#888;border-bottom:1px solid #fee2e2;">模型</td><td style="padding:12px 0;border-bottom:1px solid #fee2e2;">%s</td></tr>
  136. <tr><td style="padding:12px 0;color:#888;">上游说明</td><td style="padding:12px 0;">%s</td></tr>
  137. </table>
  138. </div>
  139. <p style="font-size:15px;line-height:1.8;color:#666;">如认为系误判,可调整请求措辞后重试,或申请获得授权的安全访问权限。</p>
  140. <p style="font-size:14px;line-height:1.8;color:#777;margin-top:28px;">此邮件由 %s 自动发送,请勿回复。</p>
  141. </div>
  142. </div>
  143. </body></html>`,
  144. html.EscapeString(userName),
  145. html.EscapeString(log.CreatedAt.Format("2006-01-02 15:04:05")),
  146. html.EscapeString(defaultContentModerationString(log.Model, "-")),
  147. html.EscapeString(defaultContentModerationString(log.Error, "-")),
  148. html.EscapeString(siteName),
  149. )
  150. }