| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- package service
- import (
- "errors"
- "fmt"
- "os"
- "strconv"
- "strings"
- "time"
- "github.com/2930134478/AI-CS/backend/models"
- "github.com/2930134478/AI-CS/backend/repository"
- "github.com/2930134478/AI-CS/backend/utils"
- )
- // EmailNotificationConfigResult 返回给前端的配置(密码脱敏)
- type EmailNotificationConfigResult struct {
- ID uint `json:"id"`
- Enabled bool `json:"enabled"`
- SMTPHost string `json:"smtp_host"`
- SMTPPort int `json:"smtp_port"`
- SMTPUser string `json:"smtp_user"`
- SMTPPasswordMasked string `json:"smtp_password_masked"`
- FromEmail string `json:"from_email"`
- FromName string `json:"from_name"`
- OfflineDelaySeconds int `json:"offline_delay_seconds"`
- EffectiveEnabled bool `json:"effective_enabled"`
- EffectiveDelaySeconds int `json:"effective_delay_seconds"`
- PersistedInDatabase bool `json:"persisted_in_database"`
- EnvEnabled bool `json:"env_enabled"`
- EnvDelaySeconds int `json:"env_delay_seconds"`
- UpdatedAt time.Time `json:"updated_at,omitempty"`
- }
- // UpdateEmailNotificationConfigInput 更新离线邮件配置
- type UpdateEmailNotificationConfigInput struct {
- Enabled *bool
- SMTPHost *string
- SMTPPort *int
- SMTPUser *string
- SMTPPassword *string
- FromEmail *string
- FromName *string
- OfflineDelaySeconds *int
- }
- // ResolvedSMTPConfig 实际发信用的 SMTP 配置
- type ResolvedSMTPConfig struct {
- Enabled bool
- SMTPHost string
- SMTPPort int
- SMTPUser string
- SMTPPassword string
- FromEmail string
- FromName string
- OfflineDelaySeconds int
- }
- // EmailNotificationConfigService 离线邮件配置服务
- type EmailNotificationConfigService struct {
- repo *repository.EmailNotificationConfigRepository
- userRepo *repository.UserRepository
- }
- // NewEmailNotificationConfigService 创建服务实例
- func NewEmailNotificationConfigService(
- repo *repository.EmailNotificationConfigRepository,
- userRepo *repository.UserRepository,
- ) *EmailNotificationConfigService {
- return &EmailNotificationConfigService{repo: repo, userRepo: userRepo}
- }
- func emailNotificationEnabledFromEnv() bool {
- v := strings.ToLower(strings.TrimSpace(os.Getenv("OFFLINE_EMAIL_ENABLED")))
- return v == "1" || v == "true" || v == "yes"
- }
- func offlineEmailDelaySecondsFromEnv() int {
- sec := 60
- if v := os.Getenv("OFFLINE_EMAIL_DELAY_SECONDS"); v != "" {
- if parsed, err := strconv.Atoi(v); err == nil && parsed >= 0 {
- sec = parsed
- }
- }
- return sec
- }
- func smtpConfigFromEnv() (host, user, password, fromEmail, fromName string, port int) {
- host = strings.TrimSpace(os.Getenv("SMTP_HOST"))
- user = strings.TrimSpace(os.Getenv("SMTP_USER"))
- password = os.Getenv("SMTP_PASSWORD")
- fromEmail = strings.TrimSpace(os.Getenv("SMTP_FROM_EMAIL"))
- fromName = strings.TrimSpace(os.Getenv("SMTP_FROM_NAME"))
- port = 465
- if v := os.Getenv("SMTP_PORT"); v != "" {
- if parsed, err := strconv.Atoi(v); err == nil && parsed > 0 {
- port = parsed
- }
- }
- return
- }
- // ResolveEffective 合并 DB 与 .env,DB 优先
- func (s *EmailNotificationConfigService) ResolveEffective() (ResolvedSMTPConfig, error) {
- envEnabled := emailNotificationEnabledFromEnv()
- envDelay := offlineEmailDelaySecondsFromEnv()
- envHost, envUser, envPass, envFrom, envFromName, envPort := smtpConfigFromEnv()
- resolved := ResolvedSMTPConfig{
- Enabled: envEnabled,
- SMTPHost: envHost,
- SMTPPort: envPort,
- SMTPUser: envUser,
- SMTPPassword: envPass,
- FromEmail: envFrom,
- FromName: envFromName,
- OfflineDelaySeconds: envDelay,
- }
- row, err := s.repo.Get()
- if err != nil {
- return resolved, err
- }
- if row == nil {
- return resolved, nil
- }
- resolved.Enabled = row.Enabled
- if row.SMTPHost != "" {
- resolved.SMTPHost = row.SMTPHost
- }
- if row.SMTPPort > 0 {
- resolved.SMTPPort = row.SMTPPort
- }
- if row.SMTPUser != "" {
- resolved.SMTPUser = row.SMTPUser
- }
- if row.FromEmail != "" {
- resolved.FromEmail = row.FromEmail
- }
- if row.FromName != "" {
- resolved.FromName = row.FromName
- }
- if row.OfflineDelaySeconds >= 0 {
- resolved.OfflineDelaySeconds = row.OfflineDelaySeconds
- }
- if row.SMTPPassword != "" {
- decrypted, err := utils.DecryptAPIKey(row.SMTPPassword)
- if err != nil {
- return resolved, fmt.Errorf("解密 SMTP 密码失败: %w", err)
- }
- resolved.SMTPPassword = decrypted
- }
- return resolved, nil
- }
- // GetForAPI 返回给前端的配置
- func (s *EmailNotificationConfigService) GetForAPI() (*EmailNotificationConfigResult, error) {
- envEnabled := emailNotificationEnabledFromEnv()
- envDelay := offlineEmailDelaySecondsFromEnv()
- effective, err := s.ResolveEffective()
- if err != nil {
- return nil, err
- }
- envHost, envUser, _, envFrom, envFromName, envPort := smtpConfigFromEnv()
- result := &EmailNotificationConfigResult{
- Enabled: envEnabled,
- SMTPPort: envPort,
- OfflineDelaySeconds: envDelay,
- EffectiveEnabled: effective.Enabled,
- EffectiveDelaySeconds: effective.OfflineDelaySeconds,
- EnvEnabled: envEnabled,
- EnvDelaySeconds: envDelay,
- SMTPHost: envHost,
- SMTPUser: envUser,
- FromEmail: envFrom,
- FromName: envFromName,
- }
- row, err := s.repo.Get()
- if err != nil {
- return nil, err
- }
- if row != nil {
- result.PersistedInDatabase = true
- result.ID = row.ID
- result.Enabled = row.Enabled
- result.SMTPHost = row.SMTPHost
- result.SMTPPort = row.SMTPPort
- if result.SMTPPort <= 0 {
- result.SMTPPort = 465
- }
- result.SMTPUser = row.SMTPUser
- result.FromEmail = row.FromEmail
- result.FromName = row.FromName
- result.OfflineDelaySeconds = row.OfflineDelaySeconds
- result.UpdatedAt = row.UpdatedAt
- if row.SMTPPassword != "" {
- result.SMTPPasswordMasked = "******"
- }
- }
- return result, nil
- }
- // Update 更新配置(仅管理员)
- func (s *EmailNotificationConfigService) Update(userID uint, input UpdateEmailNotificationConfigInput) (*EmailNotificationConfigResult, error) {
- user, err := s.userRepo.GetByID(userID)
- if err != nil || user == nil {
- return nil, errors.New("用户不存在")
- }
- if user.Role != "admin" {
- return nil, errors.New("仅管理员可修改离线邮件配置")
- }
- c, err := s.repo.Get()
- if err != nil {
- return nil, err
- }
- if c == nil {
- c = &models.EmailNotificationConfig{ID: 1, SMTPPort: 465, OfflineDelaySeconds: 60}
- }
- if input.Enabled != nil {
- c.Enabled = *input.Enabled
- }
- if input.SMTPHost != nil {
- c.SMTPHost = strings.TrimSpace(*input.SMTPHost)
- }
- if input.SMTPPort != nil && *input.SMTPPort > 0 {
- c.SMTPPort = *input.SMTPPort
- }
- if input.SMTPUser != nil {
- c.SMTPUser = strings.TrimSpace(*input.SMTPUser)
- }
- if input.SMTPPassword != nil && strings.TrimSpace(*input.SMTPPassword) != "" {
- encrypted, err := utils.EncryptAPIKey(strings.TrimSpace(*input.SMTPPassword))
- if err != nil {
- return nil, fmt.Errorf("加密 SMTP 密码失败: %w", err)
- }
- c.SMTPPassword = encrypted
- }
- if input.FromEmail != nil {
- c.FromEmail = strings.TrimSpace(*input.FromEmail)
- }
- if input.FromName != nil {
- c.FromName = strings.TrimSpace(*input.FromName)
- }
- if input.OfflineDelaySeconds != nil {
- if *input.OfflineDelaySeconds < 0 {
- return nil, errors.New("离线延迟秒数不能为负数")
- }
- c.OfflineDelaySeconds = *input.OfflineDelaySeconds
- }
- if err := s.repo.Save(c); err != nil {
- return nil, err
- }
- return s.GetForAPI()
- }
- // ResetToEnv 删除数据库覆盖,恢复 .env
- func (s *EmailNotificationConfigService) ResetToEnv(userID uint) (*EmailNotificationConfigResult, error) {
- user, err := s.userRepo.GetByID(userID)
- if err != nil || user == nil {
- return nil, errors.New("用户不存在")
- }
- if user.Role != "admin" {
- return nil, errors.New("仅管理员可修改离线邮件配置")
- }
- if err := s.repo.Delete(); err != nil {
- return nil, err
- }
- return s.GetForAPI()
- }
|