Browse Source

first init

master
gsd 2 years ago
commit
9b9ee374f0
  1. 2
      .gitignore
  2. 85
      pom.xml
  3. 11
      src/main/java/app/MainApi.java
  4. 17
      src/main/java/app/configs/JobRunrConfig.java
  5. 41
      src/main/java/app/configs/ProtocolA2S.java
  6. 25
      src/main/java/app/controllers/StatsController.java
  7. 27
      src/main/java/app/entities/other/SteamID.java
  8. 96
      src/main/java/app/entities/server/Server.java
  9. 9
      src/main/java/app/entities/server/players/DefaultPlayer.java
  10. 34
      src/main/java/app/entities/server/players/RCONPlayer.java
  11. 15
      src/main/java/app/entities/server/players/SourcePlayer.java
  12. 27
      src/main/java/app/services/GeoIP.java
  13. 38
      src/main/java/app/services/ServersReader.java
  14. 31
      src/main/java/app/services/Stats.java
  15. 43
      src/main/java/app/updates/BanCountUpdater.java
  16. 68
      src/main/java/app/updates/CountriesUpdater.java
  17. 91
      src/main/java/app/updates/PlayersUpdater.java
  18. 107
      src/main/java/app/updates/UniqueUpdater.java
  19. 85
      src/main/java/app/utils/SteamIDConverter.java
  20. 35
      src/main/resources/application.yaml

2
.gitignore

@ -0,0 +1,2 @@
/.idea/
/target/

85
pom.xml

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>MainApi</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>18</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.14.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.32</version>
</dependency>
<dependency>
<groupId>org.jobrunr</groupId>
<artifactId>jobrunr</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>org.jobrunr</groupId>
<artifactId>jobrunr-spring-boot-starter</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.ibasco.agql</groupId>
<artifactId>agql-source-query</artifactId>
<version>1.0.7</version>
</dependency>
<dependency>
<groupId>com.ibasco.agql</groupId>
<artifactId>agql-source-rcon</artifactId>
<version>1.0.7</version>
</dependency>
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>3.0.2</version>
</dependency>
</dependencies>
</project>

11
src/main/java/app/MainApi.java

@ -0,0 +1,11 @@
package app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MainApi {
public static void main(String[] args) {
SpringApplication.run(MainApi.class);
}
}

17
src/main/java/app/configs/JobRunrConfig.java

@ -0,0 +1,17 @@
package app.configs;
import org.jobrunr.jobs.mappers.JobMapper;
import org.jobrunr.storage.InMemoryStorageProvider;
import org.jobrunr.storage.StorageProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JobRunrConfig {
@Bean
public StorageProvider storageProvider(JobMapper jobMapper) {
InMemoryStorageProvider storageProvider = new InMemoryStorageProvider();
storageProvider.setJobMapper(jobMapper);
return storageProvider;
}
}

41
src/main/java/app/configs/ProtocolA2S.java

@ -0,0 +1,41 @@
package app.configs;
import com.ibasco.agql.core.enums.RateLimitType;
import com.ibasco.agql.core.util.FailsafeOptions;
import com.ibasco.agql.core.util.GeneralOptions;
import com.ibasco.agql.protocols.valve.source.query.SourceQueryClient;
import com.ibasco.agql.protocols.valve.source.query.SourceQueryOptions;
import com.ibasco.agql.protocols.valve.source.query.rcon.SourceRconClient;
import com.ibasco.agql.protocols.valve.source.query.rcon.SourceRconOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Configuration
public class ProtocolA2S {
//https://ribasco.github.io/async-gamequery-lib/examples/source_query_example.html#blocking-example
@Scope("prototype")
@Bean
public SourceQueryClient GetSourceQueryClient() {
ExecutorService customExecutor = Executors.newCachedThreadPool();
SourceQueryOptions options = SourceQueryOptions.builder()
.option(FailsafeOptions.FAILSAFE_RATELIMIT_TYPE, RateLimitType.BURST)
.option(GeneralOptions.THREAD_EXECUTOR_SERVICE, customExecutor)
.build();
return new SourceQueryClient(options);
}
@Scope("prototype")
@Bean
public SourceRconClient GetSourceRconClient() {
ExecutorService customExecutor = Executors.newCachedThreadPool();
SourceRconOptions options = SourceRconOptions.builder()
//.option(FailsafeOptions.FAILSAFE_RATELIMIT_TYPE, RateLimitType.BURST)
.option(GeneralOptions.THREAD_EXECUTOR_SERVICE, customExecutor)
.build();
return new SourceRconClient(options);
}
}

