feat(db): split migrations into dialect-specific sqlite and postgres packages

This commit is contained in:
ryan
2026-08-28 15:36:03 +08:00
parent 06508b6f13
commit 692b4b4851
29 changed files with 830 additions and 99 deletions
@@ -1,4 +1,5 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS w_schedules (
id BIGINT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
@@ -15,6 +16,9 @@ CREATE INDEX IF NOT EXISTS idx_w_schedules_is_active ON w_schedules (is_active);
INSERT INTO w_schedules (id, name, task_type, cron, payload, is_active, created_at, updated_at)
VALUES (1, '系统定期垃圾清理', 'system_cleanup', '0 3 * * *', '{}', TRUE, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT (id) DO NOTHING;
-- +goose StatementEnd
-- +goose Down
DROP TABLE IF EXISTS w_schedules;
-- +goose StatementBegin
DROP TABLE IF EXISTS w_schedules;
-- +goose StatementEnd
@@ -0,0 +1,24 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS w_schedules (
id BIGINT PRIMARY KEY,
name VARCHAR(128) NOT NULL,
task_type VARCHAR(64) NOT NULL,
cron VARCHAR(64) NOT NULL,
payload TEXT,
is_active BOOLEAN NOT NULL DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_w_schedules_is_active ON w_schedules (is_active);
-- Seed initial cleanup task
INSERT INTO w_schedules (id, name, task_type, cron, payload, is_active, created_at, updated_at)
VALUES (1, '系统定期垃圾清理', 'system_cleanup', '0 3 * * *', '{}', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT (id) DO NOTHING;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS w_schedules;
-- +goose StatementEnd
@@ -16,9 +16,10 @@ import (
"Wavelet/core"
"Wavelet/core/contracts"
"Wavelet/pkg/config"
)
//go:embed migrations/*.sql
//go:embed migrations/*/*.sql
var cronMigrations embed.FS
// Option configures the Asynq cron scheduler driver plugin.
@@ -148,7 +149,25 @@ func (p *Plugin) Start(_ context.Context) error {
opts.Location = p.location
}
p.scheduler = asynq.NewScheduler(p.redisOpt, opts)
opt := p.redisOpt
if opt == nil {
if RedisOpt != nil {
opt = RedisOpt
} else {
redisCfg := config.Config.Redis
addr := "127.0.0.1:6379"
if len(redisCfg.Addrs) > 0 && redisCfg.Addrs[0] != "" {
addr = redisCfg.Addrs[0]
}
opt = asynq.RedisClientOpt{
Addr: addr,
Username: redisCfg.Username,
Password: redisCfg.Password,
DB: redisCfg.DB,
}
}
}
p.scheduler = asynq.NewScheduler(opt, opts)
}
if p.coreCtx != nil && p.coreCtx.Schedules() != nil {
@@ -5,6 +5,8 @@ package driver_asynq_worker
import (
"sync"
"Wavelet/core/extpoints"
)
// TaskParam 任务参数定义
@@ -61,30 +63,65 @@ func GetDispatchableTasks() []TaskMeta {
return metas
}
// GetTaskMeta 根据任务类型获取元数据
func GetTaskMeta(taskType string) *TaskMeta {
dispatchableTasksMutex.RLock()
defer dispatchableTasksMutex.RUnlock()
for _, t := range dispatchableTasks {
if t.Type == taskType {
copied := t
return &copied
var (
activeTaskRegMutex sync.RWMutex
activeTaskReg extpoints.TaskExtension
)
// SetActiveTaskExtension sets the active task extension registry for task resolution.
func SetActiveTaskExtension(reg extpoints.TaskExtension) {
activeTaskRegMutex.Lock()
defer activeTaskRegMutex.Unlock()
activeTaskReg = reg
}
func getFromActiveTaskExtension(taskType string) *TaskMeta {
activeTaskRegMutex.RLock()
defer activeTaskRegMutex.RUnlock()
if activeTaskReg == nil {
return nil
}
if td, ok := activeTaskReg.Get(taskType); ok {
return &TaskMeta{
Type: td.Pattern,
Name: td.Pattern,
AsynqTask: td.Pattern,
Queue: "default",
Retryable: td.Retry > 0,
MaxRetry: td.Retry,
}
}
return nil
}
// GetTaskMetaByAsynqTask 根据 Asynq 任务名称获取元数据
func GetTaskMetaByAsynqTask(asynqTask string) *TaskMeta {
// GetTaskMeta 根据任务类型获取元数据
func GetTaskMeta(taskType string) *TaskMeta {
dispatchableTasksMutex.RLock()
defer dispatchableTasksMutex.RUnlock()
for _, t := range dispatchableTasks {
if t.AsynqTask == asynqTask {
if t.Type == taskType {
copied := t
dispatchableTasksMutex.RUnlock()
return &copied
}
}
return nil
dispatchableTasksMutex.RUnlock()
return getFromActiveTaskExtension(taskType)
}
// GetTaskMetaByAsynqTask 根据 Asynq 任务名称获取元数据
func GetTaskMetaByAsynqTask(asynqTask string) *TaskMeta {
dispatchableTasksMutex.RLock()
for _, t := range dispatchableTasks {
if t.AsynqTask == asynqTask {
copied := t
dispatchableTasksMutex.RUnlock()
return &copied
}
}
dispatchableTasksMutex.RUnlock()
return getFromActiveTaskExtension(asynqTask)
}
// GetRegisteredAsynqTasks 返回所有已注册的 Asynq 任务名称,以便动态注册路由
@@ -1,4 +1,5 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS w_task_executions (
id BIGINT PRIMARY KEY,
task_id VARCHAR(128) NOT NULL UNIQUE,
@@ -23,6 +24,9 @@ CREATE INDEX IF NOT EXISTS idx_w_task_executions_task_type ON w_task_executions
CREATE INDEX IF NOT EXISTS idx_w_task_executions_status ON w_task_executions (status);
CREATE INDEX IF NOT EXISTS idx_w_task_executions_started_at ON w_task_executions (started_at);
CREATE INDEX IF NOT EXISTS idx_w_task_executions_created_at ON w_task_executions (created_at);
-- +goose StatementEnd
-- +goose Down
DROP TABLE IF EXISTS w_task_executions;
-- +goose StatementBegin
DROP TABLE IF EXISTS w_task_executions;
-- +goose StatementEnd
@@ -0,0 +1,32 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS w_task_executions (
id BIGINT PRIMARY KEY,
task_id VARCHAR(128) NOT NULL UNIQUE,
task_type VARCHAR(64) NOT NULL,
task_name VARCHAR(128),
status VARCHAR(32) NOT NULL,
retryable BOOLEAN NOT NULL DEFAULT 0,
max_retry INTEGER NOT NULL DEFAULT 0,
retry_count INTEGER NOT NULL DEFAULT 0,
log TEXT,
error_message TEXT,
result TEXT,
started_at DATETIME,
finished_at DATETIME,
duration BIGINT,
payload TEXT,
triggered_by VARCHAR(32) NOT NULL DEFAULT 'system',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_w_task_executions_task_type ON w_task_executions (task_type);
CREATE INDEX IF NOT EXISTS idx_w_task_executions_status ON w_task_executions (status);
CREATE INDEX IF NOT EXISTS idx_w_task_executions_started_at ON w_task_executions (started_at);
CREATE INDEX IF NOT EXISTS idx_w_task_executions_created_at ON w_task_executions (created_at);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS w_task_executions;
-- +goose StatementEnd
@@ -16,6 +16,7 @@ import (
"Wavelet/core"
"Wavelet/core/contracts"
"Wavelet/pkg/config"
)
const (
@@ -23,7 +24,7 @@ const (
defaultShutdownTimeout = 10 * time.Second
)
//go:embed migrations/*.sql
//go:embed migrations/*/*.sql
var workerMigrations embed.FS
// Option configures the Asynq worker driver plugin.
@@ -131,11 +132,13 @@ func (p *Plugin) Apply(ctx *core.Context) error {
// 1. Provide contracts.TaskService
p.taskSvc = &taskServiceImpl{}
core.Provide[contracts.TaskService](ctx, p.taskSvc)
SetActiveTaskExtension(ctx.Tasks())
// 2. Register migrations for w_task_executions table
ctx.Migrations().Register("driver_asynq_worker", workerMigrations)
ctx.OnDispose(func() error {
SetActiveTaskExtension(nil)
shutdownCtx, cancel := context.WithTimeout(context.Background(), p.shutdownTimeout)
defer cancel()
return p.Stop(shutdownCtx)
@@ -175,8 +178,26 @@ func (p *Plugin) Start(_ context.Context) error {
}
if p.server == nil {
opt := p.redisOpt
if opt == nil {
if RedisOpt != nil {
opt = RedisOpt
} else {
redisCfg := config.Config.Redis
addr := "127.0.0.1:6379"
if len(redisCfg.Addrs) > 0 && redisCfg.Addrs[0] != "" {
addr = redisCfg.Addrs[0]
}
opt = asynq.RedisClientOpt{
Addr: addr,
Username: redisCfg.Username,
Password: redisCfg.Password,
DB: redisCfg.DB,
}
}
}
p.server = asynq.NewServer(
p.redisOpt,
opt,
asynq.Config{
Concurrency: p.concurrency,
Queues: p.queues,