红中机器人 http协议

master^2
miaoqingshuai 2026-06-17 20:04:36 +08:00
parent f22e96c4d4
commit a24b061c0f
4 changed files with 312 additions and 15 deletions

View File

@ -29,6 +29,9 @@ public class Config {
/** Web组加入房间协议 */
public static final String WEB_GROUP_JOIN_ROOM = "225";
public static final String WEB_GROUP_JOIN_ROOM11 = "285";
/** Web组主动重连协议 */
public static final String WEB_GROUP_ACTIVE_RECONNECT = "226";
@ -36,11 +39,11 @@ public class Config {
/** 游戏服务器主机地址 */
/*public static final String GAME_SERVER_HOST = "8.134.76.43";
public static final String DEFAULT_GROUP_ID = "762479";*/
public static final String DEFAULT_GROUP_ID = "383709";
public static final String GAME_SERVER_HOST = "8.163.97.101";
public static final String DEFAULT_GROUP_ID = "426149";
public static final String GAME_SERVER_HOST = "8.138.162.178";
/** 游戏服务器端口 */
public static final String GAME_SERVER_PORT = "26421";
public static final String GAME_SERVER_PORT = "16421";
/** 默认密码 */
public static final String DEFAULT_PASSWORD = "123456";
@ -48,5 +51,13 @@ public class Config {
/** 默认PID */
public static final String DEFAULT_PID = "22";
/**
* 机器人HTTP服务端口(接收 web_group 通过 HTTP 发送的 225 协议)
* 规则:TCP端口 + 1000(TCP=8722,HTTP=9722)
*/
public static final int HTTP_SERVER_PORT = 9722;
/** 机器人HTTP服务路径 */
public static final String HTTP_PATH_JOIN_ROOM = "/robot/joinRoom";
}

View File

@ -229,8 +229,10 @@ public class EXGameController extends GameController {
}
}
/**
* 接收来自web_group的加入房间协议
* 接收来自web_group的加入房间协议(TCP 入口,保留原有行为)
* 核心逻辑已提取到 processWebGroupJoin,供 HTTP 入口复用
*/
@ActionKey(value = Config.WEB_GROUP_JOIN_ROOM, validate = GameInterceptor.NOT_PLAYER)
public void webGroup(Session session, ITObject params, int gid) {
@ -238,6 +240,32 @@ public class EXGameController extends GameController {
String roomId = params.getString("roomid");
int groupId = params.getInt("groupid");
ITObject result = processWebGroupJoin(robotId, roomId, groupId, params);
// 仅在"ID冲突"场景下回错误响应,其他场景保持原有静默行为不变
int code = result.containsKey("code") ? result.getInt("code") : 0;
if (code == 1) {
ITObject errorResponse = TObject.newInstance();
errorResponse.putString("status", "failed");
errorResponse.putString("message", result.containsKey("message") ? result.getString("message") : "处理失败");
MainServer.instance.sendResponse(gid, 1, errorResponse, session);
}
}
/**
* 处理 web_group 加入房间请求 - 核心逻辑(与传输方式无关)
* 供 TCP 入口(webGroup)和 HTTP 入口(RobotHttpServer)共用
*
* @param robotId 机器人ID
* @param roomId 房间ID
* @param groupId 群组ID
* @param params 原始参数
* @return ITObject 处理结果,包含:
* code (0=成功, 1=ID冲突需告知调用方, 2=不同机器人冲突静默忽略)
* message (描述信息)
*/
public ITObject processWebGroupJoin(int robotId, String roomId, int groupId, ITObject params) {
ITObject result = TObject.newInstance();
String lockKey = "room_lock:" + roomId;
synchronized (lockKey.intern()) {
if (checkRobotInRoomRedis(roomId, String.valueOf(robotId))) {
@ -249,11 +277,9 @@ public class EXGameController extends GameController {
} else {
if (isPlayerIdConflictInRoom(roomId, robotId)) {
log.warn("检测到机器人{"+robotId+"}与房间{"+roomId+"}中的真人玩家 ID 冲突,拒绝加入");
ITObject errorResponse = TObject.newInstance();
errorResponse.putString("status", "failed");
errorResponse.putString("message", "机器人 ID 与房间内玩家冲突");
MainServer.instance.sendResponse(gid, 1, errorResponse, session);
return;
result.putInt("code", 1);
result.putString("message", "机器人 ID 与房间内玩家冲突");
return result;
}
}
}
@ -281,7 +307,9 @@ public class EXGameController extends GameController {
if (robotId != existingRobotId) {
//不同机器人的冲突
log.warn("房间{"+roomId+"}中Redis已存在机器人{"+existingRobotId+"},当前机器人{"+robotId+"}不执行加入逻辑");
return;
result.putInt("code", 2);
result.putString("message", "Redis已存在其他机器人,不执行加入逻辑");
return result;
}
}
}
@ -291,8 +319,76 @@ public class EXGameController extends GameController {
joinRoomCommon(robotId, roomId, groupId, params);
log.info("225 已进入房间准备成功:room:{"+roomId+"} robot:{"+robotId+"}");
}
result.putInt("code", 0);
result.putString("message", "success");
return result;
}
// /**
// * 接收来自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");
//
// String lockKey = "room_lock:" + roomId;
// synchronized (lockKey.intern()) {
// if (checkRobotInRoomRedis(roomId, String.valueOf(robotId))) {
// log.info("机器人{"+robotId+"}已在房间{"+roomId+"}中(Redis 中存在),直接允许加入");
// } else {
// RobotUser existingRobotUser = getRobotRoomInfo(String.valueOf(robotId));
// if (existingRobotUser != null && existingRobotUser.getCurrentRoomId() == Integer.parseInt(roomId)) {
// log.info("机器人{"+robotId+"}已在房间{"+roomId+"}中(本地映射存在),直接允许加入");
// } else {
// if (isPlayerIdConflictInRoom(roomId, robotId)) {
// log.warn("检测到机器人{"+robotId+"}与房间{"+roomId+"}中的真人玩家 ID 冲突,拒绝加入");
// ITObject errorResponse = TObject.newInstance();
// errorResponse.putString("status", "failed");
// errorResponse.putString("message", "机器人 ID 与房间内玩家冲突");
// MainServer.instance.sendResponse(gid, 1, errorResponse, session);
// return;
// }
// }
// }
//
// //检查Redis中该房间是否真的包含当前机器人
// if (!checkRobotInRoomRedis(roomId, String.valueOf(robotId))) {
// //Redis中不存在该机器人 清理本地可能的错误映射
// List<RobotUser> robotUsers = getRobotUsersByRoomId(Integer.parseInt(roomId));
// if (!robotUsers.isEmpty()) {
// synchronized (robotUsers) {
// RobotUser robotUser = robotUsers.get(0);
// log.warn("房间{"+roomId+"}中Redis未找到机器人{"+robotId+"},但本地映射存在{"+robotUser.getRobotId()+"},清理本地映射");
// robotRoomMapping.remove(robotUser.getConnecId());
// robotRoomMapping.remove(robotUser.getRobotId());
// }
// }
// } else {
// //Redis中存在该机器人 检查是否是不同机器人的冲突
// List<RobotUser> 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("房间{"+roomId+"}中Redis已存在机器人{"+existingRobotId+"},当前机器人{"+robotId+"}不执行加入逻辑");
// return;
// }
// }
// }
// }
// log.info("225 开始进房间:room:{"+roomId+"} robot:{"+robotId+"}");
// //加入房间
// joinRoomCommon(robotId, roomId, groupId, params);
// log.info("225 已进入房间准备成功:room:{"+roomId+"} robot:{"+robotId+"}");
// }
// }
/**
* 接收来自web_group的主动重连协议
*/

View File

@ -17,19 +17,28 @@ import taurus.client.NetManager;
import static robot.mj.EXGameController.robotRoomMapping;
/**
* 红中麻将机器人主服务器
* TCP服务端接收robot_mgr的协议 同时作为客户端连接game_mj_cs处理AI逻辑
*/
public class EXMainServer extends MainServer{
private static final Logger log = LoggerFactory.getLogger(EXMainServer.class);
private static final RobotConnectionManager robotConnectionManager = new RobotConnectionManager();
/** 机器人HTTP服务(接收 web_group 通过 HTTP 发送的 225 协议,替代TCP异步方式) */
private RobotHttpServer robotHttpServer;
@Override
public void onStart() {
super.onStart();
// 启动机器人HTTP服务(接收 web_group 通过 HTTP 发送的 225 协议)
// 替代原 TCP 异步多线程方式(TaurusClient+CompletableFuture),降低CPU占用
try {
robotHttpServer = new RobotHttpServer();
robotHttpServer.start(Config.HTTP_SERVER_PORT);
} catch (Exception e) {
log.error("启动 RobotHttpServer 失败,端口:" + Config.HTTP_SERVER_PORT, e);
}
//JVM关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("收到JVM关闭信号,开始优雅关闭...");
@ -163,6 +172,12 @@ public class EXMainServer extends MainServer{
public void onStop() {
super.onStop();
// 停止机器人HTTP服务
if (robotHttpServer != null) {
robotHttpServer.stop();
robotHttpServer = null;
}
log.info("红中麻将机器人服务器已停止");
}
}

View File

@ -0,0 +1,175 @@
package robot.mj;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.google.gson.Gson;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.taurus.core.entity.ITObject;
import com.taurus.core.entity.TObject;
import com.taurus.core.util.Logger;
import com.robot.Global;
public class RobotHttpServer {
private static final Logger log = Logger.getLogger(RobotHttpServer.class);
/** 工作线程数(同时处理的并发请求数) */
private static final int WORKER_THREADS = 8;
/** HTTP 响应超时(用于优雅关闭,秒) */
private static final int STOP_DELAY = 2;
private HttpServer server;
private ExecutorService executor;
private final Gson gson = new Gson();
/**
* 启动 HTTP 服务
* @param port 监听端口
* @throws IOException 端口被占用等异常
*/
public void start(int port) throws IOException {
server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext(Config.HTTP_PATH_JOIN_ROOM, new JoinRoomHandler());
executor = Executors.newFixedThreadPool(WORKER_THREADS);
server.setExecutor(executor);
server.start();
log.info("RobotHttpServer已启动,监听端口:" + port + ",路径:" + Config.HTTP_PATH_JOIN_ROOM + ",工作线程:" + WORKER_THREADS);
}
/**
* 停止 HTTP 服务
*/
public void stop() {
if (server != null) {
server.stop(STOP_DELAY);
server = null;
}
if (executor != null) {
executor.shutdown();
try {
if (!executor.awaitTermination(STOP_DELAY, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
executor = null;
}
log.info("RobotHttpServer已停止");
}
/**
* 处理 /robot/joinRoom 请求的 Handler
*/
private class JoinRoomHandler implements HttpHandler {
@Override
public void handle(HttpExchange exchange) throws IOException {
// 仅接受 POST
if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) {
writeJsonResponse(exchange, 405, buildErrorResponse("Method Not Allowed", "只支持POST请求"));
return;
}
Map<String, Object> respData = new HashMap<>();
try {
// 读取请求体
String body = readRequestBody(exchange);
log.info("HTTP 收到加入房间请求:" + body);
// 解析 JSON
@SuppressWarnings("unchecked")
Map<String, Object> data = gson.fromJson(body, Map.class);
if (data == null) {
writeJsonResponse(exchange, 400, buildErrorResponse("Bad Request", "请求体为空或非合法JSON"));
return;
}
Number robotIdNum = (Number) data.get("robotid");
Object roomIdObj = data.get("roomid");
Number groupIdNum = (Number) data.get("groupid");
if (robotIdNum == null || roomIdObj == null || groupIdNum == null) {
writeJsonResponse(exchange, 400, buildErrorResponse("Bad Request", "缺少必要参数 robotid/roomid/groupid"));
return;
}
int robotId = robotIdNum.intValue();
String roomId = String.valueOf(roomIdObj);
int groupId = groupIdNum.intValue();
// 构造 ITObject 参数(与原 TCP 入参保持一致)
ITObject params = TObject.newInstance();
params.putInt("robotid", robotId);
params.putString("roomid", roomId);
params.putInt("groupid", groupId);
// 调用公共处理逻辑(与 TCP webGroup 方法共用)
ITObject result = ((EXGameController) Global.gameCtr).processWebGroupJoin(robotId, roomId, groupId, params);
int code = result.containsKey("code") ? result.getInt("code") : 0;
String message = result.containsKey("message") ? result.getString("message") : "success";
respData.put("code", code);
respData.put("message", message);
} catch (Exception e) {
log.error("HTTP处理加入房间请求异常", e);
respData.put("code", 500);
respData.put("message", "服务器内部错误: " + e.getMessage());
}
writeJsonResponse(exchange, 200, respData);
}
/**
* 读取请求体为字符串
*/
private String readRequestBody(HttpExchange exchange) throws IOException {
InputStream in = exchange.getRequestBody();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) > 0) {
baos.write(buf, 0, n);
}
in.close();
return new String(baos.toByteArray(), StandardCharsets.UTF_8);
}
/**
* 写入 JSON 响应
*/
private void writeJsonResponse(HttpExchange exchange, int httpCode, Map<String, Object> data) throws IOException {
String json = gson.toJson(data);
byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8");
exchange.sendResponseHeaders(httpCode, bytes.length);
OutputStream out = exchange.getResponseBody();
out.write(bytes);
out.close();
}
/**
* 构造错误响应
*/
private Map<String, Object> buildErrorResponse(String error, String message) {
Map<String, Object> data = new HashMap<>();
data.put("code", -1);
data.put("error", error);
data.put("message", message);
return data;
}
}
}