25
src/main/java/app/controllers/StatsController.java

@ -0,0 +1,25 @@
package app.controllers;
import app.services.Stats;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("api/stats")
public class StatsController {
private Stats stats;
@Autowired
public StatsController(Stats stats){
this.stats = stats;
}
@GetMapping
public ResponseEntity GetStats(){
return new ResponseEntity<>(stats, HttpStatus.OK);
}
}

27
src/main/java/app/entities/other/SteamID.java

@ -0,0 +1,27 @@
package app.entities.other;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@JsonSerialize
public
class SteamID {
public String steam3;
public String steam2;
public long steam64;
public String steam_url;
public long account_id;
public SteamID(String steam3, String steam2, String steam64, long account_id) {
this.steam3 = steam3;
this.steam2 = steam2;
this.steam64 = Long.parseLong(steam64);
this.steam_url = String.format("https://steamcommunity.com/profiles/%s", steam64);
this.account_id = account_id;
}
@Override
public String toString(){
return steam_url;
}
}

96
src/main/java/app/entities/server/Server.java

@ -0,0 +1,96 @@
package app.entities.server;
import app.entities.server.players.DefaultPlayer;
import app.entities.server.players.RCONPlayer;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.ibasco.agql.protocols.valve.source.query.info.SourceQueryInfoResponse;
import com.ibasco.agql.protocols.valve.source.query.players.SourcePlayer;
import com.ibasco.agql.protocols.valve.source.query.players.SourceQueryPlayerResponse;
import jakarta.persistence.criteria.CriteriaBuilder;
import lombok.Data;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@Data
public class Server {
String name;
String description;
String address;
String preview;
int player_count;
int max_players;
boolean status;
String color;
String workshop;
List<String> naming;
HashMap<String, Long> uniq = new HashMap<>();
List<DefaultPlayer> players = new ArrayList<>();
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String db;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String dc;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String rcon_password;
public void UpdateUniq(String key, Long count) {
uniq.merge(key, count, (x,y) -> y);
}
public InetSocketAddress getInetAddress() {
String[] splitted_address = address.split(":", 2);
return new InetSocketAddress(splitted_address[0], Integer.parseInt(splitted_address[1]));
}
public void UpdateStatusFromA2S(SourceQueryInfoResponse response) {
SetDownStatus();
if (response == null) return;
setMax_players(response.getResult().getMaxPlayers());
setPlayer_count(response.getResult().getNumOfPlayers());
setStatus(true);
}
public void UpdatePlayersFromA2S(SourceQueryPlayerResponse response) {
players.clear();
if (response != null) {
for (SourcePlayer player: response.getResult()) {
players.add(new app.entities.server.players.SourcePlayer(player));
}
}
}
public void UpdatePlayersFromRCON(String response) {
List<String> players_list = Arrays.stream(response.substring(response.indexOf("# userid"), response.length()).split("\n")).toList();
boolean skip_table_header = true;
for(String player_text: players_list) {
if (skip_table_header || player_text.length() < 1) {
skip_table_header = false;
continue;
}
/////////////////////////////////////////////////////
List<String> player_line = Arrays.stream(player_text.split("\\s+")).toList();
RCONPlayer player = new RCONPlayer(player_line);
for (DefaultPlayer sourcePlayer: players) {
if (sourcePlayer.getName().equals(player.getName())) {
player.setScore(sourcePlayer.getScore());
players.remove(sourcePlayer);
players.add(player);
break;
}
}
}
}
public void SetDownStatus() {
setStatus(false);
setMax_players(0);
setPlayer_count(0);
players.clear();
}
}

