为什么不敢在 Folia 服里直接用老经济插件?

在多线程区域化调度的 Folia 服里做经济系统,是一件走钢丝的事。传统的经济插件大多把玩家余额缓存在内存里,再异步写回文件或数据库;可在 Folia 里,买家和卖家处在不同线程区域,如果两个人同时点下交易按钮,或者有人卡着跨线程延迟狂点购买,没有数据库级事务兜底很容易就被刷出双倍金币。

更头疼的是离线服(或混合验证服)的改名问题:传统插件全靠离线 UUID 认人,玩家只要改个游戏名,离线 UUID 一变,原来的存款就全找不到了。

绑在 igng_id 上的 BIGINT 双账本

2026 年 8 月 25 日(Commit ccc2064),我专门针对 Folia 26.1.2 / Paper 26.2(Java 25 环境)从头写了 igng-economy。

第一步就是把钱包的地基打牢:钱包主键彻底抛弃离线 UUID,而是顺着 AuthMe -> mc_mapping -> igng_id 这条身份链,直接绑定在 (server_id, igng_id) 上。这样一来,同一个 IGNG 账号在本服内哪怕换了游戏 ID 也能无缝继承余额,同时不同子服之间的经济账本又做到了严格隔离。

数据库里绝不用浮点数存钱,所有金额一律用 BIGINT 定点整数存储,并且把每个人的钱包拆成 available_amount(可用余额)和 reserved_amount(冻结余额)双账本。所有扣款、转账和结算全部走 MySQL InnoDB 的行锁事务,绝不在内存里算糊涂账。

Escrow 物品托管与三段式提货租约

光管住钱还不够,交易市场里的物品流转更不能出岔子。我设计了一套完整的 Escrow(第三方托管)与 Claim(提货仓)机制:

当玩家挂出求购买单时,系统先在事务里把他的可用余额划转到 reserved_amount 冻结起来;当玩家上架出售物品时,先把他背包里的物品扣下序列化放进 Escrow 托管表。一旦撮合成交,钱在数据库事务里瞬间划转,而成交的物品则进入买家的 Claim 提货仓。

玩家从提货仓往自己背包里拿东西时,必须经过 AVAILABLE -> DELIVERING -> CLAIMED 的租约状态机:先在数据库里把物品锁成 DELIVERING(派送中),再切回该玩家所属的 Folia 实体线程往背包里塞物品,塞成功了才核销为 CLAIMED。哪怕塞物品那一下玩家刚好掉线或者服务器崩了,物品也只会安安静静留在托管仓里等下次恢复,绝对不会凭空蒸发,更不可能被卡出复制 Bug。