refactor(数据库): 迁移持久层到 Ent 并清理 GORM
将仓储层/基础设施改为 Ent + 原生 SQL 执行路径,并移除 AutoMigrate 与 GORM 依赖。 重构内容包括: - 仓储层改用 Ent/SQL(含 usage_log/account 等复杂查询),统一错误映射 - 基础设施与 setup 初始化切换为 Ent + SQL migrations - 集成测试与 fixtures 迁移到 Ent 事务模型 - 清理遗留 GORM 模型/依赖,补充迁移与文档说明 - 增加根目录 Makefile 便于前后端编译 测试: - go test -tags unit ./... - go test -tags integration ./...
This commit is contained in:
@@ -170,14 +170,3 @@ CREATE INDEX IF NOT EXISTS idx_usage_logs_account_id ON usage_logs(account_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_model ON usage_logs(model);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_created_at ON usage_logs(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_user_created ON usage_logs(user_id, created_at);
|
||||
|
||||
-- 插入默认管理员用户
|
||||
-- 密码: admin123 (bcrypt hash)
|
||||
INSERT INTO users (email, password_hash, role, balance, concurrency, status)
|
||||
VALUES ('admin@sub2api.com', '$2a$10$N9qo8uLOickgx2ZMRZoMye.IjJbDdJeCo0U2bBPJj9lS/5LqD.C.C', 'admin', 0, 10, 'active')
|
||||
ON CONFLICT (email) DO NOTHING;
|
||||
|
||||
-- 插入默认分组
|
||||
INSERT INTO groups (name, description, rate_multiplier, is_exclusive, status)
|
||||
VALUES ('default', '默认分组', 1.0, false, 'active')
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
|
||||
42
backend/migrations/005_schema_parity.sql
Normal file
42
backend/migrations/005_schema_parity.sql
Normal file
@@ -0,0 +1,42 @@
|
||||
-- Align SQL migrations with current GORM persistence models.
|
||||
-- This file is designed to be safe on both fresh installs and existing databases.
|
||||
|
||||
-- users: add fields added after initial migration
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS username VARCHAR(100) NOT NULL DEFAULT '';
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS wechat VARCHAR(100) NOT NULL DEFAULT '';
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS notes TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- api_keys: allow longer keys (GORM model uses size:128)
|
||||
ALTER TABLE api_keys ALTER COLUMN key TYPE VARCHAR(128);
|
||||
|
||||
-- accounts: scheduling and rate-limit fields used by repository queries
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS schedulable BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS rate_limited_at TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS rate_limit_reset_at TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS overload_until TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS session_window_start TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS session_window_end TIMESTAMPTZ;
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS session_window_status VARCHAR(20);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_schedulable ON accounts(schedulable);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_rate_limited_at ON accounts(rate_limited_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_rate_limit_reset_at ON accounts(rate_limit_reset_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_overload_until ON accounts(overload_until);
|
||||
|
||||
-- redeem_codes: subscription redeem fields
|
||||
ALTER TABLE redeem_codes ADD COLUMN IF NOT EXISTS group_id BIGINT REFERENCES groups(id) ON DELETE SET NULL;
|
||||
ALTER TABLE redeem_codes ADD COLUMN IF NOT EXISTS validity_days INT NOT NULL DEFAULT 30;
|
||||
CREATE INDEX IF NOT EXISTS idx_redeem_codes_group_id ON redeem_codes(group_id);
|
||||
|
||||
-- usage_logs: billing type used by filters and stats
|
||||
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS billing_type SMALLINT NOT NULL DEFAULT 0;
|
||||
CREATE INDEX IF NOT EXISTS idx_usage_logs_billing_type ON usage_logs(billing_type);
|
||||
|
||||
-- settings: key-value store
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
-- Fix legacy subscription records with invalid expires_at (year > 2099).
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('public.user_subscriptions') IS NOT NULL THEN
|
||||
UPDATE user_subscriptions
|
||||
SET expires_at = TIMESTAMPTZ '2099-12-31 23:59:59+00'
|
||||
WHERE expires_at > TIMESTAMPTZ '2099-12-31 23:59:59+00';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
20
backend/migrations/007_add_user_allowed_groups.sql
Normal file
20
backend/migrations/007_add_user_allowed_groups.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
-- Add user_allowed_groups join table to replace users.allowed_groups (BIGINT[]).
|
||||
-- Phase 1: create table + backfill from the legacy array column.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_allowed_groups (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
group_id BIGINT NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (user_id, group_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_allowed_groups_group_id ON user_allowed_groups(group_id);
|
||||
|
||||
-- Backfill from the legacy users.allowed_groups array.
|
||||
INSERT INTO user_allowed_groups (user_id, group_id)
|
||||
SELECT u.id, x.group_id
|
||||
FROM users u
|
||||
CROSS JOIN LATERAL unnest(u.allowed_groups) AS x(group_id)
|
||||
JOIN groups g ON g.id = x.group_id
|
||||
WHERE u.allowed_groups IS NOT NULL
|
||||
ON CONFLICT DO NOTHING;
|
||||
34
backend/migrations/migrations.go
Normal file
34
backend/migrations/migrations.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Package migrations 包含嵌入的 SQL 数据库迁移文件。
|
||||
//
|
||||
// 该包使用 Go 1.16+ 的 embed 功能将 SQL 文件嵌入到编译后的二进制文件中。
|
||||
// 这种方式的优点:
|
||||
// - 部署时无需额外的迁移文件
|
||||
// - 迁移文件与代码版本一致
|
||||
// - 便于版本控制和代码审查
|
||||
package migrations
|
||||
|
||||
import "embed"
|
||||
|
||||
// FS 包含本目录下所有嵌入的 SQL 迁移文件。
|
||||
//
|
||||
// 迁移命名规范:
|
||||
// - 使用零填充的数字前缀确保正确的执行顺序
|
||||
// - 格式:NNN_description.sql(如 001_init.sql, 002_add_users.sql)
|
||||
// - 描述部分使用下划线分隔的小写单词
|
||||
//
|
||||
// 迁移文件要求:
|
||||
// - 必须是幂等的(可重复执行而不产生错误)
|
||||
// - 推荐使用 IF NOT EXISTS / IF EXISTS 语法
|
||||
// - 一旦应用,不应修改已有的迁移文件(通过 checksum 校验)
|
||||
//
|
||||
// 示例迁移文件:
|
||||
//
|
||||
// -- 001_init.sql
|
||||
// CREATE TABLE IF NOT EXISTS users (
|
||||
// id BIGSERIAL PRIMARY KEY,
|
||||
// email VARCHAR(255) NOT NULL UNIQUE,
|
||||
// created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
// );
|
||||
//
|
||||
//go:embed *.sql
|
||||
var FS embed.FS
|
||||
Reference in New Issue
Block a user