Просмотр исходного кода

Merge branch 'dev' of http://43.226.57.217:3000/yishanyou/chuanzhanServer into dev

jeson_fxd 2 недель назад
Родитель
Сommit
3fd1ad2b7c

+ 260 - 0
docs/多区配置指南.md

@@ -0,0 +1,260 @@
+# 服务器多区配置指南
+
+> 本文档说明如何在本项目架构基础上新增游戏区服(多区)。
+
+---
+
+## 一、架构概览
+
+### 1.1 服务器节点类型
+
+| 节点名称 | 作用 | 部署数量 |
+|----------|------|----------|
+| **NameServer** | 全局命名/协调节点,管理所有ServiceNode | 全局1个 |
+| **PublicNode** | 公共服务节点(账号、公会、支付、好友、聊天、管理后台等) | 全局1个 |
+| **GameNode** | **游戏服节点**(每个区一个),包含Logic、Connector、Arena等 | 每区1个 |
+| **AccountNode** | 账号服务节点(可独立部署或在PublicNode中) | 1个 |
+
+### 1.2 关键概念
+
+- **区服 = GameNode**:一个 GameNode 对应一个游戏区。玩家在不同区之间数据完全隔离。
+- **serverID**:区的唯一数字标识,贯穿所有配置。
+- **Realm**:大区/领域概念,用于环境隔离(dev/qa/prod),与区服ID不同。
+- **Connector端口**:每个区的客户端连接端口不同(如19821、19822...),这是客户端区分不同区的入口。
+
+---
+
+## 二、现有配置
+
+### 2.1 测试服当前只有一个区
+
+`start/_launch_server.xml` 中 GameNode1 配置:
+
+| 配置项 | 当前值 |
+|--------|--------|
+| serverID | 1 |
+| Connector端口 | 19821 |
+| Redis DB | db=1 |
+| RPC端口 | 17020 |
+
+### 2.2 已有参考:config_dev_2 是第二个区的完整配置
+
+项目已有 `config_dev_2` 目录,展示了**独立部署第二个区**的完整方案(不同服务器IP、独立端口)。
+
+---
+
+## 三、开新区操作步骤
+
+在**同一台服务器**上增加新区(从1区扩展为2区),需要在 `_launch_server.xml` 中新增一个 `GameNode2` 配置。
+
+### 3.1 修改 `_launch_server.xml`
+
+在 `<ServiceNodes>` 内已有 `<GameNode1>` 之后,添加 `<GameNode2>`。
+
+以下是模板(参考 `start/_launch_server.xml` 中已有的 GameNode2 配置):
+
+```xml
+<!-- 游戏服节点 2区 -->
+<GameNode2>
+  <Redis>127.0.0.1,password=你的Redis密码,allowAdmin=true,syncTimeout=30000,responseTimeout=30000,connectRetry=1000,connectTimeout=10000;db=2</Redis>
+  <Mysql>server=127.0.0.1;User ID=root;Password=你的MySQL密码;database=orm</Mysql>
+  <Ip>127.0.0.1</Ip>
+  <RpcConfig>
+    <LocalNodeType>GameNode</LocalNodeType>
+    <LocalNodeName>GameNode2</LocalNodeName>
+    <LocalEndPoint>127.0.0.1:17021</LocalEndPoint>           <!-- RPC端口 +1 避免冲突 -->
+    <NameServerEndPoint>127.0.0.1:17000</NameServerEndPoint>
+    <RequestTickTimeMS>5000</RequestTickTimeMS>
+    <NetworkTimeoutMS>30000</NetworkTimeoutMS>
+    <DefaultTaskExecuteTimeout>60000</DefaultTaskExecuteTimeout>
+    <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+    <AcceptTypeMappings>
+      <CenterService>OpenCards.Service.Center.CenterService</CenterService>
+      <AccountServer>OpenCards.Server.Account.AccountServer</AccountServer>
+      <ConnectorService>OpenCards.Server.Connector.ConnectorService</ConnectorService>
+      <ArenaManagerService>OpenCards.Server.Arena.ArenaManagerService</ArenaManagerService>
+      <ArenaValorService>OpenCards.Server.Arena.ArenaValorService</ArenaValorService>
+      <ArenaHighendService>OpenCards.Server.Arena.ArenaHighendService</ArenaHighendService>
+      <SessionService>OpenCards.Server.Connector.SessionService</SessionService>
+      <LogicService>OpenCards.Server.Logic.LogicService</LogicService>
+      <LogicManagerService>OpenCards.Server.Logic.LogicManagerService</LogicManagerService>
+    </AcceptTypeMappings>
+  </RpcConfig>
+  <StartService>
+    <!-- LogicManagerService -->
+    <LogicManagerService>
+      <ServiceName>LogicManagerService_2</ServiceName>       <!-- 名称改为 _2 -->
+      <ServiceType>LogicManagerService</ServiceType>
+      <Config>
+        <FightPostURL>http://127.0.0.1:8088/fight/reqstartbattle</FightPostURL>
+        <serverID>2</serverID>                                <!-- ⚠️ 区ID改为 2 -->
+      </Config>
+    </LogicManagerService>
+    <!-- Center -->
+    <CenterService>
+      <ServiceName>CenterService_2</ServiceName>              <!-- 名称改为 _2 -->
+      <ServiceType>CenterService</ServiceType>
+      <Config>
+        <serverID>2</serverID>                                <!-- ⚠️ 区ID改为 2 -->
+      </Config>
+    </CenterService>
+    <!-- ArenaManager -->
+    <ArenaManagerService>
+      <ServiceName>ArenaManagerService_2</ServiceName>        <!-- 名称改为 _2 -->
+      <ServiceType>ArenaManagerService</ServiceType>
+      <Config>
+        <ServerID>2</ServerID>                                <!-- ⚠️ 区ID改为 2 -->
+      </Config>
+    </ArenaManagerService>
+    <!-- ArenaHighend -->
+    <ArenaHighendService>
+      <ServiceName>ArenaHighendService_2</ServiceName>        <!-- 名称改为 _2 -->
+      <ServiceType>ArenaHighendService</ServiceType>
+      <Config></Config>
+    </ArenaHighendService>
+    <!-- Connector -->
+    <ConnectorService>
+      <ServiceName>ConnectorService_2</ServiceName>           <!-- 名称改为 _2 -->
+      <ServiceType>ConnectorService</ServiceType>
+      <Config>
+        <ServerId>2</ServerId>                                <!-- ⚠️ 区ID改为 2 -->
+        <Host>0.0.0.0</Host>
+        <Port>19822</Port>                                    <!-- ⚠️ 端口换一个,不能和1区冲突 -->
+        <NetCodec>OpenCards.Core.Serializer</NetCodec>
+        <KeepAlive>true</KeepAlive>
+        <KeepAliveInterval>30000</KeepAliveInterval>
+        <RecvBufferSize>16384</RecvBufferSize>
+        <SendBufferSize>16384</SendBufferSize>
+        <MaxConnections>300000</MaxConnections>
+      </Config>
+    </ConnectorService>
+  </StartService>
+</GameNode2>
+```
+
+### 3.2 关键配置变更清单
+
+新增一个区需要改以下值(**确保不与其他区冲突**):
+
+| 配置项 | 位置 | 1区值 | 2区值 | 说明 |
+|--------|------|-------|-------|------|
+| **Redis DB** | `GameNode/Redis` | `db=1` | `db=2` | 不同区用不同Redis DB隔离数据 |
+| **RPC端口** | `GameNode/RpcConfig/LocalEndPoint` | `17020` | `17021` | 节点间RPC通信端口 |
+| **Connector端口** | `ConnectorService/Config/Port` | `19821` | `19822` | **客户端连接端口**,每区唯一 |
+| **serverID** | `LogicManagerService`, `CenterService`, `ArenaManagerService`, `ConnectorService` | `1` | `2` | 区ID,必须全局唯一 |
+| **ServiceName** | 各Service的ServiceName | `xxx_1` | `xxx_2` | 服务实例名称后缀 |
+
+### 3.3 修改启动脚本
+
+在 `start/start.sh` 的 `cmd_start()` 中增加 GameNode2 的启动:
+
+```bash
+start_node "NameServer"
+start_node "PublicNode"
+start_node "GameNode1" "global.RealmID=1 global.ServerID=1"
+start_node "GameNode2" "global.RealmID=1 global.ServerID=2"    # 新增
+start_node "AccountNode1"
+```
+
+同时在 `cmd_stop()` 中增加停止(反序):
+
+```bash
+cmd_stop() {
+    stop_node "AccountNode1"
+    stop_node "GameNode2"      # 新增
+    stop_node "GameNode1"
+    stop_node "PublicNode"
+    stop_node "NameServer"
+    ...
+}
+```
+
+Windows 下则新建 `start/3_launch_game_r1s2.bat`:
+
+```bat
+title gameserver2
+dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml GameNode2 global.RealmID=1 global.ServerID=2
+```
+
+### 3.4 更新服务器列表
+
+客户端通过 `AccountServer` 从 OSS 下载 `serverlist.json` 来显示区服列表。需要在 OSS 上的 `serverlist.json` 中增加2区的条目:
+
+```json
+[
+  {
+    "id": 1,
+    "index": 0,
+    "name": "测试服1区",
+    "address": "47.109.111.123:19821",
+    "state": 1,
+    "is_open": true,
+    "capacity": 2000,
+    "serverid": 1,
+    "groupid": 1,
+    "note": "测试服"
+  },
+  {
+    "id": 2,
+    "index": 1,
+    "name": "测试服2区",
+    "address": "47.109.111.123:19822",
+    "state": 1,
+    "is_open": true,
+    "capacity": 2000,
+    "serverid": 2,
+    "groupid": 1,
+    "note": "测试服"
+  }
+]
+```
+
+> 注意 `address` 中的端口要对应 `ConnectorService` 的 `Port`(19822)。
+
+### 3.5 检查防火墙/安全组
+
+确保新增的端口(如 `19822`、`17021`)在防火墙和安全组中已放行。
+
+---
+
+## 四、注意事项
+
+### 4.1 数据隔离
+
+- **Redis**:每个区使用不同的 `db` 编号(db=1, db=2, ...),或者在配置中使用独立的 Redis 实例。
+- **MySQL**:当前配置中 GameNode 共用同一个 MySQL `database=orm`,内部通过 `serverID` 字段区分数据。如果需要物理隔离,可以为每个区创建独立数据库。
+
+### 4.2 公共服务是共享的
+
+`PublicNode` 中的服务(公会GuildService、好友FriendService、支付PayServer等)是**跨区共享**的,不需要为每个区单独部署。它们内部通过 `serverID` 来区分不同区的数据。
+
+### 4.3 战斗服
+
+战斗服(`server_battle` 目录)是独立部署的,多区可以共用同一个战斗服,或每个区部署独立战斗服。
+
+### 4.4 GM命令与多区
+
+管理后台(`AdminService`)的GM命令需要指定 `server_list`(目标serverID)来精确定位操作哪个区。
+
+### 4.5 独立物理机部署多区
+
+如果要在**不同服务器**上部署新区(如 `config_dev_2` 的方案),需要:
+- 修改 `_launch_server.xml` 中的IP地址为各服务器的实际IP
+- NameServer 的地址需要在所有节点的 `NameServerEndPoint` 中保持一致
+- 确保各服务器间网络互通
+
+---
+
+## 五、快速操作总结
+
+1. **复制 `GameNode1` 配置块** → 粘贴为 `GameNode2`
+2. **修改4个关键值**:serverID→2,Connector端口→19822,RPC端口→17021,Redis DB→2
+3. **添加启动脚本**:`start.sh` 增加 GameNode2 行 / Windows 新建 `.bat`
+4. **更新 serverlist.json**:添加2区信息到 OSS
+5. **放行端口**:防火墙开通 19822、17021
+6. **重启服务**:先停服,再按序启动
+
+---
+
+> 文档生成时间:2026年6月11日
+> 基于当前 `start/_launch_server.xml` 和 `config_dev_2` 配置分析

