Guide · Java
PDFs zusammenfügen und mit Passwort schützen mit Java
Rechnung, AGB und Datenblatt sollen den Kunden als eine Datei erreichen, und sie soll nicht für jeden lesbar sein, der sie findet. Dieses Java-Programm verbindet bereits gerenderte Dokumente und verschlüsselt das Ergebnis, in zwei Aufrufen und ohne PDF-Bibliothek.
Kostenloser Plan mit 100 Einheiten im Monat, keine Kreditkarte
Was Sie brauchen
- Einen API-Key. Legen Sie ihn in der App unter API-Schlüssel an und exportieren Sie ihn als
FORMFEED_API_KEY. Zum Ausprobieren ist ein Test-Key (ff_test_…) richtig. Siehe Quickstart. - Java. Sie brauchen Java 17 oder neuer;
java.net.http.HttpClientund Textblöcke gehören zum JDK. - Die Quellen.
sources.jsonneben dem Programm: ein JSON-Array dessen, was in das Dokument kommt, in dieser Reihenfolge. Eine Render-ID (rnd_…) oder der Name eines PDFs in der Workspace-Bibliothek, zwei bis fünfzig davon. - Ein Passwort. In
FORMFEED_PDF_PASSWORD. Erzeugen Sie eines je Dokument und senden Sie es über einen anderen Kanal als die Datei. - Einen Tarif mit PDF-Tools. Die Tools gehören zu Starter und darüber.
Das Programm
Das Programm liest die Quellen, fügt sie zu einem PDF zusammen, verschlüsselt das Ergebnis mit dem Passwort und speichert die fertige Datei.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Bundle {
static final HttpClient CLIENT = HttpClient.newHttpClient();
static String api(String path, String body) throws Exception {
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api-eu.formfeed.dev/v1" + path))
.header("Authorization", "Bearer " + System.getenv("FORMFEED_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
System.err.println(response.body());
System.exit(1);
}
return response.body();
}
/** The value of a top-level JSON field; a project would use Jackson or Gson instead. */
static String field(String json, String name) {
Matcher m = Pattern.compile("\"" + name + "\"\\s*:\\s*(?:\"([^\"]*)\"|([^,}\\s]+))").matcher(json);
return m.find() ? (m.group(1) != null ? m.group(1) : m.group(2)) : "";
}
public static void main(String[] args) throws Exception {
String sources = Files.readString(Path.of("sources.json")).strip();
// 1. join the documents, in the order they are given
String merged = api("/pdf/merge", "{\"sources\": %s, \"filename\": \"bundle.pdf\"}".formatted(sources));
// 2. encrypt the result; printing stays allowed, copying and editing do not
String protectedPdf = api("/pdf/protect", """
{"source": "%s", "user_password": "%s", "permissions": ["print"]}
""".formatted(field(merged, "id"), System.getenv("FORMFEED_PDF_PASSWORD")));
CLIENT.send(HttpRequest.newBuilder(URI.create(field(protectedPdf, "download_url"))).build(),
HttpResponse.BodyHandlers.ofFile(Path.of("bundle.pdf")));
System.out.println(field(protectedPdf, "id") + " " + field(protectedPdf, "page_count") + " page(s), " + field(protectedPdf, "units") + " unit(s)");
}
}Ausführen
Es gibt die ID des Renderings, die Seitenzahl und die Einheiten aus, und bundle.pdf liegt neben dem Programm. Beim Öffnen fragt es nach dem Passwort; Drucken geht, Text kopieren nicht.
export FORMFEED_API_KEY=ff_test_...
export FORMFEED_PDF_PASSWORD=$(openssl rand -base64 18)
java Bundle.javaWas an Java besonders ist
- Keine JSON-Bibliothek im JDK. Um ohne Abhängigkeiten auszukommen, ist der Body ein Textblock, und die Werte, die das Programm aus JSON braucht (
idunddownload_urldes Renderings, eine Rechnungsnummer), liest ein regulärer Ausdruck. In einem Projekt bauen Sie den Body und lesen die Antwort mit Jackson oder Gson. - Start aus einer Datei.
java Render.javakompiliert und startet in einem Schritt; zum Ausprobieren braucht es kein Build-Tool. - Direkt auf die Platte.
BodyHandlers.ofFilestreamt das PDF in die Datei, ohne es im Speicher zu halten.
Warum zwei Aufrufe
- Die Reihenfolge zählt. Ein verschlüsseltes PDF lässt sich nicht mehr ändern, das Passwort kommt also zuletzt. Ein bereits geschütztes Dokument zusammenzufügen scheitert.
- Jeder Schritt ist ein Rendering. Zusammenfügen und Schützen erzeugen je ein eigenes Rendering mit ID, Download-URL und Zeile im Render-Log; das zweite nimmt die ID des ersten als Quelle.
- Je eine halbe Einheit. Ein Tool kostet mit einem Live-Schlüssel 0,5 Einheiten, unabhängig von der Seitenzahl. Die Renderings, die die Quellen erzeugt haben, wurden gezählt, als sie liefen.
- Oder in einer Anfrage. Ein Rendering kann dieselben Schritte als
postmitbringen und alles in einem Aufruf erledigen. Nehmen Sie die Tools, wenn die Dokumente schon da sind, undpost, wenn Sie ohnehin rendern. Siehe PDF-Tools.
Was die Berechtigungen tun
Alles, was nicht in permissions steht, ist verboten (fehlt die Liste, sind alle vier erlaubt: print, copy, modify und annotate): Mit ["print"] darf gedruckt, aber kein Text kopiert, bearbeitet oder kommentiert werden. Ohne user_password kann jeder das Dokument öffnen, und die Berechtigungen gelten trotzdem; das ist die richtige Einstellung für ein Dokument, das öffentlich ist, aber nicht bearbeitet werden soll. Ein owner_password hebt die Beschränkungen auf; ohne Angabe wird ein zufälliges verwendet. PDF-Viewer beachten die Berechtigungen, sie schützen aber nicht vor jemandem, der die Datei öffnen kann: Vertraulich macht sie erst das user_password.
Wenn die API nein sagt
- Eine Form für jeden Fehler. Fehler sind Problem-Dokumente (RFC 9457) mit
type,title,status,detail, einem stabilencodeund derrequest_id, die Sie dem Support nennen; dietype-URL führt zu einer Seite über diesen Fehler. Die Liste steht in der Fehlerreferenz. - Die üblichen.
401 unauthorizedbei fehlendem oder widerrufenem Schlüssel,404 template_not_foundbei falschem Vorlagennamen,422 template_runtime_error, wenn die Vorlage an den Daten scheitert,429 rate_limitedüber den Anfragen pro Sekunde des Tarifs,402 quota_exceeded, wenn die Einheiten des Free-Tarifs verbraucht sind (ein bezahlter Tarif rechnet weitere Einheiten ab, bis zu seiner Obergrenze). - Sicher wiederholen. Senden Sie beim POST einen
Idempotency-Key-Header und wiederholen Sie die Anfrage mit demselben Schlüssel, wenn Sie nicht wissen, ob der erste Versuch ankam: 24 Stunden lang liefert der Schlüssel die erste Antwort, statt erneut zu rendern und zu berechnen. Gespeichert wird die Antwort, sobald der erste Versuch beantwortet ist; wiederholen Sie also nicht, solange er noch laufen kann. Auch Probleme unter 500 werden gespeichert, eine korrigierte Anfrage braucht deshalb einen neuen Schlüssel.
Die Aufgabe um dieses Programm
Dieser Guide ist ein Programm. Was die Aufgabe darum herum braucht, von der Vorlage bis zu Limits und Preis, steht auf der Seite Verträge und Vereinbarungen als PDF.
Weitere Guides für Java
FAQ
- Woher kommen die Render-IDs?
- Aus den Renderings, die Sie vorher gemacht haben: die
idjeder Antwort. Sie müssen zum selben Workspace gehören, und ihre Ausgabe muss noch existieren. Dateien, die Sie in die Bibliothek geladen haben, nennen Sie stattdessen beim Namen. - Welche Verschlüsselung wird verwendet?
- AES-256, ausgeführt von qpdf. Das Passwort erscheint nie im Render-Log und wird nicht gespeichert: Geht es verloren, lässt sich das Dokument nicht mehr öffnen.
- Kann ich auch ein Wasserzeichen setzen?
- Ja,
/pdf/watermarkist das dritte Tool und nimmt einen Text oder ein Bild. Hängen Sie es vor das Passwort, wie das Zusammenfügen. Siehe PDF-Tools.