feat: 插件接口预留 — Plugin 接口 + Registry + 示例插件
Some checks failed
CI / Build & Test (push) Failing after 40s
Some checks failed
CI / Build & Test (push) Failing after 40s
- kernel/plugin: 定义 Plugin 接口(Metadata/FxOption/SetupRoutes/Init) - kernel/plugin: 全局 Registry(Register/All/InitAll/Reset) - modules/example: 完整 4 层示例插件,实现 Plugin 接口 - config: 新增 PluginConfig(enabled 列表) - app.go: 集成插件系统 — fx.Invoke 注册 + plugin.All() 路由 + InitAll 生命周期
This commit is contained in:
parent
9148d2f6da
commit
9bb2c447ec
@ -11,6 +11,7 @@ import (
|
|||||||
"mengstack/internal/app/health"
|
"mengstack/internal/app/health"
|
||||||
"mengstack/internal/app/middleware"
|
"mengstack/internal/app/middleware"
|
||||||
"mengstack/internal/config"
|
"mengstack/internal/config"
|
||||||
|
"mengstack/internal/kernel/plugin"
|
||||||
"mengstack/internal/logger"
|
"mengstack/internal/logger"
|
||||||
authinterfaces "mengstack/internal/modules/auth/interfaces"
|
authinterfaces "mengstack/internal/modules/auth/interfaces"
|
||||||
rbacinterfaces "mengstack/internal/modules/rbac/interfaces"
|
rbacinterfaces "mengstack/internal/modules/rbac/interfaces"
|
||||||
@ -18,6 +19,7 @@ import (
|
|||||||
auditinterfaces "mengstack/internal/modules/audit/interfaces"
|
auditinterfaces "mengstack/internal/modules/audit/interfaces"
|
||||||
settingsinterfaces "mengstack/internal/modules/settings/interfaces"
|
settingsinterfaces "mengstack/internal/modules/settings/interfaces"
|
||||||
notificationinterfaces "mengstack/internal/modules/notification/interfaces"
|
notificationinterfaces "mengstack/internal/modules/notification/interfaces"
|
||||||
|
exampleinterfaces "mengstack/internal/modules/example/interfaces"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
@ -75,6 +77,10 @@ func newEngine(
|
|||||||
settingsinterfaces.SetupRoutes(r, settingsHandler, authMW)
|
settingsinterfaces.SetupRoutes(r, settingsHandler, authMW)
|
||||||
notificationinterfaces.SetupRoutes(r, notificationHandler, authMW)
|
notificationinterfaces.SetupRoutes(r, notificationHandler, authMW)
|
||||||
|
|
||||||
|
for _, p := range plugin.All() {
|
||||||
|
p.SetupRoutes(r, authMW)
|
||||||
|
}
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +97,9 @@ func newServer(cfg *config.Config, engine *gin.Engine) *http.Server {
|
|||||||
func registerLifecycle(lc fx.Lifecycle, srv *http.Server, log *zap.Logger) {
|
func registerLifecycle(lc fx.Lifecycle, srv *http.Server, log *zap.Logger) {
|
||||||
lc.Append(fx.Hook{
|
lc.Append(fx.Hook{
|
||||||
OnStart: func(ctx context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
|
if err := plugin.InitAll(log); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
log.Info("server starting", zap.String("addr", srv.Addr))
|
log.Info("server starting", zap.String("addr", srv.Addr))
|
||||||
go func() {
|
go func() {
|
||||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
@ -122,6 +131,10 @@ func NewApp() *fx.App {
|
|||||||
auditinterfaces.Module,
|
auditinterfaces.Module,
|
||||||
settingsinterfaces.Module,
|
settingsinterfaces.Module,
|
||||||
notificationinterfaces.Module,
|
notificationinterfaces.Module,
|
||||||
|
exampleinterfaces.Module,
|
||||||
|
fx.Invoke(func(p *exampleinterfaces.ExamplePlugin) {
|
||||||
|
plugin.Register(p)
|
||||||
|
}),
|
||||||
Module,
|
Module,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ type Config struct {
|
|||||||
Redis RedisConfig `mapstructure:"redis"`
|
Redis RedisConfig `mapstructure:"redis"`
|
||||||
JWT JWTConfig `mapstructure:"jwt"`
|
JWT JWTConfig `mapstructure:"jwt"`
|
||||||
Log LogConfig `mapstructure:"log"`
|
Log LogConfig `mapstructure:"log"`
|
||||||
|
Plugin PluginConfig `mapstructure:"plugin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
@ -48,6 +49,10 @@ type LogConfig struct {
|
|||||||
Format string `mapstructure:"format"`
|
Format string `mapstructure:"format"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PluginConfig struct {
|
||||||
|
Enabled []string `mapstructure:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
func Load() (*Config, error) {
|
func Load() (*Config, error) {
|
||||||
v := viper.New()
|
v := viper.New()
|
||||||
v.SetConfigName("config")
|
v.SetConfigName("config")
|
||||||
|
|||||||
46
internal/kernel/plugin/plugin.go
Normal file
46
internal/kernel/plugin/plugin.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Package plugin defines the contract for MengStack plugins.
|
||||||
|
//
|
||||||
|
// A plugin is a self-contained extension that follows the same 4-layer
|
||||||
|
// architecture as built-in modules. Each plugin provides:
|
||||||
|
//
|
||||||
|
// - FxOption: dependency injection wiring (repositories, services, handlers)
|
||||||
|
// - SetupRoutes: HTTP route registration on the Gin engine
|
||||||
|
// - Init: optional startup hook for background workers, caches, etc.
|
||||||
|
//
|
||||||
|
// To create a plugin:
|
||||||
|
// 1. Create a package under internal/modules/<name>/
|
||||||
|
// 2. Follow the 4-layer pattern: domain → infrastructure → application → interfaces
|
||||||
|
// 3. Implement the Plugin interface
|
||||||
|
// 4. Register in internal/app/app.go's plugin list
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metadata describes a plugin's identity.
|
||||||
|
type Metadata struct {
|
||||||
|
Name string
|
||||||
|
Version string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin is the contract every MengStack plugin must implement.
|
||||||
|
type Plugin interface {
|
||||||
|
// Metadata returns the plugin's identity.
|
||||||
|
Metadata() Metadata
|
||||||
|
|
||||||
|
// FxOption returns the fx.Module for dependency injection.
|
||||||
|
FxOption() fx.Option
|
||||||
|
|
||||||
|
// SetupRoutes registers HTTP routes on the Gin engine.
|
||||||
|
// authMW is the JWT authentication middleware.
|
||||||
|
SetupRoutes(engine *gin.Engine, authMW gin.HandlerFunc)
|
||||||
|
|
||||||
|
// Init is called after all dependencies are resolved and before
|
||||||
|
// the HTTP server starts. Use it for background workers, warm-up
|
||||||
|
// caches, or external service connections.
|
||||||
|
// Returning nil indicates success.
|
||||||
|
Init() error
|
||||||
|
}
|
||||||
52
internal/kernel/plugin/registry.go
Normal file
52
internal/kernel/plugin/registry.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
registry []Plugin
|
||||||
|
mu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register adds a plugin to the global registry.
|
||||||
|
// Typically called in init() or during application bootstrap.
|
||||||
|
func Register(p Plugin) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
registry = append(registry, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All returns a copy of all registered plugins.
|
||||||
|
func All() []Plugin {
|
||||||
|
mu.RLock()
|
||||||
|
defer mu.RUnlock()
|
||||||
|
out := make([]Plugin, len(registry))
|
||||||
|
copy(out, registry)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitAll initializes all registered plugins in order.
|
||||||
|
func InitAll(log *zap.Logger) error {
|
||||||
|
for _, p := range All() {
|
||||||
|
meta := p.Metadata()
|
||||||
|
log.Info("initializing plugin",
|
||||||
|
zap.String("name", meta.Name),
|
||||||
|
zap.String("version", meta.Version),
|
||||||
|
)
|
||||||
|
if err := p.Init(); err != nil {
|
||||||
|
return fmt.Errorf("plugin %s init failed: %w", meta.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears the registry. Used in tests only.
|
||||||
|
func Reset() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
registry = nil
|
||||||
|
}
|
||||||
58
internal/modules/example/application/service.go
Normal file
58
internal/modules/example/application/service.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"mengstack/internal/kernel/model"
|
||||||
|
"mengstack/internal/modules/example/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service contains the business logic for examples.
|
||||||
|
type Service struct {
|
||||||
|
repo domain.Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new example service.
|
||||||
|
func NewService(repo domain.Repository) *Service {
|
||||||
|
return &Service{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Create(ctx context.Context, tenantID string, req domain.CreateExampleRequest) (*domain.ExampleDTO, error) {
|
||||||
|
e := &domain.Example{
|
||||||
|
BaseModel: model.BaseModel{TenantID: tenantID},
|
||||||
|
Name: req.Name,
|
||||||
|
Description: req.Description,
|
||||||
|
Status: 1,
|
||||||
|
ExpiresAt: req.ExpiresAt,
|
||||||
|
}
|
||||||
|
if err := s.repo.Create(ctx, e); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dto := domain.ToDTO(e)
|
||||||
|
return &dto, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Get(ctx context.Context, tenantID string, id uint) (*domain.ExampleDTO, error) {
|
||||||
|
e, err := s.repo.FindByID(ctx, tenantID, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dto := domain.ToDTO(e)
|
||||||
|
return &dto, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) List(ctx context.Context, tenantID string) ([]domain.ExampleDTO, error) {
|
||||||
|
items, err := s.repo.FindAll(ctx, tenantID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dtos := make([]domain.ExampleDTO, len(items))
|
||||||
|
for i := range items {
|
||||||
|
dtos[i] = domain.ToDTO(&items[i])
|
||||||
|
}
|
||||||
|
return dtos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Delete(ctx context.Context, tenantID string, id uint) error {
|
||||||
|
return s.repo.Delete(ctx, tenantID, id)
|
||||||
|
}
|
||||||
61
internal/modules/example/domain/example.go
Normal file
61
internal/modules/example/domain/example.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"mengstack/internal/kernel/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Example is a sample entity demonstrating the plugin data model.
|
||||||
|
type Example struct {
|
||||||
|
model.BaseModel
|
||||||
|
Name string `json:"name" gorm:"type:varchar(255);not null"`
|
||||||
|
Description string `json:"description" gorm:"type:text"`
|
||||||
|
Status int `json:"status" gorm:"type:smallint;default:1"`
|
||||||
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Example) TableName() string { return "examples" }
|
||||||
|
|
||||||
|
// ExampleDTO is the response projection.
|
||||||
|
type ExampleDTO struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
TenantID string `json:"tenant_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDTO converts an Example entity to its DTO.
|
||||||
|
func ToDTO(e *Example) ExampleDTO {
|
||||||
|
return ExampleDTO{
|
||||||
|
ID: e.ID,
|
||||||
|
TenantID: e.TenantID,
|
||||||
|
Name: e.Name,
|
||||||
|
Description: e.Description,
|
||||||
|
Status: e.Status,
|
||||||
|
ExpiresAt: e.ExpiresAt,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateExampleRequest is the input for creating an example.
|
||||||
|
type CreateExampleRequest struct {
|
||||||
|
Name string `json:"name" binding:"required,max=255"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repository defines the data access contract for examples.
|
||||||
|
type Repository interface {
|
||||||
|
Create(ctx context.Context, e *Example) error
|
||||||
|
FindByID(ctx context.Context, tenantID string, id uint) (*Example, error)
|
||||||
|
FindAll(ctx context.Context, tenantID string) ([]Example, error)
|
||||||
|
Update(ctx context.Context, tenantID string, e *Example) error
|
||||||
|
Delete(ctx context.Context, tenantID string, id uint) error
|
||||||
|
}
|
||||||
60
internal/modules/example/infrastructure/example_repo.go
Normal file
60
internal/modules/example/infrastructure/example_repo.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package infrastructure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"mengstack/internal/modules/example/domain"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type exampleRepo struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRepository returns a domain.Repository backed by GORM.
|
||||||
|
func NewRepository(db *gorm.DB) domain.Repository {
|
||||||
|
return &exampleRepo{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *exampleRepo) Create(ctx context.Context, e *domain.Example) error {
|
||||||
|
return r.db.WithContext(ctx).Create(e).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *exampleRepo) FindByID(ctx context.Context, tenantID string, id uint) (*domain.Example, error) {
|
||||||
|
var e domain.Example
|
||||||
|
err := r.db.WithContext(ctx).Where("id = ? AND tenant_id = ?", id, tenantID).First(&e).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *exampleRepo) FindAll(ctx context.Context, tenantID string) ([]domain.Example, error) {
|
||||||
|
var items []domain.Example
|
||||||
|
err := r.db.WithContext(ctx).Where("tenant_id = ?", tenantID).Find(&items).Error
|
||||||
|
return items, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *exampleRepo) Update(ctx context.Context, tenantID string, e *domain.Example) error {
|
||||||
|
return r.db.WithContext(ctx).
|
||||||
|
Model(e).
|
||||||
|
Where("id = ? AND tenant_id = ?", e.ID, tenantID).
|
||||||
|
Updates(map[string]any{
|
||||||
|
"name": e.Name,
|
||||||
|
"description": e.Description,
|
||||||
|
"status": e.Status,
|
||||||
|
"expires_at": e.ExpiresAt,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *exampleRepo) Delete(ctx context.Context, tenantID string, id uint) error {
|
||||||
|
return r.db.WithContext(ctx).
|
||||||
|
Where("id = ? AND tenant_id = ?", id, tenantID).
|
||||||
|
Delete(&domain.Example{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate runs auto-migration for the example table.
|
||||||
|
func Migrate(db *gorm.DB) error {
|
||||||
|
return db.AutoMigrate(&domain.Example{})
|
||||||
|
}
|
||||||
79
internal/modules/example/interfaces/handler.go
Normal file
79
internal/modules/example/interfaces/handler.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"mengstack/internal/kernel/response"
|
||||||
|
"mengstack/internal/modules/example/application"
|
||||||
|
"mengstack/internal/modules/example/domain"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler handles HTTP requests for the example plugin.
|
||||||
|
type Handler struct {
|
||||||
|
svc *application.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHandler creates a new example handler.
|
||||||
|
func NewHandler(svc *application.Service) *Handler {
|
||||||
|
return &Handler{svc: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create handles POST /api/v1/examples
|
||||||
|
func (h *Handler) Create(c *gin.Context) {
|
||||||
|
var req domain.CreateExampleRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.Fail(c, response.NewBadRequest("invalid request: "+err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tenantID := c.GetString("tenant_id")
|
||||||
|
dto, err := h.svc.Create(c.Request.Context(), tenantID, req)
|
||||||
|
if err != nil {
|
||||||
|
response.HandleError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get handles GET /api/v1/examples/:id
|
||||||
|
func (h *Handler) Get(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.Fail(c, response.NewBadRequest("invalid id"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tenantID := c.GetString("tenant_id")
|
||||||
|
dto, err := h.svc.Get(c.Request.Context(), tenantID, uint(id))
|
||||||
|
if err != nil {
|
||||||
|
response.HandleError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List handles GET /api/v1/examples
|
||||||
|
func (h *Handler) List(c *gin.Context) {
|
||||||
|
tenantID := c.GetString("tenant_id")
|
||||||
|
dtos, err := h.svc.List(c.Request.Context(), tenantID)
|
||||||
|
if err != nil {
|
||||||
|
response.HandleError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, dtos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete handles DELETE /api/v1/examples/:id
|
||||||
|
func (h *Handler) Delete(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
response.Fail(c, response.NewBadRequest("invalid id"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tenantID := c.GetString("tenant_id")
|
||||||
|
if err := h.svc.Delete(c.Request.Context(), tenantID, uint(id)); err != nil {
|
||||||
|
response.HandleError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Success(c, nil)
|
||||||
|
}
|
||||||
75
internal/modules/example/interfaces/plugin.go
Normal file
75
internal/modules/example/interfaces/plugin.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mengstack/internal/app/middleware"
|
||||||
|
"mengstack/internal/kernel/plugin"
|
||||||
|
"mengstack/internal/modules/example/application"
|
||||||
|
"mengstack/internal/modules/example/infrastructure"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExamplePlugin implements plugin.Plugin.
|
||||||
|
type ExamplePlugin struct {
|
||||||
|
handler *Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates the example plugin.
|
||||||
|
// Call this during application bootstrap to register the plugin.
|
||||||
|
func New(handler *Handler) *ExamplePlugin {
|
||||||
|
return &ExamplePlugin{handler: handler}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ExamplePlugin) Metadata() plugin.Metadata {
|
||||||
|
return plugin.Metadata{
|
||||||
|
Name: "example",
|
||||||
|
Version: "0.1.0",
|
||||||
|
Description: "示例插件 — 演示插件开发模式",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ExamplePlugin) FxOption() fx.Option {
|
||||||
|
return fx.Module("example-plugin",
|
||||||
|
fx.Provide(
|
||||||
|
infrastructure.NewRepository,
|
||||||
|
application.NewService,
|
||||||
|
NewHandler,
|
||||||
|
),
|
||||||
|
fx.Invoke(func(db *gorm.DB, log *zap.Logger) {
|
||||||
|
if err := infrastructure.Migrate(db); err != nil {
|
||||||
|
log.Fatal("example plugin migration failed", zap.Error(err))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ExamplePlugin) SetupRoutes(engine *gin.Engine, authMW gin.HandlerFunc) {
|
||||||
|
g := engine.Group("/api/v1/examples", authMW, middleware.MultiTenant())
|
||||||
|
g.POST("", p.handler.Create)
|
||||||
|
g.GET("", p.handler.List)
|
||||||
|
g.GET("/:id", p.handler.Get)
|
||||||
|
g.DELETE("/:id", p.handler.Delete)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ExamplePlugin) Init() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module is the fx module for the example plugin.
|
||||||
|
// It provides all dependencies and the ExamplePlugin itself.
|
||||||
|
var Module = fx.Module("example-plugin",
|
||||||
|
fx.Provide(
|
||||||
|
infrastructure.NewRepository,
|
||||||
|
application.NewService,
|
||||||
|
NewHandler,
|
||||||
|
New,
|
||||||
|
),
|
||||||
|
fx.Invoke(func(db *gorm.DB, log *zap.Logger) {
|
||||||
|
if err := infrastructure.Migrate(db); err != nil {
|
||||||
|
log.Fatal("example plugin migration failed", zap.Error(err))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue
Block a user