+ 1 - 0
start/1_launch_name_server.bat

@@ -0,0 +1 @@
+dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml NameServer

+ 1 - 0
start/2_launch_PublicNode.bat

@@ -0,0 +1 @@
+dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml PublicNode

+ 2 - 0
start/3_launch_game_r1s1.bat

@@ -0,0 +1,2 @@
+title gameserver
+dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml GameNode1 global.RealmID=1 global.ServerID=1

+ 2 - 0
start/4_launch_account.bat

@@ -0,0 +1,2 @@
+title AccountNode1
+dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml AccountNode1

+ 1 - 0
start/_launch_account.bat

@@ -0,0 +1 @@
+dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml AccountNode1

+ 1 - 0
start/_launch_game_r1s1.bat

@@ -0,0 +1 @@
+dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml GameNode1 global.RealmID=1 global.ServerID=1

+ 1 - 0
start/_launch_guild.bat

@@ -0,0 +1 @@
+dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml GuildNode global.RealmID=1

+ 1 - 0
start/_launch_name_server.bat

@@ -0,0 +1 @@
+dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml NameServer

+ 1 - 0
start/_launch_pay.bat

@@ -0,0 +1 @@
+dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml PayNode

+ 176 - 0
start/_launch_public.xml

