需求分析

现状:授权到期前的提醒没有提前期和多次提醒;

目标:1、授权过期前,合理设置提前周期性提醒客户,只提醒平台管理员账号(登录时提醒);
2、授权到期后,直接提醒所有用户,有开关选择(登录时提醒,每个请求都提醒) 3、只是弹窗提醒,不影响功能使用。

核心目标

  1. 完善授权到期前后的提醒机制
  2. 分阶段提醒:到期前60天、30天、15天、到期后、超期7天
  3. 差异化提醒对象:平台管理员/租户管理员 vs 所有用户
  4. 不同触发频率:每月、每天、每次登录、每个请求
  5. 提醒不阻塞业务(除超期后的概率异常)

策略阶段分析

| 阶段 | 时间区间 | 提醒对象 | 触发条件 | 业务影响 | |——|———-|———-|———-|———-| | 阶段1 | 到期前60天开始 | 平台/租户管理员 | 每月一次,登录时 | 不阻塞 | | 阶段2 | 到期前30天开始 | 平台/租户管理员 | 每天一次,登录时 | 不阻塞 | | 阶段3 | 到期前15天开始 | 平台/租户管理员 | 每次登录时 | 不阻塞 | | 阶段4 | 到期后0天开始 | 所有用户 + 平台/租户管理员 | 所有请求 + 登录时 | 概率阻塞 | | 阶段5 | 超期7天开始 | 所有用户 + 平台/租户管理员 | 所有请求 + 登录时 | 概率阻塞 |

Go语言实现方案

1. 数据结构定义

package main

import (
    "fmt"
    "time"
    "math/rand"
)

// 许可证信息
type License struct {
    ExpireTime   time.Time
    IsValid      bool
    LicenseType  string
}

// 用户信息
type User struct {
    ID          string
    Username    string
    Role        UserRole
    LastRemind  map[RemindStage]time.Time
}

// 用户角色
type UserRole int

const (
    RoleNormalUser UserRole = iota
    RoleTenantAdmin
    RolePlatformAdmin
)

// 提醒阶段
type RemindStage int

const (
    Stage60DaysBefore RemindStage = iota
    Stage30DaysBefore
    Stage15DaysBefore
    StageAfterExpire
    Stage7DaysAfterExpire
)

// 提醒策略配置
type RemindStrategy struct {
    Stage           RemindStage
    DaysFromExpire  int // 正数表示到期前,负数表示到期后
    TargetUsers     []UserRole
    TriggerCondition string
    Frequency       time.Duration
    MessageTemplate string
    BlockBusiness   bool
    ExceptionRate   float64 // 异常抛出概率(0-1)
}

// 提醒记录
type RemindRecord struct {
    UserID    string
    Stage     RemindStage
    RemindTime time.Time
    Message   string
    IsShown   bool
}

2. 策略配置管理

// 策略管理器
type RemindStrategyManager struct {
    strategies map[RemindStage]*RemindStrategy
    license    *License
    remindHistory map[string][]*RemindRecord // userID -> records
}

func NewRemindStrategyManager(license *License) *RemindStrategyManager {
    manager := &RemindStrategyManager{
        license:       license,
        strategies:    make(map[RemindStage]*RemindStrategy),
        remindHistory: make(map[string][]*RemindRecord),
    }
    manager.initStrategies()
    return manager
}

