网络架构 (Network Architecture)
Maingraph for MC 使用 NeoForge 的网络系统来实现客户端与服务端之间的数据同步。在多人游戏模式下,所有蓝图的存储和逻辑均以服务器为准(Source of Truth)。
核心组件
网络系统的实现主要分布在以下类中:
MGMCNetwork: 负责所有网络负载(Payload)的注册。BlueprintNetworkHandler: 包含服务端和客户端处理负载的具体逻辑。ltd.opens.mg.mc.network.payloads: 存放所有的负载类,每个类都实现了CustomPacketPayload。
通信流程
1. 获取蓝图列表
当玩家打开蓝图管理器时:
- 客户端 发送
RequestBlueprintListPayload。 - 服务端 扫描存档目录下的
mgmc_blueprints文件夹,返回ResponseBlueprintListPayload。 - 客户端 接收后更新 GUI 列表。
2. 加载蓝图数据
当玩家在列表中选择并打开一个蓝图时:
- 客户端 发送
RequestBlueprintDataPayload(name)。 - 服务端 读取对应的 JSON 文件,并返回
ResponseBlueprintDataPayload(name, data, version)。 - 客户端 接收后使用
BlueprintIO反序列化并渲染。
3. 保存蓝图
当玩家在编辑器中点击保存或关闭确认时:
- 客户端 将当前节点和连线序列化为 JSON 字符串。
- 客户端 发送
SaveBlueprintPayload(name, data, expectedVersion)。 - 服务端 检查版本(竞态校验),写入文件,并返回
SaveResultPayload。 - 客户端 根据结果显示“保存成功”或错误提示。
4. 管理操作 (重命名/删除)
- 重命名: 客户端发送
RenameBlueprintPayload-> 服务端执行文件移动 -> 服务端广播或回复新列表。 - 删除: 客户端发送
DeleteBlueprintPayload-> 服务端执行文件删除 -> 服务端回复新列表。
技术细节
负载定义 (Payload)
每个负载都包含一个唯一的 Type 和一个 StreamCodec 用于二进制序列化。例如:
java
public record SaveBlueprintPayload(String name, String data, long expectedVersion) implements CustomPacketPayload {
public static final Type<SaveBlueprintPayload> TYPE = new Type<>(Identifier.parse(MaingraphforMC.MODID + ":save_blueprint"));
public static final StreamCodec<ByteBuf, SaveBlueprintPayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.STRING_UTF8, SaveBlueprintPayload::name,
ByteBufCodecs.STRING_UTF8, SaveBlueprintPayload::data,
ByteBufCodecs.VAR_LONG, SaveBlueprintPayload::expectedVersion,
SaveBlueprintPayload::new
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}竞态条件处理
为了防止多人同时编辑同一个蓝图导致数据覆盖,我们引入了版本校验机制:
- 客户端在请求数据时会收到一个
version。 - 保存时,客户端必须带上这个
expectedVersion。 - 服务端会对比文件当前的真实版本,如果不一致(说明期间有人保存过),则拒绝保存并提示玩家。
安全性与权限校验 (v0.1.2+)
1. 服务端 OP 权限校验
所有对蓝图执行“修改”的操作(保存、重命名、删除)在服务端均会进行权限检查。
- 校验逻辑:服务端会通过
ServerPlayer#hasPermissions(2)检查发送者的权限等级。只有具备 Level 2 或更高权限的玩家(通常为服务器管理员)才能操作蓝图文件。普通玩家只能通过mgrun命令(如果配置允许)触发已存在的蓝图,而不能修改它们。
2. 路径穿越防护 (Path Traversal)
服务端在处理任何带有文件名的负载(如 name 字段)时,都会调用 isValidFileName 进行严格校验:
- 严禁非法字符:文件名只能包含英文字母、数字、下划线、连字符和点。
- 严禁目录跳转:文件名中严禁出现
..、/或\。 - 锁定根目录:所有操作被强制锁定在存档目录下的
mgmc_blueprints文件夹内。
3. 异步 IO 与负载控制
- 异步保存:
SaveBlueprintPayload的文件写入操作在专用的 IO 线程池中异步执行,确保不阻塞 Minecraft 主线程。 - 格式校验:保存时会对上传的 JSON 数据进行预解析,拒绝损坏的或非蓝图格式的负载。
多人模式与单机模式的区别
- 单机模式: 逻辑直接操作本地文件路径(
Path),不经过网络负载处理逻辑。 - 多人模式: 客户端被设计为“无状态”的。它不保存任何本地蓝图文件,所有数据必须通过网络负载获取。这种设计确保了数据的一致性,避免了客户端显示过时缓存的问题。