@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="utf-8"?>
+<doc>
+
+  <!-- ================================================================ -->
+  <!--  公共服配置:NameServer + PublicNode + AccountNode               -->
+  <!--  此文件由手动从 _launch_server.xml 提取生成                       -->
+  <!-- ================================================================ -->
+
+  <define name="GameNodeDefine" param1="" param2=""/>
+
+  <!-- IService全局配置 -->
+  <GlobalConfig>
+    <Env>dev</Env>
+    <Realm>1</Realm>
+    <Mysql></Mysql>
+    <AcceptClientVersion>0.0.0</AcceptClientVersion>
+    <TemplateRoot>../../data/ServerData/</TemplateRoot>
+    <NoticeErrorUrl>https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=d9d262b8-17c8-4297-8b9f-bb1b7f72ac89</NoticeErrorUrl>
+    <DebugPay>true</DebugPay>
+    <BattleRecordURLPrefix>https://zqtfcn.oss-cn-shanghai.aliyuncs.com/apollo_dev/server/playback/</BattleRecordURLPrefix>
+  </GlobalConfig>
+
+  <!-- 根节点,管理所有ServiceNode,全局唯一 -->
+  <NameServer>
+    <RpcConfig>
+      <LocalEndPoint>127.0.0.1:17000</LocalEndPoint>
+      <NetworkTimeoutMS>15000</NetworkTimeoutMS>
+      <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+    </RpcConfig>
+  </NameServer>
+
+  <ServiceNodes>
+
+    <!-- 账号服务节点 -->
+    <AccountNode1>
+      <Redis>127.0.0.1,password=tmKjD1ENs3HfZ7adzLJO!,allowAdmin=true,syncTimeout=30000,responseTimeout=30000,connectRetry=1000,connectTimeout=10000;db=1</Redis>
+      <RpcConfig>
+        <LocalNodeType>AccountNode</LocalNodeType>
+        <LocalNodeName>AccountNode1</LocalNodeName>
+        <LocalEndPoint>127.0.0.1:17010</LocalEndPoint>
+        <NameServerEndPoint>127.0.0.1:17000</NameServerEndPoint>
+        <RequestTickTimeMS>5000</RequestTickTimeMS>
+        <NetworkTimeoutMS>30000</NetworkTimeoutMS>
+        <DefaultTaskExecuteTimeout>60000</DefaultTaskExecuteTimeout>
+        <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+        <AcceptTypeMappings>
+          <AccountServer>OpenCards.Server.Account.AccountServer</AccountServer>
+        </AcceptTypeMappings>
+      </RpcConfig>
+      <StartService>
+        <AccountServer>
+          <ServiceName>AccountServer</ServiceName>
+          <ServiceType>AccountServer</ServiceType>
+          <Config>
+            <HttpListen>http://+:18081/account/</HttpListen>
+            <SecretKey>3dbf6b137a80d10953507929a0216d8b</SecretKey>
+            <ServerListUrl>http://43.226.57.217:8007/apollo_60000/serverlist.json</ServerListUrl>
+            <YmnCheckUrl>https://heimdall.vgplay.vn/check</YmnCheckUrl>
+            <client_storage_url>http://43.226.57.217:8007/apollo_60000/</client_storage_url>
+            <apply_app_verison>0.0.0</apply_app_verison>
+            <apply_server_id>2</apply_server_id>
+            <StopServerDataURL>http://43.226.57.217:8007/apollo_60000/stop_server_list.json</StopServerDataURL>
+            <UpdateServerConfig>http://43.226.57.217:8007/apollo_60000/update_server_config.json</UpdateServerConfig>
+          </Config>
+        </AccountServer>
+      </StartService>
+    </AccountNode1>
+
+    <!-- 公共服务节点:公会/支付/好友/聊天/管理后台/竞技场巅峰/账单 -->
+    <PublicNode>
+      <Redis>127.0.0.1,password=tmKjD1ENs3HfZ7adzLJO!,allowAdmin=true,syncTimeout=30000,responseTimeout=30000,connectRetry=1000,connectTimeout=10000;db=1</Redis>
+      <Mysql>server=127.0.0.1;User ID=root;Password=CmASfW98lOKbFieqEQox;database=orm</Mysql>
+      <RpcConfig>
+        <LocalNodeType>PublicNode</LocalNodeType>
+        <LocalNodeName>PublicNode</LocalNodeName>
+        <LocalEndPoint>127.0.0.1:17060</LocalEndPoint>
+        <NameServerEndPoint>127.0.0.1:17000</NameServerEndPoint>
+        <RequestTickTimeMS>5000</RequestTickTimeMS>
+        <NetworkTimeoutMS>30000</NetworkTimeoutMS>
+        <DefaultTaskExecuteTimeout>60000</DefaultTaskExecuteTimeout>
+        <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+        <AcceptTypeMappings>
+          <GuildService>OpenCards.Service.Guild.GuildService</GuildService>
+          <PublicService>OpenCards.Service.Public.PublicService</PublicService>
+          <PayServer>OpenCards.Service.Pay.PayServer</PayServer>
+          <FriendService>OpenCards.Service.Friend.FriendService</FriendService>
+          <ChatService>OpenCards.Service.Chat.ChatService</ChatService>
+          <ArenaPinnacleService>OpenCards.Server.Arena.ArenaPinnacleService</ArenaPinnacleService>
+          <AdminService>OpenCards.Service.Admin.AdminService</AdminService>
+          <BillService>OpenCards.Service.Bill.BillService</BillService>
+          <AccountServer>OpenCards.Server.Account.AccountServer</AccountServer>
+        </AcceptTypeMappings>
+      </RpcConfig>
+      <StartService>
+        <AccountServer>
+          <ServiceName>AccountServer</ServiceName>
+          <ServiceType>AccountServer</ServiceType>
+          <Config>
+            <HttpListen>http://+:18081/account/</HttpListen>
+            <SecretKey>3dbf6b137a80d10953507929a0216d8b</SecretKey>
+            <ServerListUrl>http://43.226.57.217:8007/apollo_60000/serverlist.json</ServerListUrl>
+            <YmnCheckUrl>https://heimdall.vgplay.vn/check</YmnCheckUrl>
+            <client_storage_url>http://43.226.57.217:8007/apollo_60000/</client_storage_url>
+            <apply_app_verison>0.0.0</apply_app_verison>
+            <apply_server_id>2</apply_server_id>
+            <StopServerDataURL>http://43.226.57.217:8007/apollo_60000/stop_server_list.json</StopServerDataURL>
+            <UpdateServerConfig>http://43.226.57.217:8007/apollo_60000/update_server_config.json</UpdateServerConfig>
+          </Config>
+        </AccountServer>
+        <BillService>
+          <ServiceName>BillService</ServiceName>
+          <ServiceType>BillService</ServiceType>
+          <Config>
+            <platform>oss</platform>
+            <accessKeyId>LTAI4GF8sN2RpWMyfqRy1swY</accessKeyId>
+            <accessKeySecret>2vuPQ4eBoWYAZLsCNCbfJmVNP1EIxv</accessKeySecret>
+            <endpoint>https://oss-cn-shanghai.aliyuncs.com/</endpoint>
+            <path>apollo_60000/server/playback/{0}/{1}</path>
+            <bucketName>zqtfcn</bucketName>
+          </Config>
+        </BillService>
+        <GuildService>
+          <ServiceName>GuildService</ServiceName>
+          <ServiceType>GuildService</ServiceType>
+          <Config></Config>
+        </GuildService>
+        <PublicService>
+          <ServiceName>PublicService</ServiceName>
+          <ServiceType>PublicService</ServiceType>
+          <Config></Config>
+        </PublicService>
+        <PayServer>
+          <ServiceName>PayServer</ServiceName>
+          <ServiceType>PayServer</ServiceType>
+          <Config>
+            <HttpListen>http://+:18082/pay/</HttpListen>
+            <SecretKey>643752757fde484d03adc694b2462322</SecretKey>
+            <NotifyUrl>http://127.0.0.1:18082/pay/deliver</NotifyUrl>
+            <Mysql>server=127.0.0.1;User ID=root;Password=CmASfW98lOKbFieqEQox;database=dbpay;</Mysql>
+          </Config>
+        </PayServer>
+        <FriendService>
+          <ServiceName>FriendService</ServiceName>
+          <ServiceType>FriendService</ServiceType>
+          <Config></Config>
+        </FriendService>
+        <ChatService>
+          <ServiceName>ChatService</ServiceName>
+          <ServiceType>ChatService</ServiceType>
+          <Config>
+            <HttpListen>http://+:18089/chat/</HttpListen>
+            <AppKey>YOUME77489D86AE5330CCFFB62E3BB8B4C6B4F48AA54A</AppKey>
+            <AppSecret>rItImoJh1R593aBuzrVzzq4yDSFAxHwEDH2EXp40nX8ISeb2Tn/xVAjGreDuFILst50JIlpFRCStbOHk8h9XyLZ5Pcd992f8L0D6GUg60gQyxr9gCvrR/HF0QQHdWN2KxAOTYhVP6G/htOY/bxmtJbQw0Yn/+Yd3T9n69cCQS+0BAAE=</AppSecret>
+          </Config>
+        </ChatService>
+        <ArenaPinnacleService>
+          <ServiceName>ArenaPinnacleService</ServiceName>
+          <ServiceType>ArenaPinnacleService</ServiceType>
+          <Config></Config>
+        </ArenaPinnacleService>
+        <AdminService>
+          <ServiceName>AdminService</ServiceName>
+          <ServiceType>AdminService</ServiceType>
+          <Config>
+            <HttpListen>http://+:18088/api/</HttpListen>
+            <APIKey>SeaOfVanGuard</APIKey>
+            <GMTBaseUrl>http://127.0.0.1:7001</GMTBaseUrl>
+            <AccountUrl>http://127.0.0.1:18081/account/</AccountUrl>
+            <PayUrl>http://127.0.0.1:18082/pay/</PayUrl>
+          </Config>
+        </AdminService>
+      </StartService>
+    </PublicNode>
+
+  </ServiceNodes>
+</doc>

+ 352 - 0
start/_launch_server.xml