9
src/main/java/app/entities/server/players/DefaultPlayer.java

@ -0,0 +1,9 @@
package app.entities.server.players;
import lombok.Data;
@Data
public class DefaultPlayer {
String name;
int score;
}

34
src/main/java/app/entities/server/players/RCONPlayer.java

@ -0,0 +1,34 @@
package app.entities.server.players;
import app.entities.other.SteamID;
import app.entities.server.players.DefaultPlayer;
import app.utils.SteamIDConverter;
import lombok.Data;
import java.util.List;
@Data
public class RCONPlayer extends DefaultPlayer {
String duration;
int id;
String ip;
int loss;
int ping;
String state;
String steam2;
SteamID steam;
public RCONPlayer(List<String> status_line) {
id = Integer.parseInt(status_line.get(1));
ip = status_line.get(status_line.size() - 1);
state = status_line.get(status_line.size() - 2);
loss = Integer.parseInt(status_line.get(status_line.size() - 3));
ping = Integer.parseInt(status_line.get(status_line.size() - 4));
duration = status_line.get(status_line.size() - 5);
steam2 = status_line.get(status_line.size() - 6);
name = String.join(" ", status_line.subList(2, status_line.size() - 6));
name = name.substring(1, name.length()-1);
////////////////////////////////////////////////////////////////////////////////
steam = SteamIDConverter.getSteamID(steam2);
}
}

15
src/main/java/app/entities/server/players/SourcePlayer.java

@ -0,0 +1,15 @@
package app.entities.server.players;
import app.entities.server.players.DefaultPlayer;
import lombok.Data;
@Data
public class SourcePlayer extends DefaultPlayer {
float duration;
public SourcePlayer(com.ibasco.agql.protocols.valve.source.query.players.SourcePlayer player) {
name = player.getName();
duration = player.getDuration();
score = player.getScore();
}
}

27
src/main/java/app/services/GeoIP.java

@ -0,0 +1,27 @@
package app.services;
import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Component
public class GeoIP {
private DatabaseReader databaseReader;
@Autowired
public GeoIP(@Value("${backend.geoip_file}") String data) throws IOException {
databaseReader = new DatabaseReader.Builder(new File(data)).build();
}
public String GetCountry(String ip) throws UnknownHostException, GeoIp2Exception, IOException {
return databaseReader.country(InetAddress.getByName(ip)).getCountry().getName();
}
}

38
src/main/java/app/services/ServersReader.java

@ -0,0 +1,38 @@
package app.services;
import app.entities.server.Server;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
@Component
public class ServersReader {
Stats stats;
ObjectMapper objectMapper;
@Autowired
public ServersReader(Stats stats, @Value("${backend.servers_file}") String servers_path) {
this.stats = stats;
this.objectMapper = new ObjectMapper();
try {
System.out.printf("Read from: %s\n", servers_path);
JsonNode node = this.objectMapper.readTree(new File(servers_path));
Iterator<Map.Entry<String, JsonNode>> iterator = node.fields();
while (iterator.hasNext()) {
Map.Entry<String, JsonNode> server = iterator.next();
stats.servers.put(server.getKey(), this.objectMapper.treeToValue(server.getValue(), Server.class));
}
} catch (IOException err) {
System.out.printf("Cannot read servers file: %s\n", servers_path);
}
}
}

31
src/main/java/app/services/Stats.java

@ -0,0 +1,31 @@
package app.services;
import app.entities.server.Server;
import lombok.Data;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@Data
@Component
@Scope(value = "singleton")
public class Stats {
long ban_count = 0;
int discord_users = 0;
int freevip_players = 0;
int server_uptime = 0;
int vip_players = 0;
int vk_users = 0;
HashMap<String, Integer> countries = new HashMap<>();
HashMap<String, Server> servers = new HashMap<>();
HashMap<String, Long> uniq = new HashMap<>();
HashMap<String, Long> updates = new HashMap<>();
public void UpdateUniq(String key, Long value) {
uniq.merge(key, value, (x,y) -> y);
}
}

