Разработка бэкенда
Что даёт вам эта страница: карту Kotlin-половины вашего дополнения — входного класса, с которого стартует Pano, его хуков жизненного цикла и того, как Pano находит и строит ваши классы за вас, — плюс указатель на посвящённую страницу для каждой возможности бэкенда (эндпоинты, база данных, конфигурация, события, разрешения).
Бэкенд — это Kotlin-половина вашего дополнения: часть, которая выполняется внутри собственного Java-процесса Pano. Ей принадлежат ваши таблицы базы данных, ваши JSON-эндпоинты, ваши разрешения и ваши журналы админской активности. Эти страницы строят бэкенд-часть Shoutbox — небольшого дополнения, которое мы проносим через документацию, где посетители видят последние «выкрики» на главной странице, а админы публикуют и удаляют их из панели.
Дополнения в коде — это плагины
Везде в тексте мы говорим дополнение, но имена уровня кода используют слово plugin — PanoPlugin, pluginId, PluginConfig и так далее. Так и должно быть; ничего не переименовывайте в коде.
Прежде чем начать
У вас уже должно быть переименованное, собирающееся дополнение из Начала работы — та страница также поднимает работающий экземпляр Pano, внутри которого живёт ваше дополнение. Пожалуйста, сначала прочтите Архитектуру, если ещё не сделали этого; весь этот раздел опирается на неё. Одна идея, которую нужно оттуда вынести, простыми словами:
Spring — это библиотека, которую Pano использует для создания ваших классов за вас, так что вы никогда не пишете
new. Контекст — это просто коробка готовых объектов, которые Spring заполняет. Pano даёт вашему дополнению собственную коробку и кладёт по одной копии каждого из ваших классов в неё — вы затем просите у коробки то, что вам нужно.
Бэкенд живёт под src/main/kotlin/com/panomc/plugins/shoutbox/. Вот для чего каждая часть, простыми словами:
ShoutboxPlugin.kt— входной класс, с которого стартует Pano (эта страница).config/— файл настроек, который может редактировать владелец сайта.db/— всё о вашей таблице базы данных, разделённое наmodel/(одна строка как объект Kotlin),dao/(список запросов, которые вы обещаете предоставить),impl/(реальный SQL, который держит это обещание) иmigration/(шаги, меняющие форму таблицы в более поздней версии).routes/— ваши URL, разделённые наapi/(публичные) иpanel/(только для админов).permission/— разрешение, которое ограждает админскую возможность.event/— код, реагирующий на действия платформы (например, завершение настройки).log/— запись, вносимая в ленту админской активности.
Вы создаёте эти файлы по одному; каждая посвящённая страница ниже называет файл, который она строит — не создавайте их все сразу.
Как Pano строит ваши классы за вас
Вы никогда не связываете эти классы вручную — никаких new, никаких вызовов «зарегистрируй это». Весь фокус в четырёх простых идеях:
- Аннотация — это метка, начинающаяся с
@и сидящая прямо над классом, вроде@Endpoint. Это не комментарий — компилятор и Pano оба читают её. - Сканирование: когда ваше дополнение загружается, Pano просматривает ваш пакет и находит каждый класс, носящий одну из этих меток —
@Endpoint,@Dao,@Migration,@EventListenerили@PermissionDefinition. - Для каждого найденного Pano создаёт один экземпляр (один объект) и хранит его. Созданный Pano и хранимый Pano объект вроде этого называется bean — вот и всё, что «bean» означает где-либо в этой документации: объект, который Spring создал за вас.
- Внедрение через конструктор: если один из ваших классов запрашивает другой из ваших bean-ов в своём конструкторе —
class GetShoutsAPI(private val shoutDao: ShoutDao)— Pano передаёт вам готовый. Представьте это как службу доставки: вы перечисляете ингредиенты в бланке заказа (параметры конструктора), и они прибывают к вашей двери — вы никогда не вызываете конструктор сами.
Ещё одна вещь, которая избавляет вас от самого частого краха: есть две коробки.
- Коробка Pano (контекст хоста) содержит собственные сервисы Pano:
DatabaseManager,AuthProvider,SetupManager,PluginDatabaseManager. - Ваша коробка (контекст плагина) содержит написанные вами классы: ваши эндпоинты, DAO, слушатели.
Внедрение через конструктор дотягивается только до вашей коробки. Чтобы взять что-то из коробки Pano, вы запрашиваете это вручную: applicationContext.getBean(SomeService::class.java). Вы увидите это почти на каждой странице.
Изменения Kotlin никогда не горячие — пересоберите и перезапустите
Правка .kt-файла сама по себе ничего не меняет. Каждый раз, когда вы трогаете Kotlin, вы должны пересобрать jar, скопировать его в папку plugins/ вашего экземпляра и перезапустить Pano:
./gradlew build -Pnoui
cp build/libs/pano-plugin-shoutbox-local-build.jar <your-pano-instance>/plugins/-Pnoui пропускает пересборку Svelte UI, которая вам не нужна при работе над Kotlin — это делает сборку намного быстрее.
Отключение и повторное включение дополнения из Панель → Дополнения недостаточно: Pano не может подменить уже работающий Java-код, так что только полный перезапуск загружает новый jar. (Техническая причина: загрузчик плагинов PF4J в Pano держит уже загруженный classloader, а работающая JVM не может заменить его на месте.) Svelte UI вашего дополнения горячо перезагружается под bun run dev — но Kotlin никогда. Держите этот шаг пересборки-и-перезапуска в голове для каждой страницы ниже.
Входной класс
У каждого дополнения есть один главный класс, расширяющий PanoPlugin. Наш — ShoutboxPlugin (файл ShoutboxPlugin.kt), и при запуске он делает ровно одну работу: инициализирует конфигурацию и базу данных — но только после того, как завершился собственный мастер настройки Pano.
package com.panomc.plugins.shoutbox
import com.panomc.platform.api.PanoPlugin
import com.panomc.platform.api.PluginDatabaseManager
import com.panomc.platform.api.config.PluginConfigManager
import com.panomc.platform.setup.SetupManager
import com.panomc.plugins.shoutbox.config.ShoutboxConfig
class ShoutboxPlugin : PanoPlugin() {
private val pluginDatabaseManager by lazy { applicationContext.getBean(PluginDatabaseManager::class.java) }
private val setupManager by lazy { applicationContext.getBean(SetupManager::class.java) }
private var isInitialized = false
override suspend fun onStart() {
startPlugin()
}
internal suspend fun startPlugin() {
if (isInitialized || !setupManager.isSetupDone()) return
val configManager = PluginConfigManager(this, ShoutboxConfig::class.java)
pluginBeanContext.beanFactory.registerSingleton(PluginConfigManager::class.java.name, configManager)
pluginDatabaseManager.initialize(this)
isInitialized = true
}
override suspend fun onDisable() {
isInitialized = false
}
override suspend fun onUninstall() {
pluginDatabaseManager.uninstall(this)
}
}Три кусочка синтаксиса Kotlin, которые вы увидите на всех этих страницах:
suspendпомечает функцию, которой разрешено ждать — базу данных, сеть — не замораживая весь сервер. Большинство функций, которые вы переопределяете, объявленыsuspend, так что оставляйте это, даже если вы сами никогда не пишете код с корутинами. (Единственное исключение, которое вы встретите, —getValidationHandler, который базовый класс объявляет безsuspend— всегда точно повторяйте сигнатуру переопределяемой функции.)by lazy { ... }означает «не выполнять это, пока оно реально не понадобится в первый раз».getBean(X::class.java)означает «дай мне готовый объект X от Pano» — он дотягивается до коробки Pano (контекста хоста) сверху.PluginDatabaseManagerиSetupManagerнельзя внедрить в ваши конструкторы, так что вы извлекаете их так.
Что делает класс, сверху вниз:
onStart()выполняется при загрузке дополнения и вызываетstartPlugin().PluginConfigManagerсоздаётся один раз и регистрируется как bean в вашей собственной коробке (pluginBeanContext). Никогда не беритеPluginConfigManagerкак параметр конструктора в эндпоинте — его ещё не существует в момент, когда строятся ваши эндпоинты, так что внедрение упадёт. Конфигурация объясняет, почему именно, и показывает безопасный способ чтения конфигурации.pluginDatabaseManager.initialize(this)создаёт ваши таблицы и выполняет любые ожидающие миграции.
Зачем ранний возврат? Если кто-то устанавливает ваше дополнение до того, как завершил мастер первоначальной настройки Pano, базы данных ещё нет — initialize() упал бы. Поэтому startPlugin() возвращается рано, а небольшой слушатель событий повторно запускает его в момент завершения настройки. Этот слушатель-страж настройки, и всё остальное о реагировании на действия платформы, живёт в разделе События.
PluginDatabaseManager против DatabaseManager
Два разных bean-а, оба извлекаются через getBean:
PluginDatabaseManagerуправляет вашими таблицами и миграциями —initialize(plugin)иuninstall(plugin).DatabaseManager— сервис базы данных хоста. Используйте его для общего SQL-клиента (databaseManager.getSqlClient()) и для доступа к встроенным таблицам самого Pano — пользователи, посты, журналы активности, … — через их core DAO (DAO — небольшой класс, единственная задача которого — общение с одной таблицей базы данных), которые вы через него и читаете, и пишете. Работа с собственными таблицами Pano таким образом — это именно то, что делаетpano-plugin-bans— посмотрите там этот паттерн.
Контрольная точка: загрузилось ли?
После пересборки и перезапуска следите за консолью Pano — она должна залогировать загрузку вашего дополнения — и откройте Панель → Дополнения: Shoutbox должен быть в списке. Если имя jar в строке cp выше не совпадает с тем, что вы реально собрали, загляните в build/libs/ — имя берётся из вашего pluginId (который вы задали ещё в Начале работы).
Соберите остальное, по одной части за раз
Каждая возможность — это своя посвящённая страница. Обращайтесь к той, что соответствует тому, что вы добавляете:
- Добавить API (URL, возвращающий JSON) → Эндпоинты
- Хранить данные в таблице → База данных и миграции
- Добавить файл настроек, который может редактировать владелец сайта → Конфигурация
- Реагировать на действия платформы (завершение настройки, входы, ваши собственные межаддонные события) → События
- Ограждать админские возможности и записывать админские действия → Разрешения и журналы активности
Shoutbox использует лишь часть поверхности бэкенда. Доступно больше — межаддонные события, подписанные токены и шаблонные письма (pano-plugin-auth-guard), уведомления в панели и для пользователей, коммуникация с Minecraft-сервером, консольные команды и загрузка файлов. Каждая точка расширения каталогизирована в Справочнике Backend API.
Куда дальше
- Эндпоинты — откройте ваш первый публичный JSON API и эндпоинт панели только для админов.
- База данных и миграции — добавьте таблицу с моделью, DAO и её SQL.
- Справочник Backend API — каждая точка расширения бэкенда по имени, с её сигнатурой и местом в исходниках.
- Разработка фронтенда — соберите виджет Shoutbox и UI панели, вызывающие эндпоинты, которые вы пишете.