@@ -0,0 +1,352 @@
+<?xml version="1.0" encoding="utf-8"?>
+<doc>
+
+  <!-- #################################################################################### -->
+  <define name="GameNodeDefine" param1="" param2="">
+
+  </define>
+
+  <!-- #################################################################################### -->
+  <!-- IService全局配置 -->
+  <GlobalConfig>
+    <Env>dev</Env>
+    <Realm>1</Realm>
+    <Mysql></Mysql>
+    <AcceptClientVersion>0.0.0</AcceptClientVersion>
+    <TemplateRoot>../../data/ServerData/</TemplateRoot>
+    <NoticeErrorUrl>https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=d9d262b8-17c8-4297-8b9f-bb1b7f72ac89</NoticeErrorUrl>
+	<DebugPay>true</DebugPay>
+	  <BattleRecordURLPrefix>https://zqtfcn.oss-cn-shanghai.aliyuncs.com/apollo_dev/server/playback/</BattleRecordURLPrefix>
+  </GlobalConfig>
+
+  <!-- #################################################################################### -->
+  <!-- 根节点配置,负责管理所有的ServiceNode,全局唯一节点 -->
+  <NameServer>
+    <RpcConfig>
+      <LocalEndPoint>127.0.0.1:17000</LocalEndPoint>
+      <NetworkTimeoutMS>15000</NetworkTimeoutMS>
+      <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+    </RpcConfig>
+  </NameServer>
+
+  <!-- #################################################################################### -->
+  <!-- 所有服务节点配置,服务节点为具体服务的容器 -->
+  <ServiceNodes>
+  
+    <!-- 账号服务节点 -->
+    <AccountNode1>
+      <Redis>127.0.0.1,password=tmKjD1ENs3HfZ7adzLJO!,allowAdmin=true,syncTimeout=30000,responseTimeout=30000,connectRetry=1000,connectTimeout=10000;db=1</Redis>
+      <RpcConfig>
+        <LocalNodeType>AccountNode</LocalNodeType>
+        <LocalNodeName>AccountNode1</LocalNodeName>
+        <LocalEndPoint>127.0.0.1:17010</LocalEndPoint>
+        <NameServerEndPoint>127.0.0.1:17000</NameServerEndPoint>
+        <RequestTickTimeMS>5000</RequestTickTimeMS>
+        <NetworkTimeoutMS>30000</NetworkTimeoutMS>
+        <DefaultTaskExecuteTimeout>60000</DefaultTaskExecuteTimeout>
+        <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+        <AcceptTypeMappings>
+          <AccountServer>OpenCards.Server.Account.AccountServer</AccountServer>
+        </AcceptTypeMappings>
+      </RpcConfig>
+      <StartService>
+        <AccountServer>
+          <ServiceName>AccountServer</ServiceName>
+          <ServiceType>AccountServer</ServiceType>
+          <Config>
+            <HttpListen>http://+:18081/account/</HttpListen>
+            <SecretKey>3dbf6b137a80d10953507929a0216d8b</SecretKey>
+            <ServerListUrl>http://43.226.57.217:8007/apollo_60000/serverlist.json</ServerListUrl>
+            <YmnCheckUrl>https://heimdall.vgplay.vn/check</YmnCheckUrl>
+            <client_storage_url>http://43.226.57.217:8007/apollo_60000/</client_storage_url>
+            <apply_app_verison>0.0.0</apply_app_verison>
+            <apply_server_id>2</apply_server_id>
+            <StopServerDataURL>http://43.226.57.217:8007/apollo_60000/stop_server_list.json</StopServerDataURL>
+			<UpdateServerConfig>http://43.226.57.217:8007/apollo_60000/update_server_config.json</UpdateServerConfig>
+          </Config>
+        </AccountServer>
+      </StartService>
+    </AccountNode1>
+	
+    <!-- 公共服务节点 -->
+    <PublicNode>
+      <Redis>127.0.0.1,password=tmKjD1ENs3HfZ7adzLJO!,allowAdmin=true,syncTimeout=30000,responseTimeout=30000,connectRetry=1000,connectTimeout=10000;db=1</Redis>
+      <Mysql>server=127.0.0.1;User ID=root;Password=CmASfW98lOKbFieqEQox;database=orm</Mysql>
+      <RpcConfig>
+        <LocalNodeType>PublicNode</LocalNodeType>
+        <LocalNodeName>PublicNode</LocalNodeName>
+        <LocalEndPoint>127.0.0.1:17060</LocalEndPoint>
+        <NameServerEndPoint>127.0.0.1:17000</NameServerEndPoint>
+        <RequestTickTimeMS>5000</RequestTickTimeMS>
+        <NetworkTimeoutMS>30000</NetworkTimeoutMS>
+        <DefaultTaskExecuteTimeout>60000</DefaultTaskExecuteTimeout>
+        <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+        <AcceptTypeMappings>
+          <GuildService>OpenCards.Service.Guild.GuildService</GuildService>
+          <!--           <CenterService>OpenCards.Service.Center.CenterService</CenterService> -->
+          <PublicService>OpenCards.Service.Public.PublicService</PublicService>
+          <PayServer>OpenCards.Service.Pay.PayServer</PayServer>
+          <FriendService>OpenCards.Service.Friend.FriendService</FriendService>
+          <ChatService>OpenCards.Service.Chat.ChatService</ChatService>
+          <ArenaPinnacleService>OpenCards.Server.Arena.ArenaPinnacleService</ArenaPinnacleService>
+          <AdminService>OpenCards.Service.Admin.AdminService</AdminService>
+          <BillService>OpenCards.Service.Bill.BillService</BillService>
+          <AccountServer>OpenCards.Server.Account.AccountServer</AccountServer>
+        </AcceptTypeMappings>
+      </RpcConfig>
+      <StartService>
+        <AccountServer>
+          <ServiceName>AccountServer</ServiceName>
+          <ServiceType>AccountServer</ServiceType>
+          <Config>
+            <HttpListen>http://+:18081/account/</HttpListen>
+            <SecretKey>3dbf6b137a80d10953507929a0216d8b</SecretKey>
+            <ServerListUrl>http://43.226.57.217:8007/apollo_60000/serverlist.json</ServerListUrl>
+            <YmnCheckUrl>https://heimdall.vgplay.vn/check</YmnCheckUrl>
+            <client_storage_url>http://43.226.57.217:8007/apollo_60000/</client_storage_url>
+            <apply_app_verison>0.0.0</apply_app_verison>
+            <apply_server_id>2</apply_server_id>
+            <!-- 			<Apply_Server_ID_URL>47.250.46.43:19821</Apply_Server_ID_URL>
+			<Apply_Bind_Verison>0.1.9</Apply_Bind_Verison> -->
+            <StopServerDataURL>http://43.226.57.217:8007/apollo_60000/stop_server_list.json</StopServerDataURL>
+			<UpdateServerConfig>http://43.226.57.217:8007/apollo_60000/update_server_config.json</UpdateServerConfig>
+          </Config>
+        </AccountServer>
+        <BillService>
+          <ServiceName>BillService</ServiceName>
+          <ServiceType>BillService</ServiceType>
+          <Config>
+			<platform>oss</platform>
+            <accessKeyId>LTAI4GF8sN2RpWMyfqRy1swY</accessKeyId>
+            <accessKeySecret>2vuPQ4eBoWYAZLsCNCbfJmVNP1EIxv</accessKeySecret>
+            <endpoint>https://oss-cn-shanghai.aliyuncs.com/</endpoint>
+            <path>apollo_60000/server/playback/{0}/{1}</path>
+            <bucketName>zqtfcn</bucketName>
+          </Config>
+        </BillService>
+        <GuildService>
+          <ServiceName>GuildService</ServiceName>
+          <ServiceType>GuildService</ServiceType>
+          <Config></Config>
+        </GuildService>
+        <PublicService>
+          <ServiceName>PublicService</ServiceName>
+          <ServiceType>PublicService</ServiceType>
+          <Config></Config>
+        </PublicService>
+        <PayServer>
+          <ServiceName>PayServer</ServiceName>
+          <ServiceType>PayServer</ServiceType>
+          <Config>
+            <HttpListen>http://+:18082/pay/</HttpListen>
+            <SecretKey>643752757fde484d03adc694b2462322</SecretKey>
+            <NotifyUrl>http://127.0.0.1:18082/pay/deliver</NotifyUrl>
+            <Mysql>server=127.0.0.1;User ID=root;Password=CmASfW98lOKbFieqEQox;database=dbpay;</Mysql>
+          </Config>
+        </PayServer>
+        <FriendService>
+          <ServiceName>FriendService</ServiceName>
+          <ServiceType>FriendService</ServiceType>
+          <Config></Config>
+        </FriendService>
+        <ChatService>
+          <ServiceName>ChatService</ServiceName>
+          <ServiceType>ChatService</ServiceType>
+          <Config>
+            <HttpListen>http://+:18089/chat/</HttpListen>
+            <AppKey>YOUME77489D86AE5330CCFFB62E3BB8B4C6B4F48AA54A</AppKey>
+            <AppSecret>rItImoJh1R593aBuzrVzzq4yDSFAxHwEDH2EXp40nX8ISeb2Tn/xVAjGreDuFILst50JIlpFRCStbOHk8h9XyLZ5Pcd992f8L0D6GUg60gQyxr9gCvrR/HF0QQHdWN2KxAOTYhVP6G/htOY/bxmtJbQw0Yn/+Yd3T9n69cCQS+0BAAE=</AppSecret>
+          </Config>
+        </ChatService>
+        <ArenaPinnacleService>
+          <ServiceName>ArenaPinnacleService</ServiceName>
+          <ServiceType>ArenaPinnacleService</ServiceType>
+          <Config></Config>
+        </ArenaPinnacleService>
+        <AdminService>
+          <ServiceName>AdminService</ServiceName>
+          <ServiceType>AdminService</ServiceType>
+          <Config>
+            <HttpListen>http://+:18088/api/</HttpListen>
+            <APIKey>SeaOfVanGuard</APIKey>
+            <GMTBaseUrl>http://127.0.0.1:7001</GMTBaseUrl>
+            <AccountUrl>http://127.0.0.1:18081/account/</AccountUrl>
+            <PayUrl>http://127.0.0.1:18082/pay/</PayUrl>
+          </Config>
+        </AdminService>
+      </StartService>
+    </PublicNode>
+
+    <!-- 游戏服节点 -->
+    <GameNode1>
+      <Redis>127.0.0.1,password=tmKjD1ENs3HfZ7adzLJO!,allowAdmin=true,syncTimeout=30000,responseTimeout=30000,connectRetry=1000,connectTimeout=10000;db=1</Redis>
+      <Mysql>server=127.0.0.1;User ID=root;Password=CmASfW98lOKbFieqEQox;database=orm</Mysql>
+      <Ip>127.0.0.1</Ip>
+      <RpcConfig>
+        <LocalNodeType>GameNode</LocalNodeType>
+        <LocalNodeName>GameNode1</LocalNodeName>
+        <LocalEndPoint>127.0.0.1:17020</LocalEndPoint>
+        <NameServerEndPoint>127.0.0.1:17000</NameServerEndPoint>
+        <RequestTickTimeMS>5000</RequestTickTimeMS>
+        <NetworkTimeoutMS>30000</NetworkTimeoutMS>
+        <DefaultTaskExecuteTimeout>60000</DefaultTaskExecuteTimeout>
+        <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+        <AcceptTypeMappings>
+          <CenterService>OpenCards.Service.Center.CenterService</CenterService>
+          <AccountServer>OpenCards.Server.Account.AccountServer</AccountServer>
+          <ConnectorService>OpenCards.Server.Connector.ConnectorService</ConnectorService>
+          <ArenaPinnacleGameService>OpenCards.Server.Arena.ArenaPinnacleGameService</ArenaPinnacleGameService>
+          <ArenaManagerService>OpenCards.Server.Arena.ArenaManagerService</ArenaManagerService>
+          <ArenaValorService>OpenCards.Server.Arena.ArenaValorService</ArenaValorService>
+          <ArenaHighendService>OpenCards.Server.Arena.ArenaHighendService</ArenaHighendService>
+          <SessionService>OpenCards.Server.Connector.SessionService</SessionService>
+          <LogicService>OpenCards.Server.Logic.LogicService</LogicService>
+          <LogicManagerService>OpenCards.Server.Logic.LogicManagerService</LogicManagerService>
+	      <StageRankService>OpenCards.Service.StageRank.StageRankService</StageRankService>
+        </AcceptTypeMappings>
+      </RpcConfig>
+      <StartService>
+        <!-- LogicManagerService -->
+        <LogicManagerService>
+          <ServiceName>LogicManagerService_1</ServiceName>
+          <ServiceType>LogicManagerService</ServiceType>
+          <Config>
+            <FightPostURL>http://127.0.0.1:8088/fight/reqstartbattle</FightPostURL>
+            <serverID>1</serverID>
+          </Config>
+        </LogicManagerService>
+        <!-- Center -->
+        <CenterService>
+          <ServiceName>CenterService_1</ServiceName>
+          <ServiceType>CenterService</ServiceType>
+          <Config>
+            <serverID>1</serverID>
+          </Config>
+        </CenterService>
+		<StageRankService>
+			<ServiceName>StageRankService</ServiceName>
+			<ServiceType>StageRankService</ServiceType>
+			<Config>
+				<serverID>1</serverID>
+			</Config>
+		  </StageRankService>
+		  
+        <!-- ArenaManager -->
+        <ArenaManagerService>
+          <ServiceName>ArenaPinnacleGameService_1</ServiceName>
+          <ServiceType>ArenaPinnacleGameService</ServiceType>
+          <Config>
+            <ServerID>1</ServerID>
+          </Config>
+        </ArenaManagerService>
+        <!-- ArenaManager -->
+        <ArenaManagerService>
+          <ServiceName>ArenaManagerService_1</ServiceName>
+          <ServiceType>ArenaManagerService</ServiceType>
+          <Config>
+            <ServerID>1</ServerID>
+          </Config>
+        </ArenaManagerService>
+        <!-- ArenaHighend -->
+        <ArenaHighendService>
+          <ServiceName>ArenaHighendService_1</ServiceName>
+          <ServiceType>ArenaHighendService</ServiceType>
+          <Config></Config>
+        </ArenaHighendService>
+        <!-- Connector -->
+        <ConnectorService>
+          <ServiceName>ConnectorService_1</ServiceName>
+          <ServiceType>ConnectorService</ServiceType>
+          <Config>
+            <ServerId>1</ServerId>
+            <Host>0.0.0.0</Host>
+            <Port>19821</Port>
+            <NetCodec>OpenCards.Core.Serializer</NetCodec>
+            <KeepAlive>true</KeepAlive>
+            <KeepAliveInterval>30000</KeepAliveInterval>
+            <RecvBufferSize>16384</RecvBufferSize>
+            <SendBufferSize>16384</SendBufferSize>
+            <MaxConnections>300000</MaxConnections>
+          </Config>
+        </ConnectorService>
+      </StartService>
+    </GameNode1>
+
+    <!-- 游戏服节点 -->
+    <GameNode2>
+      <Redis>127.0.0.1,password=tmKjD1ENs3HfZ7adzLJO!,allowAdmin=true,syncTimeout=30000,responseTimeout=30000,connectRetry=1000,connectTimeout=10000;db=2</Redis>
+      <Mysql>server=127.0.0.1;User ID=root;Password=CmASfW98lOKbFieqEQox;database=orm</Mysql>
+      <Ip>127.0.0.1</Ip>
+      <RpcConfig>
+        <LocalNodeType>GameNode</LocalNodeType>
+        <LocalNodeName>GameNode2</LocalNodeName>
+        <LocalEndPoint>127.0.0.1:17021</LocalEndPoint>
+        <NameServerEndPoint>127.0.0.1:17000</NameServerEndPoint>
+        <RequestTickTimeMS>5000</RequestTickTimeMS>
+        <NetworkTimeoutMS>30000</NetworkTimeoutMS>
+        <DefaultTaskExecuteTimeout>60000</DefaultTaskExecuteTimeout>
+        <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+        <AcceptTypeMappings>
+          <CenterService>OpenCards.Service.Center.CenterService</CenterService>
+          <AccountServer>OpenCards.Server.Account.AccountServer</AccountServer>
+          <ConnectorService>OpenCards.Server.Connector.ConnectorService</ConnectorService>
+          <ArenaManagerService>OpenCards.Server.Arena.ArenaManagerService</ArenaManagerService>
+          <ArenaValorService>OpenCards.Server.Arena.ArenaValorService</ArenaValorService>
+          <ArenaHighendService>OpenCards.Server.Arena.ArenaHighendService</ArenaHighendService>
+          <SessionService>OpenCards.Server.Connector.SessionService</SessionService>
+          <LogicService>OpenCards.Server.Logic.LogicService</LogicService>
+          <LogicManagerService>OpenCards.Server.Logic.LogicManagerService</LogicManagerService>
+        </AcceptTypeMappings>
+      </RpcConfig>
+      <StartService>
+        <!-- LogicManagerService -->
+        <LogicManagerService>
+          <ServiceName>LogicManagerService_2</ServiceName>
+          <ServiceType>LogicManagerService</ServiceType>
+          <Config>
+            <FightPostURL>http://127.0.0.1:8088/fight/reqstartbattle</FightPostURL>
+            <serverID>2</serverID>
+          </Config>
+        </LogicManagerService>
+        <!-- Center -->
+        <CenterService>
+          <ServiceName>CenterService_2</ServiceName>
+          <ServiceType>CenterService</ServiceType>
+          <Config>
+            <serverID>2</serverID>
+          </Config>
+        </CenterService>
+        <!-- ArenaManager -->
+        <ArenaManagerService>
+          <ServiceName>ArenaManagerService_2</ServiceName>
+          <ServiceType>ArenaManagerService</ServiceType>
+          <Config>
+            <ServerID>2</ServerID>
+          </Config>
+        </ArenaManagerService>
+        <!-- ArenaHighend -->
+        <ArenaHighendService>
+          <ServiceName>ArenaHighendService_2</ServiceName>
+          <ServiceType>ArenaHighendService</ServiceType>
+          <Config></Config>
+        </ArenaHighendService>
+        <!-- Connector -->
+        <ConnectorService>
+          <ServiceName>ConnectorService_2</ServiceName>
+          <ServiceType>ConnectorService</ServiceType>
+          <Config>
+            <ServerId>2</ServerId>
+            <Host>0.0.0.0</Host>
+            <Port>19822</Port>
+            <NetCodec>OpenCards.Core.Serializer</NetCodec>
+            <KeepAlive>true</KeepAlive>
+            <KeepAliveInterval>30000</KeepAliveInterval>
+            <RecvBufferSize>16384</RecvBufferSize>
+            <SendBufferSize>16384</SendBufferSize>
+            <MaxConnections>300000</MaxConnections>
+          </Config>
+        </ConnectorService>
+      </StartService>
+    </GameNode2>
+  </ServiceNodes>
+</doc>

