102 lines
7.4 KiB
Go
102 lines
7.4 KiB
Go
package retention
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"strings"
|
||
"time"
|
||
"unicode/utf8"
|
||
|
||
core "git.yuntuo.hk/k2safe/orbit/model"
|
||
"git.yuntuo.hk/k2safe/orbit/platform/auth"
|
||
)
|
||
|
||
// ArchiveKinds 是周期归档支持的固定类别,不接受任意表名或 SQL。
|
||
var ArchiveKinds = [...]string{"tasks", "workflows", "events", "audit", "deliveries", "inbox", "idempotency"}
|
||
|
||
// ArchivePlan 是命名空间内单一类别的周期计划;失败作业阻止本类别再生成新作业。
|
||
type ArchivePlan struct {
|
||
NamespaceID core.ID `json:"namespace_id"` // NamespaceID 由登录会话确定。
|
||
Kind string `json:"kind"` // Kind 同一命名空间内唯一的归档类别。
|
||
Revision uint64 `json:"revision,string"` // Revision 配置版本;尚未保存时为0,后台触发不改变配置版本。
|
||
Enabled bool `json:"enabled"` // Enabled 仅控制新作业触发,关闭不会中断已有作业。
|
||
PolicyRevision uint64 `json:"policy_revision,string"` // PolicyRevision 管理员确认的保留策略版本,变更策略后须重新保存计划。
|
||
StorageID core.ID `json:"storage_id"` // StorageID 固定归档卷身份,由部署配置提供,不接受浏览器任意路径。
|
||
IntervalSeconds uint32 `json:"interval_seconds"` // IntervalSeconds 周期秒数,3600至2592000;错过多次只合并触发一次。
|
||
BatchSize int `json:"batch_size"` // BatchSize 单批根记录数,1至100。
|
||
MaxBytes int64 `json:"max_bytes"` // MaxBytes 每个作业的文件字节上限,1 MiB至1 GiB。
|
||
NextRunAt *time.Time `json:"next_run_at"` // NextRunAt 下次触发UTC时间,未保存时为空;首次启用立即到期。
|
||
ActiveJobID *core.ID `json:"active_job_id"` // ActiveJobID 未完成或待处理失败作业;成功或人工关闭后清空。
|
||
UpdatedAt *time.Time `json:"updated_at"` // UpdatedAt 最后配置修改UTC时间,未保存为空。
|
||
}
|
||
|
||
// PlanInput 完整替换计划配置;保存与管理审计、幂等回执同事务。
|
||
type PlanInput struct {
|
||
ExpectedRevision uint64 `json:"expected_revision,string" required:"true"` // ExpectedRevision 已读取的计划版本。
|
||
PolicyRevision uint64 `json:"policy_revision,string" required:"true"` // PolicyRevision 已确认的保留策略版本。
|
||
Enabled bool `json:"enabled" required:"true"` // Enabled 是否开始周期触发。
|
||
IntervalSeconds uint32 `json:"interval_seconds" required:"true"` // IntervalSeconds 周期秒数。
|
||
BatchSize int `json:"batch_size" required:"true"` // BatchSize 每批根记录数。
|
||
MaxBytes int64 `json:"max_bytes" required:"true"` // MaxBytes 单作业总文件预算,字节。
|
||
Reason string `json:"reason" required:"true"` // Reason 单行操作原因,最多1024个UTF-8字节。
|
||
}
|
||
|
||
// ManagedJob 保存周期作业权威状态;文件内容和游标不通过管理列表返回。
|
||
type ManagedJob struct {
|
||
ID core.ID `json:"id"` // ID 与目录名及文件清单作业ID一致。
|
||
NamespaceID core.ID `json:"namespace_id"` // NamespaceID 所属命名空间。
|
||
Kind string `json:"kind"` // Kind 固定归档类别。
|
||
PlanRevision uint64 `json:"plan_revision,string"` // PlanRevision 创建时固定的计划配置版本。
|
||
PolicyRevision uint64 `json:"policy_revision,string"` // PolicyRevision 作业期间不可替换的保留策略版本。
|
||
StorageID core.ID `json:"storage_id"` // StorageID 固定归档卷身份,接管节点必须挂载同一物理卷。
|
||
BatchSize int `json:"batch_size"` // BatchSize 创建时固定的单批根记录上限。
|
||
MaxBytes int64 `json:"max_bytes"` // MaxBytes 累计字节预算,仅失败重试时允许提高。
|
||
State string `json:"state"` // State 为PENDING/RUNNING/SUCCEEDED/FAILED/ABANDONED。
|
||
Revision uint64 `json:"revision,string"` // Revision 每次领取、确认、人工操作递增,用于防止迟到覆盖。
|
||
Attempts uint32 `json:"attempts"` // Attempts 累计批次领取次数,包含崩溃接管,不等于业务任务重试次数。
|
||
Batches int `json:"batches"` // Batches 已验证并入库的连续批次数。
|
||
Roots int64 `json:"roots"` // Roots 已验证根记录总数。
|
||
Bytes int64 `json:"bytes"` // Bytes 已验证清单和批次总字节数。
|
||
LastSHA256 string `json:"last_sha256"` // LastSHA256 数据库确认的文件链尾摘要;恢复必须验证此前缀。
|
||
ErrorCode string `json:"error_code"` // ErrorCode 固定错误分类,不保存原始路径、业务正文或数据库错误。
|
||
LeaseID core.ID `json:"-"` // LeaseID 当前领取随机代次,不向浏览器返回。
|
||
LeaseUntil *time.Time `json:"lease_until"` // LeaseUntil UTC租约截止,非执行中为空;过期可接管。
|
||
CreatedAt time.Time `json:"created_at"` // CreatedAt 作业受理UTC时间。
|
||
UpdatedAt time.Time `json:"updated_at"` // UpdatedAt 最近状态变更UTC时间。
|
||
}
|
||
|
||
// JobActionInput 是失败作业的人工恢复或关闭输入,必须留有原因。
|
||
type JobActionInput struct {
|
||
ExpectedRevision uint64 `json:"expected_revision,string" required:"true"` // ExpectedRevision 已读取的作业版本。
|
||
Action string `json:"action" required:"true"` // Action 仅retry或abandon;后者保留已有文件与审计。
|
||
MaxBytes int64 `json:"max_bytes" required:"true"` // MaxBytes 重试可提高预算,关闭时保持原值。
|
||
Reason string `json:"reason" required:"true"` // Reason 人工操作原因。
|
||
}
|
||
|
||
// JobPage 采用已返回作业ID作为翻页位置;位置必须属于当前命名空间。
|
||
type JobPage struct {
|
||
Items []ManagedJob `json:"items"` // Items 本页最多20个作业。
|
||
NextBefore string `json:"next_before"` // NextBefore 下一页位置;空表示当前已到末页。
|
||
}
|
||
|
||
// SchedulerStore 将控制台管理与后台执行的持久事务统一到存储层。
|
||
type SchedulerStore interface {
|
||
ListArchivePlans(context.Context, auth.Token) ([]ArchivePlan, error)
|
||
SaveArchivePlan(context.Context, auth.Token, string, string, core.ID, PlanInput) (ArchivePlan, error)
|
||
ListArchiveJobs(context.Context, auth.Token, string) (JobPage, error)
|
||
ActArchiveJob(context.Context, auth.Token, core.ID, string, JobActionInput) (ManagedJob, error)
|
||
}
|
||
|
||
func ValidReason(s string) bool {
|
||
return strings.TrimSpace(s) != "" && utf8.ValidString(s) && len(s) <= 1024 && !strings.ContainsAny(s, "\r\n")
|
||
}
|
||
func (in PlanInput) Validate(namespace core.ID, kind string) error {
|
||
if err := (JobSpec{namespace, kind, in.PolicyRevision, in.BatchSize}).Validate(); err != nil {
|
||
return err
|
||
}
|
||
if in.IntervalSeconds < 3600 || in.IntervalSeconds > 2592000 || in.MaxBytes < 1<<20 || in.MaxBytes > 1<<30 || !ValidReason(in.Reason) {
|
||
return errors.New("invalid archive interval, byte budget or reason")
|
||
}
|
||
return nil
|
||
}
|