mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 15:46:37 +08:00
merge(wavelet): sync upstream changes
This commit is contained in:
+122
-52
@@ -1,77 +1,147 @@
|
||||
# Downstream Custom Plugins
|
||||
# 下游插件开发指南 (Downstream Custom Plugins)
|
||||
|
||||
This directory is the designated location for downstream (deployment-specific) Cordis plugins.
|
||||
本目录为 Wavelet 下游业务定制插件(Deployment-specific plugins)的专属开发目录。
|
||||
|
||||
## Architecture
|
||||
所有插件开发均以标准模板 [`custom_example`](./plugins/custom_example) 为基准进行构建。
|
||||
|
||||
```
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
downstream/
|
||||
├── README.md
|
||||
└── plugins/
|
||||
└── custom_example/ # Example plugin — copy & rename to get started
|
||||
└── plugin.go
|
||||
└── custom_example/ # 标准插件开发基准模板(可直接复制并重命名开发新插件)
|
||||
├── plugin.go # 插件入口:实现 core.Plugin (Name & Apply)
|
||||
├── consts/ # 常量与错误码定义
|
||||
│ └── consts.go
|
||||
├── controller/ # 控制器层 (HTTP API 接口、参数绑定与信封响应)
|
||||
│ └── hello/
|
||||
│ └── hello.go
|
||||
├── service/ # 业务逻辑层 (用例编排、事务控制与领域事件)
|
||||
├── dao/ # 数据访问层 (GORM CRUD、SQL 防注入与转义)
|
||||
├── model/ # 数据模型与实体定义
|
||||
│ ├── do/ # Domain Object 领域对象与 DTO
|
||||
│ └── entity/ # 数据表映射实体 (TableName() 带专属前缀)
|
||||
└── migrations/ # Goose SQL 双方言独立数据库迁移
|
||||
├── postgres/ # PostgreSQL 迁移脚本
|
||||
└── sqlite/ # SQLite 迁移脚本
|
||||
```
|
||||
|
||||
Downstream plugins follow the same `core.Plugin` contract as platform plugins:
|
||||
---
|
||||
|
||||
```go
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Apply(ctx *core.Context) error
|
||||
}
|
||||
## 插件开发规范与分层职责
|
||||
|
||||
每个下游插件均需遵循标准分层架构(`controller -> service -> dao -> model`):
|
||||
|
||||
1. **`plugin.go` (插件装配入口)**:
|
||||
- 实现 `core.Plugin` 接口(`Name() string` 与 `Apply(ctx *core.Context) error`)。
|
||||
- 负责在 `Apply` 中注册路由组(`ctx.Router()`)、异步任务(`ctx.Task()`)、定时调度(`ctx.Schedule()`)与数据库迁移(`ctx.Migrations()`)。
|
||||
- 依赖注入统一使用 `core.Inject` 或 `ctx.Using` 获取平台服务(如 `contracts.AuthService`、`contracts.DBService`、`contracts.CacheService`)。
|
||||
|
||||
2. **`controller/` (控制器层 / Handler)**:
|
||||
- 负责 HTTP API 请求参数绑定(`c.ShouldBindJSON` / `c.ShouldBindQuery`)、用户会话获取(`oauth.GetCurrentUser`)。
|
||||
- 调用 Service 层处理业务,严禁直接包含复杂业务逻辑或直接执行 SQL 操作。
|
||||
- 统一使用 `response.OK` 或 `response.Abort*` 返回标准信封响应。
|
||||
|
||||
3. **`service/` (业务逻辑层)**:
|
||||
- 纯 Go 业务用例,方法入参首位统一为 `context.Context`。
|
||||
- 严禁依赖 `*gin.Context` 或 HTTP 相关对象,确保逻辑具备可移植性与可测试性。
|
||||
- 涉及数据修改可通过 `ctx.Events().Emit()` 发射强类型领域事件。
|
||||
|
||||
4. **`dao/` (数据访问层 / Repository)**:
|
||||
- 负责底层数据库交互,通过 `contracts.DBService` 获取受保护的 GORM 数据库句柄。
|
||||
- 模糊查询必须调用 `pkg/util.EscapeLike` 并显式声明 `ESCAPE '\\'` 防注入。
|
||||
- 遵循**表单一所有者原则**:严禁跨过其他所有者插件直读或修改其他插件的数据表。
|
||||
|
||||
5. **`model/` (模型与 DTO)**:
|
||||
- `model/entity/`:数据库表映射结构体,`TableName()` 必须带有插件专属前缀(如 `w_custom_*`)。
|
||||
- `model/do/`:业务领域对象、入参校验 Request DTO 与响应 Response DTO。
|
||||
|
||||
6. **`consts/` (常量与错误定义)**:
|
||||
- 定义插件内部常量、配置键名及驼峰式(camelCase)错误标识字符串。
|
||||
|
||||
7. **`migrations/` (双方言 SQL 迁移)**:
|
||||
- 包含 `postgres/` 与 `sqlite/` 双方言 Goose SQL 迁移文件,使用 `//go:embed` 打包并在 `Apply()` 中注册。
|
||||
|
||||
---
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 1. 基于模板复制创建新插件
|
||||
|
||||
```bash
|
||||
cp -r backend/downstream/plugins/custom_example backend/downstream/plugins/my_plugin
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Naming**: Each plugin directory name becomes its import path and plugin ID (kebab-case recommended).
|
||||
2. **Dependencies**: Downstream plugins may import `core/`, `core/contracts/`, `pkg/`, and `plugins/infra/` packages from the platform. They MUST NOT import domain plugin internal packages — use `core.Inject[contracts.XxxService](ctx)` instead.
|
||||
3. **Registration**: Add your downstream plugin to `cmd/app.go` before the platform plugins or after, depending on which services it needs:
|
||||
```go
|
||||
// newWaveletApp in cmd/app.go
|
||||
app.Use(
|
||||
database.New(),
|
||||
cache.New(),
|
||||
logger.New(),
|
||||
storage.New(),
|
||||
// ... platform domain plugins ...
|
||||
custom_hello.New(), // your downstream plugin
|
||||
driver_http.New(),
|
||||
driver_asynq_worker.New(),
|
||||
driver_asynq_cron.New(),
|
||||
)
|
||||
```
|
||||
4. **Migration**: If your plugin needs database tables, embed SQL files in a `migrations/` directory and register via `ctx.Migrations().Register(...)` in `Apply()`.
|
||||
|
||||
## Quick Start
|
||||
### 2. 实现插件入口 (`plugin.go`)
|
||||
|
||||
```go
|
||||
package custom_example
|
||||
package my_plugin
|
||||
|
||||
import (
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
"github.com/gin-gonic/gin"
|
||||
"Wavelet/core"
|
||||
"Wavelet/core/contracts"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Plugin struct{}
|
||||
|
||||
func New() *Plugin { return &Plugin{} }
|
||||
func New() *Plugin {
|
||||
return &Plugin{}
|
||||
}
|
||||
|
||||
func (p *Plugin) Name() string { return "custom_example" }
|
||||
func (p *Plugin) Name() string {
|
||||
return "my_plugin"
|
||||
}
|
||||
|
||||
func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
// Example: register a route that uses AuthService
|
||||
var authSvc contracts.AuthService
|
||||
if err := ctx.Using(func(svc contracts.AuthService) { authSvc = svc }); err != nil {
|
||||
return err
|
||||
}
|
||||
// 通过容器解析认证服务
|
||||
var authSvc contracts.AuthService
|
||||
if err := core.Using[contracts.AuthService](ctx, func(svc contracts.AuthService) { authSvc = svc }); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g := ctx.Router().Group("/api/v1/custom", authSvc.RequireAuthMiddleware().(gin.HandlerFunc))
|
||||
g.GET("/hello", func(c *gin.Context) {
|
||||
user, _ := authSvc.GetCurrentUser(c.Request.Context())
|
||||
c.JSON(200, gin.H{"message": "Hello " + user.Username})
|
||||
})
|
||||
// 注册带鉴权中间件的路由组
|
||||
g := ctx.Router().Group("/api/v1/my-plugin", authSvc.RequireAuthMiddleware().(gin.HandlerFunc))
|
||||
g.GET("/hello", func(c *gin.Context) {
|
||||
user, err := authSvc.GetCurrentUser(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Hello " + user.Username})
|
||||
})
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### 3. 在应用装配入口注册插件 (`cmd/app.go`)
|
||||
|
||||
在 `cmd/app.go` 中的 `newWaveletApp` 函数内注册你的新插件:
|
||||
|
||||
```go
|
||||
app.Use(
|
||||
database.New(),
|
||||
cache.New(),
|
||||
logger.New(),
|
||||
storage.New(),
|
||||
// ... 官方平台插件 ...
|
||||
my_plugin.New(), // 注册下游定制插件
|
||||
driver_http.New(),
|
||||
driver_asynq_worker.New(),
|
||||
driver_asynq_cron.New(),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 严格红线与规范 (Guardrails)
|
||||
|
||||
- **严禁跨插件直接 import**:下游插件可依赖 `core/`、`core/contracts/`、`pkg/`、`plugins/infra/`,**严禁直接 import `plugins/domain/*` 内部私有实现**,一律通过 `contracts` 接口或事件总线调用。
|
||||
- **禁止 GORM AutoMigrate**:数据表结构定义必须通过 `migrations/` 下嵌入的 Goose SQL 管理。
|
||||
- **Goroutine 并发安全**:严禁裸 `go func()`,后台并发任务统一使用 `backend/pkg/util.Go`。
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package consts defines constants and error codes for custom_example plugin.
|
||||
package consts
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package hello provides HTTP API handlers for the custom_example plugin.
|
||||
package hello
|
||||
Reference in New Issue
Block a user