From 8e888698e604da589b6b8539db7e6726bf413520 Mon Sep 17 00:00:00 2001 From: zhouwei <849588297@qq.com> Date: Wed, 4 Mar 2026 18:58:13 +0800 Subject: [PATCH] =?UTF-8?q?init=E7=A6=8F=E7=A6=84=E5=AF=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- robots/zhipai/robot_zp_fls/.idea/.gitignore | 8 + robots/zhipai/robot_zp_fls/.idea/compiler.xml | 14 + .../zhipai/robot_zp_fls/.idea/encodings.xml | 7 + .../robot_zp_fls/.idea/jarRepositories.xml | 25 + robots/zhipai/robot_zp_fls/.idea/misc.xml | 12 + robots/zhipai/robot_zp_fls/.idea/pom.xml.iml | 9 + robots/zhipai/robot_zp_fls/.idea/vcs.xml | 7 + .../robot_zp_fls/config/game-config.xml | 10 + .../robot_zp_fls/config/log4j.properties | 20 + .../robot_zp_fls/config/taurus-core.xml | 62 ++ .../robot_zp_fls/config/taurus-permanent.xml | 75 ++ robots/zhipai/robot_zp_fls/pom.xml | 47 + .../src/main/java/robot/zp/Config.java | 37 + .../main/java/robot/zp/EXGameController.java | 369 ++++++++ .../src/main/java/robot/zp/EXMainServer.java | 169 ++++ .../src/main/java/robot/zp/EXPlayer.java | 26 + .../src/main/java/robot/zp/EXRoom.java | 32 + .../java/robot/zp/RobotConnectionManager.java | 877 ++++++++++++++++++ .../robot/zp/business/AccountBusiness.java | 291 ++++++ .../robot/zp/handler/FuLuShouHandler.java | 342 +++++++ .../main/java/robot/zp/info/RobotUser.java | 164 ++++ .../robot/zp/thread/ResourceCleanupUtil.java | 72 ++ .../robot/zp/thread/ThreadPoolConfig.java | 117 +++ .../main/java/taurus/util/FuLuShouSuanFa.java | 4 + .../main/java/taurus/util/ROBOTEventType.java | 13 + .../src/test/java/robot_zp_fulushou/Main.java | 15 + 26 files changed, 2824 insertions(+) create mode 100644 robots/zhipai/robot_zp_fls/.idea/.gitignore create mode 100644 robots/zhipai/robot_zp_fls/.idea/compiler.xml create mode 100644 robots/zhipai/robot_zp_fls/.idea/encodings.xml create mode 100644 robots/zhipai/robot_zp_fls/.idea/jarRepositories.xml create mode 100644 robots/zhipai/robot_zp_fls/.idea/misc.xml create mode 100644 robots/zhipai/robot_zp_fls/.idea/pom.xml.iml create mode 100644 robots/zhipai/robot_zp_fls/.idea/vcs.xml create mode 100644 robots/zhipai/robot_zp_fls/config/game-config.xml create mode 100644 robots/zhipai/robot_zp_fls/config/log4j.properties create mode 100644 robots/zhipai/robot_zp_fls/config/taurus-core.xml create mode 100644 robots/zhipai/robot_zp_fls/config/taurus-permanent.xml create mode 100644 robots/zhipai/robot_zp_fls/pom.xml create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/Config.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXGameController.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXMainServer.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXPlayer.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXRoom.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/RobotConnectionManager.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/business/AccountBusiness.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/handler/FuLuShouHandler.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/info/RobotUser.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/thread/ResourceCleanupUtil.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/robot/zp/thread/ThreadPoolConfig.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/taurus/util/FuLuShouSuanFa.java create mode 100644 robots/zhipai/robot_zp_fls/src/main/java/taurus/util/ROBOTEventType.java create mode 100644 robots/zhipai/robot_zp_fls/src/test/java/robot_zp_fulushou/Main.java diff --git a/robots/zhipai/robot_zp_fls/.idea/.gitignore b/robots/zhipai/robot_zp_fls/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/robots/zhipai/robot_zp_fls/.idea/compiler.xml b/robots/zhipai/robot_zp_fls/.idea/compiler.xml new file mode 100644 index 0000000..c03c746 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/.idea/compiler.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/.idea/encodings.xml b/robots/zhipai/robot_zp_fls/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/robots/zhipai/robot_zp_fls/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/.idea/jarRepositories.xml b/robots/zhipai/robot_zp_fls/.idea/jarRepositories.xml new file mode 100644 index 0000000..af6ee75 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/.idea/misc.xml b/robots/zhipai/robot_zp_fls/.idea/misc.xml new file mode 100644 index 0000000..d5cd614 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/.idea/pom.xml.iml b/robots/zhipai/robot_zp_fls/.idea/pom.xml.iml new file mode 100644 index 0000000..dfa92c3 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/.idea/pom.xml.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/.idea/vcs.xml b/robots/zhipai/robot_zp_fls/.idea/vcs.xml new file mode 100644 index 0000000..77a3cc7 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/config/game-config.xml b/robots/zhipai/robot_zp_fls/config/game-config.xml new file mode 100644 index 0000000..45e5d06 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/config/game-config.xml @@ -0,0 +1,10 @@ + + + + 8.134.76.43 + 8.134.76.43 + 8707 + 8707 + 107 + true + \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/config/log4j.properties b/robots/zhipai/robot_zp_fls/config/log4j.properties new file mode 100644 index 0000000..6786dba --- /dev/null +++ b/robots/zhipai/robot_zp_fls/config/log4j.properties @@ -0,0 +1,20 @@ + +log4j.rootLogger = INFO,consoleAppender,fileAppender + +# ConsoleAppender +log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender +log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.consoleAppender.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%t] %c{2} %3x - %m%n + + +# Regular FileAppender +log4j.appender.fileAppender=org.apache.log4j.DailyRollingFileAppender +log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout +log4j.appender.fileAppender.File=${WORKDIR}/logs/web_main.log +log4j.appender.fileAppender.layout.ConversionPattern=%d{dd MMM yyyy | HH:mm:ss,SSS} | %-5p | %t | %c{3} | %3x | %m%n +log4j.appender.fileAppender.Encoding=UTF-8 +log4j.appender.fileAppender.DatePattern='.'yyyy-MM-dd +log4j.appender.dailyFile.Append=true + +# The file is rolled over very day +log4j.appender.fileAppender.DatePattern ='.'yyyy-MM-dd \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/config/taurus-core.xml b/robots/zhipai/robot_zp_fls/config/taurus-core.xml new file mode 100644 index 0000000..2dd31d5 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/config/taurus-core.xml @@ -0,0 +1,62 @@ + + + log4j.properties + + + redis + com.taurus.core.plugin.redis.RedisPlugin + + + + 200 + + 50 + + 20 + + 2000 + + false + + false + + true + + 100 + + 60000 + + 30000 + + 1800000 + + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/config/taurus-permanent.xml b/robots/zhipai/robot_zp_fls/config/taurus-permanent.xml new file mode 100644 index 0000000..1619e2c --- /dev/null +++ b/robots/zhipai/robot_zp_fls/config/taurus-permanent.xml @@ -0,0 +1,75 @@ + + + 1 + + 512 + + Heap + + Heap + + 524288 + + 16384 + + 32768 + + 512 + + + 4 + 2 + 2 + + + true + + 300 + + + + + + + + + + 1.2.3.4 + + + 127.0.0.1 + + 10000 + + + + false +
0.0.0.0
+ 80 +
+ + + + robot - test + robot.zp.EXMainServer + + + + + Sys + 16 + 32 + 60000 + 5000 + + + + + Ext + 16 + 32 + 60000 + 5000 + + +
\ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/pom.xml b/robots/zhipai/robot_zp_fls/pom.xml new file mode 100644 index 0000000..1f536b3 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + + com.robot + robot_zp_fulushou + 1.0.0 + jar + + robot_zp_fulushou + http://maven.apache.org + + + UTF-8 + + + + + com.robot + robot_common + 1.0.0 + + + + + + + + + robot + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + + 1.8 + 1.8 + UTF-8 + + + + + + + diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/Config.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/Config.java new file mode 100644 index 0000000..b20d162 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/Config.java @@ -0,0 +1,37 @@ +package robot.zp; + +public class Config { + + /** + * 发送准备 - robot_zp_fls to game_zp_fls 的协议号 + */ + public static final String GAME_READY_FLS = "1003"; + + /** + * 加入房间 - robot_mgr to game_zp_fls 的内部协议号 + */ + public static final String JOIN_ROOM_FLS = "1002"; + + /** Web组加入房间协议 */ + public static final String WEB_GROUP_JOIN_ROOM = "225"; + + /** Web组主动重连协议 */ + public static final String WEB_GROUP_ACTIVE_RECONNECT = "226"; + + //==================== 游戏服务器配置 ==================== + /** 游戏服务器主机地址 */ + public static final String GAME_SERVER_HOST = "127.0.0.1"; + + /** 游戏服务器端口 */ + public static final String GAME_SERVER_PORT = "8971"; + + /** 默认密码 */ + public static final String DEFAULT_PASSWORD = "123456"; + + /** 默认PID */ + public static final String DEFAULT_PID = "107"; + + /** 默认群组ID */ + public static final String DEFAULT_GROUP_ID = "426149"; + +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXGameController.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXGameController.java new file mode 100644 index 0000000..2728bd3 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXGameController.java @@ -0,0 +1,369 @@ +package robot.zp; + +import com.robot.GameController; +import com.robot.GameInterceptor; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TObject; +import com.taurus.core.plugin.redis.Redis; +import com.taurus.core.routes.ActionKey; +import com.taurus.permanent.data.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import robot.zp.info.RobotUser; +import robot.zp.thread.ThreadPoolConfig; +import taurus.client.TaurusClient; +import taurus.client.business.GroupRoomBusiness; +import taurus.util.ROBOTEventType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import static robot.zp.thread.ThreadPoolConfig.scheduleDelay; + +/** + * 福禄寿游戏控制器 - 处理游戏协议 + */ +public class EXGameController extends GameController { + private static final Logger log = LoggerFactory.getLogger(EXGameController.class); + + private static final RobotConnectionManager robotConnectionManager = new RobotConnectionManager(); + + //机器人房间 + protected static final Map robotRoomMapping = new ConcurrentHashMap<>(); + + //记录最近访问时间 + private static final Map lastAccessTime = new ConcurrentHashMap<>(); + + //设置连接超时时间(5分钟) + private static final long CONNECTION_TIMEOUT = 5 * 60 * 1000; + + public EXGameController() { + super(); + log.info("福禄寿游戏控制器已初始化"); + } + + /** + * 接收来自web_group的加入房间协议 + */ + @ActionKey(value = Config.WEB_GROUP_JOIN_ROOM, validate = GameInterceptor.NOT_PLAYER) + public void webGroup(Session session, ITObject params, int gid) { + int robotId = params.getInt("robotid"); + String roomId = params.getString("roomid"); + int groupId = params.getInt("groupid"); + + //检查Redis中该房间是否真的包含当前机器人 + if (!checkRobotInRoomRedis(roomId, String.valueOf(robotId))) { + //Redis中不存在该机器人 清理本地可能的错误映射 + List robotUsers = getRobotUsersByRoomId(Integer.parseInt(roomId)); + if (!robotUsers.isEmpty()) { + synchronized (robotUsers) { + RobotUser robotUser = robotUsers.get(0); + log.warn("房间{}中Redis未找到机器人{},但本地映射存在{},清理本地映射", roomId, robotId, robotUser.getRobotId()); + robotRoomMapping.remove(robotUser.getConnecId()); + robotRoomMapping.remove(robotUser.getRobotId()); + } + } + } else { + //Redis中存在该机器人 检查是否是不同机器人的冲突 + List robotUsers = getRobotUsersByRoomId(Integer.parseInt(roomId)); + if (!robotUsers.isEmpty()) { + synchronized (robotUsers) { + RobotUser robotUser = robotUsers.get(0); + int existingRobotId = Integer.parseInt(robotUser.getRobotId()); + + if (robotId != existingRobotId) { + //不同机器人的冲突 + log.warn("房间{}中Redis已存在机器人{},当前机器人{}不执行加入逻辑", roomId, existingRobotId, robotId); + return; + } + } + } + } + log.info("225开始进房间: room:{} robot:{}", roomId, robotId); + //加入房间 + joinRoomCommon(robotId, roomId, groupId, params); + log.info("225已进入房间准备成功: room:{} robot:{}", roomId, robotId); + } + + /** + * 接收来自web_group的主动重连协议 + */ + @ActionKey(value = Config.WEB_GROUP_ACTIVE_RECONNECT, validate = GameInterceptor.NOT_PLAYER) + public void webGroupActive(Session session, ITObject params, int gid) { + int robotId = params.getInt("robotid"); + String roomId = params.getString("roomid"); + log.info("226开始进房间: room:{} robot:{}", roomId, robotId); + //加入房间 + joinRoomCommon(params.getInt("robotid"), params.getString("roomid"), params.getInt("groupid"), params); + log.info("226已进入房间准备成功: room:{} robot:{}", roomId, robotId); + } + + /** + * 重启服务断线重连 + * */ + public void webGroupJoinRoom(RobotUser robotUser) { + String connecId = robotUser.getConnecId(); + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + //重启检查 + try { + Set robotTokens = jedis0.smembers("{user}:"+robotUser.getRobotId()+"_token"); + String robotSession = null; + + for (String token : robotTokens) { + if (jedis0.exists(token)) { + robotSession = token; + break; + } + } + String gallrobot = jedis2.hget("gallrobot", robotUser.getRobotId()); + if (gallrobot.equals("0")) { + robotRoomMapping.remove(connecId); + return; + } + + log.info("重启后开始进房间: room:{} robot:{}", robotUser.getCurrentRoomId(), robotUser.getRobotId()); + ITObject params = new TObject(); + params.putString("session", "{user}:" + robotUser.getRobotId() + "," + robotSession); + //加入房间 + joinRoomCommon(Integer.parseInt(robotUser.getRobotId()), String.valueOf(robotUser.getCurrentRoomId()), Integer.parseInt(robotUser.getRobotGroupid()), params); + log.info("重启后已进入房间准备成功: room:{} robot:{}", robotUser.getCurrentRoomId(), robotUser.getRobotId()); + + } catch (Exception e) { + log.error("重启服务断线重连时发生错误", e); + } finally { + jedis0.close(); + jedis2.close(); + } + } + + /** + * 加入房间逻辑 + */ + private void joinRoomCommon(int robotId, String roomId, int groupId, ITObject params) { + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + try { + Set robotTokens = jedis0.smembers("{user}:" + robotId + "_token"); + String robotSession = null; + + for (String token : robotTokens) { + if (jedis0.exists(token)) { + robotSession = token; + break; + } + } + + log.info("开始进房间: room:{}", roomId); + log.info("开始进房间: {user}:{}", robotId); + + TaurusClient client = getFlsGameServerConnection(roomId + "_" + robotId); + GroupRoomBusiness.joinRoom(groupId, "room:" + roomId, "{user}:" + robotId, null); + + //机器人房间映射关系 + RobotUser robotUser = getRobotRoomInfo(String.valueOf(robotId)); + String connecId = roomId + "_" + robotId; + if (robotUser.getCurrentRoomId() == 0) { + robotUser.setCurrentRoomId(Integer.parseInt(roomId)); + robotUser.setClient(client); + robotUser.setConnecId(connecId); + } + + //先不放入映射 等确认加入成功后再放入 + //robotRoomMapping.put(robotUser.getConnecId(), robotUser); + robotRoomMapping.remove(robotUser.getRobotId()); + //非阻塞延迟替代Thread.sleep + scheduleDelay(() -> { + + }, 2, TimeUnit.SECONDS); + params.putString("session", "{user}:" + robotId + "," + robotSession); + + //发送加入房间请求到game_zp_fls + client.send(Config.JOIN_ROOM_FLS, params, response -> { + //成功响应后才建立映射关系 + robotRoomMapping.put(robotUser.getConnecId(), robotUser); + robotConnectionManager.reconnectToGameServer(response, robotUser, client); + }); + + log.info("已进入房间成功: {}", robotUser.getConnecId()); + Thread.sleep(1000); + if (client.isConnected()) { + client.send(Config.GAME_READY_FLS, params, response -> { + log.info("1003:{}", response); + }); + jedis2.hset("gallrobot", String.valueOf(robotUser.getRobotId()), "1"); + + robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_READY); + robotConnectionManager.setSessionAndToken("{user}:" + robotId, robotSession, robotUser.getConnecId()); + } + //添加超时检查机制 + CompletableFuture.runAsync(() -> { + try { + //定时任务替代Thread.sleep + scheduleDelay(() -> { + //15秒后还没有建立映射关系 加入可能失败 + if (robotRoomMapping.get(robotUser.getConnecId()) == null) { + log.warn("机器人{}加入房间{}超时,清理临时状态", robotId, roomId); + robotConnectionManager.disconnectFromGameServer(connecId); + } + }, 15, TimeUnit.SECONDS); + //15秒后还没有建立映射关系 加入可能失败 + if (robotRoomMapping.get(robotUser.getConnecId()) == null) { + log.warn("机器人{}加入房间{}超时,清理临时状态", robotId, roomId); + robotConnectionManager.disconnectFromGameServer(connecId); + } + } catch (Exception e) { + log.error("机器人加入房间超时", e); + } + }, ThreadPoolConfig.getBusinessThreadPool());//指定自定义线程池 + robotUser.setIntoRoomTime(robotConnectionManager.getTime()); + log.info("已进入房间准备成功: {}", robotUser.getConnecId()); + } catch (Exception e) { + log.error("加入房间时发生错误", e); + } finally { + jedis0.close(); + jedis2.close(); + } + } + + /** + * 根据机器人ID获取其所在的房间信息 + */ + public static RobotUser getRobotRoomInfo(String robotId) { + RobotUser robotUser = robotRoomMapping.get(robotId); + if (robotUser ==null) { + RobotUser robotUserCopy = new RobotUser(); + robotUserCopy.setRobotId(robotId); + robotUserCopy.setPassword(Config.DEFAULT_PASSWORD); + robotUserCopy.setGameHost(Config.GAME_SERVER_HOST); + robotUserCopy.setGamePort(Config.GAME_SERVER_PORT); + robotUserCopy.setRobotGroupid(Config.DEFAULT_GROUP_ID); + robotUserCopy.setRobotPid(Config.DEFAULT_PID); + return robotUserCopy; + } + return robotRoomMapping.get(robotId); + } + + /** + * 根据房间ID获取所有对应的RobotUser + */ + public List getRobotUsersByRoomId(int roomId) { + String prefix = roomId + "_"; + List result = new ArrayList<>(); + + for (Map.Entry entry : robotRoomMapping.entrySet()) { + if (entry.getKey().startsWith(prefix)) { + result.add(entry.getValue()); + } + } + return result; + } + + /** + * 根据机器人ID删除其所在的房间信息 + */ + public static void removeRobotRoomInfo(String robotId) { + RobotUser removedUser = robotRoomMapping.remove(robotId); + lastAccessTime.remove(robotId); + + // 如果有连接ID,也一并清理 + if (removedUser != null && removedUser.getConnecId() != null) { + robotRoomMapping.remove(removedUser.getConnecId()); + lastAccessTime.remove(removedUser.getConnecId()); + } + + log.info("清理机器人房间信息: {}", robotId); + } + + /** + * 更新连接的最后访问时间 + */ + public static void updateLastAccessTime(String connecId) { + lastAccessTime.put(connecId, System.currentTimeMillis()); + } + + /** + * 清理超时的连接 + */ + public static void cleanupExpiredConnections() { + long currentTime = System.currentTimeMillis(); + List expiredConnections = new ArrayList<>(); + + for (Map.Entry entry : lastAccessTime.entrySet()) { + if (currentTime - entry.getValue() > CONNECTION_TIMEOUT) { + expiredConnections.add(entry.getKey()); + } + } + + for (String connecId : expiredConnections) { + RobotUser robotUser = robotRoomMapping.get(connecId); + if (robotUser != null) { + log.info("清理超时连接: {}, 机器人ID: {}", connecId, robotUser.getRobotId()); + robotConnectionManager.disconnectFromGameServer(connecId); + } + robotRoomMapping.remove(connecId); + lastAccessTime.remove(connecId); + } + + if (!expiredConnections.isEmpty()) { + log.info("本次清理了 {} 个超时连接", expiredConnections.size()); + } + } + + /** + * 检查Redis中房间是否包含指定机器人 + * @param roomId 房间ID + * @param robotId 机器人ID + * @return 是否包含该机器人 + */ + private boolean checkRobotInRoomRedis(String roomId, String robotId) { + Jedis jedis = Redis.use().getJedis(); + try { + //查询该房间的玩家信息 + String playersStr = jedis.hget("room:" + roomId, "players"); + if (playersStr == null || playersStr.equals("[]")) { + return false; + } + + String players = playersStr.substring(1, playersStr.length() - 1); + String[] playerIds = players.split(","); + + //检查是否包含该机器人 + int targetRobotId = Integer.parseInt(robotId); + for (String playerIdStr : playerIds) { + int playerId = Integer.parseInt(playerIdStr.trim()); + if (playerId == targetRobotId) { + return true; + } + } + return false; + } catch (Exception e) { + log.error("检查Redis房间玩家信息时发生错误,roomId: {}, robotId: {}", roomId, robotId, e); + return false; + } finally { + jedis.close(); + } + } + + /** + * 根据机器人ID和连接ID获取福禄寿游戏服务器连接 + * 基于robotId和connectionId的组合复用连接 + */ + public static TaurusClient getFlsGameServerConnection(String connecId) { + TaurusClient taurusClient = robotConnectionManager.getGameClient(connecId); + log.info("根据机器人ID和连接ID获取福禄寿游戏服务器连接 client: {}", taurusClient); + if (taurusClient != null) { + log.debug("成功获取游戏服务器连接,connecId: {}", connecId); + return taurusClient; + } + taurusClient = robotConnectionManager.connectToGameServer(connecId); + return taurusClient; + } + +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXMainServer.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXMainServer.java new file mode 100644 index 0000000..f8f1d65 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXMainServer.java @@ -0,0 +1,169 @@ +package robot.zp; + +import java.util.Map; + +import com.robot.GameController; +import com.robot.MainServer; +import com.robot.data.Player; +import com.robot.data.Room; +import com.taurus.core.plugin.redis.Redis; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.Jedis; +import robot.zp.info.RobotUser; +import robot.zp.thread.ResourceCleanupUtil; +import robot.zp.thread.ThreadPoolConfig; +import taurus.client.NetManager; + +import static robot.zp.EXGameController.robotRoomMapping; + +/** + * 福禄寿机器人主服务器 + * TCP服务端接收robot_mgr的协议 同时作为客户端连接game_zp_fls处理AI逻辑 + */ +public class EXMainServer extends MainServer{ + private static final Logger log = LoggerFactory.getLogger(EXMainServer.class); + + private static final RobotConnectionManager robotConnectionManager = new RobotConnectionManager(); + + @Override + public void onStart() { + super.onStart(); + + //JVM关闭钩子 + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + log.info("收到JVM关闭信号,开始优雅关闭..."); + try { + //关闭所有机器人连接 + for (Map.Entry entry : robotRoomMapping.entrySet()) { + RobotUser robotUser = entry.getValue(); + if (robotUser.getClient() != null && robotUser.getClient().isConnected()) { + robotConnectionManager.disconnectFromGameServer(robotUser.getConnecId()); + } + } + + //关闭线程池 + robot.zp.thread.ThreadPoolConfig.shutdown(); + + log.info("优雅关闭完成"); + } catch (Exception e) { + log.error("关闭过程中发生异常", e); + } + })); + + // 1. 先启动独立的事件处理线程(只启动一次) + startNetEventThread(); + + // 2. 启动资源清理定时任务 + startResourceCleanupScheduler(); + + // 3. 启动系统监控 + //startConnectionCheckScheduler(); + //测试 + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + String robotskey = "g{"+Config.DEFAULT_GROUP_ID+"}:play:"+Config.DEFAULT_PID; + Map maprobot = jedis2.hgetAll(robotskey); + for(Map.Entry entry : maprobot.entrySet()) { + log.info("{}:{}", entry.getKey(), entry.getValue()); + //是否创建 + RobotUser robotUser = new RobotUser(); + robotUser.setRobotId(entry.getKey()); + robotUser.setPassword(Config.DEFAULT_PASSWORD); + robotUser.setGameHost(Config.GAME_SERVER_HOST); + robotUser.setGamePort(Config.GAME_SERVER_PORT); + robotUser.setRobotGroupid(Config.DEFAULT_GROUP_ID); + robotUser.setRobotPid(Config.DEFAULT_PID); + + robotRoomMapping.put(entry.getKey(), robotUser); + } + + for(Map.Entry entry : robotRoomMapping.entrySet()) { + RobotUser robotUser = entry.getValue(); + //1、登录 + //判断是否登录 + if(!robotUser.isLogin){ + robotConnectionManager.login(robotUser); + } + } + + log.info("福禄寿机器人服务器已启动"); + log.info("服务器将监听端口 {} 用于接收robot_mgr管理协议", gameSetting.port); + log.info("当前线程池配置: {}", ThreadPoolConfig.getThreadPoolStatus()); + + jedis2.close(); + } + + /** + * 独立的事件处理线程 + */ + private void startNetEventThread() { + Thread eventThread = new Thread(() -> { + while (true) { + NetManager.processEvents(); + try { + Thread.sleep(2); + } catch (InterruptedException e) { + break; + } catch (Exception e) { + } + } + }, "Changsha_Thread"); + + eventThread.setDaemon(true); //设置为守护线程 + eventThread.start(); + } + + /** + * 启动资源清理定时任务 + */ + private void startResourceCleanupScheduler() { + Thread cleanupThread = new Thread(() -> { + while (true) { + try { + //每30秒执行一次资源清理 + Thread.sleep(30000); + ResourceCleanupUtil.performCleanup(); + log.info("线程池状态: {}", ThreadPoolConfig.getThreadPoolStatus()); + } catch (InterruptedException e) { + break; + } catch (Exception e) { + log.error("资源清理任务异常: {}", e.getMessage(), e); + // 发生异常时尝试清理 + try { + ResourceCleanupUtil.performCleanup(); + } catch (Exception cleanupEx) { + log.error("异常清理也失败: {}", cleanupEx.getMessage(), cleanupEx); + } + } + } + }, "ResourceCleanupThread"); + + cleanupThread.setDaemon(true); + cleanupThread.start(); + log.info("资源清理定时任务已启动"); + } + + + + @Override + public Room newRoom(String roomid, Map redis_room_map) { + return new EXRoom(roomid, redis_room_map); + } + + @Override + public Player newPlayer(int i, Room room, String s) { + return new EXPlayer(i, room, s); + } + + + protected GameController newController() { + return new EXGameController(); + } + + @Override + public void onStop() { + super.onStop(); + + log.info("福禄寿机器人服务器已停止"); + } +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXPlayer.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXPlayer.java new file mode 100644 index 0000000..fe596c7 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXPlayer.java @@ -0,0 +1,26 @@ +package robot.zp; + +import com.robot.data.Player; +import com.robot.data.Room; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * + */ +public class EXPlayer extends Player { + + + private static final Logger log = LoggerFactory.getLogger(EXPlayer.class); + + public EXPlayer(int playerid, Room table, String session_id) { + super(playerid, table, session_id); + log.info("new robot"); + } + + public EXRoom getRoom() { + return (EXRoom) room; + } + +} diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXRoom.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXRoom.java new file mode 100644 index 0000000..ee57e52 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/EXRoom.java @@ -0,0 +1,32 @@ +package robot.zp; + +import com.robot.data.Room; + + +import java.util.Map; + +public class EXRoom extends Room { + + public EXRoom(String roomid, Map redis_room_map) { + super(roomid, redis_room_map); + } + @Override + protected void roomResult() { + + } + @Override + public void endGame() { + super.endGame(); + } + + @Override + public void saveMilitaryTotal(boolean dissmiss) { + super.saveMilitaryTotal(dissmiss); + } + + @Override + public void clear() { + super.clear(); + } + +} diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/RobotConnectionManager.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/RobotConnectionManager.java new file mode 100644 index 0000000..d75dbe2 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/RobotConnectionManager.java @@ -0,0 +1,877 @@ +package robot.zp; + +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TArray; +import com.taurus.core.entity.TObject; +import com.taurus.core.events.Event; +import com.taurus.core.events.IEventListener; +import com.taurus.core.plugin.redis.Redis; +import com.taurus.core.util.ICallback; +import com.taurus.core.util.Logger; +import com.taurus.core.util.StringUtil; +import robot.zp.business.AccountBusiness; +import robot.zp.info.RobotUser; +import robot.zp.thread.ThreadPoolConfig; +import taurus.client.Message; +import taurus.client.MessageResponse; +import taurus.client.TaurusClient; +import taurus.client.SocketCode; +import redis.clients.jedis.Jedis; +import robot.zp.handler.FuLuShouHandler; +import taurus.util.ROBOTEventType; + +import java.util.*; +import java.util.concurrent.*; + +import static robot.zp.thread.ThreadPoolConfig.scheduleDelay; + +import static robot.zp.EXGameController.robotRoomMapping; + +/** + * 机器人连接管理器 - 管理与游戏服务器的连接 + */ +public class RobotConnectionManager { + + private final Logger log = Logger.getLogger(RobotConnectionManager.class); + private static final Map fuLuShouHandlerInstances = new ConcurrentHashMap<>(); + + //记录活跃连接 用于资源清理判断 + private static final Set activeConnections = ConcurrentHashMap.newKeySet(); + + //记录连接创建时间 + private static final Map connectionCreationTime = new ConcurrentHashMap<>(); + + //连接最大生存时间(5 分钟) + private static final long MAX_CONNECTION_LIFETIME = 5 * 60 * 1000; + private final EXGameController exGameController; + + private final String host= Config.GAME_SERVER_HOST; + private final int port= Integer.parseInt(Config.GAME_SERVER_PORT); + + /*福禄寿游戏算法相关 start*/ + private final Map>> playerOutcardsMapByConn = new ConcurrentHashMap<>(); + private final Map>> playerchisMapByConn = new ConcurrentHashMap<>(); + private final Map>> playerpengsMapByConn = new ConcurrentHashMap<>(); + private final Map>> playermingsMapByConn = new ConcurrentHashMap<>(); + private final Map>> playerzisMapByConn = new ConcurrentHashMap<>(); + + private Map> getPlayerOutcardsMap(String connecId) { + return playerOutcardsMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>()); + } + private Map> getPlayerchisMap(String connecId) { + return playerchisMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>()); + } + private Map> getPlayerpengsMap(String connecId) { + return playerpengsMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>()); + } + private Map> getPlayermingsMap(String connecId) { + return playermingsMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>()); + } + private Map> getPlayerzisMap(String connecId) { + return playerzisMapByConn.computeIfAbsent(connecId, k -> new ConcurrentHashMap<>()); + } + private int pid = 0; + private Map count = new HashMap(); + /*福禄寿游戏算法相关 end*/ + + + + + public RobotConnectionManager() { + exGameController = new EXGameController(); + } + + /** + * 获取福禄寿处理器实例 + */ + private FuLuShouHandler getFuLuShouHandlerInstance(String connecId) { + //标记连接为活跃状态 + activeConnections.add(connecId); + connectionCreationTime.put(connecId, System.currentTimeMillis()); + + //定期清理过期连接 + cleanupExpiredInstances(); + + FuLuShouHandler existingInstance = fuLuShouHandlerInstances.get(connecId); + if (existingInstance != null) { + return existingInstance; + } + + FuLuShouHandler newInstance = new FuLuShouHandler(); + log.info("创建新的 FuLuShouHandler 实例:{}", connecId); + + fuLuShouHandlerInstances.put(connecId, newInstance); + log.info("当前 FuLuShouHandler 实例总数:{}", fuLuShouHandlerInstances.size()); + return newInstance; + } + + /** + * 设置会话和令牌 + */ + public void setSessionAndToken(String session, String token, String connecId) { + FuLuShouHandler handler = getFuLuShouHandlerInstance(connecId); + handler.session = session; + handler.token = token; + } + + /** + * 连接到福禄寿游戏服务器 + */ + public TaurusClient connectToGameServer(String connecId) { + try { + //创建Taurus客户端 + TaurusClient client = new TaurusClient(host + ":" + port, "game", TaurusClient.ConnectionProtocol.Tcp); + + //设置事件监听器 + setupEventListeners(client, connecId); + + client.connect(); + + return client; + } catch (Exception e) { + log.error("连接到游戏服务器时发生异常", e); + return null; + } + } + + /** + * 断开与游戏服务器的连接(主动断开) + */ + public void disconnectFromGameServer(String connecId) { + log.info("开始主动断开连接:{}", connecId); + RobotUser robotUser = robotRoomMapping.remove(connecId); + + //标记连接为非活跃 + activeConnections.remove(connecId); + connectionCreationTime.remove(connecId); + + //清理连接数据 + if (connecId != null) { + FuLuShouHandler handler = fuLuShouHandlerInstances.get(connecId); + if (handler != null) { + //清理所有集合数据以释放内存 + handler.clearAllData(); + log.info("清空 FuLuShouHandler 集合数据:{}", connecId); + } + + //移除实例和相关数据 + fuLuShouHandlerInstances.remove(connecId); + playerOutcardsMapByConn.remove(connecId); + playerchisMapByConn.remove(connecId); + playerpengsMapByConn.remove(connecId); + playermingsMapByConn.remove(connecId); + playerzisMapByConn.remove(connecId); + + log.info("清理完成,当前活跃连接数:{}, 实例数:{}", activeConnections.size(), fuLuShouHandlerInstances.size()); + } + + if (robotUser != null) { + TaurusClient client = robotUser.getClient(); + if (client != null) { + try { + if (client.isConnected()) { + client.killConnection(); + } + log.info("客户端主动断开连接完成:{}", connecId); + } catch (Exception e) { + log.error("断开客户端连接时发生异常:{}, 错误:{}", connecId, e.getMessage(), e); + } + } else { + log.warn("客户端连接不存在:{}", connecId); + } + + //同时清理机器人房间映射 + EXGameController.removeRobotRoomInfo(robotUser.getRobotId()); + } + } + + /** + * 清理过期的实例 + */ + private void cleanupExpiredInstances() { + long currentTime = System.currentTimeMillis(); + List expiredConnections = new ArrayList<>(); + + //检查连接生存时间 + for (Map.Entry entry : connectionCreationTime.entrySet()) { + if (currentTime - entry.getValue() > MAX_CONNECTION_LIFETIME) { + expiredConnections.add(entry.getKey()); + } + } + + //清理过期连接 + for (String connecId : expiredConnections) { + log.info("清理过期连接实例:{}", connecId); + disconnectFromGameServer(connecId); + } + + if (!expiredConnections.isEmpty()) { + log.info("本次清理了 {} 个过期连接实例", expiredConnections.size()); + } + } + + /** + * 设置事件监听器 + */ + public void setupEventListeners(TaurusClient client, String connecId) { + //添加消息事件监听器 + IEventListener messageListener = new IEventListener() { + @Override + public void handleEvent(Event event) { + //获取 msg + Message message = (Message) event.getParameter("msg"); + + ITObject param = message.param; + //回调协议号 + String command = message.command; + log.debug("fls OnEvent msg: {}", command); + + //根据玩法ID处理不同的回调 + if (StringUtil.isNotEmpty(command)) { + //直接处理协议 + handleProtocol(command, message, client, connecId); + } + } + }; + + //添加连接状态监听器 + IEventListener connectListener = new IEventListener() { + @Override + public void handleEvent(Event event) { + Message message = (Message) event.getParameter("msg"); + SocketCode code = (SocketCode) event.getParameter("code"); + + } + }; + + //注册事件监听器 + client.addEventListener(TaurusClient.NetClientEvent.OnEvent, messageListener); + client.addEventListener(TaurusClient.NetClientEvent.Connect, connectListener); + } + + + /** + * 机器人断线重连 + */ + public void reconnectToGameServer(MessageResponse response, RobotUser robotUser, TaurusClient client) { + String connecId = robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId(); + if(client.isConnected()){ + try { + ITObject obj = response.messageData.param.getTObject("tableInfo"); + ITObject reloadInfo = response.messageData.param.getTObject("reloadInfo"); + if (obj != null) { + //处理 seat + //获取机器人的seat + ITArray playerData = obj.getTArray("playerData"); + for (int i = 0; i < playerData.size(); i++) { + ITObject tms = playerData.getTObject(i); + Integer tmuserid = tms.getInt("aid"); + if (tmuserid == Integer.parseInt(robotUser.getRobotId())) { + Integer seat = tms.getInt("seat"); + robotUser.setSeat(seat); + } + } + log.info("playerData: {}", playerData); + + log.info("obj: {}", obj); + log.info("reloadInfo: {}", reloadInfo); + if (reloadInfo != null) { + //重连回来的 + int curren_outcard_seat = reloadInfo.getInt("curren_outcard_seat"); + if (curren_outcard_seat == robotUser.getSeat()) { + //同步手牌 + ITArray hand_card = reloadInfo.getTArray("hand_card"); + ITArray info_list = reloadInfo.getTArray("info_list"); + + List hcard = new ArrayList<>(); + if (hand_card != null) { + for (int i = 0; i < hand_card.size(); i++) { + hcard.add(hand_card.getInt(i)); + } + } + ITArray outcard_list = new TArray(); + if (info_list != null) { + for (int i = 0; i < info_list.size(); i++) { + ITObject tms = info_list.getTObject(i); + Integer playerid = tms.getInt("playerid"); + if (playerid == Integer.parseInt(robotUser.getRobotId())) { + outcard_list = tms.getTArray("outcard_list"); + } + } + } + + log.info("hcard>0{}", hcard); + if (hcard.size() > 0) { + //同步手牌 + FuLuShouHandler currentInstance = getFuLuShouHandlerInstance(connecId); + + //同步逻辑比较手牌数量 + List currentHand = currentInstance.getChangShaCardInhand(); + if (currentHand.isEmpty() || hcard.size() > currentHand.size()) { + //手牌集合为空 或者 玩家出牌了 + currentInstance.updateHandCard(hcard); + log.info("断线重连:同步手牌数据,服务器手牌:{}", hcard); + } else { + log.info("断线重连:使用Redis恢复的手牌数据,数量:{}", currentHand.size()); + } + + if (outcard_list.size() > 0) { + List outcards = new ArrayList<>(); + for (int i = 0; i < outcard_list.size(); i++) { + outcards.add(outcard_list.getInt(i)); + } + + //检查出牌记录是否需要同步 + List currentOutCards = currentInstance.getChuGuoCardInhand(); + if (currentOutCards.isEmpty() || outcards.size() > currentOutCards.size()) { + currentInstance.updateOutCard(outcards); + log.info("断线重连:同步出牌数据,服务器出牌:{}", outcards); + } else { + log.info("断线重连:使用Redis恢复的出牌数据,数量:{}", currentOutCards.size()); + } + } + + //非阻塞的延迟执行,增加更完善的异常处理 + scheduleDelay(() -> { + try { + //重新获取当前实例,确保数据一致性 + FuLuShouHandler reconnectedInstance = getFuLuShouHandlerInstance(connecId); + Map> currentPlayerOutcardsMap = getPlayerOutcardsMap(connecId); + Map> currentPlayerchisMap = getPlayerchisMap(connecId); + Map> currentPlayerpengsMap = getPlayerpengsMap(connecId); + Map> currentPlayermingsMap = getPlayermingsMap(connecId); + Map> currentPlayerzisMap = getPlayerzisMap(connecId); + + reconnectedInstance.outCard(client, currentPlayerOutcardsMap, currentPlayerchisMap, currentPlayerpengsMap, currentPlayermingsMap, currentPlayerzisMap); + log.info("断线重连后成功执行出牌操作"); + } catch (Exception e) { + log.error("断线重连后执行出牌操作时发生异常", e); + //即使出牌失败,也要确保连接状态正确 + try { + if (robotUser != null) { + robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_READY); + } + } catch (Exception statusEx) { + log.error("更新机器人状态时发生异常", statusEx); + } + } + }, 2, TimeUnit.SECONDS); + } else { + log.warn("警告:重连时未获取到手牌数据"); + } + } + } + } + }catch (Exception e) { + log.error("机器人断线重连异常"); + } + }else { + renconnect(robotUser); + } + } + + /** + * 处理接收到的游戏协议 + * 福禄寿支持的协议: + * 核心流程:811(发牌), 819(摸牌), 812(出牌广播), 813(出牌提示), 814(放招提示), 612(动作), 611(出牌), 815(动作通知), 816(胡牌), 817(结算), 820(换玩家) + * 飘鸟系统:1015(飘操作), 833(飘鸟提示), 2031(飘鸟提示 reload), 2032(飘鸟事件) + * 房间相关:2001, 2002, 2005, 2008, 2009 + */ + private void handleProtocol(String command, Message message, TaurusClient client, String connecId) { + RobotUser robotUser = robotRoomMapping.get(connecId); + + //更新连接的最后访问时间 + EXGameController.updateLastAccessTime(connecId); + + if (robotUser == null) { + log.error("未找到机器人用户信息,连接ID: {}", connecId); + return; + } + + int robotId = Integer.parseInt(robotUser.getRobotId()); + ITObject param = message.param; + FuLuShouHandler handler = getFuLuShouHandlerInstance(connecId); + Jedis jedis0 = Redis.use().getJedis(); + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + try { + //福禄寿 机器人处理事件 + //初始化手牌 + if ("811".equalsIgnoreCase(command)) { + robotUser.setStatus(ROBOTEventType.ROBOT_INTOROOM_WORKING); + //初始化手牌 + String key = robotId+""; + if (jedis2.hget("{robortInfo}:" + key, "circleId") != null && jedis2.hget("{robortInfo}:" + key, "pid") != null) { + String circleId = jedis2.hget("{robortInfo}:" + key, "circleId"); + String pid = jedis2.hget("{robortInfo}:" + key, "pid"); + String getStart = "g{" + circleId + "}:play:" + pid; + if (!pid.equals("0")){ + jedis2.hset(getStart, key, "2"); + } + } + handler.initHandCards(message); + } + //出牌广播 + else if ("812".equalsIgnoreCase(command)) { + ITArray outcard_map = param.getTArray("outcard_map"); + ITArray opchicards = param.getTArray("opchicards"); + ITArray oppengcards = param.getTArray("oppengcards"); + ITArray opmingcards = param.getTArray("opmingcards"); + ITArray opzicards = param.getTArray("opzicards"); + + //获取当前连接专用的Maps + Map> currentPlayerOutcardsMap = getPlayerOutcardsMap(connecId); + Map> currentPlayerchisMap = getPlayerchisMap(connecId); + Map> currentPlayerpengsMap = getPlayerpengsMap(connecId); + Map> currentPlayermingsMap = getPlayermingsMap(connecId); + Map> currentPlayerzisMap = getPlayerzisMap(connecId); + + //清空旧数据 用新数据完全覆盖 + currentPlayerOutcardsMap.clear(); + currentPlayerchisMap.clear(); + currentPlayerpengsMap.clear(); + currentPlayermingsMap.clear(); + currentPlayerzisMap.clear(); + //出过的牌 + if (outcard_map != null) { + for (int i = 0; i < outcard_map.size(); i++) { + ITObject playerData = outcard_map.getTObject(i); + int playerId = playerData.getInt("playerId"); + ITArray outcardsArray = playerData.getTArray("outcards"); + + List outcardsList = new ArrayList<>(); + for (int j = 0; j < outcardsArray.size(); j++) { + outcardsList.add(outcardsArray.getInt(j)); + } + + //存储到当前连接的Map中(覆盖旧数据) + currentPlayerOutcardsMap.put(playerId, outcardsList); + } + } + + //吃的牌 + if (opchicards != null) { + for (int i = 0; i < opchicards.size(); i++) { + ITObject playerData = opchicards.getTObject(i); + int playerId = playerData.getInt("playerId"); + ITArray outchiArray = playerData.getTArray("opchicards"); + + List outchiList = new ArrayList<>(); + for (int j = 0; j < outchiArray.size(); j++) { + outchiList.add(outchiArray.getInt(j)); + } + currentPlayerchisMap.put(playerId, outchiList); + } + } + + //碰的牌 + if (oppengcards != null) { + for (int i = 0; i < oppengcards.size(); i++) { + ITObject playerData = oppengcards.getTObject(i); + int playerId = playerData.getInt("playerId"); + ITArray outpengArray = playerData.getTArray("oppengcards"); + + List outpengList = new ArrayList<>(); + for (int j = 0; j < outpengArray.size(); j++) { + outpengList.add(outpengArray.getInt(j)); + } + currentPlayerpengsMap.put(playerId, outpengList); + } + } + + //明杠的牌 + if (opmingcards != null) { + for (int i = 0; i < opmingcards.size(); i++) { + ITObject playerData = opmingcards.getTObject(i); + int playerId = playerData.getInt("playerId"); + ITArray outmingArray = playerData.getTArray("opmingcards"); + + List outmingList = new ArrayList<>(); + for (int j = 0; j < outmingArray.size(); j++) { + outmingList.add(outmingArray.getInt(j)); + } + currentPlayermingsMap.put(playerId, outmingList); + } + } + + //暗杠的牌 + if (opzicards != null) { + for (int i = 0; i < opzicards.size(); i++) { + ITObject playerData = opzicards.getTObject(i); + int playerId = playerData.getInt("playerId"); + ITArray outziArray = playerData.getTArray("opzicards"); + + List outziList = new ArrayList<>(); + for (int j = 0; j < outziArray.size(); j++) { + outziList.add(outziArray.getInt(j)); + } + currentPlayerzisMap.put(playerId, outziList); + } + } + + handler.onDiscardBroadcast(message); + } + //摸牌 + else if ("819".equalsIgnoreCase(command)) { + handler.drawCard(message); + } + //出牌提示 + else if ("813".equalsIgnoreCase(command)) { + //获取当前连接的 Maps + Map> currentPlayerOutcardsMap = getPlayerOutcardsMap(connecId); + Map> currentPlayerchisMap = getPlayerchisMap(connecId); + Map> currentPlayerpengsMap = getPlayerpengsMap(connecId); + Map> currentPlayermingsMap = getPlayermingsMap(connecId); + Map> currentPlayerzisMap = getPlayerzisMap(connecId); + + handler.makeDiscardDecision(client, currentPlayerOutcardsMap, currentPlayerchisMap, currentPlayerpengsMap, currentPlayermingsMap, currentPlayerzisMap); + } + //放招提示 + else if ("814".equalsIgnoreCase(command)) { + handler.actionTip(param, client); + } + //飘操作 + else if ("1015".equalsIgnoreCase(command)) { + log.info("收到飘操作协议:{}", param); + } + //2026.02.03修改 玩家加入房间 + else if ("2001".equalsIgnoreCase(command)) { + //直接使用定时任务替代Thread.sleep,避免嵌套异步调用 + scheduleDelay(() -> { + Jedis jedis = Redis.use().getJedis(); + try { + String roomKey = String.valueOf(robotUser.getCurrentRoomId()); + + //查询该房间的玩家信息 + String playersStr = jedis.hget("room:"+roomKey, "players"); + if (playersStr != null && !playersStr.equals("[]")) { + String players = playersStr.substring(1, playersStr.length() - 1); + String[] playerIds = players.split(","); + + //判断只有当前机器人一个玩家 + if (playerIds.length == 1) { + int playerId = Integer.parseInt(playerIds[0].trim()); + if (playerId == robotId) { + //发送退出房间协议 + ITObject params = TObject.newInstance(); + client.send("1005", params, response -> { + EXGameController.removeRobotRoomInfo(String.valueOf(robotId)); + //更新机器人剩余数量 + updateLeftoverRobot(robotId); + disconnectFromGameServer(connecId); + log.info("2001发送退出房间协议1005,robotId: {}", robotId); + }); + } + } + } + } catch (Exception e) { + log.error("处理玩家加入房间检查时发生异常", e); + } finally { + // 确保Jedis连接关闭 + if (jedis != null) { + jedis.close(); + } + } + }, 6, TimeUnit.SECONDS); + log.info("玩家{}加入房间:{}", robotUser.getCurrentRoomId(), param); + } + //2026.02.03修改 玩家退出房间也要检查 + else if ("2002".equalsIgnoreCase(command)) { + //直接使用定时任务替代Thread.sleep,避免嵌套异步调用 + scheduleDelay(() -> { + Jedis jedis = Redis.use().getJedis(); + try { + String roomKey = String.valueOf(robotUser.getCurrentRoomId()); + + //查询该房间的玩家信息 + String playersStr = jedis.hget("room:"+roomKey, "players"); + if (playersStr != null && !playersStr.equals("[]")) { + String players = playersStr.substring(1, playersStr.length() - 1); + String[] playerIds = players.split(","); + + //判断只有当前机器人一个玩家 + if (playerIds.length == 1) { + int playerId = Integer.parseInt(playerIds[0].trim()); + if (playerId == robotId) { + //发送退出房间协议 + ITObject params = TObject.newInstance(); + client.send("1005", params, response -> { + EXGameController.removeRobotRoomInfo(String.valueOf(robotId)); + //更新机器人剩余数量 + updateLeftoverRobot(robotId); + disconnectFromGameServer(connecId); + log.info("2002发送退出房间协议1005,robotId: {}", robotId); + }); + } + } + } + } catch (Exception e) { + log.error("处理玩家退出房间检查时发生异常", e); + } finally { + if (jedis != null) { + jedis.close(); + } + } + }, 6, TimeUnit.SECONDS); + } + //2026.02.05修改 玩家解散房间 + else if ("2005".equalsIgnoreCase(command)) { + EXGameController.removeRobotRoomInfo(String.valueOf(robotId)); + //更新机器人剩余数量 + updateLeftoverRobot(robotId); + disconnectFromGameServer(connecId); + log.info("2005玩家发送解散房间协议,robotId: {}", robotId); + } + //2026.02.03修改 解散房间时候恢复机器人账号可以使用 + else if ("2008".equalsIgnoreCase(command)) { + updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId())); + disconnectFromGameServer(connecId); + } + //2026.02.03修改 通过机器人房间映射直接获取房间信息 + else if ("2009".equalsIgnoreCase(command)) { + //直接使用定时任务替代Thread.sleep,避免嵌套异步调用 + scheduleDelay(() -> { + Jedis jedis = null; + try { + jedis = Redis.use().getJedis(); + Integer paramRobotId = param.getInt("aid"); + if (robotUser != null && paramRobotId != null) { + String roomKey = String.valueOf(robotUser.getCurrentRoomId()); + + //查询该房间的玩家信息 + String playersStr = jedis.hget(roomKey, "players"); + if (playersStr != null && !playersStr.equals("[]")) { + String players = playersStr.substring(1, playersStr.length() - 1); + String[] playerIds = players.split(","); + + //判断只有当前机器人一个玩家 + if (playerIds.length == 1) { + int playerId = Integer.parseInt(playerIds[0].trim()); + if (playerId == paramRobotId) { + String gpid = jedis.hget(roomKey, "gpid"); + + //更新机器人剩余数量 + if (gpid != null && count != null && count.containsKey(Integer.parseInt(gpid))) { + Integer currentValue = count.get(Integer.parseInt(gpid)); + if (currentValue != null && currentValue > 0) { + count.put(Integer.parseInt(gpid), currentValue - 1); + } + } + + //发送退出房间协议 + ITObject params = TObject.newInstance(); + client.send("1005", params, response -> { + EXGameController.removeRobotRoomInfo(String.valueOf(paramRobotId)); + //断开连接 + disconnectFromGameServer(connecId); + //更新机器人剩余数量 + updateLeftoverRobot(paramRobotId); + log.info("2009发送退出房间协议1005,robotId: {}", paramRobotId); + }); + } + } + } + } + } catch (NumberFormatException e) { + log.error("2009协议数字格式异常,robotId: {}, connecId: {}", param.get("aid"), connecId); + } catch (NullPointerException e) { + log.error("2009协议空指针异常,connecId: {}", connecId); + } catch (Exception e) { + log.error("2009协议处理异常: {}, connecId: {}", e.getMessage(), connecId, e); + } finally { + if (jedis != null) { + jedis.close(); + } + } + }, 6, TimeUnit.SECONDS); + } + //结算 + else if ("817".equalsIgnoreCase(command)) { + //清空所有 FuLuShouHandler 相关的集合数据 + handler.clearAllData(); + + Integer type = param.getInt("type"); + if (type == 1 || type == 2) { //为 1 为大结算 为 2 为解散 + if (count != null && count.containsKey(pid)) { + Integer currentValue = count.get(pid); + if (currentValue > 0) { + count.put(pid, currentValue - 1); + } + } + //更新机器人剩余数量 + updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId())); + + //游戏结束后主动断开连接 + disconnectFromGameServer(connecId); + } + ITObject params = TObject.newInstance(); + params.putString("session", client.getSession()); + client.send("1003", params, new ICallback() { + @Override + public void action(MessageResponse messageResponse) { + + } + }); + } + //服务器通知客户端有玩家执行了操作 + else if ("815".equalsIgnoreCase(command)) { + handler.onPlayerAction(param); + } + //飘鸟提示 + else if ("833".equalsIgnoreCase(command)) { + handler.piaoNiaoTip(); + } + //飘鸟提示 reload + else if ("2031".equalsIgnoreCase(command)) { + log.info("收到飘鸟提示 reload: {}", param); + } + //飘鸟事件 + else if ("2032".equalsIgnoreCase(command)) { + log.info("收到飘鸟事件:{}", param); + } + //2001-2009 房间相关协议保持原有逻辑 + } catch (Exception e) { + log.error("处理接收到的游戏协议异常:{}, command: {}", e.getMessage(), command); + } finally { + if (jedis0 != null) { + jedis0.close(); + } + if (jedis2 != null) { + jedis2.close(); + } + } + } + + /** + * 增加 leftover_robot 数量 机器人退出房间 + */ + private void updateLeftoverRobot(int robotId) { + Jedis jedis2 = Redis.use("group1_db2").getJedis(); + try { + + jedis2.hset("gallrobot", String.valueOf(robotId), "0"); + + jedis2.hset("{grobot}:" + robotId, "start", "0"); + + log.info("机器人 {} 退出房间,修改gallrobot为0", robotId); + } finally { + jedis2.close(); + } + } + + /** + * 机器人登录 + */ + public void login(RobotUser robotUser){ + log.info("login:{}", robotUser.getRobotId()); + ITObject object = null; + AccountBusiness accountBusiness = null; + accountBusiness = new AccountBusiness(); + try { + //先快速登录 + object = accountBusiness.fastLogin(Integer.parseInt(robotUser.getRobotId())); + log.info("object:{}", object); + if(object==null){ + object = accountBusiness.idPasswordLogin(Integer.parseInt(robotUser.getRobotId()), robotUser.getPassword()); + } + ITObject finalObject = object; + CompletableFuture.runAsync(() -> { + if (finalObject != null) { + //判断是否有房间 + if(finalObject.getTObject("account")!=null){ + ITObject validate = TObject.newInstance(); + validate.putString("token", finalObject.getString("token")); + robotUser.setToken(finalObject.getString("token"));; + robotUser.setLoginsession("{user}:"+robotUser.getRobotId()); + if (robotUser.getLoginsession() != null) { + robotUser.setIsLogin(true); + } + if(finalObject.getTObject("account").get("roomid")!=null){ + String roomid = finalObject.getTObject("account").get("roomid").toString(); + robotUser.setCurrentRoomId(Integer.parseInt(roomid)); + connectGame(robotUser); + + robotUser.setConnecId(robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId()); + log.info("重启获取的机器人还有当前房间,准备加入: {}", robotUser.getConnecId()); + exGameController.webGroupJoinRoom(robotUser); + } + } + } + }, ThreadPoolConfig.getBusinessThreadPool()); //指定自定义线程池 + } catch (Exception e) { + log.error("机器人登录异常"); + } + } + + public void connectGame(RobotUser robotUser){ + if(robotUser.isLogin){ + if(robotUser.getClient()==null){ + TaurusClient client = new TaurusClient(robotUser.getGameHost()+":"+robotUser.getGamePort(), "cm"+robotUser.getRobotId(), TaurusClient.ConnectionProtocol.Tcp); + client.setSession(robotUser.getLoginsession()); + client.connect(); + setupEventListeners(client, robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId()); + robotUser.setIsconnect(client.isConnected()); + try { + Thread.sleep(1000); + } catch (Exception e) { + log.error("连接游戏服务器时发生异常", e); + } + robotUser.setClient(client); + EXGameController.robotRoomMapping.put(robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId(), robotUser); + }else{ + log.info("reconnect"); + log.info("client.isConnected(){}", robotUser.getClient().isConnected()); + if(robotUser.getClient().isConnected()){ + robotUser.setIsconnect(true); + }else{ + log.info("reconnect{}", robotUser.getClient().getGameID()); + TaurusClient client = new TaurusClient(robotUser.getGameHost()+":"+robotUser.getGamePort(), "cm"+robotUser.getRobotId(), TaurusClient.ConnectionProtocol.Tcp); + client.setSession(robotUser.getLoginsession()); + client.connect(); + robotUser.setIsconnect(client.isConnected()); + try { + Thread.sleep(1000); + } catch (Exception e) { + log.error("重新连接游戏服务器时发生异常", e); + } + robotUser.setClient(client); + EXGameController.robotRoomMapping.put(robotUser.getCurrentRoomId()+"_"+robotUser.getRobotId(), robotUser); + } + } + } + } + + /** + * 重连 + */ + public void renconnect(RobotUser robotUser){ + TaurusClient client = robotUser.getClient(); + if(client!=null){ + if(client.isConnected()){ + client.connect(); + robotUser.setIsconnect(client.isConnected()); + } + } + } + + /** + * 根据connecId获取游戏服务器连接 + */ + public TaurusClient getGameClient(String connecId) { + return robotRoomMapping.get(connecId) != null ? robotRoomMapping.get(connecId).getClient() : null; + } + + + public int getTime(){ + return Integer.parseInt((System.currentTimeMillis() + "").substring(0, 10)); + } + + public static void sleepTime(int time) { + try { + //添加延迟 + Thread.sleep(time); + } catch (InterruptedException e) { + } + } + +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/business/AccountBusiness.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/business/AccountBusiness.java new file mode 100644 index 0000000..eab7670 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/business/AccountBusiness.java @@ -0,0 +1,291 @@ +package robot.zp.business; + +import com.data.bean.AccountBean; +import com.data.bean.GameBean; +import com.data.cache.AccountCache; +import com.data.cache.BaseCache; +import com.data.cache.GameCache; +import com.data.util.Utility; +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TArray; +import com.taurus.core.entity.TObject; +import com.taurus.core.plugin.database.DataBase; +import com.taurus.core.plugin.redis.Redis; +import com.taurus.core.plugin.redis.RedisLock; +import com.taurus.core.util.Logger; +import com.taurus.core.util.StringUtil; +import com.taurus.core.util.Utils; +import com.taurus.web.Controller; +import redis.clients.jedis.Jedis; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class AccountBusiness extends Controller { + private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(AccountBusiness.class); + private static Logger logger = Logger.getLogger(AccountBusiness.class); + + private ITObject fillLoginData(String session, int accountid) { + ITObject resData = TObject.newInstance(); + ITObject userData = TObject.newInstance(); + resData.putTObject("account", userData); + resData.putUtfString("session_id", session); + resData.putTArray("games", getOnlineGames()); + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + try { + Map map = jedis0.hgetAll(session); + userData.putInt("id", accountid); + userData.putInt("diamo", Integer.parseInt(map.get("diamo"))); + userData.putUtfString("nick", map.get("nick")); + userData.putUtfString("portrait", map.get("portrait")); + userData.putInt("sex", Integer.parseInt(map.get("sex"))); + userData.putInt("type", Integer.parseInt(map.get("type"))); + int mng = Integer.parseInt(map.get("mng")); + userData.putInt("mng", mng); + + String phone = map.get("phone"); + if (StringUtil.isNotEmpty(phone)) { + userData.putUtfString("phone", phone); + } + + String address = map.get("address"); + if (StringUtil.isNotEmpty(address)) { + userData.putUtfString("address", address); + } + + String real_info = map.get("real_info"); + if (StringUtil.isNotEmpty(real_info)) { + userData.putTObject("real_info", TObject.newFromJsonData(real_info)); + } + String oldRoom = Utility.getOldRoomV2(jedis0, 0, session, accountid); + if (StringUtil.isNotEmpty(oldRoom)) { + String roomid = oldRoom.replace("room:", ""); + String group = jedis0.hget(oldRoom, "group"); + int groupId = 0; + if (StringUtil.isNotEmpty(group)) { + groupId = Integer.parseInt(group); + } + userData.putUtfString("roomid", roomid); + userData.putInt("groupId", groupId); + } + } finally { + jedis0.close(); + } + + resData.putUtfString("groupWeb", Redis.use("group1_db1").hget("web_requrl", "groupWeb_jefe")); + return resData; + } + + public final ITObject fastLogin(int userid) { + Jedis jedis = Redis.use("group1_db0").getJedis(); + ITObject resData = null; + try { + Set usertoken = jedis.smembers("{user}:"+userid+"_token"); + if (usertoken.size()<=0){ + return null; + } + String token = ""; + for (String item : usertoken) { + token = item; + } + String session ="{user}:"+userid; + + + AccountBean acc_bean = AccountCache.getAccount(session); + resData = fillLoginData(session, acc_bean.id); + String idPwdBan = Redis.use("group1_db0").get(acc_bean.id+"_login_ban"); + if (StringUtil.isNotEmpty(idPwdBan)) + { + logger.error("id:"+acc_bean.id+" ban login"); + //throw new WebException(ErrorCode.BAN_LOGIN); + } + resData.putString("token", token); + return resData; + }catch (Exception e){ + + }finally { + jedis.close(); + } + + return resData; + + } + + + public final ITObject idPasswordLogin(int id, String password) { + logger.info("id:" + id + " login"); + + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + RedisLock lock = new RedisLock("wx_" + id, jedis0); + try { + + logger.info("==========> password111 = " + password); + String superPwd = Redis.use("group1_db1").get("superpwd2021"); + String sql = ""; + if (!StringUtil.isEmpty(superPwd)) { + if (!password.equals(superPwd)) { + password = Utils.getMD5Hash(password); + sql = String.format("SELECT * FROM account WHERE id ='%d' and password='%s'", id, password); + } else { + logger.info("==========> password = " + password); + + sql = String.format("SELECT * FROM account WHERE id ='%d' ", id); + } + } else { + password = Utils.getMD5Hash(password); + sql = String.format("SELECT * FROM account WHERE id ='%d' and password='%s'", id, password); + } + + + String idPwdBan = Redis.use("group1_db0").get(id + "_login_ban"); + if (StringUtil.isNotEmpty(idPwdBan)) { + logger.info("进入了77777777777777777777"); + logger.error("id:" + id + " ban login"); + //throw new WebException(ErrorCode.BAN_LOGIN); + } + logger.info("进入了9999999999999"); + + ITArray resultArray = null; + try { + resultArray = DataBase.use().executeQueryByTArray(sql); + } catch (SQLException e) { + log.error(e); + } + if (resultArray.size() == 0) { + if (Redis.use("group1_db0").exists(id + "_pwd_token")) { + Redis.use("group1_db0").incrBy(id + "_pwd_token", 1); + } else { + Redis.use("group1_db0").set(id + "_pwd_token", 1 + ""); + Redis.use("group1_db0").expire(id + "_pwd_token", 300); + } + + String idPwdToken = Redis.use("group1_db0").get(id + "_pwd_token"); + if (StringUtil.isNotEmpty(idPwdToken)) { + long count = Long.parseLong(idPwdToken); + if (count >= 10) { + Redis.use("group1_db0").set(id + "_login_ban", "1"); + Redis.use("group1_db0").expire(id + "_login_ban", 1800); + logger.error("pwd error count:" + count + " not login"); + logger.info("进入了00000000000"); + + //throw new WebException(ErrorCode._NO_SESSION); + + } + } + logger.info("进入了111111111111"); + + //throw new WebException(ErrorCode._FAILED); + } + + ITObject userData = resultArray.getTObject(0); + int accountid = userData.getInt("id"); + UpdateUserData(userData, accountid); + + AccountBean acc_bean = AccountCache.getAccount(accountid); + String session = acc_bean.redis_key; + this.setSession(session); + + if (resultArray.size() > 0) { + this.setSession(session); + } + + ITObject resData = fillLoginData(session, accountid); + String token = Utils.getMD5Hash(id + "_" + password + "_" + System.currentTimeMillis() + "e4!Fesu]]{QyUuEA" + + Math.random() * 1000000); + Redis.use("group1_db0").sadd(session + "_token", token); + + Redis.use("group1_db0").hset(token, "user", session); + Redis.use("group1_db0").hset(token, "create_time", "" + System.currentTimeMillis() / 1000); + Redis.use("group1_db0").expire(token, 172800); + + logger.info("进入了2222222222222"); + + long tokenNum = Redis.use("group1_db0").scard(session + "_token"); + if (tokenNum >= 10) { + logger.warn("id:" + accountid + " repeat login, token count:" + tokenNum); + } + logger.info("进入了33333333333333333332"); + + resData.putString("token", token); + return resData; + } finally { + lock.unlock(); + } + } + + private static String updateSession(ITObject userData, int id) { + String session = AccountCache.genKey(id); + Map map = new HashMap(); + Utils.objectToMap(userData, map); + + Jedis jedis0 = Redis.use("group1_db0").getJedis(); + try { + jedis0.hmset(session, map); + BaseCache.updateCacheVer(jedis0, session); + } finally { + jedis0.close(); + } + + return session; + } + + /** + * 获取在线游戏 + */ + public static ITArray getOnlineGames() { + ITArray games = new TArray(); + Jedis jedis1 = Redis.use("group1_db1").getJedis(); + try { + Set list = jedis1.zrevrangeByScore("online_games", 1000, 1); + for (String game : list) { + int gameId = Integer.parseInt(game); + GameBean gb = GameCache.getGame(gameId); + if (gb == null) + continue; + ITObject gameObj = gb.getTObject(); + + for (Entry entry : gb.pay.entrySet()) { + gameObj.putInt(entry.getKey(), entry.getValue()); + } + games.addTObject(gameObj); + } + } finally { + jedis1.close(); + } + return games; + } + + /** + * + * @return + */ + private int UpdateUserData(ITObject reqData, long id) { + ITObject userData = TObject.newInstance(); + userData.putInt("id", (int) id); + + userData.putUtfString("acc", reqData.getUtfString("acc")); + userData.putUtfString("portrait", reqData.getUtfString("portrait")); + userData.putUtfString("nick", reqData.getUtfString("nick")); + int sex = reqData.getInt("sex"); + if (sex == 0) { + sex = 1; + reqData.putInt("sex", sex); + } + userData.putInt("sex", sex); + + userData.putInt("mng", 0); + userData.putInt("type", 0); + if (reqData.containsKey("diamo")) { + userData.putInt("diamo", reqData.getInt("diamo")); + } + + userData.putInt("invitation", 1); + String session = updateSession(userData, (int) id); + this.setSession(session); + return (int) id; + } +} diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/handler/FuLuShouHandler.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/handler/FuLuShouHandler.java new file mode 100644 index 0000000..73d9aaf --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/handler/FuLuShouHandler.java @@ -0,0 +1,342 @@ +package robot.zp.handler; + +import com.taurus.core.entity.ITArray; +import com.taurus.core.entity.ITObject; +import com.taurus.core.entity.TArray; +import com.taurus.core.entity.TObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import taurus.client.Message; +import taurus.client.TaurusClient; +import taurus.util.CardUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * 福禄寿游戏算法处理器 + * 专门处理福禄寿麻将的游戏逻辑和算法 + */ +public class FuLuShouHandler { + private static final Logger log = LoggerFactory.getLogger(FuLuShouHandler.class); + + //手牌 + private final List handCards = new ArrayList<>(); + + //出过的牌 + private final List outCards = new ArrayList<>(); + + //碰的牌 + private final List pongGroup = new ArrayList<>(); + + //吃的牌 + private final List chowGroup = new ArrayList<>(); + + //杠的牌 + private final List gangGroup = new ArrayList<>(); + + //会话标识 + public String session = ""; + // 访问令牌 + public String token = ""; + + // 当前操作牌 + private int currentCard = 0; + + /** + * 获取手牌 + */ + public List getHandCards() { + return handCards; + } + + /** + * 获取出过的牌 + */ + public List getOutCards() { + return outCards; + } + + /** + * 获取碰的牌 + */ + public List getPongGroup() { + return pongGroup; + } + + /** + * 获取吃的牌 + */ + public List getChowGroup() { + return chowGroup; + } + + /** + * 获取杠的牌 + */ + public List getGangGroup() { + return gangGroup; + } + + /** + * 获取手牌 + */ + public List getChangShaCardInhand() { + return handCards; + } + + /** + * 获取出过的牌 + */ + public List getChuGuoCardInhand() { + return outCards; + } + + /** + * 初始化手牌 (协议 811) + */ + public void initHandCards(Message message) { + ITObject param = message.param; + if (param == null) { + return; + } + + ITArray cardList = param.getTArray("card_list"); + handCards.clear(); + + for (int i = 0; i < cardList.size(); i++) { + handCards.add(cardList.getInt(i)); + } + + log.info("福禄寿初始化手牌:{} 张", handCards.size()); + log.debug("手牌详情:{}", handCards); + } + + /** + * 摸牌处理 (协议 819) + */ + public void drawCard(Message message) { + ITObject param = message.param; + if (param == null) { + return; + } + + int drawnCard = param.getInt("card"); + if (drawnCard > 0) { + handCards.add(drawnCard); + currentCard = drawnCard; + log.info("福禄寿摸牌:{}", drawnCard); + log.debug("当前手牌数量:{}", handCards.size()); + } + } + + /** + * 出牌广播处理 (协议 812) + */ + public void onDiscardBroadcast(Message message) { + ITObject param = message.param; + if (param == null) { + return; + } + + currentCard = param.getInt("card"); + log.debug("出牌广播:card={}", currentCard); + } + + /** + * 动作提示处理 (协议 814) + * 处理吃、碰、杠、胡等决策 + */ + public void actionTip(ITObject param, TaurusClient client) { + ITArray tipList = param.getTArray("tip_list"); + if (tipList == null || tipList.size() == 0) { + return; + } + + log.info("收到动作提示,tip_list 数量:{}", tipList.size()); + + // 优先处理胡牌 + for (int i = 0; i < tipList.size(); i++) { + TObject tip = (TObject) tipList.get(i).getObject(); + int type = tip.getInt("type"); + int id = tip.getInt("id"); + + if (type == 6) { // 胡牌 + ITObject params = TObject.newInstance(); + params.putString("session", session + "," + token); + params.putInt("qi", 0); + params.putInt("id", id); + + delayedAction(client, params, "胡牌"); + return; + } + } + + // TODO: 实现福禄寿专用的吃碰杠决策算法 + // 这里需要根据福禄寿的规则来实现评分系统 + + // 默认过 + ITObject params = TObject.newInstance(); + params.putString("session", session + "," + token); + params.putInt("qi", 0); + params.putInt("id", 0); + delayedAction(client, params, "默认过"); + } + + /** + * 同步手牌 + */ + public void updateHandCard(List handCard) { + log.info("updateHandCard 同步手牌:{}", handCard); + handCards.clear(); + handCards.addAll(handCard); + log.info("updateHandCard 同步手牌完成,数量:{}", handCards.size()); + } + + /** + * 同步出牌 + */ + public void updateOutCard(List outCard) { + outCards.clear(); + outCards.addAll(outCard); + log.info("updateOutCard 同步出牌完成,数量:{}", outCards.size()); + } + + /** + * 出牌决策 (协议 813) - 兼容旧方法名 + */ + public void outCard(TaurusClient client, + Map> playerOutcardsMap, + Map> playerchisMap, + Map> playerpengsMap, + Map> playermingsMap, + Map> playerzisMap) { + makeDiscardDecision(client, playerOutcardsMap, playerchisMap, playerpengsMap, playermingsMap, playerzisMap); + } + public void makeDiscardDecision(TaurusClient client, + Map> playerOutcardsMap, + Map> playerchisMap, + Map> playerpengsMap, + Map> playermingsMap, + Map> playerzisMap) { + if (handCards.isEmpty()) { + log.warn("手牌为空,无法出牌"); + return; + } + + //TODO: 实现福禄寿专用的出牌算法 + //需要分析其他玩家的出牌、吃碰杠情况 + + //临时:选择第一张牌 + int cardToOut = handCards.get(0); + + ITObject params = TObject.newInstance(); + params.putInt("card", cardToOut); + + //添加历史出牌 + if (!outCards.isEmpty()) { + List cardsToSend = new ArrayList<>(outCards); + params.putTArray("outcard_list", CardUtil.maJiangToTArray(cardsToSend)); + } + + params.putTArray("card_list", CardUtil.maJiangToTArray(handCards)); + params.putString("session", session + "," + token); + + // 记录出牌 + outCards.add(cardToOut); + handCards.remove(Integer.valueOf(cardToOut)); + + log.info("福禄寿出牌:{}", cardToOut); + log.debug("剩余手牌:{}", handCards); + + // 延迟发送,模拟思考时间 + delayedDiscard(client, params); + } + + /** + * 玩家动作通知处理 (协议 815) + */ + public void onPlayerAction(ITObject param) { + Integer card = param.getInt("card"); + Integer type = param.getInt("type"); + Integer playerId = param.getInt("playerid"); + + log.debug("玩家动作通知:playerId={}, type={}, card={}", playerId, type, card); + + // 根据类型更新牌型记录 + if (type == 2) { // 碰 + pongGroup.add(card); + pongGroup.add(card); + pongGroup.add(card); + } else if (type == 1) { // 吃 + chowGroup.add(card); + } else if (type == 3 || type == 4 || type == 5) { // 杠 + gangGroup.add(card); + gangGroup.add(card); + gangGroup.add(card); + gangGroup.add(card); + } + } + + /** + * 飘鸟提示处理 (协议 833) + */ + public void piaoNiaoTip() { + log.info("收到飘鸟提示"); + //TODO: 实现飘鸟决策逻辑 + } + + /** + * 清理所有数据 + */ + public void clearAllData() { + handCards.clear(); + outCards.clear(); + pongGroup.clear(); + chowGroup.clear(); + gangGroup.clear(); + currentCard = 0; + log.info("福禄寿处理器数据已清空"); + } + + /** + * 延迟执行动作 + */ + private void delayedAction(TaurusClient client, ITObject params, String actionName) { + Thread thread = new Thread(() -> { + try { + int delaySeconds = 1 + new Random().nextInt(2); + log.info("执行{}动作,延迟{}秒", actionName, delaySeconds); + Thread.sleep(delaySeconds * 1000); + + client.send("612", params, response -> { + log.info("{}动作发送完成", actionName); + }); + } catch (Exception e) { + log.error("执行{}动作时发生异常:{}", actionName, e.getMessage(), e); + } + }); + thread.start(); + } + + /** + * 延迟出牌 + */ + private void delayedDiscard(TaurusClient client, ITObject params) { + Thread thread = new Thread(() -> { + try { + int delay = new Random().nextInt(4); + Thread.sleep(delay * 1000); + + client.send("611", params, response -> { + log.debug("出牌发送完成"); + }); + } catch (Exception e) { + log.error("出牌时发生异常:{}", e.getMessage(), e); + } + }); + thread.start(); + } +} diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/info/RobotUser.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/info/RobotUser.java new file mode 100644 index 0000000..dd6de18 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/info/RobotUser.java @@ -0,0 +1,164 @@ +package robot.zp.info; + +import taurus.client.TaurusClient; + +/** + * 机器人房间信息类 + */ +public class RobotUser { + private String connecId; + private int userId; + private String robotId; + + private int seat; + public int status; //工作状态 0,1:等待,2:干活 + public boolean isconnect = false; //是否连接上 + public int intoRoomTime; //进入房间时间戳 + public String password; + public String gameHost; + public String gamePort; + public String robotGroupid; + public String robotPid; + public boolean isLogin = false; + private String token; + private String loginsession; + public int currentRoomId;//当前房间id + public TaurusClient client = null; + + public TaurusClient getClient() { + return client; + } + + public void setClient(TaurusClient client) { + this.client = client; + } + + public int getCurrentRoomId() { + return currentRoomId; + } + + public void setCurrentRoomId(int currentRoomId) { + this.currentRoomId = currentRoomId; + } + + public String getLoginsession() { + return loginsession; + } + + public void setLoginsession(String loginsession) { + this.loginsession = loginsession; + } + + public boolean getIsLogin() { + return isLogin; + } + + public void setIsLogin(boolean login) { + isLogin = login; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getGameHost() { + return gameHost; + } + + public void setGameHost(String gameHost) { + this.gameHost = gameHost; + } + + public String getGamePort() { + return gamePort; + } + + public void setGamePort(String gamePort) { + this.gamePort = gamePort; + } + + public String getRobotGroupid() { + return robotGroupid; + } + + public void setRobotGroupid(String robotGroupid) { + this.robotGroupid = robotGroupid; + } + + public String getRobotPid() { + return robotPid; + } + + public void setRobotPid(String robotPid) { + this.robotPid = robotPid; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public int getIntoRoomTime() { + return intoRoomTime; + } + + public void setIntoRoomTime(int intoRoomTime) { + this.intoRoomTime = intoRoomTime; + } + + public boolean getIsconnect() { + return isconnect; + } + + public void setIsconnect(boolean isconnect) { + this.isconnect = isconnect; + } + + public int getSeat() { + return seat; + } + + public void setSeat(int seat) { + this.seat = seat; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getConnecId() { + return connecId; + } + + public void setConnecId(String connecId) { + this.connecId = connecId; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public String getRobotId() { + return robotId; + } + + public void setRobotId(String robotId) { + this.robotId = robotId; + } + +} diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/thread/ResourceCleanupUtil.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/thread/ResourceCleanupUtil.java new file mode 100644 index 0000000..106891e --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/thread/ResourceCleanupUtil.java @@ -0,0 +1,72 @@ +package robot.zp.thread; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import robot.zp.EXGameController; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 资源清理工具类 + * 用于管理和清理不再使用的资源 防止内存泄漏 + */ +public class ResourceCleanupUtil { + private static final Logger log = LoggerFactory.getLogger(ResourceCleanupUtil.class); + + //需要清理的资源 + private static final Set pendingCleanupResources = ConcurrentHashMap.newKeySet(); + + /** + * 执行资源清理 + * 清理已完成对局但仍在内存中的资源 + */ + public static void performCleanup() { + if (pendingCleanupResources.isEmpty()) { + //执行常规清理 + performRegularCleanup(); + return; + } + + log.info("开始执行资源清理,待清理资源数: {}", pendingCleanupResources.size()); + int cleanedCount = 0; + + Set resourcesToClean = ConcurrentHashMap.newKeySet(); + resourcesToClean.addAll(pendingCleanupResources); + + for (String resourceId : resourcesToClean) { + try { + //从待清理列表中移除 + pendingCleanupResources.remove(resourceId); + cleanedCount++; + + log.info("已清理资源: {}", resourceId); + } catch (Exception e) { + log.error("清理资源时发生异常: {}, 错误: {}", resourceId, e.getMessage(), e); + } + } + + log.info("资源清理完成,共清理: {} 个资源", cleanedCount); + + //执行常规清理 + performRegularCleanup(); + } + + /** + * 执行常规清理 + */ + private static void performRegularCleanup() { + try { + //清理过期的机器人连接 + EXGameController.cleanupExpiredConnections(); + + //输出当前系统状态 + log.info("=== 系统资源状态 ==="); + log.info("{}", ThreadPoolConfig.getThreadPoolStatus()); + + } catch (Exception e) { + log.error("执行常规清理时发生异常: {}", e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/thread/ThreadPoolConfig.java b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/thread/ThreadPoolConfig.java new file mode 100644 index 0000000..2511401 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/robot/zp/thread/ThreadPoolConfig.java @@ -0,0 +1,117 @@ +package robot.zp.thread; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 线程池配置类 + */ +public class ThreadPoolConfig { + + private static final Logger log = LoggerFactory.getLogger(ThreadPoolConfig.class); + //线程池配置 + private static final ExecutorService BUSINESS_THREAD_POOL = + new ThreadPoolExecutor( + 5, //核心线程数 + 20, //最大线程数 + 60, //空闲线程存活时间 + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(5000), + new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "RobotBusinessThread-" + threadNumber.getAndIncrement()); + t.setDaemon(true); + t.setPriority(Thread.NORM_PRIORITY - 1); + return t; + } + }, + new ThreadPoolExecutor.CallerRunsPolicy() + ); + + //添加定时任务线程池 + private static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = + new ScheduledThreadPoolExecutor(2, new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "RobotScheduledThread-" + threadNumber.getAndIncrement()); + t.setDaemon(true); + t.setPriority(Thread.NORM_PRIORITY - 1); + return t; + } + }); + + public static ExecutorService getBusinessThreadPool() { + return BUSINESS_THREAD_POOL; + } + + public static ScheduledExecutorService getScheduledExecutorService() { + return SCHEDULED_EXECUTOR_SERVICE; + } + + /** + * 执行延迟任务,替代Thread.sleep + */ + public static void scheduleDelay(Runnable task, long delay, TimeUnit unit) { + log.debug("提交延迟任务: 延迟{} {}, 当前时间: {}", delay, unit, System.currentTimeMillis()); + SCHEDULED_EXECUTOR_SERVICE.schedule(() -> { + try { + log.debug("执行延迟任务开始: 当前时间: {}", System.currentTimeMillis()); + task.run(); + log.debug("执行延迟任务完成: 当前时间: {}", System.currentTimeMillis()); + } catch (Exception e) { + log.error("延迟任务执行异常: {}", e.getMessage(), e); + } + }, delay, unit); + } + + /** + * 关闭线程池 释放资源 + */ + public static void shutdown() { + log.info("开始关闭线程池..."); + + //关闭定时任务线程池 + SCHEDULED_EXECUTOR_SERVICE.shutdown(); + try { + if (!SCHEDULED_EXECUTOR_SERVICE.awaitTermination(3, TimeUnit.SECONDS)) { + log.info("定时任务线程池强制关闭"); + SCHEDULED_EXECUTOR_SERVICE.shutdownNow(); + } + } catch (InterruptedException e) { + SCHEDULED_EXECUTOR_SERVICE.shutdownNow(); + Thread.currentThread().interrupt(); + } + + //关闭业务线程池 + BUSINESS_THREAD_POOL.shutdown(); + try { + if (!BUSINESS_THREAD_POOL.awaitTermination(5, TimeUnit.SECONDS)) { + log.info("业务线程池强制关闭"); + BUSINESS_THREAD_POOL.shutdownNow(); + } + } catch (InterruptedException e) { + BUSINESS_THREAD_POOL.shutdownNow(); + Thread.currentThread().interrupt(); + } + + log.info("线程池关闭完成"); + } + + /** + * 获取线程池状态信息 + */ + public static String getThreadPoolStatus() { + ThreadPoolExecutor executor = (ThreadPoolExecutor) BUSINESS_THREAD_POOL; + return String.format("线程池状态 - 核心:%d, 活跃:%d, 完成:%d, 队列:%d", + executor.getCorePoolSize(), + executor.getActiveCount(), + executor.getCompletedTaskCount(), + executor.getQueue().size()); + } +} diff --git a/robots/zhipai/robot_zp_fls/src/main/java/taurus/util/FuLuShouSuanFa.java b/robots/zhipai/robot_zp_fls/src/main/java/taurus/util/FuLuShouSuanFa.java new file mode 100644 index 0000000..7f63d4a --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/taurus/util/FuLuShouSuanFa.java @@ -0,0 +1,4 @@ +package taurus.util; + +public class FuLuShouSuanFa { +} diff --git a/robots/zhipai/robot_zp_fls/src/main/java/taurus/util/ROBOTEventType.java b/robots/zhipai/robot_zp_fls/src/main/java/taurus/util/ROBOTEventType.java new file mode 100644 index 0000000..d242292 --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/main/java/taurus/util/ROBOTEventType.java @@ -0,0 +1,13 @@ +package taurus.util; + +public class ROBOTEventType { + /** + * 机器人状态 + */ + public static final int ROBOT_INTOROOM_READY = 1;//等待状态 + public static final int ROBOT_INTOROOM_WORKING = 2;//工作状态 + public static final int ROBOT_UNUSE = 0;//未使用状态 + + + +} diff --git a/robots/zhipai/robot_zp_fls/src/test/java/robot_zp_fulushou/Main.java b/robots/zhipai/robot_zp_fls/src/test/java/robot_zp_fulushou/Main.java new file mode 100644 index 0000000..c2d06cb --- /dev/null +++ b/robots/zhipai/robot_zp_fls/src/test/java/robot_zp_fulushou/Main.java @@ -0,0 +1,15 @@ +package robot_zp_fulushou; + +import com.taurus.permanent.TPServer; + +public class Main { + public static void main(String[] args) { + System.out.println("启动福禄寿机器人服务器..."); + System.out.println("服务器将监听端口8707用于游戏协议"); + + //启动机器人服务 + TPServer.me().start(); + + System.out.println("福禄寿机器人服务器已启动"); + } +} \ No newline at end of file