Wiki source code of Basisprojekt

Last modified by Marco Grawunder on 2025/10/16 11:59

Show last authors
1 [[image:Main.Organisatorisches.WebHome@softwareprojekt_logo_transparent.png||alt="SoftwareprojektLogo.png" data-xwiki-image-style-alignment="end" height="136" width="309"]]
2
3 Hier folgen Erklärungen des neuen Basisprojekts. Es wird um die folgenden Themen gehen:
4
5 {{toc/}}
6
7
8 = Basisprojekt mit IntelliJ einrichten =
9
10 [[image:1755245956916-184.png]]
11
12 == Clone ==
13
14 Achtung! In dem Screenshot wird das globale Basisprojekt verwendet. Für jede Gruppe existiert bereits ein eigenes Repository, welches verwendet werden sollte,
15
16 Sie finden die Clone-URL wie folgt:
17
18 * Loggen Sie sich auf [[https:~~/~~/gitlab.swl.informatik.uni-oldenburg.de/>>https://gitlab.swl.informatik.uni-oldenburg.de/]] ein
19 * Falls nicht vorausgewählt, wählen Sie auf der linken Seite "Projects"
20 [[image:1757398628416-879.png||height="119" width="541"]]
21 * Da Sie bisher noch nichts gemacht haben, ist diese Seite leer. Wechseln Sie auf den Reiter Member
22 * Dort sollte ihr Repository zu finden sein.
23 * Oben rechts gibt es einen Button Code. Kopieren Sie dort die URL, die hinter "Clone with HTTPS" steht.
24 [[image:1757398731084-704.png||height="454" width="323"]]
25
26 [[image:1755245971657-468.png]]
27
28 == Access Token ==
29
30 Beim ersten Mal clonen müssen Sie Ihre Gitlab-Zugangsdaten verwenden. Statt Name und Passwort ist es sinnvoll, einen Access-Token zu definieren. Das können Sie machen, in dem Sie auf ihre Profilbild klicken (auf der linken Seite) und dort "Edit Profile" auswählen. In dem nun folgenden Menü gibt es den Punkt Access tokens.
31
32 [[image:1757398899497-714.png||height="246" width="278"]]
33
34 Dort können Sie mit [[image:1757398947128-748.png||height="89" width="197"]] ein neues Token anlegen.
35
36 Als Namen können Sie z.B. IntelliJ verwenden. Wichtig! Das Token ist aus Sicherheitsgründen nur eine bestimmte Zeit gültig. In diesem Gitlab kann dies maximal ein Jahr sein, standardmäßig ist ist hier aber ein Monat gewählt. Sie sollten also das Feld anpassen.
37
38 [[image:1757399088336-273.png||height="92" width="547"]]
39
40 Bei den Scopes sollten die beiden Rechte "read_repository" und "write_repository" gewählt werden.
41
42 Danach wird das Access Token generiert
43
44 [[image:1757399184270-348.png||height="99" width="978"]]
45
46 Jetzt ist es wichtig, dass Sie sich das Token sichern!
47
48 Beim **Einloggen **(Achtung! Gemeint ist hier, wenn Intellij (bzw. git) nach den Account-Daten beim Clonen fragt. Man kann auch einen Gitlab-Account hinterlegen (der dann aber parallel existiert). Hier für ist es wichtig, dass noch "api" und "read_user" als Recht vergeben wird.) in IntelliJ können Sie dieses Token im Passwort-Feld verwenden. Geben Sie ihren Account bei Name ein.
49
50
51 == Nach dem Clonen ==
52
53 sollten Sie einen Bildschirm ähnlich zu dem folgenden sehen:
54
55 [[image:1755245980026-164.png]]
56
57 Auf dem main-Branch (master) kann keine Änderung gemacht werden, deswegen muss auf einen anderen Branch gewechselt werden. Im Beispiel development.
58
59 [[image:1755245996886-733.png]]
60
61 == Initialer Build (Generierung) ==
62
63 Dem neuen Code fehlen einige generierte Dateien. Diese werden wie folgt erzeugt. 
64 Hinweis: Immer, wenn an dem OpenAPI-Dokument etwas geändert wird oder neue Abhängigkeiten für Maven definiert werden, muss diese Aktion durchgeführt werden.
65
66 [[image:1755246008466-477.png]]
67
68
69 [[image:1755246018789-616.png]]
70
71
72 == Lombok Plugin ==
73
74 **Lombok Plugin installiert? Wenn nein jetzt machen**
75
76 [[image:1755248508652-523.png]]
77
78 **Hinweis: Wenn man schon hier ist, kann man auch das Spring-(Boot)-Plugin **installieren
79
80 [[image:1756886220468-891.png]]
81
82 == Server laufen lassen ==
83
84 Den Serverbereich aufklappen und dort auf die Datei ServerApp mit der rechten Maustaste klicken.
85
86 [[image:1755246035428-328.png]]
87
88
89 ... und ggf. Lombok aktivieren (Man wird nach dem Start der Anwendung gefragt). WICHTIG! Wenn das Lombok-Plugin nicht installiert und die folgende Funktion nicht aktiviert ist, muss jede Änderung mit Hilfe von Maven passieren.
90
91 [[image:1755246072443-191.png]]
92
93
94 [[image:1755246118807-452.png]]
95
96
97 == Logging umstellen ==
98
99 Wenn man möchte, kann man das Logging umstellen.
100
101 [[image:1755246135109-325.png]]
102
103
104 [[image:1755246147827-679.png]]
105
106
107 [[image:1755246162330-595.png]]
108
109 == Development Profil aktivieren ==
110
111 Damit das Testen einfacher geht, gibt es ein spezielles Spring-Profil. In diesem werden automatisch user1-user9 mit jeweils dem identischen Passwort angelegt und man spart sich die Registierung.
112
113 Wenn man die Anwendung einmal gestartet hat, kann man dies Configuration anpassen:
114
115 [[image:1757399848941-253.png||height="209" width="558"]]
116
117 [[image:1755246173415-934.png]]
118
119
120 **Wenn man IntelliJ nicht in der Pro-Version verwendet bzw. das Spring Boot Plugin nicht installiert hat, muss kann man ein Spring-Profil über eine Umgebungsvariable in der Konfiguration des Servers setzen: **
121
122 **SPRING_PROFILES_ACTIVE=dev**
123
124 [[image:1755248752596-839.png]]
125
126 Danach muss man den Server neu starten!
127
128 == Client starten ==
129
130 Wenn der Server gestartet ist, kann man mehrere Clients starten. Dafür auf jeden Fall die Klasse Main verwenden.
131
132 **Sollte es nun Problem geben, schauen Sie bitte, ob Sie noch eine alte Java-Version auf ihrem System installiert haben. Wir brauchen mindestens Java 21!**
133
134 [[image:1755246257400-525.png]]
135
136
137 [[image:1755246212916-883.png]]
138
139
140 [[image:1755246223246-834.png]]
141
142
143 === Mehrere Instanzen des Clients ermöglichen ===
144
145 Standardmäßig erlaubt IntelliJ nicht das Starten mehrerer Clients. Man könnte nun mehrere Configurations für den Client anlegen. Man kann aber auch in der Konfiguration unter "Modify options" den Haken bei "Allow multiple instances" setzen. Dann kann eine beliebige Anzahl von Clients gestartet werden.
146
147 [[image:1755246233218-893.png]]
148
149
150 Wenn folgendes kommt, ist entweder das "dev" Profil nicht aktiviert, der Nutzer wurde nicht angelegt. Wenn der Server nicht läuft, gibt es eine andere Fehlermeldung.
151
152 [[image:1755246292057-581.png]]
153
154
155 = Kurzer Blick ins Basisprojekt =
156
157 [[image:1755249096987-249.png]]
158
159
160 [[image:1755249136156-419.png]]
161
162
163 == Screenshots ==
164
165 [[image:1755249228556-469.png]]
166
167
168 = Kommunikation Client ~-~-> Server =
169
170 [[image:1755249285866-367.png]]
171
172 Der Server verwendet das REST-Protokoll und als Austauschformat JSON
173
174 = OpenAPI =
175
176 (Hinweis: Ich bin nicht sicher, ob IntelliJ diese Darstellung auch hat, wenn man nicht die Ultimate Version verwendet. Ggf. muss man das OpenAPI-Plugin installieren)
177
178 [[image:1755250026156-269.png]]
179
180 [[image:1755250050031-304.png]]
181
182 * **Paths**: Endpunkte der API (z.B. /users, /lobbies).
183 * **Operations**: Spezifikation von Methoden wie GET, POST.
184 * **Definitions**: Detaillierte Beschreibung von Eingabe- und Ausgabestrukturen.
185 * **Security**: Authentifizierungsmechanismen.
186
187 [[image:1755250061990-172.png]]
188
189 Kann JSON oder YAML (Yet Another Markup Language) verwenden YAML ist wie JSON nur mit weniger Klammern
190
191 [[image:1755250157536-746.png]]
192
193 Die aktuelle Version des OpenAPI Dokumentes findet sich im Basisprojekt 2 [[https:~~/~~/gitlab.swl.informatik.uni-oldenburg.de/SPB/SWPBasisprojekt2/-/blob/master/openapi.yaml?ref_type=heads>>https://gitlab.swl.informatik.uni-oldenburg.de/SPB/SWPBasisprojekt2/-/blob/master/openapi.yaml?ref_type=heads]]
194
195 Dort wird die Datei auch grafisch dargestellt.
196
197
198 = Maven und OpenAPI =
199
200 Die OpenAPI Datei kann verwendet werden, um sich die [[REST-Schnittstellen>>doc:||anchor="HErweiterungderREST-Schnittstelle"]] generieren  zu lassen. Diese Erzeugung erfolgt mit dem OpenAPI Generator [[https:~~/~~/github.com/OpenAPITools/openapi-generator>>https://github.com/OpenAPITools/openapi-generator]]
201
202 Man kann dabei jetzt einen Kommandozeilenaufruf verwenden oder man setzt auf das im Basisprojekt vorhandene MVN ()
203
204 Dafür ist in den Maven-Dateien bereits das OpenAPI Generator Plugin integriert. Da im Client und im Server unterschiedliche Arten verwendet werden, erfolgt die Konfiguration im Client und im Server unterschiedlich:
205
206 == Client ==
207
208 Im Client werden die Apache Http Bibliothek verwendet.
209
210
211 [[image:1756887005209-855.png]]
212
213 == Server ==
214
215 Im Server wird Spring (Boot) verwendet
216
217 [[image:1756887037619-847.png]]
218
219 TODO: Weitere Informationen zu
220
221 - Lombok
222
223 - Dependency Injection
224
225 - Spring (Boot), siehe auch [[https:~~/~~/www.marcobehler.com/guides/spring-framework>>https://www.marcobehler.com/guides/spring-framework]]
226
227
228
229 = Erweiterung der REST-Schnittstelle =
230
231 In diesem Beispiel wird einmal gezeigt, wie die REST-Schnittstelle des Basisprojektes einfach erweitert werden kann.
232
233 In diesem Beispiel soll die aktuelle Schnittstelle um die Möglichkeit erweitert werden, alle Lobbies vom Server zu bekommen.
234
235 == Schritt 1: Erweitere das OpenAPI-Dokument ==
236
237 Um diese neue Funktion sowohl im Client als auch im Server verwenden zu können, ist es notwendig, diese neue Funktion im OpenAPI-Dokument zu definieren.
238
239 Die Funktion soll sehr einfach sein und keine Parameter verlangen. Dafür bietet sich die GET-Funktion an.
240
241 Im folgenden Bild sind alle Anpassungen zu sehen:
242
243 [[image:1756887436525-790.png||height="355" width="974"]]
244
245
246 Nach dem Speichern, sollte das OpenAPI-Dokument wie folgt aussehen
247
248 [[image:1756887488020-376.png||height="642" width="904"]]
249
250
251 Jetzt kann man entweder in IntelliJ
252
253 [[image:1756888245896-845.png||height="347" width="620"]]
254
255 oder im Terminal (z.B. auch in IntelliJ)
256
257 [[image:1756888279902-777.png||height="637" width="1053"]]
258
259 Wobei hier auch clean compile reichen würde.
260
261 **ACHTUNG! Falls maven Problem macht, kann das auch an einer falschen Java-Version im System liegen (siehe auch [[FAQ>>doc:.Basisprojekt FAQ.WebHome]])**
262
263 Es werden durch den Aufruf neue Inhalte generiert (bzw. die alten überschrieben).
264
265 [[image:1756888428042-802.png||height="538" width="1077"]]
266
267 Hinweis: Niemals Änderungen unterhalb des target-Ordners machen. Das wird von Maven bei clean gelöscht.
268
269 === Wie bekommt man dann aber nun die Funktionalität rein? ===
270
271 Für jeden Endpunkt (also aktuell lobbies und users) werden drei Interfaces/Klassen erzeugt:
272
273 * *Api (z.B, LobbiesApi): Beschreibung der REST-Methoden, vor allem auch das Mapping von z.B. /lobbies/join auf die Methode lobbyJoin(String)
274 * (((
275 *ApiController implements *Api (Für Spring) (z.B. LobbiesApiController)
276 )))
277 * (((
278 *ApiDelegate (z.B. LobbiesApiDelegate): Macht die eigentliche Arbeit und muss** im eigenen Code-Bereich** erweitert werden!
279 )))
280
281
282
283 == Schritt 2: Erweiterung auf Server-Seite ==
284
285 Da es schon Funktionen für die Lobbies gibt, gibt es auch bereits eine Implementierung, die LobbiesApiDelegate überschreibt
286
287 [[image:1756888762381-912.png||height="48" width="789"]]
288
289 Wenn man einen neuen Endpunkt definiert, muss man auch einen neuen Service definieren. (Hinweis: Der Service muss eine Spring Komponenten sein, damit sie in den Spring Context aufgenommen wird).
290
291 In der Klasse muss man dann die neue Methode lobbyList aus der API überschreiben.
292
293 [[image:1756888929507-312.png||height="156" width="1161"]]
294
295 Dabei wird folgendes gemacht:
296
297 1. Es wird ein Rückgabeobjekt vom Typ Liste erzeugt
298 1. Es wird über alles Lobbies auf dem Server gegangen (lobbyManagement.getLobbies())
299 1. Da der Client u.U. nicht die vollständigen Informationen über die Lobbies bekommen soll, gibt es zwei unterschiedliche Klassen: ServerLobby und LobbyDTO.
300 1. Die Foreach-Schleife sorgt dafür, dass in das Rückgabeobjekt nur die LobbyDTOs eingefügt werden.
301 1. Dafür wird eine Funktion mit dem Namen lobbyMapping verwendet
302 1. Schließlich wird am Ende gesagt, dass alles ok ist und eine Antwort ResponseEntity.ok mit dem Rückgabeobjekt (lobbies) gesendet.
303
304 **Anmerkung**: Das Basisprojekt ist aktuell so eingerichtet, dass Spring Exceptions auffängt und entsprechend an den Client leitet. Diese findet in der Klasse  GlobalExceptionHandler statt
305
306 Auf Server-Seite fehlt jetzt noch die Methode getLobbies im LobbyManagement
307
308 [[image:1756889590500-656.png||height="81" width="518"]]
309
310
311 === LobbyMapping ===
312
313 Da man relativ oft Server-Objekt in DTO umwandeln muss gibt es im Basisprojekt MapStruct. Damit muss man nur die DTO-Klasse anlegen (i.d.R. über OpenAPI!!)
314
315 Also z.B.
316
317 [[image:1756889440395-856.png]]
318
319 und definiert ein Interface mit einer Annotation
320
321 [[image:1756889472103-847.png]]
322
323 und damit kann man die Funktion aufrufen. Hinweis: Der Mapper ist im LobbyService über die Spring Dependency Injection gebunden.
324
325 == Schritt 3: Erweiterung auf Client-Seite (Java) ==
326
327 Hinweis: Das Beispiel bezieht sich hier auf eine Client mit Java. Für andere Clients wie Angular ist das Vorgehen anders.
328
329 Auf der Client-Seite wird die komplette Kommunikation mit dem Server in der generierten Klasse DefaultApi gekapselt.
330
331 [[image:1756889795622-530.png]]
332
333 Dort gibt es eine neue Methode lobbyList. Die sorgt dafür, dass der REST-Aufruf auf die Server-Seite geht und liefert das passende Objekt List<LobbyDTO> zurück
334
335 Im Client gibt es auch eine Klasse LobbyService. Dort ist die DefaultApi Klasse über Dependency Injection gebunden.
336
337 [[image:1756889917681-650.png]]
338
339 Dort kann man nun eine neue Methode getLobbies() integrieren:
340
341 [[image:1756889979252-910.png]]
342
343 Und das Ganze dann z.B. im MainMenuPresenter verwenden:
344
345 [[image:1756890010118-149.png||height="116" width="972"]]
346
347 Anmerkung: Obwohl DefaultApi alle Funktionen zum Server kapselt, sollte man im Client spezifische Services für bestimmte Bereich haben, die diese Klasse verwenden.  Das führt zu einer besseren Trennung von Funktionalitäten im Code.
348
349
350 = Kommunikation: Server ~-~-> Client (WebSockets) =
351
352 [[image:1756890800817-370.png||height="604" width="1121"]]
353
354 Da man mit REST nicht Nachrichten vom Server an den Client schicken kann, werden im Basisprojekt dafür WebSockets verwendet.
355
356 Spring bietet eine native Unterstützung von WebSockets. Für eigene Funktionen kann man sich in die Kommunikation über die Serverklasse WebSocketHandler einklinken
357
358
359 [[image:1756890924024-346.png||height="377" width="1092"]]
360
361
362 Sobald sich jemand beim Server für WebSockets angemeldet hat wird von Spring ein org.springframework.web.socket.messaging.SessionConnectedEvent
363 geworfen, welches in der folgenden Methode (im WebSocketHandler) aufgefangen wird
364
365 [[image:1756890958794-603.png||height="373" width="1087"]]
366
367 Die Methode ist Observer für das Event SessionConnectedEvent
368
369 Der WebSocketServer kennt die Nutzer und erlaubt das Einloggen nur, wenn Login und Passwort stimmen (durch Spring Security)
370
371 [[image:1756891019715-621.png]]
372
373 1)Aus dem Event kann der Nutzer gelesen werden (der sollte nie leer sein)
374
375 2) Dann wird sich aus dem Repository (später mehr) der Nutzer geholt, der durch den Namen identifiziert ist (z.B. „test1“)
376
377 3) Schließlich werden allen anderen darüber informiert, dass ein neuer Nutzer da ist
378
379 == STOMP ==
380
381 WebSockets haben kein Protokoll (wie z.B. http)
382
383 Es können entweder binäre oder textuelle Daten verarbeitet werden (die jeweiligen Gegenstellen müssen das wissen!)
384
385 Wenn man jetzt mehr als nur Text verschicken möchte, muss man sich überlegen, wie man Objekte z.B. mit JSON serialisiert (analog zu REST)
386
387 STOMP: Streaming Text Oriented Messaging Protocol
388
389 Definiert ein einfaches Protokoll, welches es erlaubt, sinnvoll über WebSockets zu kommunizieren
390
391 Ist ein Teil von Spring
392
393 Methoden sind z.B. CONNECT, SEND oder SUBSCRIBE
394
395 STOMP arbeitet mit Topics
396
397 Ein Client registriert (SUBSCRIBE) sich für bestimmte Ereignistypen
398
399
400 §Wenn auf der Server-Seite dieser Typ veröffentlich wird dann wird dies an die jeweils interessierten Clients geschickt
401
402 Publish/Subscribe-Pattern
403
404 [[https:~~/~~/docs.spring.io/spring-framework/reference/web/websocket/stomp.html>>url:https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html]]
405
406
407 §Der Server definiert unterschiedliche Topics (je nach Modul)
408
409 §Beim Nutzermanagement aktuell:
410
411 §/topic/users/loggedIn: Es hat sich ein neuer Nutzer angemeldet
412
413 §/topic/users/loggedOut: Ein Nutzer hat sich ausgeloggt
414
415
416 [[image:1756891125969-748.png||height="317" width="726"]]
417
418 §Topic-Namen sind Strings, sollte aber Aufbau von oben entsprechen
419
420 §In der Lobby würde es stattdessen /topic/lobbies/* heißen
421
422 == WebSockets: Versenden von Nachrichten ==
423
424 [[image:1756891180516-843.png||height="426" width="801"]]
425
426 [[image:1756891216134-578.png||height="428" width="699"]]
427
428 == Nachrichteninhalt ==
429
430 [[image:1756891254830-647.png||height="101" width="777"]]
431
432 * message kann grundsätzlich alles sein, was serialisiert werden kann
433 * Man könnte nun einfach die Java-Serialisierung verwenden (im alten Basisprojekt ist das auch so)
434 * Das hat aber eine Reihe von Nachteilen
435 ** Der Empfänger muss dafür unbedingt auch ein Java-Client sein und er muss exakt dieselbe Klasse bei sich haben, damit der das Objekt wieder zurück in ein Java-Objekt umwandeln kann
436 ** Es gibt eine Reihe von Sicherheitsproblemen
437 * Besser: Definiere ein gemeinsames Austauschformat, was viele verstehen ~-~-> Im Basisprojekt (und in vielen anderen Projekten auch) JSON verwenden
438 * Insbesondere Web-Clients (JavaScript) bieten hervorragende Möglichkeiten, an JSON zu verarbeiten
439 * Client und Server haben sich damit auf Format für den Austausch geeinigt
440 ** Topic: Strings
441 ** Message: JSON
442
443 An den Clilent werden auch bei WebSockets nur DTOs verschickt! (userMapping)
444
445 [[image:1756891375095-158.png||height="266" width="775"]]
446
447 == Wie verbindet sich ein Client mit dem Server? ==
448
449 UserService bietet eine Methode zum Login an. Diese ruft nun aber keine REST-Endpunkt auf (da man sich sowieso bei JEDEM Aufruf authentifizieren muss, macht so ein Endpunkt kein Sinn). Stattdessen wird die Verbindung mit dem WebSocket hergestellt und dort Nama und Passwort überprüft.
450
451 [[image:1756891512330-186.png||height="170" width="820"]]
452
453 === Auf Server Seite (WebSocketConnectionManager): ===
454
455 [[image:1756891551794-161.png||height="387" width="1019"]]
456
457 1) Variablen definieren
458
459 2) WebSocketClient erzeugen
460
461 3) Daraus WebSocketStompClient machen
462
463 4) Jackson als Mapper definieren (DTO-Object <-> JSON)
464
465 === [[image:1756891617399-232.png||height="289" width="1006"]] ===
466
467
468 1) Asynchron die Verbindung zum Server aufbauen
469
470 2) Wenn erfolgreich in das Hauptmenü wechseln (showScene à später mehr)
471
472 3) Über den Kontext ein Event pushen LoggedInEvent
473
474 4) Jede Serververbindung hat eine Session