feat: websocket with events!!!
This commit is contained in:
parent
836d9f62ee
commit
db42422328
|
@ -2,12 +2,17 @@ package me.theclashfruit.crss;
|
||||||
|
|
||||||
import me.theclashfruit.crss.backend.error.GoneServlet;
|
import me.theclashfruit.crss.backend.error.GoneServlet;
|
||||||
import me.theclashfruit.crss.backend.IndexServlet;
|
import me.theclashfruit.crss.backend.IndexServlet;
|
||||||
import me.theclashfruit.crss.backend.error.NotFoundServlet;
|
import me.theclashfruit.crss.backend.v2.StatusServlet;
|
||||||
|
import me.theclashfruit.crss.backend.v2.gateway.GatewayServlet;
|
||||||
|
import me.theclashfruit.crss.listeners.PlayerListener;
|
||||||
|
import org.bukkit.plugin.PluginManager;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.websocket.core.server.WebSocketUpgradeHandler;
|
||||||
|
|
||||||
public class CRSSPlugin extends JavaPlugin {
|
public class CRSSPlugin extends JavaPlugin {
|
||||||
Server server = new Server(25580);
|
Server server = new Server(25580);
|
||||||
|
@ -20,26 +25,42 @@ public class CRSSPlugin extends JavaPlugin {
|
||||||
|
|
||||||
saveDefaultConfig();
|
saveDefaultConfig();
|
||||||
|
|
||||||
|
PluginManager pluginManager = getServer().getPluginManager();
|
||||||
|
|
||||||
context.setContextPath("/api");
|
context.setContextPath("/api");
|
||||||
|
|
||||||
context.addServlet(IndexServlet.class, "/");
|
context.addServlet(IndexServlet.class, "/");
|
||||||
|
|
||||||
|
// v2 api
|
||||||
|
context.addServlet(StatusServlet.class, "/v2/status");
|
||||||
|
context.addServlet(GatewayServlet.class, "/v2/gateway");
|
||||||
|
|
||||||
// errors
|
// errors
|
||||||
context.addServlet(GoneServlet.class, "/v1/*");
|
context.addServlet(GoneServlet.class, "/v1/*");
|
||||||
|
|
||||||
|
JettyWebSocketServletContainerInitializer.configure(context, null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
server.setHandler(context);
|
server.setHandler(context);
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
getLogger().severe(e.getMessage());
|
getLogger().throwing(this.getName(), "onEnable", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pluginManager.registerEvents(new PlayerListener(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
getLogger().info("Plugin disabled!");
|
getLogger().info("Plugin disabled!");
|
||||||
|
|
||||||
|
try {
|
||||||
|
server.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
getLogger().throwing(this.getName(), "onDisable", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import jakarta.servlet.annotation.WebServlet;
|
||||||
import jakarta.servlet.http.HttpServlet;
|
import jakarta.servlet.http.HttpServlet;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import me.theclashfruit.crss.models.ErrorResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -13,7 +14,14 @@ public class GoneServlet extends HttpServlet {
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
|
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
|
||||||
res.setContentType("application/json");
|
res.setContentType("application/json");
|
||||||
|
|
||||||
|
ErrorResponse error = new ErrorResponse(
|
||||||
|
410,
|
||||||
|
"This version of the API is no longer available."
|
||||||
|
);
|
||||||
|
|
||||||
|
String resJson = error.toJson();
|
||||||
|
|
||||||
res.setStatus(410);
|
res.setStatus(410);
|
||||||
res.getWriter().println("{\"error\":410,\"message\":\"This version of the API is no longer available.\"}");
|
res.getWriter().println(resJson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import jakarta.servlet.annotation.WebServlet;
|
||||||
import jakarta.servlet.http.HttpServlet;
|
import jakarta.servlet.http.HttpServlet;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import me.theclashfruit.crss.models.ErrorResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -13,7 +14,14 @@ public class NotFoundServlet extends HttpServlet {
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
|
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
|
||||||
res.setContentType("application/json");
|
res.setContentType("application/json");
|
||||||
|
|
||||||
|
ErrorResponse error = new ErrorResponse(
|
||||||
|
404,
|
||||||
|
"This endpoint does not exist."
|
||||||
|
);
|
||||||
|
|
||||||
|
String resJson = error.toJson();
|
||||||
|
|
||||||
res.setStatus(404);
|
res.setStatus(404);
|
||||||
res.getWriter().println("{\"error\":404,\"message\":\"This endpoint does not exist.\"}");
|
res.getWriter().println(resJson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package me.theclashfruit.crss.backend.v2;
|
||||||
|
|
||||||
|
import jakarta.servlet.annotation.WebServlet;
|
||||||
|
import jakarta.servlet.http.HttpServlet;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import me.theclashfruit.crss.models.StatusResponse;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@WebServlet(name = "Status API")
|
||||||
|
public class StatusServlet extends HttpServlet {
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
|
||||||
|
res.setContentType("application/json");
|
||||||
|
|
||||||
|
StatusResponse response = new StatusResponse(
|
||||||
|
Bukkit.getBukkitVersion(),
|
||||||
|
Bukkit.getOnlinePlayers().toArray().length,
|
||||||
|
Bukkit.getWorlds().stream().map(World::getName).toArray(String[]::new)
|
||||||
|
);
|
||||||
|
|
||||||
|
String resJson = response.toJson();
|
||||||
|
|
||||||
|
res.setStatus(200);
|
||||||
|
res.getWriter().println(resJson);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package me.theclashfruit.crss.backend.v2.gateway;
|
||||||
|
|
||||||
|
import jakarta.servlet.annotation.WebServlet;
|
||||||
|
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServlet;
|
||||||
|
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServletFactory;
|
||||||
|
|
||||||
|
@WebServlet(name = "Gateway WebSocket API")
|
||||||
|
public class GatewayServlet extends JettyWebSocketServlet {
|
||||||
|
@Override
|
||||||
|
protected void configure(JettyWebSocketServletFactory factory) {
|
||||||
|
factory.register(GatewaySocket.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package me.theclashfruit.crss.backend.v2.gateway;
|
||||||
|
|
||||||
|
import me.theclashfruit.crss.enums.Channels;
|
||||||
|
import me.theclashfruit.crss.models.ErrorResponse;
|
||||||
|
import me.theclashfruit.crss.models.SocketReqMessage;
|
||||||
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
import static org.bukkit.Bukkit.getLogger;
|
||||||
|
|
||||||
|
@WebSocket
|
||||||
|
public class GatewaySocket {
|
||||||
|
private static final Set<GatewaySocket> gatewayConnections = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
|
private Session session;
|
||||||
|
|
||||||
|
private final ArrayList<Channels> subscriptions = new ArrayList<>();
|
||||||
|
|
||||||
|
@OnWebSocketOpen
|
||||||
|
public void onOpen(Session session) {
|
||||||
|
this.session = session;
|
||||||
|
|
||||||
|
gatewayConnections.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnWebSocketClose
|
||||||
|
public void onClose(int statusCode, String reason) {
|
||||||
|
gatewayConnections.remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnWebSocketMessage
|
||||||
|
public void onMessage(String message) {
|
||||||
|
try {
|
||||||
|
SocketReqMessage reqMessage = (SocketReqMessage) SocketReqMessage.fromJson(message, SocketReqMessage.class);
|
||||||
|
|
||||||
|
if (Objects.equals(reqMessage.getType(), "subscribe")) {
|
||||||
|
for (String channel : reqMessage.getChannels()) {
|
||||||
|
this.subscriptions.add(Channels.getChannel(channel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
getLogger().throwing(GatewaySocket.class.getName(), "onMessage", e);
|
||||||
|
|
||||||
|
String[] stackTrace = Arrays.stream(e.getStackTrace()).map(StackTraceElement::toString).toArray(String[]::new);
|
||||||
|
StringBuilder stackTraceString = new StringBuilder();
|
||||||
|
|
||||||
|
for (String stackTraceElement : stackTrace) {
|
||||||
|
stackTraceString
|
||||||
|
.append(stackTraceElement)
|
||||||
|
.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ErrorResponse error = new ErrorResponse(
|
||||||
|
1011,
|
||||||
|
stackTraceString.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.session.sendText(error.toJson(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void broadcastToChannel(Channels channel, String message) {
|
||||||
|
gatewayConnections.stream()
|
||||||
|
.filter(gateway -> gateway.subscriptions != null)
|
||||||
|
.filter(gateway -> {
|
||||||
|
for (Channels subscription : gateway.subscriptions) {
|
||||||
|
if (subscription.equals(channel)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.forEach(gateway -> {
|
||||||
|
gateway.session.sendText(message, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
15
src/main/java/me/theclashfruit/crss/enums/Channels.java
Normal file
15
src/main/java/me/theclashfruit/crss/enums/Channels.java
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package me.theclashfruit.crss.enums;
|
||||||
|
|
||||||
|
public enum Channels {
|
||||||
|
PLAYER_MOVE,
|
||||||
|
PLAYER_JOIN,
|
||||||
|
PLAYER_LEAVE,
|
||||||
|
PLAYER_CHAT,
|
||||||
|
PLAYER_DEATH;
|
||||||
|
|
||||||
|
// TODO: add more
|
||||||
|
|
||||||
|
public static Channels getChannel(String channel) {
|
||||||
|
return valueOf(channel.toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package me.theclashfruit.crss.listeners;
|
||||||
|
|
||||||
|
import me.theclashfruit.crss.backend.v2.gateway.GatewaySocket;
|
||||||
|
import me.theclashfruit.crss.enums.Channels;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerMoveEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
|
||||||
|
public class PlayerListener implements Listener {
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
String msg = "{" +
|
||||||
|
"\"type\":\"player_join\"," +
|
||||||
|
"\"player\":{" +
|
||||||
|
"\"name\":\"" + player.getName() + "\"," +
|
||||||
|
"\"uuid\":\"" + player.getUniqueId() + "\"" +
|
||||||
|
"}" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
GatewaySocket.broadcastToChannel(Channels.PLAYER_JOIN, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
String msg = "{" +
|
||||||
|
"\"type\":\"player_leave\"," +
|
||||||
|
"\"player\":{" +
|
||||||
|
"\"name\":\"" + player.getName() + "\"," +
|
||||||
|
"\"uuid\":\"" + player.getUniqueId() + "\"" +
|
||||||
|
"}" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
GatewaySocket.broadcastToChannel(Channels.PLAYER_LEAVE, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerMove(PlayerMoveEvent event) {
|
||||||
|
Player player = event.getPlayer();
|
||||||
|
|
||||||
|
// too lazy to make classes for these lmao
|
||||||
|
String msg = "{" +
|
||||||
|
"\"type\":\"player_move\"," +
|
||||||
|
"\"player\":{" +
|
||||||
|
"\"name\":\"" + player.getName() + "\"," +
|
||||||
|
"\"uuid\":\"" + player.getUniqueId() + "\"" +
|
||||||
|
"}," +
|
||||||
|
"\"position\":{" +
|
||||||
|
"\"distance\":" + event.getFrom().distance(event.getTo()) + "," +
|
||||||
|
"\"from\":{" +
|
||||||
|
"\"world\":\"" + event.getFrom().getWorld().getName() + "\"," +
|
||||||
|
"\"x\":" + event.getFrom().getX() + "," +
|
||||||
|
"\"y\":" + event.getFrom().getY() + "," +
|
||||||
|
"\"z\":" + event.getFrom().getZ() +
|
||||||
|
"}," +
|
||||||
|
"\"to\":{" +
|
||||||
|
"\"world\":\"" + event.getTo().getWorld().getName() + "\"," +
|
||||||
|
"\"x\":" + event.getTo().getX() + "," +
|
||||||
|
"\"y\":" + event.getTo().getY() + "," +
|
||||||
|
"\"z\":" + event.getTo().getZ() +
|
||||||
|
"}" +
|
||||||
|
"}" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
if (event.getFrom().distance(event.getTo()) != 0)
|
||||||
|
GatewaySocket.broadcastToChannel(Channels.PLAYER_MOVE, msg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package me.theclashfruit.crss.models;
|
||||||
|
|
||||||
|
public class ErrorResponse extends Model {
|
||||||
|
private Integer error;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
public ErrorResponse(Integer error, String message) {
|
||||||
|
this.error = error;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setError(Integer error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,21 +2,21 @@ package me.theclashfruit.crss.models;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class IndexResponse {
|
public class IndexResponse extends Model {
|
||||||
private String latest_version;
|
private String latestVersion;
|
||||||
public ArrayList<Version> versions;
|
public ArrayList<Version> versions;
|
||||||
|
|
||||||
public IndexResponse(String latest_version, ArrayList<Version> versions) {
|
public IndexResponse(String latestVersion, ArrayList<Version> versions) {
|
||||||
this.latest_version = latest_version;
|
this.latestVersion = latestVersion;
|
||||||
this.versions = versions;
|
this.versions = versions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLatestVersion() {
|
public String getLatestVersion() {
|
||||||
return latest_version;
|
return latestVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLatestVersion(String latest_version) {
|
public void setLatestVersion(String latestVersion) {
|
||||||
this.latest_version = latest_version;
|
this.latestVersion = latestVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<Version> getVersions() {
|
public ArrayList<Version> getVersions() {
|
||||||
|
|
13
src/main/java/me/theclashfruit/crss/models/Model.java
Normal file
13
src/main/java/me/theclashfruit/crss/models/Model.java
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package me.theclashfruit.crss.models;
|
||||||
|
|
||||||
|
import me.theclashfruit.crss.utils.JsonUtil;
|
||||||
|
|
||||||
|
public class Model {
|
||||||
|
public String toJson() {
|
||||||
|
return JsonUtil.gson.toJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object fromJson(String json, Class<? extends Model> clazz) {
|
||||||
|
return JsonUtil.gson.fromJson(json, clazz);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package me.theclashfruit.crss.models;
|
||||||
|
|
||||||
|
import me.theclashfruit.crss.enums.Channels;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class SocketReqMessage extends Model {
|
||||||
|
private String type;
|
||||||
|
private String[] channels;
|
||||||
|
|
||||||
|
public SocketReqMessage(String type, String[] channels) {
|
||||||
|
this.type = type;
|
||||||
|
this.channels = channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getChannels() {
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChannels(String[] channels) {
|
||||||
|
this.channels = channels;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package me.theclashfruit.crss.models;
|
||||||
|
|
||||||
|
public class StatusResponse extends Model {
|
||||||
|
private String version;
|
||||||
|
private Integer online;
|
||||||
|
private String[] worlds;
|
||||||
|
|
||||||
|
public StatusResponse(String version, Integer online, String[] worlds) {
|
||||||
|
this.version = version;
|
||||||
|
this.online = online;
|
||||||
|
this.worlds = worlds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getOnline() {
|
||||||
|
return online;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getWorlds() {
|
||||||
|
return worlds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(String version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnline(Integer online) {
|
||||||
|
this.online = online;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWorlds(String[] worlds) {
|
||||||
|
this.worlds = worlds;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package me.theclashfruit.crss.models;
|
package me.theclashfruit.crss.models;
|
||||||
|
|
||||||
public class Version {
|
public class Version extends Model {
|
||||||
private String name;
|
private String name;
|
||||||
private String path;
|
private String path;
|
||||||
private Boolean deprecated;
|
private Boolean deprecated;
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
package me.theclashfruit.crss.utils;
|
package me.theclashfruit.crss.utils;
|
||||||
|
|
||||||
|
import com.google.gson.FieldNamingPolicy;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
public class JsonUtil {
|
public class JsonUtil {
|
||||||
public static Gson gson = new Gson();
|
public static Gson gson = new GsonBuilder()
|
||||||
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
|
.create();
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue