feat(license): 退出授权 / 释放本实例(确认后 deactivate)

设置→授权 Pro 态管理员可退出:在线 LIC- 先调 opsx deactivate(含 activation_token),成功才清本地;失败保留现状。LICOFF- 仅解本机并提示后台名额可能仍占。禁静默清码。
This commit is contained in:
opsx committed 2026-11-01 03:16:05 +08:00
1 parent f3d27ceb04
commit cec7c6eeaf
10 files changed
+338 -3

No files matched your search

+69
View File
@@ -188,6 +188,75 @@ func ActivateLicense(key string) (*models.License, error) {
return persistLicense(lic)
}
// DeactivateResult 退出授权结果。OfflineLocalOnly 表示仅解本机(LICOFF-),后台名额可能仍占。
type DeactivateResult struct {
OfflineLocalOnly bool `json:"offlineLocalOnly"`
Message string `json:"message,omitempty"`
}
// DeactivateLicense 用户主动退出授权 / 释放本实例。
// - 在线 LIC-:先 POST /v1/license/deactivate(带 activation_token),成功才清本地;失败保留现状。
// - 离线 LICOFF-:只解本机绑定,并提示后台名额可能仍占。
// - 旧 HMAC:仅清本地(无远端座位)。
// 禁止在校验失败路径静默清码;本函数仅用户确认后调用。
func DeactivateLicense() (*DeactivateResult, error) {
db := repository.GetDB()
if db == nil {
return nil, fmt.Errorf("数据库未就绪")
}
db.RLock()
lic := db.License
db.RUnlock()
if lic == nil {
return nil, fmt.Errorf("当前未激活授权")
}
// 允许对仍存证的 Pro(含宽限)或刚降级但仍占远端席位的 opsx 证做主动释放
key := strings.TrimSpace(lic.LicenseKey)
if lic.Plan != "pro" && key == "" {
return nil, fmt.Errorf("当前未激活授权")
}
token := lic.ActivationToken
instanceID := lic.InstanceID
if instanceID == "" {
instanceID = GetOrCreateInstanceID()
}
result := &DeactivateResult{}
if key != "" && isOfflineLicenseKey(key) {
clearStoredLicense()
result.OfflineLocalOnly = true
result.Message = "已解除本机离线授权。后台名额可能仍占用,如需释放请联系运营处理。"
return result, nil
}
if key != "" && isOpsxLicenseKey(key) {
if err := remoteDeactivateLicence(key, instanceID, token); err != nil {
return nil, fmt.Errorf("远端解绑失败,本机授权未清除: %w", err)
}
clearStoredLicense()
result.Message = "已退出授权并释放本实例"
return result, nil
}
// HMAC 或无 key 的本地 Pro
clearStoredLicense()
result.Message = "已清除本机授权"
return result, nil
}
func clearStoredLicense() {
db := repository.GetDB()
if db == nil {
return
}
db.Lock()
db.License = nil
db.RequestSave()
db.Unlock()
}
func persistLicense(lic *models.License) (*models.License, error) {
db := repository.GetDB()
db.Lock()