func (m *RemindStrategyManager) initStrategies() {
    // 阶段1: 到期前60天开始
    m.strategies[Stage60DaysBefore] = &RemindStrategy{
        Stage:          Stage60DaysBefore,
        DaysFromExpire: 60,
        TargetUsers:    []UserRole{RolePlatformAdmin, RoleTenantAdmin},
        TriggerCondition: "登录时",
        Frequency:      time.Hour * 24 * 30, // 每月一次
        MessageTemplate: "平台许可证将在%d天后,%s到期,为避免到期后系统不能正常使用而影响业务,请在到期前及时进行许可证续期。",
        BlockBusiness:   false,
        ExceptionRate:   0,
    }

    // 阶段2: 到期前30天开始
    m.strategies[Stage30DaysBefore] = &RemindStrategy{
        Stage:          Stage30DaysBefore,
        DaysFromExpire: 30,
        TargetUsers:    []UserRole{RolePlatformAdmin, RoleTenantAdmin},
        TriggerCondition: "登录时",
        Frequency:      time.Hour * 24, // 每天一次
        MessageTemplate: "平台许可证将在%d天后,%s到期,为避免到期后系统不能正常使用而影响业务,请在到期前及时进行许可证续期。",
        BlockBusiness:   false,
        ExceptionRate:   0,
    }

    // 阶段3: 到期前15天开始
    m.strategies[Stage15DaysBefore] = &RemindStrategy{
        Stage:          Stage15DaysBefore,
        DaysFromExpire: 15,
        TargetUsers:    []UserRole{RolePlatformAdmin, RoleTenantAdmin},
        TriggerCondition: "登录时",
        Frequency:      time.Hour * 1, // 每次登录(按小时频率)
        MessageTemplate: "平台许可证将在%d天后,%s到期,为避免到期后系统不能正常使用而影响业务,请在到期前及时进行许可证续期。",
        BlockBusiness:   false,
        ExceptionRate:   0,
    }

    // 阶段4: 到期后
    m.strategies[StageAfterExpire] = &RemindStrategy{
        Stage:          StageAfterExpire,
        DaysFromExpire: 0,
        TargetUsers:    []UserRole{RolePlatformAdmin, RoleTenantAdmin, RoleNormalUser},
        TriggerCondition: "所有请求",
        Frequency:      time.Hour * 1,
        MessageTemplate: "平台许可证已于%s到期,请在7天内完成许可证续期,否则系统将在%s后不能正常使用!",
        BlockBusiness:   true,
        ExceptionRate:   0.3, // 30%概率抛异常
    }

    // 阶段5: 超期7天开始
    m.strategies[Stage7DaysAfterExpire] = &RemindStrategy{
        Stage:          Stage7DaysAfterExpire,
        DaysFromExpire: -7,
        TargetUsers:    []UserRole{RolePlatformAdmin, RoleTenantAdmin, RoleNormalUser},
        TriggerCondition: "所有请求",
        Frequency:      time.Hour * 1,
        MessageTemplate: "平台许可证已过期,系统不能正常使用,请联系您的系统管理员进行处理!",
        BlockBusiness:   true,
        ExceptionRate:   0.5, // 50%概率抛异常
    }
}

3. 核心业务逻辑

// 获取当前提醒阶段
func (m *RemindStrategyManager) getCurrentStage() RemindStage {
    now := time.Now()
    daysUntilExpire := int(m.license.ExpireTime.Sub(now).Hours() / 24)
    
    switch {
    case daysUntilExpire >= 60:
        return Stage60DaysBefore
    case daysUntilExpire >= 30:
        return Stage30DaysBefore
    case daysUntilExpire >= 15:
        return Stage15DaysBefore
    case daysUntilExpire >= 0:
        return StageAfterExpire
    default:
        if daysUntilExpire <= -7 {
            return Stage7DaysAfterExpire
        }
        return StageAfterExpire
    }
}

// 检查用户是否需要提醒
func (m *RemindStrategyManager) shouldRemindUser(user *User, stage RemindStage, triggerType string) bool {
    strategy, exists := m.strategies[stage]
    if !exists {
        return false
    }

    // 检查触发条件
    if triggerType != strategy.TriggerCondition && strategy.TriggerCondition != "所有请求" {
        return false
    }

    // 检查用户角色
    userInTarget := false
    for _, role := range strategy.TargetUsers {
        if user.Role == role {
            userInTarget = true
            break
        }
    }
    if !userInTarget {
        return false
    }

    // 检查频率限制
    lastRemind, exists := user.LastRemind[stage]
    if exists && time.Since(lastRemind) < strategy.Frequency {
        return false
    }

    return true
}

// 生成提醒消息
func (m *RemindStrategyManager) generateMessage(stage RemindStage, user *User) string {
    strategy := m.strategies[stage]
    expireTimeStr := m.license.ExpireTime.Format("2006年01月02日 15:04:05")
    daysUntil := int(m.license.ExpireTime.Sub(time.Now()).Hours() / 24)
    
    switch stage {
    case Stage60DaysBefore, Stage30DaysBefore, Stage15DaysBefore:
        return fmt.Sprintf(strategy.MessageTemplate, daysUntil, expireTimeStr)
    case StageAfterExpire:
        deadline := m.license.ExpireTime.AddDate(0, 0, 7)
        return fmt.Sprintf(strategy.MessageTemplate, expireTimeStr, deadline.Format("2006年01月02日 15:04:05"))
    case Stage7DaysAfterExpire:
        if user.Role == RoleNormalUser {
            return "平台许可证已过期,系统不能正常使用,请联系您的系统管理员进行处理!"
        } else {
            return fmt.Sprintf("平台许可证已于%s到期,经多次提醒贵司未能在宽限期内完成许可证续期,系统已不能正常使用!", expireTimeStr)
        }
    default:
        return "许可证状态异常,请联系管理员。"
    }
}

