Skip to content

Repository files navigation

Octopus

Framework-Plugin für PostgreSQL und Redis. ByteOctopus wird einmal auf Paper und Velocity installiert und dort mit Zugangsdaten konfiguriert – alle anderen Plugins binden nur noch das API-Modul über JitPack ein und arbeiten mit Repositories, Queries und Pub/Sub-Channels. Kein eigener Hikari-Pool, kein Jedis-Setup, kein SQL.

de.j0byte.octopus.api      → API (JitPack, compileOnly im Consumer-Plugin)
de.j0byte.octopus.common   → Implementierung (Hikari, JDBC, Jedis, Gson)
de.j0byte.octopus.paper    → ByteOctopus-Paper.jar
de.j0byte.octopus.velocity → ByteOctopus-Velocity.jar

Dokumentation

Ausführliche Doku für Plugin-Entwickler: docs/

Einstieg Dependency, Plugin-Dependency, erster Zugriff
Datenbank Entities, Tabellen, Schreiben, Lesen
Queries Filter, Sortierung, Massen-Updates
Messaging Channels, publish, subscribe
Cache Key-Value, Hashes, Locks
Async & Threading CompletableFuture, Callbacks im Tick
Rezepte Fertige Lösungen
Fehler & FAQ Was schiefgeht und warum
API-Referenz Alle Methoden auf einen Blick

Installation

  1. ./gradlew build
  2. paper/build/libs/ByteOctopus-Paper-1.0.0.jar auf jeden Paper/Leaf-Server, velocity/build/libs/ByteOctopus-Velocity-1.0.0.jar auf den Proxy.
  3. Auf Paper zusätzlich das CommandAPI-Plugin (Standalone) installieren – ByteOctopus lädt sonst nicht.
  4. Einmal starten, plugins/ByteOctopus/config.json ausfüllen, /octopus reload.

config.json

Eine Datei pro Plugin, alles Konfigurierbare an einer Stelle:

{
  "serverId": "auto",
  "database": { "host": "127.0.0.1", "port": 5432, "database": "blockbucht",
                "username": "blockbucht", "password": "changeme", "schema": "public",
                "poolSize": 10, "minimumIdle": 2, "connectionTimeoutMs": 10000,
                "idleTimeoutMs": 600000, "maxLifetimeMs": 1800000, "ssl": false,
                "properties": {} },
  "redis":    { "host": "127.0.0.1", "port": 6379, "username": "", "password": "",
                "database": 0, "timeoutMs": 5000, "poolSize": 8, "ssl": false,
                "keyPrefix": "blockbucht:", "channelPrefix": "blockbucht:" },
  "async":    { "threads": 4 },
  "messages": { "prefix": "<gray>[<aqua>Octopus</aqua><gray>]</gray> ", "...": "..." }
}
  • serverId: "auto" liest SIMPLECLOUD_SERVICE_NAME, SERVICE_NAME, SERVER_NAME, HOSTNAME und fällt sonst auf den Hostnamen zurück. Diese Kennung steht als Absender in jeder Redis-Nachricht.
  • Alle Texte sind MiniMessage; auf Paper laufen sie vorher durch PlaceholderAPI (setPlaceholdersdeserialize), damit %img_prefix% und Co. funktionieren.
  • Fehlende Schlüssel werden beim Start mit Defaults ergänzt und zurückgeschrieben.

Befehle: /octopus status, /octopus reload (Permission octopus.admin).

API einbinden

repositories {
    maven("https://jitpack.io")
}

dependencies {
    compileOnly("com.github.xJ0byte.Octopus:api:1.0.0")   // Tag oder Commit-Hash
}

Paper (paper-plugin.yml):

dependencies:
  server:
    ByteOctopus:
      load: BEFORE
      required: true
      join-classpath: true

Velocity: @Plugin(id = "meinplugin", dependencies = @Dependency(id = "byteoctopus")).

Zugriff wahlweise statisch oder per Guice:

Octopus octopus = Octopus.get();
// oder
Injector injector = Guice.createInjector(new OctopusModule(), new MyModule());

PostgreSQL ohne SQL

Tabelle definieren

@Table("coin_profiles")
@Index({"coins"})
public class CoinProfile {

    @Id private UUID uuid;
    @Column(nullable = false) private String name;
    private long coins;
    @Json private List<String> unlockedKits = new ArrayList<>();
    private Instant lastSeen;

    public CoinProfile() {}   // parameterloser Konstruktor, darf private sein
}

records funktionieren ebenfalls (dann ohne @Id(generated = true)). Feldnamen werden zu snake_case, Typen automatisch abgeleitet:

Java PostgreSQL
UUID UUID
String, enum TEXT
int / long / double INTEGER / BIGINT / DOUBLE PRECISION
boolean BOOLEAN
Instant TIMESTAMPTZ
byte[] BYTEA
@Json + alles andere JSONB

Tabelle anlegen

Repository<CoinProfile> profiles = octopus.database().repository(CoinProfile.class);
profiles.createTable().join();   // CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS

Dynamisch, ohne Entity-Klasse:

octopus.database().schema().create(
    TableDefinition.builder("shop_logs")
        .column(ColumnDefinition.of("id", ColumnType.BIGSERIAL))
        .column(ColumnDefinition.of("player", ColumnType.UUID).notNull())
        .primaryKey("id")
        .index("idx_shop_logs_player", false, "player")
        .build());

