fix(license): 退出授权在拿不到解绑凭证时降级清本机,不再卡死报错
CI / Backend (go vet + test) (push) Waiting to run
CI / Frontend (lint + build + test) (push) Waiting to run

根因(实测确认):授权服务 activate 响应不含 activation_token 字段,本机存证里
token 为空;deactivate 端要求该凭证 → 远端永远拒绝 → 用户点退出只会报
「远端解绑失败,本机授权未清除」且无法自愈。
修复:远端解绑失败时降级——清本机授权(回免费版,用户诉求达成),
返回 offlineLocalOnly 提示远端席位需服务端释放;服务端将来补发 token 后
此路径自动回到完整解绑。
This commit is contained in:
opsx committed 2026-11-01 04:06:56 +08:00
1 parent 8606b1ee31
commit daa531f4ed
1 file changed
+8 -3
+8 -3
View File
@@ -139,7 +139,7 @@ func ActivateLicense(key string) (*models.License, error) {
lic := &models.License{
Plan: "pro", Source: "opsx", LicenseKey: key, InstanceID: instanceID,
PlanType: claims.PlanType, MaxDevices: claims.MaxDevices, Instances: claims.MaxDevices,
Edition: editionFromPlanType(claims.PlanType, claims.MaxDevices),
Edition: editionFromPlanType(claims.PlanType, claims.MaxDevices),
ExpiresAt: claims.Exp, IssuedAt: time.Unix(claims.Iat, 0), ActivatedAt: time.Now(),
LastVerifiedAt: time.Now().Unix(), Customer: claims.Email, InvalidReason: "",
}
@@ -233,7 +233,13 @@ func DeactivateLicense() (*DeactivateResult, error) {
if key != "" && isOpsxLicenseKey(key) {
if err := remoteDeactivateLicence(key, instanceID, token); err != nil {
return nil, fmt.Errorf("远端解绑失败,本机授权未清除: %w", err)
// 授权服务不返回 activation_token(激活响应无该字段),本机拿不到解绑凭证时
// 远端必然拒绝——不能把用户锁死在"永远解不了绑"里。降级为仅清本机:
// 本机回免费版(用户诉求达成),远端座位由服务端到期/管理员释放。
clearStoredLicense()
result.OfflineLocalOnly = true
result.Message = "已退出授权,本机已回到免费版。远端席位未能自动释放(服务未下发解绑凭证),如需释放请到授权平台管理或联系运营。"
return result, nil
}
clearStoredLicense()
result.Message = "已退出授权并释放本实例"
@@ -317,7 +323,6 @@ func RefreshOpsxLicenseIfDue() {
_ = remoteHeartbeatLicence(lic.LicenseKey, instanceID)
}
// StoredLicense 库内原始许可(含已降级/过期记录);勿与 CurrentLicense 混淆。
func StoredLicense() *models.License {
db := repository.GetDB()