43
src/main/java/app/updates/BanCountUpdater.java

@ -0,0 +1,43 @@
package app.updates;
import app.services.Stats;
import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.jobrunr.scheduling.JobScheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.time.Instant;
@Component
public class BanCountUpdater {
private Stats stats;
private JobScheduler jobScheduler;
@Value("${backend.updates.ban_count}")
boolean update = false;
@PersistenceContext
EntityManager entityManager;
@Autowired
public BanCountUpdater(Stats stats,
JobScheduler jobScheduler) {
this.stats = stats;
this.jobScheduler = jobScheduler;
}
@PostConstruct
public void SetUpdater(){
if(update) {
jobScheduler.enqueue(() -> UpdateBanCount());
jobScheduler.scheduleRecurrently("*/5 * * * *", () -> UpdateBanCount());
}
}
public void UpdateBanCount(){
stats.setBan_count((Long) entityManager.createNativeQuery("SELECT COUNT(*) as count FROM `light_bans`").getSingleResult());
stats.getUpdates().merge("ban_count", Instant.now().getEpochSecond(), (x, y) -> y);
}
}

68
src/main/java/app/updates/CountriesUpdater.java

@ -0,0 +1,68 @@
package app.updates;
import app.entities.server.Server;
import app.services.GeoIP;
import app.services.Stats;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.jobrunr.jobs.annotations.Job;
import org.jobrunr.scheduling.JobScheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Map;
@Component
public class CountriesUpdater {
Stats stats;
GeoIP geoIP;
JobScheduler jobScheduler;
@PersistenceContext
EntityManager entityManager;
@Value("${backend.updates.countries}")
private boolean update;
private final String countries_in_current_year = "SELECT DISTINCT `connect_ip` FROM `%s`.`user_connections` WHERE connection_type LIKE 'connect' AND `timestamp` > CAST(DATE_FORMAT(NOW() ,'%%Y-01-01') as DATE) UNION\n";
@Autowired
public CountriesUpdater(Stats stats,
GeoIP geoIP,
JobScheduler jobScheduler) {
this.stats = stats;
this.geoIP = geoIP;
this.jobScheduler = jobScheduler;
}
@PostConstruct
public void UpdateCountries(){
if (update) {
jobScheduler.enqueue(() -> UpdateCountriesStatistic());
jobScheduler.scheduleRecurrently("backend.stats.countries.update", "*/15 * * * *", () -> UpdateCountriesStatistic());
}
}
@Job(name = "Update countries statistic")
public void UpdateCountriesStatistic() {
stats.getCountries().clear();
String query = "";
for (Map.Entry<String, Server> stringServerEntry : stats.getServers().entrySet()) {
query += countries_in_current_year.formatted(stringServerEntry.getValue().getDb());
}
query = query.substring(0, query.length()-7) + ";";
for(Object ip:entityManager.createNativeQuery(query).getResultList()) {
try {
stats.getCountries().merge(geoIP.GetCountry(String.valueOf(ip)), 1, (x, y) -> x+y);
} catch (UnknownHostException e) {
} catch (IOException e) {
} catch (GeoIp2Exception e) {
}
}
}
}

91
src/main/java/app/updates/PlayersUpdater.java