+ 387 - 0
start/create_zone.sh

@@ -0,0 +1,387 @@
+#!/bin/bash
+# =====================================================
+# 一键开新区脚本 — 生成独立区服目录
+# 用法: ./create_zone.sh <区号> [选项]
+#
+# 生成目录结构:
+#   dev/server<N>/
+#     ├── _launch_server.xml   # 该区独立配置
+#     ├── start.sh             # 启动
+#     ├── stop.sh              # 停止
+#     ├── status.sh            # 状态
+#     └── restart.sh           # 重启
+# =====================================================
+
+set -e
+
+# ======================== 默认配置 ========================
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+BASE_OUTPUT="/data/CzServer/_output.server"
+DLL="$BASE_OUTPUT/OpenCards.Server.DotNetCore.dll"
+
+# 数据库密码(从现有配置读取)
+REDIS_PASSWORD="tmKjD1ENs3HfZ7adzLJO!"
+MYSQL_CONN="server=127.0.0.1;User ID=root;Password=CmASfW98lOKbFieqEQox;database=orm"
+NAMESERVER_ENDPOINT="127.0.0.1:17000"
+FIGHT_POST_URL="http://127.0.0.1:8088/fight/reqstartbattle"
+
+# 服务器公网IP
+SERVER_PUBLIC_IP="${SERVER_PUBLIC_IP:-127.0.0.1}"
+
+# ======================== 颜色 ========================
+RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
+log_info()  { echo -e "${GREEN}[INFO]${NC} $1"; }
+log_warn()  { echo -e "${YELLOW}[WARN]${NC} $1"; }
+log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
+log_step()  { echo -e "${CYAN}[STEP]${NC} $1"; }
+
+# ======================== 参数 ========================
+ZONE_ID=""
+ZONE_NAME=""
+CONNECTOR_PORT=""
+RPC_PORT=""
+REDIS_DB=""
+SERVER_ID=""
+REALM_ID="1"
+
+while [[ $# -gt 0 ]]; do
+    case "$1" in
+        --port)       CONNECTOR_PORT="$2"; shift 2 ;;
+        --rpc-port)   RPC_PORT="$2"; shift 2 ;;
+        --name)       ZONE_NAME="$2"; shift 2 ;;
+        --ip)         SERVER_PUBLIC_IP="$2"; shift 2 ;;
+        --redis-db)   REDIS_DB="$2"; shift 2 ;;
+        --realm)      REALM_ID="$2"; shift 2 ;;
+        --nameserver) NAMESERVER_ENDPOINT="$2"; shift 2 ;;
+        -h|--help)
+            echo "用法: $0 <区号> [选项]"
+            echo ""
+            echo "参数:"
+            echo "  区号              第几区 (1, 2, 3...)"
+            echo ""
+            echo "选项:"
+            echo "  --port <端口>      客户端连接端口 (默认: 19820+区号)"
+            echo "  --rpc-port <端口>  RPC端口 (默认: rpc=17019+区号)"
+            echo "  --name <名称>      区服显示名称 (默认: 测试服N区)"
+            echo "  --ip <IP地址>      服务器公网IP (用于serverlist)"
+            echo "  --redis-db <编号>  Redis DB (默认: 同区号)"
+            echo "  --realm <ID>       大区ID (默认: 1)"
+            echo "  --nameserver <地址> NameServer地址 (默认: 127.0.0.1:17000)"
+            echo ""
+            echo "示例:"
+            echo "  $0 1                    # 创建1区 (端口19821)"
+            echo "  $0 2                    # 创建2区 (端口19822)"
+            echo "  $0 3 --port 19823 --name 正式3区"
+            exit 0
+            ;;
+        *) ZONE_ID="$1"; shift ;;
+    esac
+done
+
+if [ -z "$ZONE_ID" ]; then
+    log_error "请指定区号,如: $0 2"
+    exit 1
+fi
+
+# 自动计算默认值
+SERVER_ID="${ZONE_ID}"
+REDIS_DB="${REDIS_DB:-${ZONE_ID}}"
+CONNECTOR_PORT="${CONNECTOR_PORT:-$((19820 + ZONE_ID))}"
+RPC_PORT="${RPC_PORT:-$((17019 + ZONE_ID))}"
+ZONE_NAME="${ZONE_NAME:-测试服${ZONE_ID}区}"
+
+# ======================== 预检查 ========================
+log_step "检查环境..."
+
+ZONE_DIR="${SCRIPT_DIR}/server${ZONE_ID}"
+GAME_NODE="GameNode${ZONE_ID}"
+
+if [ -d "$ZONE_DIR" ]; then
+    log_warn "目录 ${ZONE_DIR} 已存在"
+    read -p "是否覆盖? (y/N): " confirm
+    if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
+        log_info "已取消"
+        exit 0
+    fi
+fi
+
+check_port() {
+    local port=$1
+    (ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null) | grep -q ":${port} " && return 0
+    return 1
+}
+
+if check_port "$CONNECTOR_PORT"; then
+    log_warn "端口 ${CONNECTOR_PORT} 已被占用,请检查"
+fi
+if check_port "$RPC_PORT"; then
+    log_warn "RPC端口 ${RPC_PORT} 已被占用,请检查"
+fi
+
+# ======================== 创建目录 ========================
+log_step "创建目录: ${ZONE_DIR}"
+mkdir -p "${ZONE_DIR}"
+
+# ======================== 生成 _launch_server.xml ========================
+log_step "生成 _launch_server.xml ..."
+
+cat > "${ZONE_DIR}/_launch_server.xml" << XEOF
+<?xml version="1.0" encoding="utf-8"?>
+<doc>
+  <!-- ================================================================ -->
+  <!--  ${ZONE_NAME} 配置 (由 create_zone.sh 生成)                     -->
+  <!--  区号: ${ZONE_ID}  端口: ${CONNECTOR_PORT}  RPC: ${RPC_PORT}    -->
+  <!--  Redis: db=${REDIS_DB}    生成时间: $(date '+%Y-%m-%d %H:%M:%S')       -->
+  <!-- ================================================================ -->
+
+  <define name="GameNodeDefine" param1="" param2=""/>
+
+  <GlobalConfig>
+    <Env>dev</Env>
+    <Realm>${REALM_ID}</Realm>
+    <Mysql></Mysql>
+    <AcceptClientVersion>0.0.0</AcceptClientVersion>
+    <TemplateRoot>../../../data/ServerData/</TemplateRoot>
+    <NoticeErrorUrl>https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=d9d262b8-17c8-4297-8b9f-bb1b7f72ac89</NoticeErrorUrl>
+    <DebugPay>true</DebugPay>
+    <BattleRecordURLPrefix>https://zqtfcn.oss-cn-shanghai.aliyuncs.com/apollo_dev/server/playback/</BattleRecordURLPrefix>
+  </GlobalConfig>
+
+  <NameServer>
+    <RpcConfig>
+      <LocalEndPoint>${NAMESERVER_ENDPOINT}</LocalEndPoint>
+      <NetworkTimeoutMS>15000</NetworkTimeoutMS>
+      <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+    </RpcConfig>
+  </NameServer>
+
+  <ServiceNodes>
+    <${GAME_NODE}>
+      <Redis>127.0.0.1,password=${REDIS_PASSWORD},allowAdmin=true,syncTimeout=30000,responseTimeout=30000,connectRetry=1000,connectTimeout=10000;db=${REDIS_DB}</Redis>
+      <Mysql>${MYSQL_CONN}</Mysql>
+      <Ip>127.0.0.1</Ip>
+      <RpcConfig>
+        <LocalNodeType>GameNode</LocalNodeType>
+        <LocalNodeName>${GAME_NODE}</LocalNodeName>
+        <LocalEndPoint>127.0.0.1:${RPC_PORT}</LocalEndPoint>
+        <NameServerEndPoint>${NAMESERVER_ENDPOINT}</NameServerEndPoint>
+        <RequestTickTimeMS>5000</RequestTickTimeMS>
+        <NetworkTimeoutMS>30000</NetworkTimeoutMS>
+        <DefaultTaskExecuteTimeout>60000</DefaultTaskExecuteTimeout>
+        <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
+        <AcceptTypeMappings>
+          <CenterService>OpenCards.Service.Center.CenterService</CenterService>
+          <AccountServer>OpenCards.Server.Account.AccountServer</AccountServer>
+          <ConnectorService>OpenCards.Server.Connector.ConnectorService</ConnectorService>
+          <ArenaManagerService>OpenCards.Server.Arena.ArenaManagerService</ArenaManagerService>
+          <ArenaValorService>OpenCards.Server.Arena.ArenaValorService</ArenaValorService>
+          <ArenaHighendService>OpenCards.Server.Arena.ArenaHighendService</ArenaHighendService>
+          <SessionService>OpenCards.Server.Connector.SessionService</SessionService>
+          <LogicService>OpenCards.Server.Logic.LogicService</LogicService>
+          <LogicManagerService>OpenCards.Server.Logic.LogicManagerService</LogicManagerService>
+          <StageRankService>OpenCards.Service.StageRank.StageRankService</StageRankService>
+          <ArenaPinnacleGameService>OpenCards.Server.Arena.ArenaPinnacleGameService</ArenaPinnacleGameService>
+        </AcceptTypeMappings>
+      </RpcConfig>
+      <StartService>
+        <LogicManagerService>
+          <ServiceName>LogicManagerService_${ZONE_ID}</ServiceName>
+          <ServiceType>LogicManagerService</ServiceType>
+          <Config>
+            <FightPostURL>${FIGHT_POST_URL}</FightPostURL>
+            <serverID>${SERVER_ID}</serverID>
+          </Config>
+        </LogicManagerService>
+        <CenterService>
+          <ServiceName>CenterService_${ZONE_ID}</ServiceName>
+          <ServiceType>CenterService</ServiceType>
+          <Config>
+            <serverID>${SERVER_ID}</serverID>
+          </Config>
+        </CenterService>
+        <StageRankService>
+          <ServiceName>StageRankService_${ZONE_ID}</ServiceName>
+          <ServiceType>StageRankService</ServiceType>
+          <Config>
+            <serverID>${SERVER_ID}</serverID>
+          </Config>
+        </StageRankService>
+        <ArenaPinnacleGameService>
+          <ServiceName>ArenaPinnacleGameService_${ZONE_ID}</ServiceName>
+          <ServiceType>ArenaPinnacleGameService</ServiceType>
+          <Config>
+            <ServerID>${SERVER_ID}</ServerID>
+          </Config>
+        </ArenaPinnacleGameService>
+        <ArenaManagerService>
+          <ServiceName>ArenaManagerService_${ZONE_ID}</ServiceName>
+          <ServiceType>ArenaManagerService</ServiceType>
+          <Config>
+            <ServerID>${SERVER_ID}</ServerID>
+          </Config>
+        </ArenaManagerService>
+        <ArenaHighendService>
+          <ServiceName>ArenaHighendService_${ZONE_ID}</ServiceName>
+          <ServiceType>ArenaHighendService</ServiceType>
+          <Config></Config>
+        </ArenaHighendService>
+        <ConnectorService>
+          <ServiceName>ConnectorService_${ZONE_ID}</ServiceName>
+          <ServiceType>ConnectorService</ServiceType>
+          <Config>
+            <ServerId>${SERVER_ID}</ServerId>
+            <Host>0.0.0.0</Host>
+            <Port>${CONNECTOR_PORT}</Port>
+            <NetCodec>OpenCards.Core.Serializer</NetCodec>
+            <KeepAlive>true</KeepAlive>
+            <KeepAliveInterval>30000</KeepAliveInterval>
+            <RecvBufferSize>16384</RecvBufferSize>
+            <SendBufferSize>16384</SendBufferSize>
+            <MaxConnections>300000</MaxConnections>
+          </Config>
+        </ConnectorService>
+      </StartService>
+    </${GAME_NODE}>
+  </ServiceNodes>
+</doc>
+XEOF
+
+# ======================== 生成管理脚本 ========================
+log_step "生成管理脚本..."
+
+# --- 公共工具函数 ---
+write_script_header() {
+    local file=$1
+    cat > "$file" << 'SHEOF'
+#!/bin/bash
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+BASE_OUTPUT="/data/CzServer/_output.server"
+DLL="$BASE_OUTPUT/OpenCards.Server.DotNetCore.dll"
+LOG_DIR="$BASE_OUTPUT/logfile"
+mkdir -p "$LOG_DIR"
+
+GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
+SHEOF
+}
+
+# --- start.sh ---
+write_script_header "${ZONE_DIR}/start.sh"
+cat >> "${ZONE_DIR}/start.sh" << XEOF
+# ${ZONE_NAME} 启动脚本
+
+PID=\$(pgrep -f "server${ZONE_ID}/_launch_server.xml ${GAME_NODE}" 2>/dev/null)
+if [ -n "\$PID" ]; then
+    echo -e "\${YELLOW}[SKIP] ${ZONE_NAME} 已在运行 (PID: \$PID)\${NC}"
+    exit 0
+fi
+
+echo -e "\${GREEN}[START] ${ZONE_NAME} (端口:${CONNECTOR_PORT}, RPC:${RPC_PORT}, Redis:db=${REDIS_DB}) ...\${NC}"
+
+nohup dotnet "\$DLL" "\$SCRIPT_DIR/_launch_server.xml" ${GAME_NODE} global.RealmID=${REALM_ID} global.ServerID=${SERVER_ID} \
+    >> "\$LOG_DIR/server${ZONE_ID}.log" 2>&1 &
+
+sleep 3
+
+PID=\$(pgrep -f "server${ZONE_ID}/_launch_server.xml ${GAME_NODE}" 2>/dev/null)
+if [ -n "\$PID" ]; then
+    echo -e "\${GREEN}[OK]    ${ZONE_NAME} 启动成功 (PID: \$PID)\${NC}"
+else
+    echo -e "\${RED}[FAIL]  ${ZONE_NAME} 启动失败,查看日志:\${NC}"
+    tail -20 "\$LOG_DIR/server${ZONE_ID}.log"
+    exit 1
+fi
+XEOF
+
+# --- stop.sh ---
+write_script_header "${ZONE_DIR}/stop.sh"
+cat >> "${ZONE_DIR}/stop.sh" << XEOF
+# ${ZONE_NAME} 停止脚本
+
+PID=\$(pgrep -f "server${ZONE_ID}/_launch_server.xml ${GAME_NODE}" 2>/dev/null)
+if [ -n "\$PID" ]; then
+    echo -e "\${YELLOW}[STOP] ${ZONE_NAME} (PID: \$PID)\${NC}"
+    kill \$PID
+    sleep 2
+    if pgrep -f "server${ZONE_ID}/_launch_server.xml ${GAME_NODE}" > /dev/null 2>&1; then
+        echo -e "\${RED}[KILL] 强制终止...\${NC}"
+        kill -9 \$PID 2>/dev/null
+    fi
+    echo -e "\${GREEN}[OK]    ${ZONE_NAME} 已停止\${NC}"
+else
+    echo -e "\${YELLOW}[SKIP] ${ZONE_NAME} 未在运行\${NC}"
+fi
+XEOF
+
+# --- status.sh ---
+write_script_header "${ZONE_DIR}/status.sh"
+cat >> "${ZONE_DIR}/status.sh" << XEOF
+# ${ZONE_NAME} 状态查看
+
+PID=\$(pgrep -f "server${ZONE_ID}/_launch_server.xml ${GAME_NODE}" 2>/dev/null)
+if [ -n "\$PID" ]; then
+    echo -e "\${GREEN}[UP]   ${ZONE_NAME} 运行中"
+    echo "        PID: \$PID"
+    echo "        端口: ${CONNECTOR_PORT}"
+    echo "        RPC:  ${RPC_PORT}"
+    echo "        日志: \$LOG_DIR/server${ZONE_ID}.log\${NC}"
+else
+    echo -e "\${RED}[DOWN] ${ZONE_NAME} 未运行 (端口:${CONNECTOR_PORT})\${NC}"
+fi
+XEOF
+
+# --- restart.sh ---
+write_script_header "${ZONE_DIR}/restart.sh"
+cat >> "${ZONE_DIR}/restart.sh" << XEOF
+# ${ZONE_NAME} 重启脚本
+bash "\$SCRIPT_DIR/stop.sh"
+sleep 2
+bash "\$SCRIPT_DIR/start.sh"
+XEOF
+
+chmod +x "${ZONE_DIR}/start.sh" "${ZONE_DIR}/stop.sh" "${ZONE_DIR}/status.sh" "${ZONE_DIR}/restart.sh"
+
+# ======================== 输出 serverlist 条目 ========================
+cat << XEOF
+
+${GREEN}============================================
+  请将以下条目添加到 OSS serverlist.json:
+============================================${NC}
+  {
+    "id": ${ZONE_ID},
+    "index": $((ZONE_ID - 1)),
+    "name": "${ZONE_NAME}",
+    "address": "${SERVER_PUBLIC_IP}:${CONNECTOR_PORT}",
+    "state": 1,
+    "is_open": true,
+    "capacity": 2000,
+    "serverid": ${SERVER_ID},
+    "groupid": ${REALM_ID},
+    "note": "测试服"
+  }
+${GREEN}============================================${NC}
+
+XEOF
+
+# ======================== 完成 ========================
+echo ""
+echo -e "${GREEN}============================================${NC}"
+echo -e "${GREEN}  ${ZONE_NAME} 创建完成!${NC}"
+echo -e "${GREEN}============================================${NC}"
+echo ""
+echo -e "  目录:    ${CYAN}${ZONE_DIR}${NC}"
+echo -e "  区号:    ${CYAN}${SERVER_ID}${NC}"
+echo -e "  端口:    ${CYAN}${CONNECTOR_PORT}${NC}"
+echo -e "  RPC端口: ${CYAN}${RPC_PORT}${NC}"
+echo -e "  Redis:   ${CYAN}db=${REDIS_DB}${NC}"
+echo ""
+echo -e "  管理:"
+echo -e "    ${CYAN}bash ${ZONE_DIR}/start.sh${NC}     启动"
+echo -e "    ${CYAN}bash ${ZONE_DIR}/stop.sh${NC}      停止"
+echo -e "    ${CYAN}bash ${ZONE_DIR}/status.sh${NC}    状态"
+echo -e "    ${CYAN}bash ${ZONE_DIR}/restart.sh${NC}   重启"
+echo -e "    ${CYAN}tail -f ${BASE_OUTPUT}/logfile/server${ZONE_ID}.log${NC}  日志"
+echo ""
+echo -e "  ${YELLOW}⚠ 别忘了:${NC}"
+echo -e "    1. 更新 OSS serverlist.json(见上方)"
+echo -e "    2. 防火墙放行端口 ${CONNECTOR_PORT} 和 ${RPC_PORT}"
+echo ""

