feat(backend): 首次登录无有效授权时自动发放 30 天试用

未激活门店(seed/手动建店/老数据,无任何 license 行)此前被当作永久授权,
既不拦截也无提示。现在登录时若门店无有效授权,自动签发 30 天 trial,
之后照常走 grace/readonly/locked 降级链。须在 issueTokens 之前发放,
使 JWT 的 lic_exp 带上新到期日。幂等(已有有效授权不重发),签发失败
仅记日志不阻断登录。复用注册路径的发放逻辑(抽出 issueTrialLicense)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2Wdwo7SmgBJU37cBrkhPK
This commit is contained in:
wangjia
2026-06-18 07:59:44 +08:00
parent 2ac1cbfb24
commit 64a64e7c0a
3 changed files with 81 additions and 7 deletions
+16 -7
View File
@@ -143,12 +143,13 @@ func (s *LicenseService) Deactivate(shopID uint64, deviceID string) error {
Delete(&model.LicenseDevice{}).Error
}
// createTrialLicense 在注册事务中为新门店签发 30 天 trial license。
// 私钥未配置时 Fatal,防止生产环境静默跳过导致新注册门店无 license。
func createTrialLicense(tx *gorm.DB, shopID uint64) {
// issueTrialLicense 为门店签发并写入一条 30 天 trial license。
// 私钥未配置或签发/落库失败时返回 error,由调用方决定如何处理(注册路径 Fatal、
// 登录路径仅记日志)。
func issueTrialLicense(db *gorm.DB, shopID uint64) error {
privKey := config.C.License.Ed25519PrivateKey
if privKey == "" {
log.Fatalf("[license] Ed25519 private key not configured — cannot issue trial for shop %d; set License.Ed25519PrivateKey in config", shopID)
return fmt.Errorf("ed25519 private key not configured")
}
expiresAt := time.Now().Add(30 * 24 * time.Hour)
@@ -162,8 +163,7 @@ func createTrialLicense(tx *gorm.DB, shopID uint64) {
}
token, err := util.IssueLicenseToken(payload, privKey)
if err != nil {
log.Printf("[license] failed to issue trial token for shop %d: %v", shopID, err)
return
return err
}
lic := model.License{
@@ -174,7 +174,16 @@ func createTrialLicense(tx *gorm.DB, shopID uint64) {
IsActive: true,
MaxDevices: 1,
}
if err := tx.Create(&lic).Error; err != nil {
return db.Create(&lic).Error
}
// createTrialLicense 在注册事务中为新门店签发 30 天 trial license。
// 私钥未配置时 Fatal,防止生产环境静默跳过导致新注册门店无 license。
func createTrialLicense(tx *gorm.DB, shopID uint64) {
if config.C.License.Ed25519PrivateKey == "" {
log.Fatalf("[license] Ed25519 private key not configured — cannot issue trial for shop %d; set License.Ed25519PrivateKey in config", shopID)
}
if err := issueTrialLicense(tx, shopID); err != nil {
log.Printf("[license] failed to create trial license for shop %d: %v", shopID, err)
}
}