@ -0,0 +1,91 @@
package app.updates;
import app.services.Stats;
import com.ibasco.agql.protocols.valve.source.query.SourceQueryClient;
import com.ibasco.agql.protocols.valve.source.query.info.SourceQueryInfoResponse;
import com.ibasco.agql.protocols.valve.source.query.players.SourceQueryPlayerResponse;
import com.ibasco.agql.protocols.valve.source.query.rcon.SourceRconClient;
import com.ibasco.agql.protocols.valve.source.query.rcon.exceptions.RconException;
import com.ibasco.agql.protocols.valve.source.query.rcon.message.SourceRconAuthResponse;
import com.ibasco.agql.protocols.valve.source.query.rcon.message.SourceRconCmdResponse;
import jakarta.annotation.PostConstruct;
import org.jobrunr.jobs.annotations.Job;
import org.jobrunr.scheduling.JobScheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.CompletionException;
@Component
public class PlayersUpdater {
Stats stats;
ApplicationContext context;
JobScheduler jobScheduler;
@Value("${backend.updates.a2s}")
private boolean update = false;
@Autowired
public PlayersUpdater(Stats stats,
ApplicationContext context,
JobScheduler jobScheduler) {
this.stats = stats;
this.context = context;
this.jobScheduler = jobScheduler;
}
@PostConstruct
public void updateValues() {
if (update) {
stats.getServers().forEach((server_name, server) -> {
jobScheduler.enqueue(() -> UpdatePlayersOnServer(server_name));
jobScheduler.scheduleRecurrently("backend.stats.info.update." + server_name, "* * * * *", () -> UpdatePlayersOnServer(server_name));
});
}
}
@Job(name = "Update A2S data on: %0")
public void UpdatePlayersOnServer(String server_name) {
SourceQueryClient sourceQueryClient = context.getBean(SourceQueryClient.class);
try {
SourceQueryInfoResponse info = sourceQueryClient.getInfo(stats.getServers().get(server_name).getInetAddress()).join();
stats.getServers().get(server_name).UpdateStatusFromA2S(info);
sourceQueryClient.close();
} catch (IOException err) {
stats.getServers().get(server_name).SetDownStatus();
return;
}
if (!stats.getServers().get(server_name).isStatus() || stats.getServers().get(server_name).getPlayer_count() < 1) {
return;
}
////////////////////////////////////////////////////////////////////////
//If player count > 0 make base player request
////////////////////////////////////////////////////////////////////////
try {
sourceQueryClient = context.getBean(SourceQueryClient.class);
SourceQueryPlayerResponse players = sourceQueryClient.getPlayers(stats.getServers().get(server_name).getInetAddress()).join();
stats.getServers().get(server_name).UpdatePlayersFromA2S(players);
sourceQueryClient.close();
} catch (IOException err) {
return;
}
///////////////////////////////////////////////////////////////////////
//Extend current players of rcon result
//////////////////////////////////////////////////////////////////////
try {
SourceRconClient rcon_client = context.getBean(SourceRconClient.class);
SourceRconAuthResponse response = rcon_client.authenticate(stats.getServers().get(server_name).getInetAddress(), stats.getServers().get(server_name).getRcon_password().getBytes()).join();
if(!response.isAuthenticated()) {
return;
}
SourceRconCmdResponse rcon_response = rcon_client.execute(stats.getServers().get(server_name).getInetAddress(), "status").join();
rcon_client.cleanup();
stats.getServers().get(server_name).UpdatePlayersFromRCON(rcon_response.getResult());
} catch (RconException | CompletionException err) {
return;
}
}
}

107
src/main/java/app/updates/UniqueUpdater.java