+ 140 - 0
start/public.sh

@@ -0,0 +1,140 @@
+#!/bin/bash
+# =====================================================
+# 公共服管理脚本(NameServer + PublicNode + AccountNode)
+# =====================================================
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+CFG="$SCRIPT_DIR/_launch_public.xml"
+BASE_DIR="/data/CzServer/_output.server"
+DLL="$BASE_DIR/OpenCards.Server.DotNetCore.dll"
+LOG_DIR="$BASE_DIR/logfile"
+
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+mkdir -p "$LOG_DIR"
+
+is_running() {
+    pgrep -f "$CFG $1" > /dev/null 2>&1
+}
+
+start_node() {
+    local name=$1
+    local extra=$2
+    local log_file="$LOG_DIR/${name,,}.log"
+
+    if is_running "$name"; then
+        echo -e "${YELLOW}[SKIP] $name 已在运行${NC}"
+        return 0
+    fi
+
+    echo -e "${GREEN}[START] $name ...${NC}"
+    nohup dotnet "$DLL" "$CFG" $name $extra >> "$log_file" 2>&1 &
+    sleep 3
+
+    if is_running "$name"; then
+        echo -e "${GREEN}[OK]    $name 启动成功 (PID: $(pgrep -f "$CFG $name"))${NC}"
+    else
+        echo -e "${RED}[FAIL]  $name 启动失败,查看日志:$log_file${NC}"
+        tail -20 "$log_file"
+        return 1
+    fi
+}
+
+stop_node() {
+    local name=$1
+    local pid=$(pgrep -f "$CFG $name" 2>/dev/null)
+    if [ -n "$pid" ]; then
+        echo -e "${YELLOW}[STOP] $name (PID: $pid)${NC}"
+        kill $pid
+        sleep 1
+        if pgrep -f "$CFG $name" > /dev/null 2>&1; then
+            kill -9 $pid 2>/dev/null
+        fi
+    else
+        echo -e "${YELLOW}[SKIP] $name 未在运行${NC}"
+    fi
+}
+
+status_node() {
+    local name=$1
+    if is_running "$name"; then
+        echo -e "${GREEN}[UP]    $name (PID: $(pgrep -f "$CFG $name"))${NC}"
+    else
+        echo -e "${RED}[DOWN]  $name${NC}"
+    fi
+}
+
+case "${1:-start}" in
+    start)
+        echo "========================================"
+        echo " 启动公共服 (NameServer → PublicNode → AccountNode)"
+        echo "========================================"
+        start_node "NameServer"
+        start_node "PublicNode"
+        start_node "AccountNode1"
+        echo "========================================"
+        echo " 公共服启动完成"
+        echo "========================================"
+        ;;
+    stop)
+    stop)
+        echo "========================================"
+        echo " 停止公共服"
+        echo "========================================"
+        stop_node "AccountNode1"
+        stop_node "PublicNode"
+        stop_node "NameServer"
+        echo "========================================"
+        echo " 公共服已停止"
+        echo "========================================"
+        ;;
+    restart)
+        bash "$0" stop
+        sleep 3
+        bash "$0" start
+        ;;
+    status)
+        echo "========================================"
+        echo " 公共服状态"
+        echo "========================================"
+        status_node "NameServer"
+        status_node "PublicNode"
+        status_node "AccountNode1"
+        echo "========================================"
+        ;;
+    log)
+        local name=${2:-NameServer}
+        local log_file="$LOG_DIR/${name,,}.log"
+        if [ -f "$log_file" ]; then
+            tail -f "$log_file"
+        else
+            echo -e "${RED}日志文件不存在:$log_file${NC}"
+        fi
+        ;;
+    refresh-serverlist)
+        echo -e "${GREEN}[REFRESH] 通知 AccountServer 重新拉取 serverlist.json ...${NC}"
+        ADMIN_URL="http://127.0.0.1:18088/api/"
+        BODY='{"callBackInfo":"{\"type\":\"server_list_refresh\"}"}'
+        RSP=$(curl -s -X POST "$ADMIN_URL" -d "$BODY" -H "Content-Type: application/json" 2>&1)
+        if echo "$RSP" | grep -q '"state":true'; then
+            echo -e "${GREEN}[OK]    serverlist.json 刷新成功${NC}"
+        else
+            echo -e "${RED}[FAIL]  刷新失败: $RSP${NC}"
+            echo -e "${YELLOW}[TIP]   确保公共服已启动 (bash public.sh status)${NC}"
+        fi
+        ;;
+    *)
+        echo "用法: $0 {start|stop|restart|status|log [节点名]|refresh-serverlist}"
+        echo ""
+        echo "  start              启动公共服 (NameServer → PublicNode → AccountNode1)"
+        echo "  stop               停止公共服 (AccountNode1 → PublicNode → NameServer)"
+        echo "  restart            重启公共服"
+        echo "  status             查看公共服状态"
+        echo "  log                查看日志 (默认NameServer)"
+        echo "  refresh-serverlist 通知AccountServer重新拉取serverlist.json(更新区服列表后执行)"
+        exit 1
+        ;;
+esac

