mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-05 23:26:38 +08:00
feat(core): bind request services and implement registered tasks
Wire plugin services through Bind/InjectFrom and AppContext so HTTP and workers resolve dependencies after Apply. Register TaskHandler objects with persisted results, and implement send_email_code, mail:send, cleanup_inactive_users, and dispatch_bot_msg.
This commit is contained in:
@@ -48,7 +48,7 @@ func abortTaskLogicError(c *gin.Context, err error) bool {
|
||||
// @Failure 403 {object} response.Any "无管理员权限"
|
||||
// @Router /api/v1/admin/tasks/types [get]
|
||||
func ListTaskTypes(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response.OK(service.ListTaskTypes()))
|
||||
c.JSON(http.StatusOK, response.OK(service.ListTaskTypes(c.Request.Context())))
|
||||
}
|
||||
|
||||
// DispatchTask 下发任务
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"Wavelet/plugins/domain/admin/handler"
|
||||
"Wavelet/plugins/domain/admin/model"
|
||||
"Wavelet/plugins/domain/admin/service"
|
||||
"context"
|
||||
"embed"
|
||||
"reflect"
|
||||
|
||||
@@ -85,56 +84,13 @@ func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
_ = ctx.Config().Bind("clickhouse", &chCfg)
|
||||
service.SetClickHouseConfig(chCfg)
|
||||
|
||||
// 0. Bind Services reactively
|
||||
if db, err := core.Inject[contracts.DBService](ctx); err == nil && db != nil {
|
||||
service.SetDBService(db)
|
||||
} else {
|
||||
core.When[contracts.DBService](ctx, func(db contracts.DBService) {
|
||||
service.SetDBService(db)
|
||||
})
|
||||
}
|
||||
if cache, err := core.Inject[contracts.CacheService](ctx); err == nil && cache != nil {
|
||||
service.SetCacheService(cache)
|
||||
} else {
|
||||
core.When[contracts.CacheService](ctx, func(cache contracts.CacheService) {
|
||||
service.SetCacheService(cache)
|
||||
})
|
||||
}
|
||||
if user, err := core.Inject[contracts.UserService](ctx); err == nil && user != nil {
|
||||
service.SetUserService(user)
|
||||
} else {
|
||||
core.When[contracts.UserService](ctx, func(user contracts.UserService) {
|
||||
service.SetUserService(user)
|
||||
})
|
||||
}
|
||||
if auth, err := core.Inject[contracts.AuthService](ctx); err == nil && auth != nil {
|
||||
service.SetAuthService(auth)
|
||||
} else {
|
||||
core.When[contracts.AuthService](ctx, func(auth contracts.AuthService) {
|
||||
service.SetAuthService(auth)
|
||||
})
|
||||
}
|
||||
if task, err := core.Inject[contracts.TaskService](ctx); err == nil && task != nil {
|
||||
service.SetTaskService(task)
|
||||
} else {
|
||||
core.When[contracts.TaskService](ctx, func(task contracts.TaskService) {
|
||||
service.SetTaskService(task)
|
||||
})
|
||||
}
|
||||
if storage, err := core.Inject[contracts.StorageService](ctx); err == nil && storage != nil {
|
||||
service.SetStorageService(storage)
|
||||
} else {
|
||||
core.When[contracts.StorageService](ctx, func(storage contracts.StorageService) {
|
||||
service.SetStorageService(storage)
|
||||
})
|
||||
}
|
||||
if rc, err := core.Inject[contracts.RiskControlService](ctx); err == nil && rc != nil {
|
||||
service.SetRiskControlService(rc)
|
||||
} else {
|
||||
core.When[contracts.RiskControlService](ctx, func(rc contracts.RiskControlService) {
|
||||
service.SetRiskControlService(rc)
|
||||
})
|
||||
}
|
||||
core.Bind[contracts.DBService](ctx, service.SetDBService)
|
||||
core.Bind[contracts.CacheService](ctx, service.SetCacheService)
|
||||
core.Bind[contracts.UserService](ctx, service.SetUserService)
|
||||
core.Bind[contracts.AuthService](ctx, service.SetAuthService)
|
||||
core.Bind[contracts.TaskService](ctx, service.SetTaskService)
|
||||
core.Bind[contracts.StorageService](ctx, service.SetStorageService)
|
||||
core.Bind[contracts.RiskControlService](ctx, service.SetRiskControlService)
|
||||
service.SetEventEmitter(ctx.Events().Emit)
|
||||
|
||||
ctx.OnDispose(func() error {
|
||||
@@ -175,11 +131,7 @@ func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
ctx.Router().RegisterWhitelist("/robots.txt")
|
||||
|
||||
// 2. Register Background Tasks
|
||||
logSwitchHandler := &service.LogDBSwitchHandler{}
|
||||
ctx.Task().Register(service.LogDBSwitchTask, func(c context.Context, payload []byte) error {
|
||||
_, err := logSwitchHandler.Execute(c, payload)
|
||||
return err
|
||||
}, extpoints.WithTaskMeta(service.LogDBSwitchMeta))
|
||||
ctx.Task().Register(service.LogDBSwitchTask, &service.LogDBSwitchHandler{}, extpoints.WithTaskMeta(service.LogDBSwitchMeta))
|
||||
|
||||
// 3. Register Settings Schemas
|
||||
ctx.Settings().Register(extpoints.SettingSchema{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"Wavelet/core"
|
||||
"Wavelet/core/contracts"
|
||||
"Wavelet/pkg/cache/ram"
|
||||
"Wavelet/pkg/logger"
|
||||
@@ -56,6 +57,9 @@ func ResetServices() {
|
||||
|
||||
// GetDB returns the GORM DB instance bound to the context if available.
|
||||
func GetDB(ctx context.Context) *gorm.DB {
|
||||
if s, err := core.InjectFrom[contracts.DBService](ctx); err == nil && s != nil {
|
||||
return s.DB(ctx)
|
||||
}
|
||||
repoMu.RLock()
|
||||
defer repoMu.RUnlock()
|
||||
if dbService == nil {
|
||||
@@ -65,7 +69,10 @@ func GetDB(ctx context.Context) *gorm.DB {
|
||||
}
|
||||
|
||||
// GetCache returns the unified CacheService instance.
|
||||
func GetCache(_ context.Context) contracts.CacheService {
|
||||
func GetCache(ctx context.Context) contracts.CacheService {
|
||||
if s, err := core.InjectFrom[contracts.CacheService](ctx); err == nil && s != nil {
|
||||
return s
|
||||
}
|
||||
repoMu.RLock()
|
||||
defer repoMu.RUnlock()
|
||||
return cacheService
|
||||
|
||||
@@ -75,7 +75,7 @@ func IsAllowedLogOrigin(ctx context.Context, origin, host string) bool {
|
||||
|
||||
// AccessLogs queries the analytical access log store and decorates rows with user names.
|
||||
func AccessLogs(ctx context.Context, q model.AccessLogQuery) (model.AccessLogsResponse, error) {
|
||||
rc := GetRiskControlService()
|
||||
rc := GetRiskControlService(ctx)
|
||||
if rc == nil {
|
||||
return model.AccessLogsResponse{}, errs.ErrLogStoreUnavailable
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func AccessLogs(ctx context.Context, q model.AccessLogQuery) (model.AccessLogsRe
|
||||
|
||||
// AccessLogAnalytics aggregates the daily trend of the access log store.
|
||||
func AccessLogAnalytics(ctx context.Context) (model.LogsAnalyticsResponse, error) {
|
||||
rc := GetRiskControlService()
|
||||
rc := GetRiskControlService(ctx)
|
||||
if rc == nil {
|
||||
return model.LogsAnalyticsResponse{}, errs.ErrLogStoreUnavailable
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func (h *LogDBSwitchHandler) Execute(ctx context.Context, payload []byte) (*cont
|
||||
return nil, err
|
||||
}
|
||||
|
||||
taskSvc := GetTaskService()
|
||||
taskSvc := GetTaskService(ctx)
|
||||
if taskSvc != nil {
|
||||
taskSvc.AppendLog(ctx, "开始切换日志数据库:%s -> %s", source, p.Target)
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (h *LogDBSwitchHandler) Execute(ctx context.Context, payload []byte) (*cont
|
||||
}
|
||||
}()
|
||||
|
||||
rc := GetRiskControlService()
|
||||
rc := GetRiskControlService(ctx)
|
||||
if rc != nil {
|
||||
if err := rc.SwitchLogEngine(ctx, p.Target); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"Wavelet/core"
|
||||
"Wavelet/core/contracts"
|
||||
"Wavelet/plugins/domain/admin/errs"
|
||||
"Wavelet/plugins/domain/admin/repository"
|
||||
@@ -112,6 +113,9 @@ func ResetServices() {
|
||||
|
||||
// GetDB returns the GORM DB instance bound to the context if available.
|
||||
func GetDB(ctx context.Context) *gorm.DB {
|
||||
if s, err := core.InjectFrom[contracts.DBService](ctx); err == nil && s != nil {
|
||||
return s.DB(ctx)
|
||||
}
|
||||
servicesMu.RLock()
|
||||
defer servicesMu.RUnlock()
|
||||
if dbService == nil {
|
||||
@@ -121,42 +125,60 @@ func GetDB(ctx context.Context) *gorm.DB {
|
||||
}
|
||||
|
||||
// GetCache returns the unified CacheService instance.
|
||||
func GetCache(_ context.Context) contracts.CacheService {
|
||||
func GetCache(ctx context.Context) contracts.CacheService {
|
||||
if s, err := core.InjectFrom[contracts.CacheService](ctx); err == nil && s != nil {
|
||||
return s
|
||||
}
|
||||
servicesMu.RLock()
|
||||
defer servicesMu.RUnlock()
|
||||
return cacheService
|
||||
}
|
||||
|
||||
// GetUserService returns the UserService instance.
|
||||
func GetUserService(_ context.Context) contracts.UserService {
|
||||
func GetUserService(ctx context.Context) contracts.UserService {
|
||||
if s, err := core.InjectFrom[contracts.UserService](ctx); err == nil && s != nil {
|
||||
return s
|
||||
}
|
||||
servicesMu.RLock()
|
||||
defer servicesMu.RUnlock()
|
||||
return userService
|
||||
}
|
||||
|
||||
// GetAuthService returns the AuthService instance.
|
||||
func GetAuthService(_ context.Context) contracts.AuthService {
|
||||
func GetAuthService(ctx context.Context) contracts.AuthService {
|
||||
if s, err := core.InjectFrom[contracts.AuthService](ctx); err == nil && s != nil {
|
||||
return s
|
||||
}
|
||||
servicesMu.RLock()
|
||||
defer servicesMu.RUnlock()
|
||||
return authService
|
||||
}
|
||||
|
||||
// GetTaskService returns the TaskService instance.
|
||||
func GetTaskService() contracts.TaskService {
|
||||
func GetTaskService(ctx context.Context) contracts.TaskService {
|
||||
if s, err := core.InjectFrom[contracts.TaskService](ctx); err == nil && s != nil {
|
||||
return s
|
||||
}
|
||||
servicesMu.RLock()
|
||||
defer servicesMu.RUnlock()
|
||||
return taskService
|
||||
}
|
||||
|
||||
// GetStorageService returns the StorageService instance.
|
||||
func GetStorageService() contracts.StorageService {
|
||||
func GetStorageService(ctx context.Context) contracts.StorageService {
|
||||
if s, err := core.InjectFrom[contracts.StorageService](ctx); err == nil && s != nil {
|
||||
return s
|
||||
}
|
||||
servicesMu.RLock()
|
||||
defer servicesMu.RUnlock()
|
||||
return storageSvc
|
||||
}
|
||||
|
||||
// GetRiskControlService returns the RiskControlService instance.
|
||||
func GetRiskControlService() contracts.RiskControlService {
|
||||
func GetRiskControlService(ctx context.Context) contracts.RiskControlService {
|
||||
if s, err := core.InjectFrom[contracts.RiskControlService](ctx); err == nil && s != nil {
|
||||
return s
|
||||
}
|
||||
servicesMu.RLock()
|
||||
defer servicesMu.RUnlock()
|
||||
return riskControlService
|
||||
@@ -195,8 +217,8 @@ func requireAuthService(ctx context.Context) (contracts.AuthService, error) {
|
||||
}
|
||||
|
||||
// requireTaskService resolves the injected task contract service.
|
||||
func requireTaskService() (contracts.TaskService, error) {
|
||||
taskSvc := GetTaskService()
|
||||
func requireTaskService(ctx context.Context) (contracts.TaskService, error) {
|
||||
taskSvc := GetTaskService(ctx)
|
||||
if taskSvc == nil {
|
||||
return nil, errs.ErrTaskServiceUnavailable
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ func formatDuration(d time.Duration) string {
|
||||
func LogDatabaseStatus(ctx context.Context) model.LogDatabaseStatus {
|
||||
activeDB := logDBNameSQLite
|
||||
migration := logMigrationIdle
|
||||
if rc := GetRiskControlService(); rc != nil {
|
||||
if rc := GetRiskControlService(ctx); rc != nil {
|
||||
activeDB = rc.ActiveLogEngine(ctx)
|
||||
if rc.IsLogEngineMigrating(ctx) {
|
||||
migration = logMigrationInProgress
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
)
|
||||
|
||||
// ListTaskTypes returns every dispatchable task type declared in the task registry.
|
||||
func ListTaskTypes() []contracts.TaskMetaDTO {
|
||||
taskSvc := GetTaskService()
|
||||
func ListTaskTypes(ctx context.Context) []contracts.TaskMetaDTO {
|
||||
taskSvc := GetTaskService(ctx)
|
||||
if taskSvc == nil {
|
||||
return []contracts.TaskMetaDTO{}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ func ListTaskTypes() []contracts.TaskMetaDTO {
|
||||
|
||||
// DispatchTask validates and enqueues a manual task run, returning the new task id.
|
||||
func DispatchTask(ctx context.Context, req model.DispatchTaskRequest) (string, error) {
|
||||
taskSvc, err := requireTaskService()
|
||||
taskSvc, err := requireTaskService(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -67,29 +67,75 @@ func ListTaskExecutions(
|
||||
ctx context.Context,
|
||||
req model.ListTaskExecutionsRequest,
|
||||
) ([]model.TaskExecution, int64, error) {
|
||||
if req.TaskType != "" {
|
||||
if taskSvc := GetTaskService(); taskSvc != nil {
|
||||
if meta, ok := taskSvc.GetTaskMeta(req.TaskType); ok {
|
||||
req.TaskType = meta.Name
|
||||
}
|
||||
taskSvc, err := requireTaskService(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if req.Page <= 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize <= 0 {
|
||||
req.PageSize = 20
|
||||
}
|
||||
|
||||
filterType := req.TaskType
|
||||
if filterType != "" {
|
||||
if meta, ok := taskSvc.GetTaskMeta(filterType); ok {
|
||||
filterType = meta.AsynqTask
|
||||
}
|
||||
}
|
||||
|
||||
executions, total, err := repository.ListTaskExecutionRecords(ctx, req)
|
||||
rows, total, err := taskSvc.ListExecutions(ctx, filterType, req.Status, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
executions := make([]model.TaskExecution, 0, len(rows))
|
||||
for i := range rows {
|
||||
executions = append(executions, executionFromDTO(rows[i]))
|
||||
}
|
||||
return executions, total, nil
|
||||
}
|
||||
|
||||
// TaskExecution loads a single execution record including its buffered log.
|
||||
func TaskExecution(ctx context.Context, id uint64) (*model.TaskExecution, error) {
|
||||
return repository.GetTaskExecutionByID(ctx, id)
|
||||
taskSvc, err := requireTaskService(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dto, err := taskSvc.GetExecution(ctx, id)
|
||||
if err != nil || dto == nil {
|
||||
return nil, err
|
||||
}
|
||||
row := executionFromDTO(*dto)
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func executionFromDTO(dto contracts.TaskExecutionDTO) model.TaskExecution {
|
||||
return model.TaskExecution{
|
||||
ID: dto.ID,
|
||||
TaskID: dto.TaskID,
|
||||
TaskType: dto.TaskType,
|
||||
TaskName: dto.TaskName,
|
||||
Status: model.TaskExecutionStatus(dto.Status),
|
||||
Retryable: dto.Retryable,
|
||||
MaxRetry: dto.MaxRetry,
|
||||
RetryCount: dto.RetryCount,
|
||||
Log: dto.Log,
|
||||
ErrorMessage: dto.ErrorMessage,
|
||||
Result: dto.Result,
|
||||
StartedAt: dto.StartedAt,
|
||||
FinishedAt: dto.FinishedAt,
|
||||
Duration: dto.Duration,
|
||||
Payload: dto.Payload,
|
||||
TriggeredBy: dto.TriggeredBy,
|
||||
CreatedAt: dto.CreatedAt,
|
||||
UpdatedAt: dto.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// RetryTask re-dispatches a failed execution as a new task run.
|
||||
func RetryTask(ctx context.Context, id uint64) (string, error) {
|
||||
taskSvc, err := requireTaskService()
|
||||
taskSvc, err := requireTaskService(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -126,7 +172,7 @@ func CreateSchedule(ctx context.Context, req model.CreateScheduleRequest) (*mode
|
||||
return nil, errs.ErrInvalidCronExpression
|
||||
}
|
||||
|
||||
taskSvc, err := requireTaskService()
|
||||
taskSvc, err := requireTaskService(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -168,7 +214,7 @@ func UpdateSchedule(ctx context.Context, id uint64, req model.UpdateScheduleRequ
|
||||
return nil, errs.ErrInvalidCronExpression
|
||||
}
|
||||
|
||||
taskSvc, err := requireTaskService()
|
||||
taskSvc, err := requireTaskService(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -203,7 +249,7 @@ func DeleteSchedule(ctx context.Context, id uint64) error {
|
||||
return fmt.Errorf("%s: %w", errs.ScheduleDeleteFailed, err)
|
||||
}
|
||||
|
||||
if taskSvc := GetTaskService(); taskSvc != nil {
|
||||
if taskSvc := GetTaskService(ctx); taskSvc != nil {
|
||||
reloadScheduler(ctx, taskSvc)
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user