// 处理业务阻塞逻辑
func (m *RemindStrategyManager) checkBusinessBlock(stage RemindStage) error {
    strategy := m.strategies[stage]
    if strategy.BlockBusiness && rand.Float64() < strategy.ExceptionRate {
        return fmt.Errorf("许可证已过期,系统不能正常使用")
    }
    return nil
}

4. API接口实现

// 登录提醒处理
func (m *RemindStrategyManager) HandleLoginRemind(user *User) (*RemindRecord, error) {
    stage := m.getCurrentStage()
    
    if !m.shouldRemindUser(user, stage, "登录时") {
        return nil, nil
    }

    // 检查业务阻塞
    if err := m.checkBusinessBlock(stage); err != nil {
        return nil, err
    }

    message := m.generateMessage(stage, user)
    record := &RemindRecord{
        UserID:     user.ID,
        Stage:      stage,
        RemindTime: time.Now(),
        Message:    message,
        IsShown:    false,
    }

    // 保存提醒记录
    m.remindHistory[user.ID] = append(m.remindHistory[user.ID], record)
    user.LastRemind[stage] = time.Now()

    return record, nil
}

// 请求提醒处理
func (m *RemindStrategyManager) HandleRequestRemind(user *User) (*RemindRecord, error) {
    stage := m.getCurrentStage()
    
    if !m.shouldRemindUser(user, stage, "所有请求") {
        return nil, nil
    }

    // 检查业务阻塞
    if err := m.checkBusinessBlock(stage); err != nil {
        return nil, err
    }

    message := m.generateMessage(stage, user)
    record := &RemindRecord{
        UserID:     user.ID,
        Stage:      stage,
        RemindTime: time.Now(),
        Message:    message,
        IsShown:    false,
    }

    return record, nil
}

5. 使用示例

func main() {
    // 初始化许可证信息
    expireTime := time.Now().AddDate(0, 0, 45) // 45天后到期
    license := &License{
        ExpireTime:  expireTime,
        IsValid:     true,
        LicenseType: "企业版",
    }

    // 创建策略管理器
    strategyManager := NewRemindStrategyManager(license)

    // 模拟用户登录
    adminUser := &User{
        ID:         "user001",
        Username:   "admin",
        Role:       RolePlatformAdmin,
        LastRemind: make(map[RemindStage]time.Time),
    }

    // 处理登录提醒
    record, err := strategyManager.HandleLoginRemind(adminUser)
    if err != nil {
        fmt.Printf("业务阻塞: %v\n", err)
        return
    }

    if record != nil {
        fmt.Printf("提醒消息: %s\n", record.Message)
        fmt.Printf("提醒阶段: %v\n", record.Stage)
        fmt.Printf("提醒时间: %s\n", record.RemindTime.Format("2006-01-02 15:04:05"))
    }

    // 模拟普通用户请求
    normalUser := &User{
        ID:         "user002",
        Username:   "user1",
        Role:       RoleNormalUser,
        LastRemind: make(map[RemindStage]time.Time),
    }

    // 处理请求提醒(在到期后阶段)
    requestRecord, err := strategyManager.HandleRequestRemind(normalUser)
    if err != nil {
        fmt.Printf("请求被阻塞: %v\n", err)
        return
    }

    if requestRecord != nil {
        fmt.Printf("请求提醒: %s\n", requestRecord.Message)
    }
}

系统架构建议

1. 存储设计

  • 使用Redis缓存用户最近提醒记录
  • 使用MySQL持久化提醒历史
  • 许可证信息配置在配置中心

2. 性能优化

  • 使用本地缓存减少数据库查询
  • 异步记录提醒日志
  • 批量处理用户提醒

3. 扩展性考虑

  • 支持动态策略配置
  • 可插拔的提醒渠道(邮件、短信、站内信)
  • 多租户隔离支持

这个实现方案完整覆盖了需求中的所有策略阶段,提供了灵活的配置方式和良好的扩展性,可以直接集成到现有的微服务架构中。