+ 158 - 0
start/start.sh

@@ -0,0 +1,158 @@
+#!/bin/bash
+
+BASE_DIR="/data/CzServer/_output.server"
+DEV_DIR="$BASE_DIR/dev"
+DLL="$BASE_DIR/OpenCards.Server.DotNetCore.dll"
+CFG="$DEV_DIR/_launch_server.xml"
+LOG_DIR="$BASE_DIR/logfile"
+
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+mkdir -p "$LOG_DIR"
+
+check_dotnet() {
+    if ! command -v dotnet &> /dev/null; then
+        echo -e "${RED}[ERROR] dotnet 未找到,请先安装 .NET 5.0${NC}"
+        echo "  mkdir -p \$HOME/dotnet && tar zxf dotnet-sdk-5.0.100-linux-x64.tar.gz -C \$HOME/dotnet"
+        echo "  export DOTNET_ROOT=\$HOME/dotnet && export PATH=\$PATH:\$HOME/dotnet"
+        exit 1
+    fi
+}
+
+is_running() {
+    local name=$1
+    pgrep -f "_launch_server.xml $name" > /dev/null 2>&1
+}
+
+start_node() {
+    local name=$1
+    local extra_args=$2
+    local log_file="$LOG_DIR/${name,,}.log"
+
+    if is_running "$name"; then
+        echo -e "${YELLOW}[SKIP]  $name 已在运行${NC}"
+        return 0
+    fi
+
+    echo -e "${GREEN}[START] $name ...${NC}"
+    cd "$DEV_DIR" || exit 1
+    nohup dotnet "$DLL" "$CFG" $name $extra_args >> "$log_file" 2>&1 &
+
+    sleep 3
+
+    if is_running "$name"; then
+        echo -e "${GREEN}[OK]    $name 启动成功 (PID: $(pgrep -f "_launch_server.xml $name"))${NC}"
+    else
+        echo -e "${RED}[FAIL]  $name 启动失败,查看日志:$log_file${NC}"
+        tail -20 "$log_file"
+        exit 1
+    fi
+}
+
+stop_node() {
+    local name=$1
+    local pid
+    pid=$(pgrep -f "_launch_server.xml $name")
+    if [ -n "$pid" ]; then
+        echo -e "${YELLOW}[STOP]  $name (PID: $pid)${NC}"
+        kill "$pid"
+    else
+        echo -e "${YELLOW}[SKIP]  $name 未在运行${NC}"
+    fi
+}
+
+status_node() {
+    local name=$1
+    if is_running "$name"; then
+        local pid
+        pid=$(pgrep -f "_launch_server.xml $name")
+        echo -e "${GREEN}[UP]    $name (PID: $pid)${NC}"
+    else
+        echo -e "${RED}[DOWN]  $name${NC}"
+    fi
+}
+
+cmd_start() {
+    echo "========================================"
+    echo " 启动游戏服务端"
+    echo "========================================"
+    check_dotnet
+
+    start_node "NameServer"
+    start_node "PublicNode"
+    start_node "GameNode1" "global.RealmID=1 global.ServerID=1"
+    start_node "AccountNode1"
+
+    echo ""
+    echo "========================================"
+    echo " 全部节点启动完成"
+    echo "========================================"
+}
+
+cmd_stop() {
+    echo "========================================"
+    echo " 停止游戏服务端"
+    echo "========================================"
+
+    # 反序停止
+    stop_node "AccountNode1"
+    stop_node "GameNode1"
+    stop_node "PublicNode"
+    stop_node "NameServer"
+
+    sleep 2
+    echo "========================================"
+    echo " 全部节点已停止"
+    echo "========================================"
+}
+
+cmd_restart() {
+    cmd_stop
+    sleep 3
+    cmd_start
+}
+
+cmd_status() {
+    echo "========================================"
+    echo " 节点状态"
+    echo "========================================"
+    status_node "NameServer"
+    status_node "PublicNode"
+    status_node "GameNode1"
+    status_node "AccountNode1"
+    echo "========================================"
+}
+
+cmd_log() {
+    local name=${1:-NameServer}
+    local log_file="$LOG_DIR/${name,,}.log"
+    if [ -f "$log_file" ]; then
+        tail -f "$log_file"
+    else
+        echo -e "${RED}日志文件不存在:$log_file${NC}"
+    fi
+}
+
+case "$1" in
+    start)   cmd_start ;;
+    stop)    cmd_stop ;;
+    restart) cmd_restart ;;
+    status)  cmd_status ;;
+    log)     cmd_log "$2" ;;
+    *)
+        echo "用法: $0 {start|stop|restart|status|log [节点名]}"
+        echo ""
+        echo "  start              启动所有节点"
+        echo "  stop               停止所有节点"
+        echo "  restart            重启所有节点"
+        echo "  status             查看各节点状态"
+        echo "  log NameServer     实时查看指定节点日志"
+        echo "  log PublicNode"
+        echo "  log GameNode1"
+        echo "  log AccountNode1"
+        exit 1
+        ;;
+esac

+ 0 - 0
start/state/GameNode1/exit.txt


+ 0 - 0
start/state/PublicNode/exit.txt