插件开发指南
Plugins are a way to extend the functionality of the Answer project. You can create your own plugins to meet your own needs.
查看 官方插件代码 将帮助你快速理解和学习插件开发。
推荐:使用官方脚手架工具 create-answer-plugin 来创建和管理插件。它可以自动化大部分设置过程,包括文件生成、Go 模块配置和插件安装。
Introduction
Plugin template types
Currently we have three types of plugins:
- Backend plugin
- Standard UI plugin
- Builtin plugin
Plugin type
We classify plugins into different types. Different types of plugins have different functions. Plugins of the same type have the same effect, but are implemented differently.
| Plugin Name | Template Type | Description |
|---|---|---|
| Connector | Backend Plugin | The Connector plugin helps us to implement third-party login functionality |
| Storage | Backend Plugin | The Storage plugin helps us to upload files to third-party storage. |
| Cache | Backend Plugin | Support for using different caching middleware. |
| Search | Backend Plugin | Support for using search engines to speed up the search for question answers. |
| User Center | Backend Plugin | Using the third-party user system to manage users. |
| Notification | Backend Plugin | The Notification plugin helps us to send messages to third-party notification systems. |
| Route | Standard UI Plugin | Provides support for custom routing. |
| Editor | Standard UI Plugin | Supports extending the markdown editor's toolbar. |
| Captcha | Standard UI Plugin | Provides support for captcha. |
| Reviewer | Backend Plugin | Allows customizing the reviewer functionality. |
| Filter | Backend Plugin | Filter out illegal questions or answers. (coming soon) |
| Render | Standard UI Plugin | Parsers for different content formats. (coming soon) |
创建插件
package.json 中的 name 字段是我们添加依赖的包名;请不要使用 _ 连接此字段命名,请使用 -;例如:
"editor-chart" ✅
"editor_chart" ❌
使用脚手架工具(推荐)
创建插件最简单的方法是使用官方脚手架工具:
# 全局安装工具(可选)
npm install -g create-answer-plugin
# 或
pnpm add -g create-answer-plugin
# 或直接使用 npx(推荐)
npx create-answer-plugin create <pluginName>
# 或使用别名
npx answer-plugin create <pluginName>
# 或使用简化形式
npx answer-plugin <pluginName>
注意:包名是 create-answer-plugin,但你可以使用 create-answer-plugin 或 answer-plugin 作为命令(两者都可以使用!)。
该工具将:
- 通过交互式向导引导你选择插件类型
- 生成所有必需的文件,并具有正确的结构
- 创建 Go 包装文件(后端插件必需)
- 设置包含所有依赖项的
go.mod - 生成具有正确结构的 i18n 文件
选项:
pluginName(可选):预填充插件名称--path, -p:Answer 项目路径(根目录)。如果未指定,默认为当前目录。
示例:
# 导航到你的 Answer 项目根目录
cd /path/to/answer
# 创建插件
npx create-answer-plugin create my-plugin
# 或使用路径选项
npx create-answer-plugin create my-plugin --path /path/to/answer
# 选择:Standard UI Plugin → Route
# 输入路由路径:/hello
插件将创建在 ui/src/plugins/my-plugin/(注意:plugins 是复数形式)。
手动创建
如果你更喜欢手动创建插件:
-
进入项目的
ui > src > plugins目录(注意:plugins是复数形式)。 -
创建你的插件目录和文件,遵循现有插件的结构。
运行插件
安装插件(自动化 - 推荐)
安装插件最简单的方法是使用脚手架工具的 install 命令:
# 导航到你的 Answer 项目根目录
cd /path/to/answer
# 安装特定插件(自动处理注册)
npx create-answer-plugin install my-plugin
# 或
npx answer-plugin install my-plugin
# 或使用路径选项
npx answer-plugin install my-plugin --path /path/to/answer
# 安装所有未安装的插件
npx create-answer-plugin install
选项:
plugins(可选):要安装的插件名称(默认为所有未安装的插件)--path, -p:Answer 项目路径(默认为当前目录)
install 命令会自动:
- ✅ 在
cmd/answer/main.go中添加插件导入 - ✅ 在
go.mod中添加replace指令 - ✅ 运行
go mod tidy - ✅ 使用
go run ./cmd/answer/main.go i18n合并 i18n 资源
列出插件
列出 Answer 项目中的所有插件:
# 列出所有插件
npx create-answer-plugin list
# 或
npx answer-plugin list
# 或使用路径选项
npx answer-plugin list /path/to/answer
选项:
path(可选):Answer 项目路径(默认为当前目录)
卸载插件
从 Answer 项目中卸载插件:
# 卸载所有已安装的插件
npx create-answer-plugin uninstall
# 或
npx answer-plugin uninstall
# 卸载特定插件
npx create-answer-plugin uninstall my-plugin another-plugin
# 或使用路径选项
npx answer-plugin uninstall my-plugin --path /path/to/answer
选项:
plugins(可选):要卸载的插件名称(默认为所有已安装的插件)--path, -p:Answer 项目路径(默认为当前目录)
uninstall 命令会自动:
- ✅ 从
cmd/answer/main.go中移除插件导入 - ✅ 从
go.mod中移除replace指令 - ✅ 运行
go mod tidy - ✅ 更新 i18n 资源
运行后端插件
使用脚手架工具(推荐)
-
使用脚手架工具安装插件(见上文)。
-
构建前端:
cd ui
pnpm pre-install
pnpm build
cd .. -
合并 i18n 资源(如果未自动完成):
go run ./cmd/answer/main.go i18n -
启动项目:
go run cmd/answer/main.go run -C ./answer-data
手动安装
如果你更喜欢手动安装:
-
首先,构建前端:
cd ui
pnpm pre-install
pnpm build
cd .. -
在
cmd > answer > main.go文件中,导入你的插件:import (
answercmd "github.com/apache/answer/cmd"
// Import the plugins
_ "github.com/apache/answer/ui/src/plugins/my-plugin"
) -
使用
go mod edit将插件添加到go.mod文件:go mod edit -replace=github.com/apache/answer/ui/src/plugins/my-plugin=./ui/src/plugins/my-plugin -
更新依赖:
go mod tidy -
合并 i18n 资源:
go run ./cmd/answer/main.go i18n -
启动项目:
go run cmd/answer/main.go run -C ./answer-data
运行标准 UI 插件
使用脚手架工具(推荐)
-
使用脚手架工具安装插件:
cd /path/to/answer
npx create-answer-plugin install my-plugin -
进入
ui目录并安装依赖:cd ui
pnpm pre-install -
构建前端:
pnpm build -
开发时,启动开发服务器:
pnpm start -
合并 i18n 资源(如果未自动完成):
cd ..
go run ./cmd/answer/main.go i18n
手动安装
-
进入
ui目录。 -
安装依赖:
pnpm pre-install -
构建前端:
pnpm build -
开发时,启动开发服务器:
pnpm start -
参考 运行后端插件 部分,手动将插件添加到项目中(在
main.go中导入,添加replace指令等)。
Backend Plugin Development
Implement the Base interface
The Base interface contains basic information about the plugin and is used to display.
// Info presents the plugin information
type Info struct {
Name Translator
SlugName string
Description Translator
Author string
Version string
Link string
}
// Base is the base plugin
type Base interface {
// Info returns the plugin information
Info() Info
}
The SlugName of the plugin must be unique. Two plugins with the same SlugName will panic when registering.
Implement the function interface
Different plugin types require different interfaces of implementation.
For example, following is the Connector plugin interface.
type Connector interface {
Base
// ConnectorLogoSVG presents the logo in svg format
ConnectorLogoSVG() string
// ConnectorName presents the name of the connector
// e.g. Facebook, Twitter, Instagram
ConnectorName() Translator
// ConnectorSlugName presents the slug name of the connector
// Please use lowercase and hyphen as the separator
// e.g. facebook, twitter, instagram
ConnectorSlugName() string
// ConnectorSender presents the sender of the connector
// It handles the start endpoint of the connector
// receiverURL is the whole URL of the receiver
ConnectorSender(ctx *GinContext, receiverURL string) (redirectURL string)
// ConnectorReceiver presents the receiver of the connector
// It handles the callback endpoint of the connector, and returns the
ConnectorReceiver(ctx *GinContext, receiverURL string) (userInfo ExternalLoginUserInfo, err error)
}
Translator is a struct for translation. Please refer to the documentation for details.
Implement the configuration interface
For details on the description of each configuration item, please refer to the documentation.
type Config interface {
Base
// ConfigFields returns the list of config fields
ConfigFields() []ConfigField
// ConfigReceiver receives the config data, it calls when the config is saved or initialized.
// We recommend to unmarshal the data to a struct, and then use the struct to do something.
// The config is encoded in JSON format.
// It depends on the definition of ConfigFields.
ConfigReceiver(config []byte) error
}
Register initialization function
import "github.com/apache/answer/plugin"
func init() {
plugin.Register(&GitHubConnector{
Config: &GitHubConnectorConfig{},
})
}
Standard UI plugin Development
The default configuration is as follows:
slug_name: <slug_name>
type: <type>
version: 0.0.1
author:
import i18nConfig from './i18n';
import Component from './Component';
import info from './info.yaml';
export default {
info: {
slug_name: info.slug_name,
type: info.type,
},
i18nConfig,
component: Component,
};
Among them, type、slug_name and component are required fields. i18nConfig and hooks are optional fields.
Currently the front end supports the following types of plugins:
- editor
- route
- captcha
Editor plugin
Refer to editor-chart for details.
Route plugin
The plugin configuration of the routing type adds the route field to the configuration file.
slug_name: <slug_name>
route: /<route>
type: route
version: 0.0.1
author:
import i18nConfig from './i18n';
import Component from './Component';
import info from './info.yaml';
export default {
info: {
slug_name: info.slug_name,
type: info.type,
route: info.route,
},
i18nConfig,
component: Component,
};
Captcha plugin
Refer to captcha-basic for details.
Builtin plugin Development
It is not so different from React component, this plugin is more suitable for the following scenarios:
- There are complex business logics that cannot be separated from the code (such as Oauth).
- Some back-end plugins require UI support for business purposes (such as Search).
- This plugin has extremely low requirements for developers and requires no additional configuration work.
How to develop builtin plugin
- Get familiar with the directory structure. Go to the
ui/src/plugins/builtindirectory and create a directory, such as Demo. Then refer to the existing plugins to create the necessary files to start development.
// ui/src/plugins/builtin
.
├── ...
├── Demo
├── i18n (language file)
├── en_US.yaml (default language required)
├── index.ts (required)
├── zh_CN.ts (any language you want to provide)
├── index.tsx (component required)
├── info.yaml (plugin information required)
├── services.ts (api)
- Export the plugins you have just defined in the plugins list file
plugins/builtin/index.ts
import Demo from './Demo'
export default {
...(exists plugins),
Demo,
};
- Now you can use the PluginRender component to render the just-defined plugin where you want it!
<PluginRender
type="connector"
slug_name="third_party_connector"
/>
- Publish plugin: initiate the PR process normally and describe the plugin function and scope of influence in detail.