mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
refactor(db): use version string as primary key for ConfigVersion
-transition `of_config_versions` primary key from `id` to `version` string. -add database migration files `202606270001_make_version_primary_key.sql` for PostgreSQL and SQLite. -introduce AfterFind/AfterCreate GORM hooks to preserve JSON backward compatibility. -refactor API controllers, logics, and front-end typescript definitions to receive `string` parameter.
This commit is contained in:
+1
-1
@@ -64,7 +64,7 @@ REDIS_KEY_PREFIX=openflare:
|
||||
# ─── ClickHouse(必需)────────────────────────────────────────────────────
|
||||
CLICKHOUSE_HOST=clickhouse:9000
|
||||
CLICKHOUSE_USERNAME=default
|
||||
CLICKHOUSE_PASSWORD=
|
||||
CLICKHOUSE_PASSWORD=123456
|
||||
CLICKHOUSE_NAME=openflare
|
||||
|
||||
# ─── 日志 ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ clickhouse:
|
||||
hosts:
|
||||
- "127.0.0.1:9000"
|
||||
username: "default"
|
||||
password: ""
|
||||
password: "123456"
|
||||
database: "openflare"
|
||||
max_idle_conn: 10
|
||||
max_open_conn: 100
|
||||
|
||||
@@ -23,6 +23,7 @@ sidebar: false
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复旧版本迁移升级后,在 PostgreSQL 数据库下发布版本或插入其它新数据时报“版本号生成冲突,请重试”等主键唯一约束冲突的问题。根本原因在于迁移数据时显式指定了旧 ID 导入,导致 PostgreSQL 各核心表的自增主键序列值(sequence)落后于 MAX(id);新增 `202606270001_sync_all_postgres_sequences` 迁移在系统启动时对所有核心表的自增序列执行 `setval` 重新对齐。
|
||||
- 修复代理路由详情页点击“发布配置”时,同时弹出配置差异对话框和确认发布对话框导致重叠的问题:点击发布时不再展示配置差异,直接进行确认发布。
|
||||
- 修复配置版本发布到 Agent 后 `openresty -t` 因 `proxy_cache_path` 使用 `/var/cache/openresty` 导致非 root 用户 `mkdir` 失败的问题:发布快照与渲染将 `/var/` 下路径规范为 `__OPENFLARE_PROXY_CACHE_PATH__`,Agent 应用时落地为 `data_dir/var/cache/openflare_proxy` 并兼容重写已发布配置中的旧路径。
|
||||
- 修复配置版本发布到 Agent 后 `openresty -t` 因证书私钥无法解析而失败的问题。根因是发布快照生成 `certs/{id}.key` 时直接写入库内加密的 `KeyPEM`(`enc:v1:`),未解密为 PEM;现与证书详情接口一致,发布前通过 `OpenKeyPEM` 解密后再下发。
|
||||
|
||||
@@ -29,7 +29,7 @@ export class ConfigVersionService extends OpenFlareBaseService {
|
||||
return this.get<ConfigDiffResult>('/diff');
|
||||
}
|
||||
|
||||
static getById(id: number): Promise<ConfigVersionDetail> {
|
||||
static getById(id: string): Promise<ConfigVersionDetail> {
|
||||
return this.get<ConfigVersionDetail>(`/${id}`);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export class ConfigVersionService extends OpenFlareBaseService {
|
||||
);
|
||||
}
|
||||
|
||||
static activate(id: number): Promise<ConfigVersionDetail> {
|
||||
static activate(id: string): Promise<ConfigVersionDetail> {
|
||||
return this.post<ConfigVersionDetail>(`/${id}/activate`);
|
||||
}
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ export interface ProxyRouteMutationPayload {
|
||||
}
|
||||
|
||||
export interface ConfigVersionSummary {
|
||||
id: number;
|
||||
id: string;
|
||||
version: string;
|
||||
checksum: string;
|
||||
is_active: boolean;
|
||||
|
||||
@@ -79,9 +79,9 @@ func ListConfigVersions(ctx context.Context) ([]*model.ConfigVersionSummary, err
|
||||
return model.ListConfigVersionSummaries(ctx)
|
||||
}
|
||||
|
||||
// GetConfigVersionDetail returns a config version by id.
|
||||
func GetConfigVersionDetail(ctx context.Context, id uint) (*model.ConfigVersion, error) {
|
||||
return model.GetConfigVersionByID(ctx, id)
|
||||
// GetConfigVersionDetail returns a config version by version.
|
||||
func GetConfigVersionDetail(ctx context.Context, version string) (*model.ConfigVersion, error) {
|
||||
return model.GetConfigVersionByVersion(ctx, version)
|
||||
}
|
||||
|
||||
// GetActiveConfigVersion returns the active config version.
|
||||
@@ -243,12 +243,12 @@ func PublishConfigVersion(ctx context.Context, createdBy string, force bool) (*m
|
||||
}
|
||||
|
||||
// ActivateConfigVersion activates an existing config version.
|
||||
func ActivateConfigVersion(ctx context.Context, id uint) (*model.ConfigVersion, error) {
|
||||
version, err := model.GetConfigVersionByID(ctx, id)
|
||||
func ActivateConfigVersion(ctx context.Context, versionStr string) (*model.ConfigVersion, error) {
|
||||
version, err := model.GetConfigVersionByVersion(ctx, versionStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = model.ActivateConfigVersionTx(ctx, id); err != nil {
|
||||
if err = model.ActivateConfigVersionTx(ctx, versionStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
version.IsActive = true
|
||||
@@ -271,7 +271,7 @@ func CleanupConfigVersions(ctx context.Context, keepCount int) (*CleanupResult,
|
||||
if len(versions) <= keepCount {
|
||||
return &CleanupResult{DeletedCount: 0, Message: cleanupSuccessMessage}, nil
|
||||
}
|
||||
var deleteIDs []uint
|
||||
var deleteVersions []string
|
||||
for index, version := range versions {
|
||||
if index < keepCount {
|
||||
continue
|
||||
@@ -279,12 +279,12 @@ func CleanupConfigVersions(ctx context.Context, keepCount int) (*CleanupResult,
|
||||
if version.IsActive {
|
||||
continue
|
||||
}
|
||||
deleteIDs = append(deleteIDs, version.ID)
|
||||
deleteVersions = append(deleteVersions, version.Version)
|
||||
}
|
||||
if len(deleteIDs) == 0 {
|
||||
if len(deleteVersions) == 0 {
|
||||
return &CleanupResult{DeletedCount: 0, Message: cleanupSuccessMessage}, nil
|
||||
}
|
||||
deletedCount, err := model.DeleteConfigVersionsByIDs(ctx, deleteIDs)
|
||||
deletedCount, err := model.DeleteConfigVersionsByVersions(ctx, deleteVersions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ func setupConfigVersionTestDB(t *testing.T) func() {
|
||||
&model.OpenFlareWAFRuleGroup{},
|
||||
&model.OpenFlareWAFRuleGroupBinding{},
|
||||
&model.OpenFlareWAFIPGroup{},
|
||||
&model.SystemConfig{},
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
@@ -64,7 +65,6 @@ func TestListConfigVersionsOrdersByCreatedAtDesc(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, conn.Create(newer).Error)
|
||||
require.NoError(t, conn.Create(older).Error)
|
||||
require.Greater(t, older.ID, newer.ID)
|
||||
|
||||
versions, err := ListConfigVersions(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -91,7 +91,7 @@ func TestPublishConfigVersionCreatesVersion(t *testing.T) {
|
||||
version, err := PublishConfigVersion(ctx, "tester", false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, version)
|
||||
assert.NotZero(t, version.ID)
|
||||
assert.NotEmpty(t, version.ID)
|
||||
assert.True(t, version.IsActive)
|
||||
assert.Equal(t, "tester", version.CreatedBy)
|
||||
assert.NotEmpty(t, version.Version)
|
||||
|
||||
@@ -18,6 +18,15 @@ func handleLogicError(c *gin.Context, err error) bool {
|
||||
return apiutil.AbortNotFoundIfMissing(c, err, "记录不存在")
|
||||
}
|
||||
|
||||
func versionParam(c *gin.Context) (string, bool) {
|
||||
version := c.Param("id")
|
||||
if version == "" {
|
||||
response.AbortBadRequest(c, "无效的版本号")
|
||||
return "", false
|
||||
}
|
||||
return version, true
|
||||
}
|
||||
|
||||
// ListConfigVersionsHandler lists config versions.
|
||||
// @Summary 获取配置版本列表
|
||||
// @Description 返回所有已发布的 OpenResty 配置版本摘要,按创建时间倒序排列,需要管理员权限
|
||||
@@ -50,11 +59,11 @@ func ListConfigVersionsHandler(c *gin.Context) {
|
||||
// @Failure 404 {object} response.Any "无权限或版本不存在"
|
||||
// @Router /api/v1/d/config-versions/{id} [get]
|
||||
func GetConfigVersionHandler(c *gin.Context) {
|
||||
id, ok := apiutil.IDParam(c)
|
||||
versionStr, ok := versionParam(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
version, err := GetConfigVersionDetail(c.Request.Context(), id)
|
||||
version, err := GetConfigVersionDetail(c.Request.Context(), versionStr)
|
||||
if handleLogicError(c, err) {
|
||||
return
|
||||
}
|
||||
@@ -152,11 +161,11 @@ func PublishConfigVersionHandler(c *gin.Context) {
|
||||
// @Failure 404 {object} response.Any "无权限或版本不存在"
|
||||
// @Router /api/v1/d/config-versions/{id}/activate [post]
|
||||
func ActivateConfigVersionHandler(c *gin.Context) {
|
||||
id, ok := apiutil.IDParam(c)
|
||||
versionStr, ok := versionParam(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
version, err := ActivateConfigVersion(c.Request.Context(), id)
|
||||
version, err := ActivateConfigVersion(c.Request.Context(), versionStr)
|
||||
if handleLogicError(c, err) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
-- +goose Up
|
||||
-- Remove old PRIMARY KEY on id, set version as PRIMARY KEY, drop id column
|
||||
|
||||
-- 1. 移除旧主键约束
|
||||
ALTER TABLE of_config_versions DROP CONSTRAINT IF EXISTS of_config_versions_pkey;
|
||||
|
||||
-- 2. 移除原先在 version 字段的唯一索引(因为 version 变成主键,主键隐含唯一性)
|
||||
DROP INDEX IF EXISTS idx_of_config_versions_version;
|
||||
|
||||
-- 3. 将 version 设为新主键
|
||||
ALTER TABLE of_config_versions ADD PRIMARY KEY (version);
|
||||
|
||||
-- 4. 彻底删除 id 列
|
||||
ALTER TABLE of_config_versions DROP COLUMN IF EXISTS id;
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE of_config_versions DROP CONSTRAINT IF EXISTS of_config_versions_pkey;
|
||||
ALTER TABLE of_config_versions ADD COLUMN IF NOT EXISTS id BIGSERIAL PRIMARY KEY;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_of_config_versions_version ON of_config_versions (version);
|
||||
@@ -0,0 +1,46 @@
|
||||
-- +goose Up
|
||||
-- SQLite table reconstruction to set version as primary key and drop id column
|
||||
|
||||
-- 1. 创建临时新表
|
||||
CREATE TABLE of_config_versions_new (
|
||||
version VARCHAR(32) PRIMARY KEY,
|
||||
snapshot_json TEXT NOT NULL,
|
||||
main_config TEXT NOT NULL DEFAULT '',
|
||||
rendered_config TEXT NOT NULL,
|
||||
support_files_json TEXT NOT NULL DEFAULT '[]',
|
||||
checksum VARCHAR(64) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_by VARCHAR(64) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 2. 拷贝数据
|
||||
INSERT INTO of_config_versions_new (version, snapshot_json, main_config, rendered_config, support_files_json, checksum, is_active, created_by, created_at)
|
||||
SELECT version, snapshot_json, main_config, rendered_config, support_files_json, checksum, is_active, created_by, created_at
|
||||
FROM of_config_versions;
|
||||
|
||||
-- 3. 删除旧表
|
||||
DROP TABLE of_config_versions;
|
||||
|
||||
-- 4. 重命名新表
|
||||
ALTER TABLE of_config_versions_new RENAME TO of_config_versions;
|
||||
|
||||
-- 5. 重建索引(is_active 索引)
|
||||
CREATE INDEX IF NOT EXISTS idx_of_config_versions_is_active ON of_config_versions (is_active);
|
||||
|
||||
-- +goose Down
|
||||
DROP TABLE IF EXISTS of_config_versions;
|
||||
CREATE TABLE of_config_versions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
version VARCHAR(32) NOT NULL,
|
||||
snapshot_json TEXT NOT NULL,
|
||||
main_config TEXT NOT NULL DEFAULT '',
|
||||
rendered_config TEXT NOT NULL,
|
||||
support_files_json TEXT NOT NULL DEFAULT '[]',
|
||||
checksum VARCHAR(64) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_by VARCHAR(64) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_of_config_versions_version ON of_config_versions (version);
|
||||
CREATE INDEX IF NOT EXISTS idx_of_config_versions_is_active ON of_config_versions (is_active);
|
||||
@@ -14,18 +14,24 @@ import (
|
||||
|
||||
// ConfigVersionSummary is the list view for config versions.
|
||||
type ConfigVersionSummary struct {
|
||||
ID uint `json:"id"`
|
||||
Version string `json:"version"`
|
||||
ID string `json:"id" gorm:"-"`
|
||||
Version string `json:"version" gorm:"primaryKey;column:version"`
|
||||
Checksum string `json:"checksum"`
|
||||
IsActive bool `json:"is_active"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// AfterFind hook for ConfigVersionSummary.
|
||||
func (cvs *ConfigVersionSummary) AfterFind(_ *gorm.DB) (err error) {
|
||||
cvs.ID = cvs.Version
|
||||
return
|
||||
}
|
||||
|
||||
// ConfigVersion stores a published OpenResty configuration snapshot.
|
||||
type ConfigVersion struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Version string `json:"version" gorm:"uniqueIndex;size:32;not null"`
|
||||
ID string `json:"id" gorm:"-"`
|
||||
Version string `json:"version" gorm:"primaryKey;size:32;not null"`
|
||||
SnapshotJSON string `json:"snapshot_json" gorm:"type:text;not null"`
|
||||
MainConfig string `json:"main_config" gorm:"type:text;not null;default:''"`
|
||||
RenderedConfig string `json:"rendered_config" gorm:"type:text;not null"`
|
||||
@@ -36,6 +42,18 @@ type ConfigVersion struct {
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
}
|
||||
|
||||
// AfterFind hook for ConfigVersion.
|
||||
func (cv *ConfigVersion) AfterFind(_ *gorm.DB) (err error) {
|
||||
cv.ID = cv.Version
|
||||
return
|
||||
}
|
||||
|
||||
// AfterCreate hook for ConfigVersion.
|
||||
func (cv *ConfigVersion) AfterCreate(_ *gorm.DB) (err error) {
|
||||
cv.ID = cv.Version
|
||||
return
|
||||
}
|
||||
|
||||
// TableName returns the GORM table name.
|
||||
func (ConfigVersion) TableName() string {
|
||||
return "of_config_versions"
|
||||
@@ -49,23 +67,23 @@ func ListConfigVersionSummaries(ctx context.Context) ([]*ConfigVersionSummary, e
|
||||
}
|
||||
var versions []*ConfigVersionSummary
|
||||
err := conn.Model(&ConfigVersion{}).
|
||||
Select("id", "version", "checksum", "is_active", "created_by", "created_at").
|
||||
Order("created_at desc, id desc").
|
||||
Select("version", "checksum", "is_active", "created_by", "created_at").
|
||||
Order("created_at desc, version desc").
|
||||
Find(&versions).Error
|
||||
return versions, err
|
||||
}
|
||||
|
||||
// GetConfigVersionByID returns a config version by primary key.
|
||||
func GetConfigVersionByID(ctx context.Context, id uint) (*ConfigVersion, error) {
|
||||
// GetConfigVersionByVersion returns a config version by version string.
|
||||
func GetConfigVersionByVersion(ctx context.Context, version string) (*ConfigVersion, error) {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return nil, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
var version ConfigVersion
|
||||
if err := conn.First(&version, id).Error; err != nil {
|
||||
var cv ConfigVersion
|
||||
if err := conn.First(&cv, "version = ?", version).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &version, nil
|
||||
return &cv, nil
|
||||
}
|
||||
|
||||
// GetActiveConfigVersion returns the currently active config version.
|
||||
@@ -75,7 +93,7 @@ func GetActiveConfigVersion(ctx context.Context) (*ConfigVersion, error) {
|
||||
return nil, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
var version ConfigVersion
|
||||
if err := conn.Where("is_active = ?", true).Order("id desc").First(&version).Error; err != nil {
|
||||
if err := conn.Where("is_active = ?", true).Order("version desc").First(&version).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &version, nil
|
||||
@@ -123,7 +141,7 @@ func PublishConfigVersionTx(ctx context.Context, version *ConfigVersion) error {
|
||||
}
|
||||
|
||||
// ActivateConfigVersionTx marks the given version active and deactivates others.
|
||||
func ActivateConfigVersionTx(ctx context.Context, id uint) error {
|
||||
func ActivateConfigVersionTx(ctx context.Context, version string) error {
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return errors.New(errDatabaseNotInitialized)
|
||||
@@ -132,20 +150,20 @@ func ActivateConfigVersionTx(ctx context.Context, id uint) error {
|
||||
if err := tx.Model(&ConfigVersion{}).Where("is_active = ?", true).Update("is_active", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&ConfigVersion{}).Where("id = ?", id).Update("is_active", true).Error
|
||||
return tx.Model(&ConfigVersion{}).Where("version = ?", version).Update("is_active", true).Error
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteConfigVersionsByIDs removes config versions by ids.
|
||||
func DeleteConfigVersionsByIDs(ctx context.Context, ids []uint) (int64, error) {
|
||||
if len(ids) == 0 {
|
||||
// DeleteConfigVersionsByVersions removes config versions by versions.
|
||||
func DeleteConfigVersionsByVersions(ctx context.Context, versions []string) (int64, error) {
|
||||
if len(versions) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
conn := db.DB(ctx)
|
||||
if conn == nil {
|
||||
return 0, errors.New(errDatabaseNotInitialized)
|
||||
}
|
||||
result := conn.Where("id IN ?", ids).Delete(&ConfigVersion{})
|
||||
result := conn.Where("version IN ?", versions).Delete(&ConfigVersion{})
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user