Oder klassisch nach eurer Konvention aus src/main/resources/schema.sql:

octopus.database().schema().applyResource(getClass().getClassLoader(), "schema.sql");

schema().renderCreate(CoinProfile.class) gibt das erzeugte DDL zurück, ohne es auszuführen – praktisch zum Gegenprüfen.

Einträge schreiben und lesen

profiles.insert(profile);                 // INSERT
profiles.save(profile);                   // Upsert über den Primärschlüssel
profiles.saveAll(list);                   // Batch-Upsert in einer Transaktion
profiles.update(profile);                 // UPDATE ... WHERE id = ?
profiles.delete(profile);
profiles.deleteById(uuid);

profiles.findById(uuid);                  // CompletableFuture<Optional<CoinProfile>>
profiles.findAll();
profiles.findBy("name", "Jona");
profiles.count();
profiles.existsById(uuid);

Queries

profiles.query()
        .where(Filter.gte("coins", 1000))
        .and(Filter.or(Filter.likeIgnoreCase("name", "jo%"),
                       Filter.isNull("lastSeen")))
        .orderBy("coins", Order.DESC)
        .limit(10)
        .findAll();

Verfügbare Filter: eq, ne, gt, gte, lt, lte, like, likeIgnoreCase, between, in, notIn, isNull, notNull, and, or, not. Werte werden immer als Prepared-Statement-Parameter gebunden – kein String-Bau, keine Injection.

Massen-Update und -Delete ohne Objekte zu laden:

profiles.query().where(Filter.lt("lastSeen", cutoff)).set("coins", 0).executeUpdate();
profiles.query().where(Filter.eq("coins", 0)).delete();

Escape-Hatch, falls doch mal echtes SQL nötig ist:

octopus.database().withConnection(connection -> { ... });
octopus.database().transaction(connection -> { ... });   // mit Rollback

Threading

Alle DB- und Redis-Methoden laufen im Async-Pool von Octopus und geben ein CompletableFuture zurück. Ergebnis auf dem Main-Thread verarbeiten:

octopus.scheduler().callback(
        profiles.findById(player.getUniqueId()),
        found -> player.sendMessage(...));      // läuft im Server-Tick

Auf Velocity gibt es keinen Main-Thread – callback führt dort direkt im Pool aus.

Redis-Messaging

Channel channel = octopus.messaging().channel("economy:balances");

Subscription subscription = channel.subscribe(BalanceChanged.class, message -> {
    message.payload();     // deserialisiertes Objekt
    message.source();      // serverId des Absenders
    message.timestamp();
});

channel.publish(new BalanceChanged(uuid, 500, "shop"));
  • Payloads werden als JSON übertragen und mit Absender + Zeitstempel verpackt.
  • Ein Server empfängt seine eigenen Nachrichten nicht (subscribeIncludingSelf(...), wenn doch gewünscht).
  • Handler laufen im Async-Pool, nicht auf dem Listener-Thread und nicht im Tick.
  • Ein einziger Listener-Thread hält die Verbindung und baut sie bei Abbruch neu auf.
  • Beim Deaktivieren des Plugins subscription.unsubscribe() aufrufen.

Redis-Cache

octopus.cache().set("rank:" + uuid, rank, Duration.ofMinutes(10));
octopus.cache().get("rank:" + uuid, Rank.class);
octopus.cache().increment("kills:" + uuid, 1);
octopus.cache().hset("party:" + id, "leader", uuid.toString());

octopus.cache().tryLock("daily-reset", Duration.ofMinutes(5))
       .thenAccept(lock -> lock.ifPresent(l -> { /* nur ein Server läuft hier rein */ }));

Alle Keys bekommen den keyPrefix aus der config.json.

Build

./gradlew build          # baut alle Module + Shadow-Jars
./gradlew :api:publishToMavenLocal

Geshadet und relocated nach de.j0byte.octopus.libs.* werden HikariCP, der PostgreSQL-Treiber, Jedis, commons-pool2 und org.json. Gson und SLF4J kommen vom Server.

JitPack

jitpack.yml liegt im Repo, JitPack baut :api:publishToMavenLocal mit JDK 21. Nach einem Git-Tag ist die Version als com.github.xJ0byte.Octopus:api:<tag> verfügbar.

Vor dem ersten Build prüfen

Diese Versionen stammen aus gradle/libs.versions.toml und sollten einmal gegen die Repos gegengecheckt werden:

Artefakt Version
io.papermc.paper:paper-api 1.21.11-R0.1-SNAPSHOT
com.velocitypowered:velocity-api 3.4.0-SNAPSHOT
com.zaxxer:HikariCP 7.1.0
org.postgresql:postgresql 42.7.12
redis.clients:jedis 6.0.0
dev.jorel:commandapi-bukkit-core 11.2.0
me.clip:placeholderapi 2.11.6
studio.o7.remora (Plugin) 0.2.5 (aktuell wäre 0.4.0)
com.gradleup.shadow (Plugin) 9.4.2

Der Jedis-Pool wird in RedisProvider über new JedisPool(JedisPoolConfig, HostAndPort, JedisClientConfig) gebaut – falls Jedis 6 diese Signatur geändert hat, ist das die einzige Stelle, die angepasst werden muss.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages