193 lines
8.2 KiB
Go
193 lines
8.2 KiB
Go
// Package retention 定义 Orbit 历史数据保留策略的通用模型。
|
||
//
|
||
// 策略只描述候选数据的最短保留窗口,不直接删除任务、事件或审计记录。
|
||
// 归档执行器必须在独立流程中先完成核查、备份和归档证明,再按策略处理。
|
||
package retention
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
"unicode/utf8"
|
||
|
||
core "git.yuntuo.hk/k2safe/orbit/model"
|
||
"git.yuntuo.hk/k2safe/orbit/platform/auth"
|
||
)
|
||
|
||
const (
|
||
// DefaultIdempotencyDays 是请求幂等记录的默认保留天数。
|
||
DefaultIdempotencyDays uint32 = 90
|
||
// DefaultTaskHistoryDays 是终态任务历史的默认保留天数。
|
||
DefaultTaskHistoryDays uint32 = 365
|
||
// DefaultWorkflowHistoryDays 是终态流程历史的默认保留天数。
|
||
DefaultWorkflowHistoryDays uint32 = 365
|
||
// DefaultEventLogDays 是状态事件和结果事件的默认保留天数。
|
||
DefaultEventLogDays uint32 = 365
|
||
// DefaultDeliveryHistoryDays 是已确认交付历史的默认保留天数。
|
||
DefaultDeliveryHistoryDays uint32 = 90
|
||
// DefaultInboxHistoryDays 是已处理流程 Inbox 历史的默认保留天数。
|
||
DefaultInboxHistoryDays uint32 = 90
|
||
// DefaultAuditLogDays 是管理审计的默认保留天数,约七年。
|
||
DefaultAuditLogDays uint32 = 2555
|
||
|
||
minRetentionDays uint32 = 1
|
||
maxRetentionDays uint32 = 3650
|
||
)
|
||
|
||
// Policy 是按命名空间保存的历史数据保留策略。
|
||
// Enabled 默认关闭;即使打开,也允许只读归档导出使用满足窗口且已核验的数据。
|
||
// 当前版本不会因为配置变更自动删除任何数据。
|
||
type Policy struct {
|
||
// NamespaceID 是策略所属命名空间,不能由控制台请求替换。
|
||
NamespaceID core.ID `json:"namespace_id"`
|
||
// Revision 是乐观并发版本;默认策略未落库时为0,首次保存后从1开始。
|
||
Revision uint64 `json:"revision,string"`
|
||
// Enabled 表示是否允许归档执行器使用该策略;默认关闭以避免误清理。
|
||
Enabled bool `json:"enabled"`
|
||
// IdempotencyDays 是请求幂等记录至少保留的天数。
|
||
IdempotencyDays uint32 `json:"idempotency_days"`
|
||
// TaskHistoryDays 是终态任务及执行证据至少保留的天数。
|
||
TaskHistoryDays uint32 `json:"task_history_days"`
|
||
// WorkflowHistoryDays 是终态流程、步骤、等待和流程事件至少保留的天数。
|
||
WorkflowHistoryDays uint32 `json:"workflow_history_days"`
|
||
// EventLogDays 是状态事件和结果事件至少保留的天数。
|
||
EventLogDays uint32 `json:"event_log_days"`
|
||
// DeliveryHistoryDays 是已确认结果交付至少保留的天数;未确认交付不得按窗口清理。
|
||
DeliveryHistoryDays uint32 `json:"delivery_history_days"`
|
||
// InboxHistoryDays 是已处理流程 Inbox 事件至少保留的天数;未处理记录不得清理。
|
||
InboxHistoryDays uint32 `json:"inbox_history_days"`
|
||
// AuditLogDays 是管理审计至少保留的天数;审计记录不参与普通清理。
|
||
AuditLogDays uint32 `json:"audit_log_days"`
|
||
// UpdatedAt 是策略最后修改时间,统一使用 UTC。
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
// Source 表示策略来自默认值还是数据库配置,便于控制台明确当前状态。
|
||
Source string `json:"source"`
|
||
}
|
||
|
||
// UpdateInput 是控制台保存策略所需的完整替换输入。
|
||
// 所有窗口必须显式提交,避免遗漏字段被解释成缩短保留期。
|
||
type UpdateInput struct {
|
||
// ExpectedRevision 是页面读取到的版本;首次保存必须为0。
|
||
ExpectedRevision uint64 `json:"expected_revision,string" required:"true"`
|
||
// Enabled 是否允许归档执行器读取本策略。
|
||
Enabled bool `json:"enabled" required:"true"`
|
||
// IdempotencyDays 请求幂等记录保留天数。
|
||
IdempotencyDays uint32 `json:"idempotency_days" required:"true"`
|
||
// TaskHistoryDays 终态任务历史保留天数。
|
||
TaskHistoryDays uint32 `json:"task_history_days" required:"true"`
|
||
// WorkflowHistoryDays 终态流程历史保留天数。
|
||
WorkflowHistoryDays uint32 `json:"workflow_history_days" required:"true"`
|
||
// EventLogDays 状态和结果事件保留天数。
|
||
EventLogDays uint32 `json:"event_log_days" required:"true"`
|
||
// DeliveryHistoryDays 已确认交付历史保留天数。
|
||
DeliveryHistoryDays uint32 `json:"delivery_history_days" required:"true"`
|
||
// InboxHistoryDays 已处理 Inbox 历史保留天数。
|
||
InboxHistoryDays uint32 `json:"inbox_history_days" required:"true"`
|
||
// AuditLogDays 管理审计保留天数。
|
||
AuditLogDays uint32 `json:"audit_log_days" required:"true"`
|
||
// Reason 是本次策略变更原因,会进入管理审计。
|
||
Reason string `json:"reason" required:"true"`
|
||
}
|
||
|
||
// Store 是控制台和归档执行器共享的最小策略存储边界。
|
||
// 实现必须保证 Update 的版本核对、幂等和审计与策略写入处于同一事务。
|
||
type Store interface {
|
||
GetRetentionPolicy(context.Context, core.ID) (Policy, error)
|
||
UpdateRetentionPolicy(context.Context, auth.Token, string, UpdateInput) (Policy, error)
|
||
}
|
||
|
||
// Defaults 返回不会触发任何归档执行的安全默认策略。
|
||
func Defaults(namespace core.ID) Policy {
|
||
return Policy{
|
||
NamespaceID: namespace,
|
||
Enabled: false,
|
||
IdempotencyDays: DefaultIdempotencyDays,
|
||
TaskHistoryDays: DefaultTaskHistoryDays,
|
||
WorkflowHistoryDays: DefaultWorkflowHistoryDays,
|
||
EventLogDays: DefaultEventLogDays,
|
||
DeliveryHistoryDays: DefaultDeliveryHistoryDays,
|
||
InboxHistoryDays: DefaultInboxHistoryDays,
|
||
AuditLogDays: DefaultAuditLogDays,
|
||
Source: "default",
|
||
}
|
||
}
|
||
|
||
// Validate 检查策略身份、窗口和审计原因。
|
||
func (p Policy) Validate() error {
|
||
if p.NamespaceID.Validate() != nil {
|
||
return errors.New("retention namespace is invalid")
|
||
}
|
||
for name, days := range map[string]uint32{
|
||
"idempotency_days": p.IdempotencyDays,
|
||
"task_history_days": p.TaskHistoryDays,
|
||
"workflow_history_days": p.WorkflowHistoryDays,
|
||
"event_log_days": p.EventLogDays,
|
||
"delivery_history_days": p.DeliveryHistoryDays,
|
||
"inbox_history_days": p.InboxHistoryDays,
|
||
"audit_log_days": p.AuditLogDays,
|
||
} {
|
||
if days < minRetentionDays || days > maxRetentionDays {
|
||
return fmt.Errorf("%s must be between %d and %d days", name, minRetentionDays, maxRetentionDays)
|
||
}
|
||
}
|
||
if p.IdempotencyDays < DefaultIdempotencyDays {
|
||
return errors.New("idempotency_days cannot be less than 90 days")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ToPolicy 将完整输入转为待保存策略,版本由事务内分配。
|
||
func (in UpdateInput) ToPolicy(namespace core.ID) (Policy, error) {
|
||
if strings.TrimSpace(in.Reason) == "" || !utf8.ValidString(in.Reason) || len(in.Reason) > 1024 || strings.ContainsAny(in.Reason, "\r\n") {
|
||
return Policy{}, errors.New("retention reason must be 1-1024 UTF-8 bytes without newlines")
|
||
}
|
||
p := Policy{
|
||
NamespaceID: namespace,
|
||
Enabled: in.Enabled,
|
||
IdempotencyDays: in.IdempotencyDays,
|
||
TaskHistoryDays: in.TaskHistoryDays,
|
||
WorkflowHistoryDays: in.WorkflowHistoryDays,
|
||
EventLogDays: in.EventLogDays,
|
||
DeliveryHistoryDays: in.DeliveryHistoryDays,
|
||
InboxHistoryDays: in.InboxHistoryDays,
|
||
AuditLogDays: in.AuditLogDays,
|
||
}
|
||
if err := p.Validate(); err != nil {
|
||
return Policy{}, err
|
||
}
|
||
return p, nil
|
||
}
|
||
|
||
// FromEnv 读取部署默认值;数据库策略保存后优先于部署默认值。
|
||
// 所有节点应注入同一组默认值,保证尚未保存策略的展示一致。
|
||
func FromEnv(get func(string) string) (Policy, error) {
|
||
p := Defaults("")
|
||
for _, f := range []struct {
|
||
name string
|
||
dst *uint32
|
||
}{
|
||
{"ORBIT_RETENTION_IDEMPOTENCY_DAYS", &p.IdempotencyDays},
|
||
{"ORBIT_RETENTION_TASK_DAYS", &p.TaskHistoryDays},
|
||
{"ORBIT_RETENTION_WORKFLOW_DAYS", &p.WorkflowHistoryDays},
|
||
{"ORBIT_RETENTION_EVENT_DAYS", &p.EventLogDays},
|
||
{"ORBIT_RETENTION_DELIVERY_DAYS", &p.DeliveryHistoryDays},
|
||
{"ORBIT_RETENTION_INBOX_DAYS", &p.InboxHistoryDays},
|
||
{"ORBIT_RETENTION_AUDIT_DAYS", &p.AuditLogDays},
|
||
} {
|
||
if raw := get(f.name); raw != "" {
|
||
n, err := strconv.ParseUint(raw, 10, 32)
|
||
if err != nil || n < 1 || n > 3650 {
|
||
return Policy{}, fmt.Errorf("%s must be 1-3650 days", f.name)
|
||
}
|
||
*f.dst = uint32(n)
|
||
}
|
||
}
|
||
if p.IdempotencyDays < 90 {
|
||
return Policy{}, errors.New("ORBIT_RETENTION_IDEMPOTENCY_DAYS cannot be less than 90")
|
||
}
|
||
return p, nil
|
||
}
|