@ -0,0 +1,107 @@
package app.updates;
import app.entities.server.Server;
import app.services.Stats;
import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.jobrunr.jobs.annotations.Job;
import org.jobrunr.scheduling.JobScheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import static java.time.Instant.now;
@Component
public class UniqueUpdater {
Stats stats;
JobScheduler jobScheduler;
@PersistenceContext
EntityManager entityManager;
private final String query_total = "SELECT DISTINCT `account_id` FROM `%s`.`user_connections` WHERE `connection_type` LIKE \"disconnect\" AND `connect_duration` > 300 UNION\n";
private final String query_day = "SELECT DISTINCT `account_id` FROM `%s`.`user_connections` WHERE `connection_type` LIKE \"disconnect\" AND `connect_duration` > 300 AND `timestamp` > CAST(DATE_FORMAT(NOW() ,'%%Y-%%m-%%d') as DATE) UNION\n";
private final String query_month = "SELECT DISTINCT `account_id` FROM `%s`.`user_connections` WHERE `connection_type` LIKE \"disconnect\" AND `connect_duration` > 300 AND `timestamp` > CAST(DATE_FORMAT(NOW() ,'%%Y-%%m-01') as DATE) UNION\n";
private final String query_year = "SELECT DISTINCT `account_id` FROM `%s`.`user_connections` WHERE `connection_type` LIKE \"disconnect\" AND `connect_duration` > 300 AND `timestamp` > CAST(DATE_FORMAT(NOW() ,'%%Y-01-01') as DATE) UNION\n";
@Value("${backend.updates.unique_global}")
boolean global_update = false;
@Value("${backend.updates.unique_server}")
boolean server_update = false;
@Autowired
public UniqueUpdater(Stats stats,
JobScheduler jobScheduler) {
this.stats = stats;
this.jobScheduler = jobScheduler;
}
@PostConstruct
public void updateValues() throws InterruptedException {
if(global_update) {
Thread.sleep(2000);
jobScheduler.enqueue(() -> UpdateServerUniqueTotal());
jobScheduler.enqueue(() -> UpdateServerUniqueYear());
jobScheduler.enqueue(() -> UpdateServerUniqueMonth());
jobScheduler.enqueue(() -> UpdateServerUniqueDay());
///////////////////////////////////////////////////////////////////////////////////////
jobScheduler.scheduleRecurrently("0 0 */1 * *", () -> UpdateServerUniqueTotal());
jobScheduler.scheduleRecurrently("0 0 */1 * *", () -> UpdateServerUniqueYear());
jobScheduler.scheduleRecurrently("0 0 */1 * *", () -> UpdateServerUniqueMonth());
jobScheduler.scheduleRecurrently("0 */1 * * *", () -> UpdateServerUniqueDay());
}
///////////////////////////////////////////////////////////////////////////////////////
if(server_update) {
stats.getServers().forEach((server_name, server) -> {
jobScheduler.enqueue(() -> getServerUnique(server_name, server.getDb()));
jobScheduler.scheduleRecurrently("backend.stats.unique.update." + server_name, "*/5 * * * *", () -> getServerUnique(server_name, server.getDb()));
});
}
}
///////////////////////////////////////////////////////////////////////////////////////////
public Long getServerUniqueFromQuery(String query, String db) {
query = String.format(query, db);
query = "SELECT COUNT(*) as count FROM (" + query.substring(0, query.length()-7) + ") x;";
return (Long) entityManager.createNativeQuery(query).getSingleResult();
}
public Long getServerUniqueFromQuery(String query) {
String final_query = "SELECT COUNT(*) as count FROM (";
for(Server server: stats.getServers().values()){
final_query += query.formatted(server.getDb());
}
final_query = final_query.substring(0, final_query.length()-7) + ") x;";
return (Long) entityManager.createNativeQuery(final_query).getSingleResult();
}
///////////////////////////////////////////////////////////////////////////////////////////
@Job(name = "Get server unique statistic %0")
public void getServerUnique(String server_name, String db) {
stats.getServers().get(server_name).UpdateUniq("total", getServerUniqueFromQuery(query_total, db));
stats.getServers().get(server_name).UpdateUniq("day", getServerUniqueFromQuery(query_day, db));
stats.getServers().get(server_name).UpdateUniq("month", getServerUniqueFromQuery(query_month, db));
stats.getServers().get(server_name).UpdateUniq("year", getServerUniqueFromQuery(query_year, db));
}
@Job(name = "Get total count unique players on all server")
public void UpdateServerUniqueTotal() {
stats.UpdateUniq("total", getServerUniqueFromQuery(query_total));
}
@Job(name = "Get year count unique players on all server")
public void UpdateServerUniqueYear() {
stats.UpdateUniq("year", getServerUniqueFromQuery(query_year));
}
@Job(name = "Get month count unique players on all server")
public void UpdateServerUniqueMonth() {
stats.UpdateUniq("month", getServerUniqueFromQuery(query_month));
}
@Job(name = "Get day count unique players on all server")
public void UpdateServerUniqueDay() {
stats.UpdateUniq("day", getServerUniqueFromQuery(query_day));
}
}

