email_notification_config_service.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. package service
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/2930134478/AI-CS/backend/models"
  10. "github.com/2930134478/AI-CS/backend/repository"
  11. "github.com/2930134478/AI-CS/backend/utils"
  12. )
  13. // EmailNotificationConfigResult 返回给前端的配置(密码脱敏)
  14. type EmailNotificationConfigResult struct {
  15. ID uint `json:"id"`
  16. Enabled bool `json:"enabled"`
  17. SMTPHost string `json:"smtp_host"`
  18. SMTPPort int `json:"smtp_port"`
  19. SMTPUser string `json:"smtp_user"`
  20. SMTPPasswordMasked string `json:"smtp_password_masked"`
  21. FromEmail string `json:"from_email"`
  22. FromName string `json:"from_name"`
  23. OfflineDelaySeconds int `json:"offline_delay_seconds"`
  24. EffectiveEnabled bool `json:"effective_enabled"`
  25. EffectiveDelaySeconds int `json:"effective_delay_seconds"`
  26. PersistedInDatabase bool `json:"persisted_in_database"`
  27. EnvEnabled bool `json:"env_enabled"`
  28. EnvDelaySeconds int `json:"env_delay_seconds"`
  29. UpdatedAt time.Time `json:"updated_at,omitempty"`
  30. }
  31. // UpdateEmailNotificationConfigInput 更新离线邮件配置
  32. type UpdateEmailNotificationConfigInput struct {
  33. Enabled *bool
  34. SMTPHost *string
  35. SMTPPort *int
  36. SMTPUser *string
  37. SMTPPassword *string
  38. FromEmail *string
  39. FromName *string
  40. OfflineDelaySeconds *int
  41. }
  42. // ResolvedSMTPConfig 实际发信用的 SMTP 配置
  43. type ResolvedSMTPConfig struct {
  44. Enabled bool
  45. SMTPHost string
  46. SMTPPort int
  47. SMTPUser string
  48. SMTPPassword string
  49. FromEmail string
  50. FromName string
  51. OfflineDelaySeconds int
  52. }
  53. // EmailNotificationConfigService 离线邮件配置服务
  54. type EmailNotificationConfigService struct {
  55. repo *repository.EmailNotificationConfigRepository
  56. userRepo *repository.UserRepository
  57. }
  58. // NewEmailNotificationConfigService 创建服务实例
  59. func NewEmailNotificationConfigService(
  60. repo *repository.EmailNotificationConfigRepository,
  61. userRepo *repository.UserRepository,
  62. ) *EmailNotificationConfigService {
  63. return &EmailNotificationConfigService{repo: repo, userRepo: userRepo}
  64. }
  65. func emailNotificationEnabledFromEnv() bool {
  66. v := strings.ToLower(strings.TrimSpace(os.Getenv("OFFLINE_EMAIL_ENABLED")))
  67. return v == "1" || v == "true" || v == "yes"
  68. }
  69. func offlineEmailDelaySecondsFromEnv() int {
  70. sec := 60
  71. if v := os.Getenv("OFFLINE_EMAIL_DELAY_SECONDS"); v != "" {
  72. if parsed, err := strconv.Atoi(v); err == nil && parsed >= 0 {
  73. sec = parsed
  74. }
  75. }
  76. return sec
  77. }
  78. func smtpConfigFromEnv() (host, user, password, fromEmail, fromName string, port int) {
  79. host = strings.TrimSpace(os.Getenv("SMTP_HOST"))
  80. user = strings.TrimSpace(os.Getenv("SMTP_USER"))
  81. password = os.Getenv("SMTP_PASSWORD")
  82. fromEmail = strings.TrimSpace(os.Getenv("SMTP_FROM_EMAIL"))
  83. fromName = strings.TrimSpace(os.Getenv("SMTP_FROM_NAME"))
  84. port = 465
  85. if v := os.Getenv("SMTP_PORT"); v != "" {
  86. if parsed, err := strconv.Atoi(v); err == nil && parsed > 0 {
  87. port = parsed
  88. }
  89. }
  90. return
  91. }
  92. // ResolveEffective 合并 DB 与 .env,DB 优先
  93. func (s *EmailNotificationConfigService) ResolveEffective() (ResolvedSMTPConfig, error) {
  94. envEnabled := emailNotificationEnabledFromEnv()
  95. envDelay := offlineEmailDelaySecondsFromEnv()
  96. envHost, envUser, envPass, envFrom, envFromName, envPort := smtpConfigFromEnv()
  97. resolved := ResolvedSMTPConfig{
  98. Enabled: envEnabled,
  99. SMTPHost: envHost,
  100. SMTPPort: envPort,
  101. SMTPUser: envUser,
  102. SMTPPassword: envPass,
  103. FromEmail: envFrom,
  104. FromName: envFromName,
  105. OfflineDelaySeconds: envDelay,
  106. }
  107. row, err := s.repo.Get()
  108. if err != nil {
  109. return resolved, err
  110. }
  111. if row == nil {
  112. return resolved, nil
  113. }
  114. resolved.Enabled = row.Enabled
  115. if row.SMTPHost != "" {
  116. resolved.SMTPHost = row.SMTPHost
  117. }
  118. if row.SMTPPort > 0 {
  119. resolved.SMTPPort = row.SMTPPort
  120. }
  121. if row.SMTPUser != "" {
  122. resolved.SMTPUser = row.SMTPUser
  123. }
  124. if row.FromEmail != "" {
  125. resolved.FromEmail = row.FromEmail
  126. }
  127. if row.FromName != "" {
  128. resolved.FromName = row.FromName
  129. }
  130. if row.OfflineDelaySeconds >= 0 {
  131. resolved.OfflineDelaySeconds = row.OfflineDelaySeconds
  132. }
  133. if row.SMTPPassword != "" {
  134. decrypted, err := utils.DecryptAPIKey(row.SMTPPassword)
  135. if err != nil {
  136. return resolved, fmt.Errorf("解密 SMTP 密码失败: %w", err)
  137. }
  138. resolved.SMTPPassword = decrypted
  139. }
  140. return resolved, nil
  141. }
  142. // GetForAPI 返回给前端的配置
  143. func (s *EmailNotificationConfigService) GetForAPI() (*EmailNotificationConfigResult, error) {
  144. envEnabled := emailNotificationEnabledFromEnv()
  145. envDelay := offlineEmailDelaySecondsFromEnv()
  146. effective, err := s.ResolveEffective()
  147. if err != nil {
  148. return nil, err
  149. }
  150. envHost, envUser, _, envFrom, envFromName, envPort := smtpConfigFromEnv()
  151. result := &EmailNotificationConfigResult{
  152. Enabled: envEnabled,
  153. SMTPPort: envPort,
  154. OfflineDelaySeconds: envDelay,
  155. EffectiveEnabled: effective.Enabled,
  156. EffectiveDelaySeconds: effective.OfflineDelaySeconds,
  157. EnvEnabled: envEnabled,
  158. EnvDelaySeconds: envDelay,
  159. SMTPHost: envHost,
  160. SMTPUser: envUser,
  161. FromEmail: envFrom,
  162. FromName: envFromName,
  163. }
  164. row, err := s.repo.Get()
  165. if err != nil {
  166. return nil, err
  167. }
  168. if row != nil {
  169. result.PersistedInDatabase = true
  170. result.ID = row.ID
  171. result.Enabled = row.Enabled
  172. result.SMTPHost = row.SMTPHost
  173. result.SMTPPort = row.SMTPPort
  174. if result.SMTPPort <= 0 {
  175. result.SMTPPort = 465
  176. }
  177. result.SMTPUser = row.SMTPUser
  178. result.FromEmail = row.FromEmail
  179. result.FromName = row.FromName
  180. result.OfflineDelaySeconds = row.OfflineDelaySeconds
  181. result.UpdatedAt = row.UpdatedAt
  182. if row.SMTPPassword != "" {
  183. result.SMTPPasswordMasked = "******"
  184. }
  185. }
  186. return result, nil
  187. }
  188. // Update 更新配置(仅管理员)
  189. func (s *EmailNotificationConfigService) Update(userID uint, input UpdateEmailNotificationConfigInput) (*EmailNotificationConfigResult, error) {
  190. user, err := s.userRepo.GetByID(userID)
  191. if err != nil || user == nil {
  192. return nil, errors.New("用户不存在")
  193. }
  194. if user.Role != "admin" {
  195. return nil, errors.New("仅管理员可修改离线邮件配置")
  196. }
  197. c, err := s.repo.Get()
  198. if err != nil {
  199. return nil, err
  200. }
  201. if c == nil {
  202. c = &models.EmailNotificationConfig{ID: 1, SMTPPort: 465, OfflineDelaySeconds: 60}
  203. }
  204. if input.Enabled != nil {
  205. c.Enabled = *input.Enabled
  206. }
  207. if input.SMTPHost != nil {
  208. c.SMTPHost = strings.TrimSpace(*input.SMTPHost)
  209. }
  210. if input.SMTPPort != nil && *input.SMTPPort > 0 {
  211. c.SMTPPort = *input.SMTPPort
  212. }
  213. if input.SMTPUser != nil {
  214. c.SMTPUser = strings.TrimSpace(*input.SMTPUser)
  215. }
  216. if input.SMTPPassword != nil && strings.TrimSpace(*input.SMTPPassword) != "" {
  217. encrypted, err := utils.EncryptAPIKey(strings.TrimSpace(*input.SMTPPassword))
  218. if err != nil {
  219. return nil, fmt.Errorf("加密 SMTP 密码失败: %w", err)
  220. }
  221. c.SMTPPassword = encrypted
  222. }
  223. if input.FromEmail != nil {
  224. c.FromEmail = strings.TrimSpace(*input.FromEmail)
  225. }
  226. if input.FromName != nil {
  227. c.FromName = strings.TrimSpace(*input.FromName)
  228. }
  229. if input.OfflineDelaySeconds != nil {
  230. if *input.OfflineDelaySeconds < 0 {
  231. return nil, errors.New("离线延迟秒数不能为负数")
  232. }
  233. c.OfflineDelaySeconds = *input.OfflineDelaySeconds
  234. }
  235. if err := s.repo.Save(c); err != nil {
  236. return nil, err
  237. }
  238. return s.GetForAPI()
  239. }
  240. // ResetToEnv 删除数据库覆盖,恢复 .env
  241. func (s *EmailNotificationConfigService) ResetToEnv(userID uint) (*EmailNotificationConfigResult, error) {
  242. user, err := s.userRepo.GetByID(userID)
  243. if err != nil || user == nil {
  244. return nil, errors.New("用户不存在")
  245. }
  246. if user.Role != "admin" {
  247. return nil, errors.New("仅管理员可修改离线邮件配置")
  248. }
  249. if err := s.repo.Delete(); err != nil {
  250. return nil, err
  251. }
  252. return s.GetForAPI()
  253. }