Files
LokiStack/backend/models/license.go
T
opsx af0298e994 feat(license): dual-path opsx-licence client alongside HMAC
Wire LIC-/LICOFF- activate/verify/heartbeat to licence.rlwj.cn/api,
keep legacy HMAC accepted with deprecation banner, add instance_id
fingerprint and licensing docs (product lokistack still needs admin create).
2026-11-01 02:55:37 +08:00

71 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package models
import "time"
// License Pro 授权。
// Source: hmac(旧本地签发,迁移窗口内仍接受)| opsx(opsx-licence LIC-/LICOFF-)。
// Plan: free | pro; Edition: personal | team(opsx 侧用 plan_type 映射)。
type License struct {
Plan string `json:"plan"` // free | pro
Edition string `json:"edition"` // personal | team
Seats int `json:"seats"` // 登录席位(历史字段; opsx 按实例计,映射到 Instances)
Instances int `json:"instances"` // 部署实例上限(0 = 不限)
Customer string `json:"customer"` // 客户标识
// ExpiresAt 0 = 永久;否则 Unix 秒
ExpiresAt int64 `json:"expiresAt"`
IssuedAt time.Time `json:"issuedAt"`
ActivatedAt time.Time `json:"activatedAt"`
// --- opsx-licence 对接字段(JSON 向后兼容,缺省为零值) ---
Source string `json:"source,omitempty"` // hmac | opsx
LicenseKey string `json:"licenseKey,omitempty"` // 完整密钥(周检/心跳需要;Sanitized 不外泄)
InstanceID string `json:"instanceId,omitempty"` // 机器指纹,无 PII
PlanType string `json:"planType,omitempty"` // trial | yearly | perpetual
MaxDevices int `json:"maxDevices,omitempty"` // 服务端 max_devices(实例数)
LastVerifiedAt int64 `json:"lastVerifiedAt,omitempty"` // unix 秒
LastServerTime int64 `json:"lastServerTime,omitempty"` // 防时钟回拨
ActivationToken string `json:"activationToken,omitempty"` // 解绑令牌
DeprecationNotice string `json:"deprecationNotice,omitempty"` // 旧 HMAC 迁移提示
InGrace bool `json:"inGrace,omitempty"`
GraceDaysLeft int `json:"graceDaysLeft,omitempty"`
}
func (l *License) Sanitized() map[string]interface{} {
if l == nil {
return map[string]interface{}{"plan": "free"}
}
expired := l.ExpiresAt > 0 && l.ExpiresAt < time.Now().Unix()
plan := l.Plan
if expired && !l.InGrace {
plan = "free"
}
out := map[string]interface{}{
"plan": plan, "edition": l.Edition, "seats": l.Seats, "instances": l.Instances,
"customer": l.Customer, "expiresAt": l.ExpiresAt, "expired": expired,
}
if l.Source != "" {
out["source"] = l.Source
}
if l.PlanType != "" {
out["planType"] = l.PlanType
}
if l.MaxDevices > 0 {
out["maxDevices"] = l.MaxDevices
out["instances"] = l.MaxDevices
}
if l.DeprecationNotice != "" {
out["deprecationNotice"] = l.DeprecationNotice
}
if l.InGrace {
out["inGrace"] = true
out["graceDaysLeft"] = l.GraceDaysLeft
}
if l.LastVerifiedAt > 0 {
out["lastVerifiedAt"] = l.LastVerifiedAt
}
if l.InstanceID != "" {
out["instanceId"] = l.InstanceID
}
return out
}