diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d25e44b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,42 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+mqtt.json
+/cache
+/logs
+/.idea
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
diff --git a/config/Damage.json b/config/Damage.json
new file mode 100644
index 0000000..863c44e
--- /dev/null
+++ b/config/Damage.json
@@ -0,0 +1,134 @@
+{
+ "AirCarrier": [
+ {
+ "code": 1,
+ "value": 60000,
+ "color": "#FE7903"
+ },
+ {
+ "code": 2,
+ "value": 71000,
+ "color": "#FE7903"
+ },
+ {
+ "code": 3,
+ "value": 84000,
+ "color": "#02C9B3"
+ },
+ {
+ "code": 4,
+ "value": 113000,
+ "color": "#A00DC5"
+ }
+ ],
+ "Battleship": [
+ {
+ "code": 1,
+ "value": 64000,
+ "color": "#FE7903"
+ },
+ {
+ "code": 2,
+ "value": 72000,
+ "color": "#44B300"
+ },
+ {
+ "code": 3,
+ "value": 97000,
+ "color": "#02C9B3"
+ },
+ {
+ "code": 4,
+ "value": 108000,
+ "color": "#A00DC5"
+ }
+ ],
+ "Destroyer": [
+ {
+ "code": 1,
+ "value": 33000,
+ "color": "#FE7903"
+ },
+ {
+ "code": 2,
+ "value": 40000,
+ "color": "#44B300"
+ },
+ {
+ "code": 3,
+ "value": 55000,
+ "color": "#02C9B3"
+ },
+ {
+ "code": 4,
+ "value": 64000,
+ "color": "#A00DC5"
+ }
+ ],
+ "Cruiser": [
+ {
+ "code": 1,
+ "value": 47000,
+ "color": "#FE7903"
+ },
+ {
+ "code": 2,
+ "value": 55000,
+ "color": "#44B300"
+ },
+ {
+ "code": 3,
+ "value": 83000,
+ "color": "#02C9B3"
+ },
+ {
+ "code": 4,
+ "value": 95000,
+ "color": "#A00DC5"
+ }
+ ],
+ "Submarine": [
+ {
+ "code": 1,
+ "value": 70000,
+ "color": "#FE7903"
+ },
+ {
+ "code": 2,
+ "value": 70000,
+ "color": "#44B300"
+ },
+ {
+ "code": 3,
+ "value": 70000,
+ "color": "#02C9B3"
+ },
+ {
+ "code": 4,
+ "value": 70000,
+ "color": "#A00DC5"
+ }
+ ],
+ "Auxiliary": [
+ {
+ "code": 1,
+ "value": 60000,
+ "color": "#FE7903"
+ },
+ {
+ "code": 2,
+ "value": 71000,
+ "color": "#FE7903"
+ },
+ {
+ "code": 3,
+ "value": 84000,
+ "color": "#02C9B3"
+ },
+ {
+ "code": 4,
+ "value": 113000,
+ "color": "#A00DC5"
+ }
+ ]
+}
diff --git a/config/Nation.json b/config/Nation.json
new file mode 100644
index 0000000..cb3dec4
--- /dev/null
+++ b/config/Nation.json
@@ -0,0 +1,54 @@
+[
+ {
+ "nation": "Commonwealth",
+ "cn": "英联邦"
+ },
+ {
+ "nation": "Europe",
+ "cn": "欧洲"
+ },
+ {
+ "nation": "France",
+ "cn": "法国"
+ },
+ {
+ "nation": "Germany",
+ "cn": "德国"
+ },
+ {
+ "nation": "Italy",
+ "cn": "意大利"
+ },
+ {
+ "nation": "Japan",
+ "cn": "日本"
+ },
+ {
+ "nation": "Pan_America",
+ "cn": "泛美"
+ },
+ {
+ "nation": "Pan_Asia",
+ "cn": "泛亚"
+ },
+ {
+ "nation": "United_Kingdom",
+ "cn": "英国"
+ },
+ {
+ "nation": "USA",
+ "cn": "美国"
+ },
+ {
+ "nation": "Russia",
+ "cn": "苏联"
+ },
+ {
+ "nation": "Netherlands",
+ "cn": "荷兰"
+ },
+ {
+ "nation": "Spain",
+ "cn": "西班牙"
+ }
+]
diff --git a/config/Pr.json b/config/Pr.json
new file mode 100644
index 0000000..d3690e6
--- /dev/null
+++ b/config/Pr.json
@@ -0,0 +1,65 @@
+[
+ {
+ "code": 0,
+ "value": 0,
+ "name": "暂无数据",
+ "englishName": "No Rating",
+ "color": "#828282"
+ },
+ {
+ "code": 1,
+ "value": 750,
+ "name": "还需努力",
+ "englishName": "Bad",
+ "color": "#FE0E00"
+ },
+ {
+ "code": 2,
+ "value": 1100,
+ "name": "低于平均",
+ "englishName": "Below Average",
+ "color": "#FE7903"
+ },
+ {
+ "code": 3,
+ "value": 1350,
+ "name": "平均水平",
+ "englishName": "Average",
+ "color": "#FFC71F"
+ },
+ {
+ "code": 4,
+ "value": 1550,
+ "name": "好",
+ "englishName": "Good",
+ "color": "#44B300"
+ },
+ {
+ "code": 5,
+ "value": 1750,
+ "name": "很好",
+ "englishName": "Very Good",
+ "color": "#318000"
+ },
+ {
+ "code": 6,
+ "value": 2100,
+ "name": "非常好",
+ "englishName": "Great",
+ "color": "#02C9B3"
+ },
+ {
+ "code": 7,
+ "value": 2450,
+ "name": "大佬水平",
+ "englishName": "Unicum",
+ "color": "#D042F3"
+ },
+ {
+ "code": 8,
+ "value": 2450,
+ "name": "神佬水平",
+ "englishName": "Super Unicum",
+ "color": "#A00DC5"
+ }
+]
diff --git a/config/ShipType.json b/config/ShipType.json
new file mode 100644
index 0000000..067e96f
--- /dev/null
+++ b/config/ShipType.json
@@ -0,0 +1,26 @@
+[
+ {
+ "shipType": "AirCarrier",
+ "typeName": "航空母舰"
+ },
+ {
+ "shipType": "Battleship",
+ "typeName": "战列舰"
+ },
+ {
+ "shipType": "Destroyer",
+ "typeName": "驱逐舰"
+ },
+ {
+ "shipType": "Cruiser",
+ "typeName": "巡洋舰"
+ },
+ {
+ "shipType": "Submarine",
+ "typeName": "潜艇"
+ },
+ {
+ "shipType": "Auxiliary",
+ "typeName": "辅助航母"
+ }
+]
diff --git a/config/Wins.json b/config/Wins.json
new file mode 100644
index 0000000..67e22d4
--- /dev/null
+++ b/config/Wins.json
@@ -0,0 +1,32 @@
+[
+ {
+ "code": 1,
+ "value": 40,
+ "color": "#f44336"
+ },
+ {
+ "code": 2,
+ "value": 45,
+ "color": "#ff9800"
+ },
+ {
+ "code": 3,
+ "value": 50,
+ "color": "#8bc34a"
+ },
+ {
+ "code": 4,
+ "value": 55,
+ "color": "#00bcd4"
+ },
+ {
+ "code": 5,
+ "value": 60,
+ "color": "#9c27b0"
+ },
+ {
+ "code": 6,
+ "value": 65,
+ "color": "#673ab7"
+ }
+]
diff --git a/config/config.json b/config/config.json
new file mode 100644
index 0000000..c90f155
--- /dev/null
+++ b/config/config.json
@@ -0,0 +1,3 @@
+{
+ "wsPort": 6677
+}
diff --git a/config/proxy.json b/config/proxy.json
new file mode 100644
index 0000000..70fcaa4
--- /dev/null
+++ b/config/proxy.json
@@ -0,0 +1,31 @@
+{
+ "host": "127.0.0.1",
+ "port": 7890,
+ "server": [
+ {
+ "enableProxy": false,
+ "server": "asia",
+ "type": "api"
+ },
+ {
+ "enableProxy": false,
+ "server": "eu",
+ "type": "api"
+ },
+ {
+ "enableProxy": false,
+ "server": "ru",
+ "type": "vortex"
+ },
+ {
+ "enableProxy": false,
+ "server": "na",
+ "type": "api"
+ },
+ {
+ "enableProxy": false,
+ "server": "cn",
+ "type": "vortex"
+ }
+ ]
+}
diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml
new file mode 100644
index 0000000..0e9d634
--- /dev/null
+++ b/dependency-reduced-pom.xml
@@ -0,0 +1,84 @@
+
+
+ 4.0.0
+ com.shinoaki.wows.real
+ wows-real-web
+ 1.0-SNAPSHOT
+
+
+
+ maven-plugin-plugin
+ 3.2
+
+ wows-real-web
+ true
+
+
+
+ maven-compiler-plugin
+ 3.8.1
+
+ 20
+ 20
+ --enable-preview
+
+
+
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+
+
+ com.shinoaki.wows.real.Main
+
+
+
+
+
+
+
+
+
+
+ jakarta.websocket
+ jakarta.websocket-api
+ 2.1.1
+ provided
+
+
+ jakarta.websocket
+ jakarta.websocket-client-api
+ 2.1.1
+ provided
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ hamcrest-core
+ org.hamcrest
+
+
+
+
+
+ 20
+ 20
+ 1.2.5
+ 20
+ 2.14.2
+ UTF-8
+ 2.0.7
+ 0.3.4
+ 1.4.8
+
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..05c20db
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,138 @@
+
+ 4.0.0
+
+ com.shinoaki.wows.real
+ wows-real-web
+ 1.0-SNAPSHOT
+ jar
+
+
+ UTF-8
+ 20
+ 20
+ 20
+ 0.3.4
+ 2.14.2
+ 1.2.5
+ 2.0.7
+ 1.4.8
+
+
+
+
+ com.shinoaki.wows.api
+ wows-api
+ ${wows.api.version}
+
+
+
+ jakarta.websocket
+ jakarta.websocket-api
+ 2.1.1
+ provided
+
+
+
+ jakarta.websocket
+ jakarta.websocket-client-api
+ 2.1.1
+ provided
+
+
+
+ io.netty
+ netty-all
+ 4.1.92.Final
+
+
+
+ org.eclipse.paho
+ org.eclipse.paho.mqttv5.client
+ ${mqtt.version}
+
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${json.version}
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${json.version}
+
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jdk8
+ ${json.version}
+
+
+ ch.qos.logback
+ logback-core
+ ${logback.version}
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.api.version}
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ 3.2
+
+ wows-real-web
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 20
+ 20
+ --enable-preview
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+
+
+ com.shinoaki.wows.real.Main
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/shinoaki/wows/real/Main.java b/src/main/java/com/shinoaki/wows/real/Main.java
new file mode 100644
index 0000000..ca80e30
--- /dev/null
+++ b/src/main/java/com/shinoaki/wows/real/Main.java
@@ -0,0 +1,73 @@
+package com.shinoaki.wows.real;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.shinoaki.wows.api.utils.JsonUtils;
+import com.shinoaki.wows.real.config.Config;
+import com.shinoaki.wows.real.mqtt.MqttService;
+import com.shinoaki.wows.real.timer.ServerTimer;
+import com.shinoaki.wows.real.ws.server.WowsWebsocket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Type;
+
+/**
+ * @author Xun
+ * create or update time = 2023/7/20 16:46 星期四
+ */
+public class Main {
+ public static final Logger log = LoggerFactory.getLogger(Main.class);
+
+ public static void main(String[] args) {
+ System.setProperty("logback.configurationFile", "config/logback.xml");
+ try {
+ JsonUtils json = new JsonUtils();
+ log.info("加载配置信息... ");
+ var config = json.parse(readFileData(fileConfig("config.json")), new TypeReference() {
+ @Override
+ public Type getType() {
+ return super.getType();
+ }
+ });
+ MqttService service = new MqttService(Config.mqttConfig(json));
+ service.connect();
+ WowsWebsocket websocket = new WowsWebsocket();
+ websocket.start(config.wsPort(), "/yuyuko");
+ log.info("初始化成功...");
+ //服务配置
+ ServerTimer timer = new ServerTimer();
+ timer.updateConfig();
+ timer.timerQueryAccountId();
+ timer.timerSubTopic(service);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.error("线程异常!", e);
+ } catch (Exception e) {
+ log.error("服务运行异常!", e);
+ }
+ }
+
+
+ public static File fileConfig(String fileName) {
+ return new File(pathHome() + "config" + File.separator + fileName);
+ }
+
+ public static String readFileData(File file) throws IOException {
+ try (FileInputStream in = new FileInputStream(file)) {
+ return new String(in.readAllBytes());
+ }
+ }
+
+ /**
+ * 项目根目录 File.separator 结尾
+ *
+ * @return
+ */
+ public static String pathHome() {
+ return System.getProperty("user.dir") + File.separator;
+ }
+
+}
diff --git a/src/main/java/com/shinoaki/wows/real/cache/SubUserCache.java b/src/main/java/com/shinoaki/wows/real/cache/SubUserCache.java
new file mode 100644
index 0000000..907b47d
--- /dev/null
+++ b/src/main/java/com/shinoaki/wows/real/cache/SubUserCache.java
@@ -0,0 +1,54 @@
+package com.shinoaki.wows.real.cache;
+
+import com.shinoaki.wows.api.type.WowsServer;
+import com.shinoaki.wows.real.wows.AccountInfo;
+import io.netty.channel.ChannelId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Collectors;
+
+/**
+ * @author Xun
+ * create or update time = 2023/7/25 16:51 星期二
+ */
+public class SubUserCache {
+ private static final Logger log = LoggerFactory.getLogger(SubUserCache.class);
+
+ private SubUserCache() {
+ }
+
+ private static final ConcurrentMap> USER_CACHE = new ConcurrentHashMap<>();
+
+ public static void put(ChannelId id, Set accountId) {
+ log.info("{} 添加账号信息={}", id, accountId);
+ USER_CACHE.put(id, accountId);
+ }
+
+
+ public static Map check(long accountId) {
+ Map map = new HashMap<>();
+ for (var kv : USER_CACHE.entrySet()) {
+ Optional first = kv.getValue().stream().filter(x -> x.accountId() == accountId).findFirst();
+ first.ifPresent(aLong -> map.put(kv.getKey(), aLong));
+ }
+ return map;
+ }
+
+ public static Map> all() {
+ Set set = new HashSet<>();
+ USER_CACHE.values().forEach(set::addAll);
+ return set.stream().collect(Collectors.groupingBy(AccountInfo::server));
+ }
+
+ public static List allAccountId() {
+ Set set = new HashSet<>();
+ USER_CACHE.values().forEach(set::addAll);
+ return set.stream().map(AccountInfo::accountId).toList();
+ }
+
+
+}
diff --git a/src/main/java/com/shinoaki/wows/real/config/Config.java b/src/main/java/com/shinoaki/wows/real/config/Config.java
new file mode 100644
index 0000000..3edef69
--- /dev/null
+++ b/src/main/java/com/shinoaki/wows/real/config/Config.java
@@ -0,0 +1,37 @@
+package com.shinoaki.wows.real.config;
+
+import com.shinoaki.wows.api.utils.JsonUtils;
+import com.shinoaki.wows.real.Main;
+import com.shinoaki.wows.real.mqtt.MqttConfig;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+
+/**
+ * @author Xun
+ * create or update time = 2023/7/26 18:21 星期三
+ */
+public record Config(int wsPort) {
+
+
+ public static MqttConfig mqttConfig(JsonUtils json) throws IOException {
+ File file = Main.fileConfig("mqtt.json");
+ if (file.exists()) {
+ return json.parse(Main.readFileData(file), MqttConfig.class);
+ } else {
+ MqttConfig c = new MqttConfig();
+ c.setBroker("tcp://124.222.76.59:1883");
+ c.setClientId("yuyuko-" + UUID.randomUUID());
+ c.setUserName("yuyuko-user");
+ c.setPassword("yuyuko");
+ c.setTopic("yuyuko/wows/real");
+ try (FileOutputStream out = new FileOutputStream(file)) {
+ out.write(json.toJson(c).getBytes(StandardCharsets.UTF_8));
+ }
+ return c;
+ }
+ }
+}
diff --git a/src/main/java/com/shinoaki/wows/real/config/ProxyConfig.java b/src/main/java/com/shinoaki/wows/real/config/ProxyConfig.java
new file mode 100644
index 0000000..6168cc4
--- /dev/null
+++ b/src/main/java/com/shinoaki/wows/real/config/ProxyConfig.java
@@ -0,0 +1,37 @@
+package com.shinoaki.wows.real.config;
+
+import com.shinoaki.wows.api.type.WowsServer;
+import com.shinoaki.wows.real.config.type.DataType;
+
+/**
+ * @author Xun
+ * create or update time = 2023/7/20 11:53 星期四
+ */
+public record ProxyConfig(String host, int port, ProxyServer[] server) {
+
+ public ProxyServer findServer(WowsServer server) {
+ for (var s : server()) {
+ if (s.wowsServer() == server) {
+ return s;
+ }
+ }
+ return null;
+ }
+
+ public record ProxyServer(boolean enableProxy, String server, String type) {
+
+ public WowsServer wowsServer() {
+ return WowsServer.findCodeByNull(server);
+ }
+
+ public DataType dataType() {
+ if (DataType.API.name().equalsIgnoreCase(type)) {
+ return DataType.API;
+ } else if (DataType.VORTEX.name().equalsIgnoreCase(type)) {
+ return DataType.VORTEX;
+ } else {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/shinoaki/wows/real/config/type/DataType.java b/src/main/java/com/shinoaki/wows/real/config/type/DataType.java
new file mode 100644
index 0000000..e2ac71d
--- /dev/null
+++ b/src/main/java/com/shinoaki/wows/real/config/type/DataType.java
@@ -0,0 +1,9 @@
+package com.shinoaki.wows.real.config.type;
+
+/**
+ * @author Xun
+ * create or update time = 2023/7/20 12:07 星期四
+ */
+public enum DataType {
+ API, VORTEX
+}
diff --git a/src/main/java/com/shinoaki/wows/real/mqtt/MqttConfig.java b/src/main/java/com/shinoaki/wows/real/mqtt/MqttConfig.java
new file mode 100644
index 0000000..e651ac0
--- /dev/null
+++ b/src/main/java/com/shinoaki/wows/real/mqtt/MqttConfig.java
@@ -0,0 +1,59 @@
+package com.shinoaki.wows.real.mqtt;
+
+/**
+ * @author Xun
+ * create or update time = 2023/7/25 15:20 星期二
+ */
+
+public class MqttConfig {
+
+ private String broker;
+
+ private String clientId;
+
+ private String userName;
+
+ private String password;
+
+ private String topic;
+
+ public String getBroker() {
+ return broker;
+ }
+
+ public void setBroker(String broker) {
+ this.broker = broker;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getTopic() {
+ return topic;
+ }
+
+ public void setTopic(String topic) {
+ this.topic = topic;
+ }
+}
diff --git a/src/main/java/com/shinoaki/wows/real/mqtt/MqttService.java b/src/main/java/com/shinoaki/wows/real/mqtt/MqttService.java
new file mode 100644
index 0000000..1c81c62
--- /dev/null
+++ b/src/main/java/com/shinoaki/wows/real/mqtt/MqttService.java
@@ -0,0 +1,127 @@
+package com.shinoaki.wows.real.mqtt;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.shinoaki.wows.api.utils.JsonUtils;
+import com.shinoaki.wows.real.cache.SubUserCache;
+import com.shinoaki.wows.real.service.WsService;
+import com.shinoaki.wows.real.wows.PlayerBattleInfo;
+import org.eclipse.paho.mqttv5.client.*;
+import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence;
+import org.eclipse.paho.mqttv5.common.MqttException;
+import org.eclipse.paho.mqttv5.common.MqttMessage;
+import org.eclipse.paho.mqttv5.common.MqttSubscription;
+import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * @author Xun
+ * create or update time = 2023/7/25 16:05 星期二
+ */
+public class MqttService {
+ public static final Logger log = LoggerFactory.getLogger(MqttService.class);
+ private static final JsonUtils json = new JsonUtils();
+ private static final MemoryPersistence persistence = new MemoryPersistence();
+ private static final MqttConnectionOptions connOpts = new MqttConnectionOptions();
+ private MqttClient mqttClient = null;
+ private final MqttConfig config;
+ private final MyMqttMessageListener myMqttMessageListener;
+
+ static {
+ connOpts.setCleanStart(true);
+ connOpts.setAutomaticReconnect(true);
+ }
+
+ public MqttService(MqttConfig config) {
+ this.config = config;
+ this.myMqttMessageListener = new MyMqttMessageListener();
+ }
+
+
+ public void connect() {
+ if (mqttClient != null) {
+ return;
+ }
+ connOpts.setUserName(config.getUserName());
+ connOpts.setPassword(config.getPassword().getBytes(StandardCharsets.UTF_8));
+ try {
+ mqttClient = new MqttClient(config.getBroker(), config.getClientId(), persistence);
+ mqttClient.setCallback(new MqttCallback() {
+ @Override
+ public void disconnected(MqttDisconnectResponse disconnectResponse) {
+ log.error("mqtt 连接断开...");
+ }
+
+ @Override
+ public void mqttErrorOccurred(MqttException exception) {
+ log.error("mqtt error ", exception);
+ }
+
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+
+ }
+
+ @Override
+ public void deliveryComplete(IMqttToken token) {
+
+ }
+
+ @Override
+ public void connectComplete(boolean reconnect, String serverURI) {
+ log.info("mqtt 建立连接!");
+ onMessage();
+ }
+
+ @Override
+ public void authPacketArrived(int reasonCode, MqttProperties properties) {
+
+ }
+ });
+ mqttClient.connect(connOpts);
+ } catch (MqttException e) {
+ log.error("mqtt 服务器连接异常!");
+ }
+ log.info("mqtt 服务器连接成功!");
+ }
+
+ public void onMessage() {
+ List longs = SubUserCache.allAccountId();
+ if (longs.isEmpty()) {
+ return;
+ }
+ MqttSubscription[] subscriptions = new MqttSubscription[longs.size()];
+ for (int i = 0; i < longs.size(); i++) {
+ subscriptions[i] = new MqttSubscription(config.getTopic() + "/" + longs.get(i), 0);
+ }
+ IMqttMessageListener[] messageListeners = new IMqttMessageListener[]{
+ myMqttMessageListener
+ };
+ try {
+ mqttClient.subscribe(subscriptions, messageListeners);
+ } catch (MqttException e) {
+ log.error("订阅消息异常!", e);
+ }
+ }
+
+ public static class MyMqttMessageListener implements IMqttMessageListener {
+
+ @Override
+ public void messageArrived(String topic, MqttMessage message) throws Exception {
+ try {
+ PlayerBattleInfo battleInfo = json.parse(new String(message.getPayload()), PlayerBattleInfo.class);
+ for (var info : battleInfo.infoList()) {
+ var map = SubUserCache.check(info.accountId());
+ if (!map.isEmpty()) {
+ WsService.playerGame(battleInfo, map);
+ }
+ }
+ } catch (JsonProcessingException e) {
+ log.error("序列化消息异常!", e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/shinoaki/wows/real/service/WowsUserService.java b/src/main/java/com/shinoaki/wows/real/service/WowsUserService.java
new file mode 100644
index 0000000..9adffd2
--- /dev/null
+++ b/src/main/java/com/shinoaki/wows/real/service/WowsUserService.java
@@ -0,0 +1,260 @@
+package com.shinoaki.wows.real.service;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.shinoaki.wows.api.codec.http.WowsHttpClanTools;
+import com.shinoaki.wows.api.codec.http.WowsHttpShipTools;
+import com.shinoaki.wows.api.codec.http.WowsHttpUserTools;
+import com.shinoaki.wows.api.data.ShipInfo;
+import com.shinoaki.wows.api.developers.account.DevelopersUserInfo;
+import com.shinoaki.wows.api.developers.clan.DevelopersSearchUserClan;
+import com.shinoaki.wows.api.error.HttpStatusException;
+import com.shinoaki.wows.api.error.StatusException;
+import com.shinoaki.wows.api.type.WowsBattlesType;
+import com.shinoaki.wows.api.type.WowsServer;
+import com.shinoaki.wows.api.utils.JsonUtils;
+import com.shinoaki.wows.api.vortex.clan.account.VortexSearchClanUser;
+import com.shinoaki.wows.real.Main;
+import com.shinoaki.wows.real.config.ProxyConfig;
+import com.shinoaki.wows.real.config.type.DataType;
+import com.shinoaki.wows.real.wows.service.CacheShipMap;
+import com.shinoaki.wows.real.wows.service.WowsHttpData;
+import com.shinoaki.wows.real.wows.user.WowsClanInfo;
+import com.shinoaki.wows.real.wows.user.WowsUserInfo;
+
+import java.io.*;
+import java.lang.reflect.Type;
+import java.net.http.HttpClient;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static com.shinoaki.wows.real.Main.log;
+
+/**
+ * @author Xun
+ * create or update time = 2023/7/25 21:06 星期二
+ */
+public class WowsUserService {
+ public static final String TOKEN = "907d9c6bfc0d896a2c156e57194a97cf";
+ public static final String v1 = "ship-map-v1-cache";
+ public static final String v2 = "ship-map-v2-cache";
+
+ private WowsUserService() {
+
+ }
+
+ public static WowsUserInfo userInfo(WowsHttpData wowsHttpData, long accountId) {
+ JsonUtils json = new JsonUtils();
+ try {
+ //检查缓存
+ HttpClient httpClient = wowsHttpData.httpClient();
+ WowsUserInfo wowsUserInfo;
+ WowsHttpUserTools user = new WowsHttpUserTools(json, httpClient, wowsHttpData.server());
+ WowsHttpClanTools clan = new WowsHttpClanTools(json, httpClient, wowsHttpData.server());
+ if (wowsHttpData.dataType() == DataType.API) {
+ DevelopersUserInfo userInfo = user.userInfoDevelopers(TOKEN, accountId);
+ DevelopersSearchUserClan userClan = clan.developers(TOKEN).userSearchClanDevelopers(accountId);
+ WowsClanInfo clanInfo;
+ if (userClan.clan_id() <= 0) {
+ clanInfo = WowsClanInfo.empty();
+ } else {
+ clanInfo = WowsClanInfo.clanNoColor(userClan.clan_id(), userClan.clan().tag(), userClan.clan().name(), "");
+ }
+ wowsUserInfo = WowsUserInfo.user(accountId, userInfo.nickname(), wowsHttpData.server(), clanInfo, userInfo.created_at());
+ } else {
+ VortexSearchClanUser clanUser = clan.vortex().userSearchClanVortex(accountId);
+ var clanInfo = new WowsClanInfo(clanUser.clan_id(), clanUser.clan().tag(), clanUser.clan().name(), "", clanUser.clan().color(), -1);
+ List userName = fileUserName(accountId);
+ wowsUserInfo = WowsUserInfo.user(accountId, userName.get(0), wowsHttpData.server(), clanInfo, Long.parseLong(userName.get(1)));
+ }
+ return wowsUserInfo;
+ } catch (StatusException | HttpStatusException | IOException e) {
+ log.error("{}-{} 解析用户信息异常!", wowsHttpData.server().getCode(), accountId, e);
+ return WowsUserInfo.empty();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.error("{}-{} 解析用户信息异常! InterruptedException ", wowsHttpData.server().getCode(), accountId, e);
+ return WowsUserInfo.empty();
+ }
+ }
+
+ public static WowsHttpData http(WowsServer server) {
+ File file = Main.fileConfig("proxy.json");
+ JsonUtils json = new JsonUtils();
+ try (FileInputStream in = new FileInputStream(file)) {
+ String data = new String(in.readAllBytes());
+ var proxyConfig = json.parse(data, new TypeReference() {
+ @Override
+ public Type getType() {
+ return super.getType();
+ }
+ });
+ return new WowsHttpData(proxyConfig, server);
+ } catch (IOException e) {
+ log.error("读取配置文件异常!", e);
+ }
+ return null;
+ }
+
+ public static CacheShipMap dataV1(WowsHttpData wowsHttpData, long accountId) throws IOException {
+ return ship(v1, accountId, shipMapByPvpByRank(wowsHttpData, accountId));
+ }
+
+ public static CacheShipMap dataV2(WowsHttpData wowsHttpData, long accountId) throws IOException {
+ return ship(v2, accountId, shipMap(wowsHttpData, accountId));
+ }
+
+ private static CacheShipMap ship(String versionCache, long accountId, Map> shipMap) throws IOException {
+ JsonUtils json = new JsonUtils();
+ File file = fileShip(versionCache, accountId);
+ boolean filedCreateCheck = fileCreateCheck(file);
+ Map> ship;
+ if (file.exists()) {
+ try (FileInputStream in = new FileInputStream(file)) {
+ String data = new String(in.readAllBytes());
+ ship = json.parse(data, new TypeReference