Написание плагинов
Описание
Заголовок раздела «Описание»Давайте внимательнее посмотрим на минимальный экземпляр плагина, который мы создали в части введения:
// Plugin — экземпляр плагинаtype Plugin struct{}
// Enable — реализует plugin.Plugin// Вызывается сразу после инициализации, если плагин уже включён.// Вызывается каждый раз, когда плагин переключается на включённое состояние.func (c *Plugin) Enable() error { return nil}
// Disable — реализует plugin.Plugin// Вызывается каждый раз, когда плагин переключается на отключённое состояние.func (c *Plugin) Disable() error { return nil}Теперь реализован только базовый интерфейс Plugin.
Чтобы предоставить ему больше функциональностей, можно реализовать больше интерфейсов.
Введение
Заголовок раздела «Введение»API предоставляются как интерфейсы и вызываются во время инициализации плагина и/или вызова удалённого API для получения информации или предоставления обратных вызовов.
Интерфейсы API
Заголовок раздела «Интерфейсы API»Это все интерфейсы API, предоставляемые notifly. Некоторые реализации интерфейсов опущены в примерах.
Displayer
Заголовок раздела «Displayer»Displayer — это самая простая форма API плагина, используется для предоставления инструкций на странице плагина в WebUI.
Плагины могут динамически генерировать информацию на основе текущего состояния.
Он получает параметр location, содержащий имя хоста сервера, порт и схему, восстановленные из исходного запроса к API Displayer.
Поддерживается markdown.
REST API для этого предоставлен в /plugin/:id/display.
// Plugin — экземпляр плагинаtype Plugin struct { userCtx plugin.UserContext}
// GetDisplay — реализует plugin.Displayer// Вызывается, когда пользователь просматривает параметры плагина. Плагины не должны быть включены для обработки вызовов GetDisplay.func (c *Plugin) GetDisplay(location *url.URL) string { if (c.userCtx.Admin) { return "You are an admin! You have super cow powers." } else { return "You are **NOT** an admin! You can do nothing:(" }}
// NewNotiflyPluginInstance — создаёт экземпляр плагина для контекста пользователя.func NewNotiflyPluginInstance(ctx plugin.UserContext) plugin.Plugin { return &Plugin{ctx}}Messenger
Заголовок раздела «Messenger»Messenger используется для отправки сообщений.
Он вызывается с обратным вызовом, который экземпляры плагина могут вызвать в любой момент для отправки сообщений пользователю.
// Plugin — экземпляр плагинаtype Plugin struct { msgHandler plugin.MessageHandler}
// SetMessageHandler — реализует plugin.Messenger// Вызывается при инициализацииfunc (c *Plugin) SetMessageHandler(h plugin.MessageHandler) { c.msgHandler = h}
func (c *Plugin) Enable() error { go func() { time.Sleep(5 * time.Second) c.msgHandler.SendMessage(plugin.Message{ Message: "The plugin has been enabled for 5 seconds.", }) }() return nil}Storager
Заголовок раздела «Storager»Storager используется для сохранения постоянной информации в базу данных notifly на уровне пользователя.
Сериализация данных обрабатывается самим плагином.
// Plugin — экземпляр плагинаtype Plugin struct { storageHandler plugin.StorageHandler}
// SetStorageHandler — реализует plugin.Storager// Вызывается при инициализацииfunc (c *Plugin) SetStorageHandler(h plugin.StorageHandler) { c.storageHandler = h}
type Storage struct { EnabledTimes uint `json:"enabled_times"`}
func (c *Plugin) Enable() error { storage := new(Storage) storageBytes, err := c.storageHandler.Load() if err != nil { return err } if len(storageBytes) == 0 { storage.EnabledTimes = 1 storageBytes, _ = json.Marshal(storage) c.storageHandler.Save(storageBytes) } else { json.Unmarshal(storageBytes, storage) } log.Printf("This plugin has been enabled %d times.", storage.EnabledTimes) return nil}Webhooker
Заголовок раздела «Webhooker»Webhooker используется для регистрации пользовательских обработчиков gin.
Базовый путь — это базовый путь RouterGroup, который остаётся согласованным между перезагрузками.
Плагины могут собрать абсолютный URL webhook, объединив basePath и параметр location в вызове Displayer.
Полезно для регистрации обработчиков webhook. Теоретически вы можете даже зарегистрировать полный пользовательский интерфейс здесь.
// Plugin — экземпляр плагинаtype Plugin struct { basePath string}
// RegisterWebhook — реализует plugin.Webhooker// Вызывается при инициализации.// Webhook недоступны, когда плагины отключены.func (c *Plugin) RegisterWebhook(basePath string, mux *gin.RouterGroup) { c.basePath = basePath mux.POST("/hook", func(c *gin.Context) { // Обрабатывает webhook и выполняет действия (отправка сообщений и т.д.) })}
// GetDisplay — реализует plugin.Displayerfunc (c *Plugin) GetDisplay(location *url.URL) string { baseLocation := &url.URL{ Path: c.basePath, } if location != nil { // Если местоположение сервера можно определить, сделать URL абсолютным loc.Scheme = location.Scheme loc.Host = location.Host } loc = loc.ResolveReference(&url.URL{ Path: "hook", }) return fmt.Sprintf("Set your webhook URL to %s and you are all set", loc)}Configurer
Заголовок раздела «Configurer»Configurer используется для предоставления интерфейсов конфигурации пользователю.
Маршаллинг и анмаршаллинг обрабатывается главной программой notifly.
REST API для этого предоставлен в /plugin/:id/config.
// Plugin — экземпляр плагинаtype Plugin struct { config *Config}
type Config struct { GitHubUserName string}
// DefaultConfig — реализует plugin.Configurer// Конфигурация по умолчанию будет предоставлена пользователю для редактирования. Также используется для анмаршаллинга.// Вызывается каждый раз, когда требуется анмаршаллинг.func (c *Plugin) DefaultConfig() interface{} { return &Config{ GitHubUserName: "jmattheis", }}
// ValidateAndSetConfig — будет вызван каждый раз при инициализации плагина или изменении конфигурации пользователем.// Плагины должны проверить конфигурацию и опционально вернуть ошибку.// Параметр гарантированно имеет тот же тип, что и тип возврата DefaultConfig(), поэтому безопасно выполнить жёсткое приведение типов здесь.//// "Проверка" в этом контексте означает проверку конфликтующих или невозможных значений, таких как не-URL в поле, которое должно содержать только URL.// Чтобы убедиться, что экземпляр плагина всегда работает в допустимом состоянии, этот метод должен всегда принимать результат DefaultConfig()//// Вызывается при инициализации для предоставления начальной конфигурации. Вернуть nil для принятия или вернуть ошибку, чтобы указать, что конфиг устарел.// Когда конфигурация помечена устаревшей из-за ошибки анмаршаллинга или отклонения на стороне плагина, плагин автоматически отключается и пользователю предлагается разрешить конфликт конфигурации.// Вызывается каждый раз, когда вызывается API обновления конфигурации. Проверьте конфигурацию и вернуть nil для принятия или вернуть ошибку, чтобы указать, что конфиг недействительный.// Верните короткую и краткую ошибку здесь и, если у вас есть подробные рекомендации по решению проблемы, используйте Displayer для предоставления дополнительной информации пользователю,func (c *Plugin) ValidateAndSetConfig(c interface{}) error { config = c.(*Config) if !userNameIsValid(config.GitHubUserName) { return errors.New("the user name is not valid") } c.config = config return nil}Эффективные практики написания плагинов
Заголовок раздела «Эффективные практики написания плагинов»Хотя мы охватили способ реализации функциональностей плагина в предыдущей главе, очень важно следовать этим практикам, чтобы убедиться, что плагин может быть загружен успешно и работает эффективно.
- Используйте go modules для управления зависимостями и используйте gomod-cap для предотвращения несовместимых зависимостей.
- Обрабатывайте все ошибки. Паника в goroutine, запущенной в плагине, может привести к краху всей программы notifly.
- Предоставьте подробную информацию о плагине и используйте
Displayerдля отображения инструкций пользователям. Подробная информация о плагине будет отображаться в WebUI, что облегчает его идентификацию и использование.
Вы можете клонировать официальный шаблон плагина и посмотреть вклады сообщества, чтобы увидеть плагины в действии и/или загрузить свой проект.