85
src/main/java/app/utils/SteamIDConverter.java

@ -0,0 +1,85 @@
package app.utils;
import app.entities.other.SteamID;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.math.BigInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//https://github.com/SteamRun/SteamIDConverter/blob/master/SteamIDConverter.html
public class SteamIDConverter {
private static final String SID64_1 = "7656";
private static final long SID64_S = Long.valueOf("1197960265728");
private static final Pattern PatternSteam3ID = Pattern.compile("^\\[([Ug]):([0-9]):([0-9]+)\\]$");
private static final Pattern PatternSteamID32 = Pattern.compile("^STEAM_([0-9]):([0-9]):([0-9]+)$");
private static final Pattern PatternSteamID64 = Pattern.compile("7656([0-9]{12,14})");
private final Pattern PatternCustomUrl = Pattern.compile("steamcommunity\\.com\\/id\\/([A-Za-z0-9-_]{2,32})");
private final Pattern PatternMaybeCustomUrl = Pattern.compile("^([A-Za-z0-9-_]{2,32})$");
public static SteamID getSteamID(String text){
boolean isSteamID = false;
String steam3;
String steam2;
String steam64;
String S3ID_1;
long S3ID_2 = 0;
long S3ID_3 = 0;
long SID32_1 = 0;
long SID32_2 = 0;
long SID32_3 = 0;
long SID64_2 = 0;
Matcher result;
if((result = PatternSteam3ID.matcher(text)).find()){
//Matcher result = PatternSteam3ID.matcher(text);
S3ID_1 = result.group(1);
S3ID_2 = Long.parseLong(result.group(2));
S3ID_3 = Long.parseLong(result.group(3));
if(Math.abs(S3ID_3 % 2) == 1){
SID32_2 = 1;
} else {
SID32_2 = 0;
}
SID32_3 = (S3ID_3 - SID32_2) / 2;
SID64_2 = S3ID_3 + SID64_S;
isSteamID = true;
} else if ((result = PatternSteamID32.matcher(text)).find()){
//Matcher result = PatternSteamID32.matcher(text);
SID32_1 = Long.parseLong(result.group(1));
SID32_2 = Long.parseLong(result.group(2));
SID32_3 = Long.parseLong(result.group(3));
S3ID_3 = SID32_3 * 2 + SID32_2;
SID64_2 = S3ID_3 + SID64_S;
isSteamID = true;
} else if ((result = PatternSteamID64.matcher(text)).find()) {
//Matcher result = PatternSteamID64.matcher(text);
SID64_2 = Long.parseLong(result.group(1));
S3ID_3 = SID64_2 - SID64_S;
if(Math.abs(S3ID_3 % 2) == 1){
SID32_2 = 1;
} else {
SID32_2 = 0;
}
SID32_3 = (S3ID_3 - SID32_2) / 2;
isSteamID = true;
}
if(!isSteamID) return null;
steam3 = String.format("[U:1:%s]",S3ID_3);
steam2 = String.format("STEAM_0:%s:%s", SID32_2, SID32_3);
steam64 = String.format("%s%s", SID64_1, SID64_2);
return new SteamID(steam3, steam2, steam64, S3ID_3);
}
}

35
src/main/resources/application.yaml

@ -0,0 +1,35 @@
server:
port: 8080
spring:
application:
name: facti13_web_backend
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: none
show-sql: true
org:
jobrunr:
background-job-server:
enabled: true
dashboard:
enabled: true
backend:
servers_file: ${SERVERS_FILE}
geoip_file: ${GEOIP_FILE}
updates:
unique_global: false
unique_server: false
ban_count: false
a2s: false
countries: true
logging:
level:
com.ibasco.agql.core.util.*: OFF
Loading…
Cancel
Save