From c553237f15ea9aba28ce60bcb28ac5681e1c354f Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sat, 13 Jun 2026 12:01:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(db):=20=E7=BC=A9=E7=9F=AD=20license=5Fkey?= =?UTF-8?q?=20=E5=88=97=E8=87=B3=20VARCHAR(768)=20=E9=81=BF=E5=85=8D=20Inn?= =?UTF-8?q?oDB=20=E7=B4=A2=E5=BC=95=E8=B6=85=E9=99=90=20[tsk=5FQmk4wT=5Fdk?= =?UTF-8?q?qRR]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原 size:2048 在 utf8mb4 下产生 8192 字节索引,超过 InnoDB 3072 字节上限, 全新环境 AutoMigrate 必失败。改为 VARCHAR(768)(768×4=3072 字节,恰好 在上限内),schema.sql 同步更新列类型并将 uk_license_key 改为全列索引(无 需前缀)。新增测试 TestLicenseTokenFitsColumnLimit 验证最坏情况 token 长度 确实在 768 字符以内。 Co-Authored-By: Claude Sonnet 4.6 --- backend/internal/model/license.go | 2 +- backend/internal/util/license_key_test.go | 35 +++++++++++++++++++++++ backend/schema/schema.sql | 4 +-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/backend/internal/model/license.go b/backend/internal/model/license.go index 1dce691..6d5551d 100644 --- a/backend/internal/model/license.go +++ b/backend/internal/model/license.go @@ -5,7 +5,7 @@ import "time" type License struct { Base ShopID uint64 `gorm:"not null;index" json:"shop_id"` - LicenseKey string `gorm:"size:2048;uniqueIndex" json:"license_key"` + LicenseKey string `gorm:"size:768;uniqueIndex" json:"license_key"` Type string `gorm:"type:enum('trial','monthly','annual','lifetime');default:'trial'" json:"type"` ExpiresAt *time.Time `json:"expires_at"` IsActive bool `gorm:"default:true" json:"is_active"` diff --git a/backend/internal/util/license_key_test.go b/backend/internal/util/license_key_test.go index f8e3801..bb4b424 100644 --- a/backend/internal/util/license_key_test.go +++ b/backend/internal/util/license_key_test.go @@ -73,3 +73,38 @@ func TestVerifyLicenseToken_InvalidFormat(t *testing.T) { _, err = VerifyLicenseToken("not-a-valid-token", pub) assert.ErrorIs(t, err, ErrInvalidLicenseToken) } + +// TestLicenseTokenFitsColumnLimit verifies that a realistic (even worst-case) +// license token fits within the VARCHAR(768) column limit imposed by the +// InnoDB index constraint (768 chars × 4 bytes/char = 3072 bytes max). +func TestLicenseTokenFitsColumnLimit(t *testing.T) { + const columnLimit = 768 + + priv, _, err := GenerateEd25519KeyPair() + require.NoError(t, err) + + // Use a large features map to simulate a worst-case payload. + exp := time.Now().Add(365 * 24 * time.Hour).Unix() + payload := LicensePayload{ + ShopID: 999999999, + LicenseID: 999999999, + Type: "lifetime", + IssuedAt: time.Now().Unix(), + ExpiresAt: &exp, + MaxDevices: 99, + Features: map[string]any{ + "finance": true, + "inventory": true, + "reports": true, + "export": true, + "multi_shop": true, + "api_access": true, + }, + } + + token, err := IssueLicenseToken(payload, priv) + require.NoError(t, err) + + assert.LessOrEqual(t, len(token), columnLimit, + "license token length %d exceeds VARCHAR(%d) column limit", len(token), columnLimit) +} diff --git a/backend/schema/schema.sql b/backend/schema/schema.sql index c835b7a..59fb030 100644 --- a/backend/schema/schema.sql +++ b/backend/schema/schema.sql @@ -58,7 +58,7 @@ CREATE TABLE IF NOT EXISTS `users` ( CREATE TABLE IF NOT EXISTS `licenses` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `shop_id` BIGINT UNSIGNED NOT NULL, - `license_key` VARCHAR(2048) NOT NULL COMMENT 'Ed25519 signed token', + `license_key` VARCHAR(768) NOT NULL COMMENT 'Ed25519 signed token', `device_id` VARCHAR(255) DEFAULT NULL COMMENT 'deprecated: use license_devices', `type` ENUM('trial','monthly','annual','lifetime') NOT NULL DEFAULT 'trial', `expires_at` DATETIME DEFAULT NULL COMMENT 'NULL=永久', @@ -69,7 +69,7 @@ CREATE TABLE IF NOT EXISTS `licenses` ( `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), - UNIQUE KEY `uk_license_key` (`license_key`(255)), + UNIQUE KEY `uk_license_key` (`license_key`), KEY `idx_shop_id` (`shop_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='许可证';