GPT 5.6随便写的,介意勿用!!!(已经在生产环境测试并部署)
[MD] # LSEMultiMarket ## 使用手册 - [玩家使用手册](docs/PLAYER_GUIDE.md):自由市场、系统商店、费用、等级、仓库、筛选和玩家命令。 - [管理员使用手册](docs/ADMIN_GUIDE.md):权限、配置、费用公式、等级、系统商店、恢复、审计和管理员命令。 - [更新日志](CHANGELOG.md):面向服务器管理员的版本、安装和发布流程变化。 面向 LegacyScriptEngine QuickJS 的可恢复玩家自由市场与系统商店。领域层不依赖 LSE;LSE API 只存在于 `src/hosts/lse`。玩家市场匿名,系统商店和玩家订单簿 使用不同业务入口,但共享账户、经济、仓库、审计和恢复基础设施。 ## 本地验证 ```powershell npm install npm run check npm run journal:verify ``` `npm run check` 依次执行 TypeScript 检查、全部自动化测试、单文件构建和打包后 LSE smoke。最终入口固定为: ```text dist/LSEMultiMarket/LSEMultiMarket.js ``` 构建目录同时包含 `manifest.json` 和 `config.example.json`。bundle 为 IIFE,不含 运行时 `import`、`export` 或 `require`。 ## 数据隔离 - 配置:`./plugins/LSEMultiMarket/config.json` - JSON 数据(默认):`./plugins/LSEMultiMarket/data/store.json` manifest;玩家按 XUID 写入 `data/store/player`,公共索引和记录写入 `data/store/index`、`data/store/record` - KV 数据(可选):`./plugins/LSEMultiMarket/data/database` - journal:`./plugins/LSEMultiMarket/data/logs` - `schemaVersion` 固定为 2;配置、数据库和 journal 使用隔离路径,部署不会覆盖现有运行时数据。 配置首次启动原子创建。管理用例先验证候选配置,再通过 `.new`/`.bak` 激活;写入 或运行时激活失败时保留旧主文件和旧内存策略。 如果新路径尚不存在,启动时会把旧的带版本号配置及其备份原子迁移到 `config.json` 与 `config.json.bak`;如果 `config.json` 已存在,则不会覆盖或改动旧文件。 ## 入口 - `/market`:玩家 GUI,包含自由市场、出售/求购、快速买卖、自己的订单与历史、 账户等级、仓库、待领取货币和系统商店。 - `/market help`:逐条显示彩色分组命令帮助;玩家市场可用 `/market browse <筛选词> [页码]` 匿名筛选浏览,订单、历史、商店和报价列表也支持页码,`/market shop list` 列出商店,玩家可继续 按序号查看报价,并通过 `shopbuy` / `shopsell` 直接购买或回收手持物品。 `shop` 的模式使用 LSE 枚举 `buy|recycle|gui`,数量、单价和期限使用整数参数, 由 BDS 提供自动补全和输入校验。 - `/marketadmin`:独立管理员 GUI;需要 OP,并按持久化组合角色显示模块。 - 控制台 `/marketadmin grant|revoke <xuid> <role> <reason>`:首次授权和恢复。 - 系统商店的持久化专用命令在服务器启动时注册。 命令等价入口见 [集成说明](docs/INTEGRATION.md)。GUI 和命令共同调用 `MarketApplicationFacade` / `AdminApplicationFacade`,不会直接修改余额、物品、 订单或配置文件。 ## 资产安全 - ItemSnapshot 保存原始物品身份,Instrument 单独用于撮合;容器和不确定容器能力 均硬拒绝。 - 所有物品/货币移动使用持久化状态、不可变计划、提交点、幂等恢复和补偿。 - 外部结果为 `unknown` 时进入 `manual_review`,启动恢复不会猜测经济或背包结果。 - 玩家市场手续费始终由卖方承担;买方只支付成交额。 - 调度切片最多 32 个成交分片,快速请求最多 256 个,剩余订单继续留簿。 - journal 只走异步 File append;交易主路径不做同步 JSONL I/O。 - 价格统计从业务数据库已完成 Trade 的持久化索引读取。 ## 安装与升级 在已停止的 Windows x64 BDS/LeviLamina 服务根目录运行: ```powershell lip install github.com/lwenk/LSEMultiMarket@0.2.0 ``` LIP 会解析所需的 LegacyScriptEngine 依赖,并只将 `LSEMultiMarket.js`、`manifest.json` 和 `config.example.json` 安装到 `plugins/LSEMultiMarket`。升级前先备份整个插件目录; 发布包不会包含或覆盖现有 `config.json`、`data` 或 journal,服务器仍必须保持停止直到 安装完成。发布与回滚步骤见 [运维说明](docs/design/OPERATIONS.md),真服项目见 [中文人工测试清单](docs/MANUAL_TEST_CHECKLIST.md)。 [/MD]
[MD]
# LSEMultiMarket 玩家使用手册
本文面向普通玩家,介绍自由市场、系统商店、费用、账户等级、仓库和命令。文中的数值是插件默认值;服务器管理员可以修改费用、期限、等级和商店报价,请以游戏内当前显示为准。
## 1. 快速开始
- 输入 `/market` 打开玩家市场 GUI。
- 输入 `/market help` 查看游戏内命令帮助。
- 自由市场是玩家之间的匿名订单簿,不显示买卖双方身份。
- 系统商店由服务器创建,和玩家自由市场是两套入口、两套报价。
- 账户使用玩家 XUID 记录;改名不会产生新账户。
- 上架、求购、快速买卖、取消/改价、资产领取、升级和系统商店交易的成功、部分完成、待复核或失败会同时显示聊天栏明细和个人顶部 Toast;Toast 只发送给发起操作的玩家,不会全服广播。
最常用的操作:
1. 买东西:`/market` -> 浏览市场 -> 选择物品和具体报价 -> 快速购买。
2. 卖东西:手中或背包里准备物品 -> `/market` -> 出售物品 -> 选择物品、数量、单价和期限。
3. 查看资产:`/market warehouse` 查看待领取物品,`/market pending` 查看待领取货币。
4. 查看订单:`/market orders`;查看成交:`/market history`。
## 2. 自由市场与系统商店的区别
| 项目 | 玩家自由市场 | 系统商店 |
| --- | --- | --- |
| 交易对象 | 其他玩家的匿名订单 | 服务器配置的固定报价 |
| 价格 | 玩家下单,按价格和时间撮合 | 管理员配置固定单价 |
| 买方手续费 | 无,买方只支付成交额 | 无自由市场手续费,只支付报价金额 |
| 卖方手续费 | 成交手续费始终由卖方承担 | 无自由市场成交手续费 |
| 买到的物品 | 优先进入背包;背包空间不足时进入市场仓库 | 直接进入背包;背包放不下时不会扣款 |
| 卖出的物品 | 上架托管或快速卖给收购订单 | 从玩家背包移除后由系统回收 |
| 对手身份 | 匿名 | 显示系统商店名称 |
自由市场不能购买自己的出售订单,也不能把物品卖给自己的收购订单。
## 3. 费用计算
### 3.1 上架费
创建出售订单和收购订单都会收取一次上架费:
```text
上架费 = 向上取整(
当前等级基础费
+ 数量 × 单价 × 上架价值费率 / 10000
+ 向上取整(数量 / 100) × 每 100 件每日托管费 × 期限天数
)
```
默认参数:
- 上架价值费率:`300 bps`,即 `3%`
- 每 100 件每日托管费:`8`
- 等级基础费:Lv.1 至 Lv.5 分别为 `100 / 80 / 60 / 50 / 40`
示例:Lv.1 上架 65 件物品,单价 100,期限 3 天:
```text
基础费 100
价值费 = 向上取整(65 × 100 × 300 / 10000) = 195
托管费 = 向上取整(65 / 100) × 8 × 3 = 24
上架费 = 100 + 195 + 24 = 319
```
注意:
- 上架费在订单创建时收取一次。
- 托管费按“每 100 件每天”预收,至少计 1 天。
- 出售订单会先扣上架费,再按精确物品快照托管物品;明确扣物失败时会退回上架费。
- 收购订单会一次扣除“最大收购金额 + 上架费”,最大收购金额进入订单托管。
- 提前取消、提前完全成交或改价替换时,退还未使用整天的托管费;固定费和价值费不退。退款进入“待领取货币”。
- 自然到期不退托管费。部分成交但订单仍活动时暂不退,订单最终提前结束时再统一计算。
- 改价/续期采用“替换订单”:旧订单变为已替换并结算旧托管费,新订单重新计算并收取一次完整上架费,同时失去原来的排队时间优先级。
- 旧订单如果没有保存费用拆分数据,为避免错误退款,托管费退款为 0。
- 快速购买和快速出售不会收上架费,因为它们不创建长期订单。
### 3.2 自由市场成交手续费
默认成交手续费率为 `1500 bps`,即 `15%`,始终由卖方承担:
```text
成交额 = 成交数量 × 成交单价
卖方手续费 = 向上取整(该出售订单累计成交额 × 费率) - 已收手续费
卖方实收 = 本次成交额 - 本次卖方手续费
买方支付 = 本次成交额
```
按订单累计计算是为了避免一个订单被拆成多笔成交后重复向上取整。例:成交额 1000,默认手续费为 150,买方支付 1000,卖方实收 850。
### 3.3 仓库赎回费
默认免费仓库容量为 50 条记录、合计 4096 件物品。免费期或容量溢出规则见“仓库”章节。
只有状态为“需付费赎回”的物品收取赎回费:
```text
赎回费 = 赎回基础费 + 每件费 × 该仓库记录数量
```
默认是 `100 + 1 × 数量`。例如一条记录有 64 件,赎回费是 74。免费领取和受保护记录不收费;背包容量不足时不会扣费。
### 3.4 升级费
满足下一等级的全部条件后,执行升级时扣除目标等级配置的升级费。默认升级费为:
| 升级到 | 费用 |
| --- | ---: |
| Lv.2 | 10,000 |
| Lv.3 | 50,000 |
| Lv.4 | 200,000 |
| Lv.5 | 1,000,000 |
升级使用当前自由市场的结算货币。
## 4. 账户等级与限制
新账户从 Lv.1 开始。以下为默认限制:
| 等级 | 同时收购订单 | 同时出售订单 | 出售托管物品上限 | 收购托管货币上限 | 单笔最长期限 | 上架基础费 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Lv.1 | 5 | 5 | 2048 | 100,000 | 7 天 | 100 |
| Lv.2 | 10 | 10 | 5,000 | 200,000 | 14 天 | 80 |
| Lv.3 | 20 | 20 | 10,000 | 500,000 | 30 天 | 60 |
| Lv.4 | 40 | 40 | 16,384 | 1,000,000 | 60 天 | 50 |
| Lv.5 | 80 | 80 | 50,000 | 5,000,000 | 60 天 | 40 |
“出售托管物品上限”是所有未结束出售订单的剩余数量总和;“收购托管货币上限”是所有未结束收购订单的剩余托管金额总和。仓库容量不随等级增加,默认对所有等级相同。
默认升级条件:
| 目标等级 | 有效成交笔数 | 不同交易对手 | 累计有效成交额 | 账户天数 | 升级费 |
| --- | ---: | ---: | ---: | ---: | ---: |
| Lv.2 | 20 | 10 | 5,000 | 7 | 10,000 |
| Lv.3 | 100 | 25 | 50,000 | 30 | 50,000 |
| Lv.4 | 500 | 50 | 500,000 | 90 | 200,000 |
| Lv.5 | 2,000 | 100 | 5,000,000 | 180 | 1,000,000 |
这些条件必须全部满足。只有玩家自由市场中已完成的有效成交会增加指标;系统商店交易不计入。使用 `/market account` 查看当前等级和指标,使用 `/market upgrade` 尝试升级。
## 5. 浏览、筛选与分页
GUI 的浏览页支持原版风格的四类筛选:建筑、自然、装备、物品,并支持名称、ID、附魔、耐久、药水、Lore、标签和基岩版 Data Value。
命令浏览每页显示 6 种物品:
```text
/market browse [页码]
/market browse <筛选词> [页码]
```
示例:
```text
/market browse 2
/market browse 钻石剑
/market browse 装备,锋利
/market browse 久经使用
/market browse dv:5
```
逗号分隔的多个条件按“同时满足”筛选。`DV` 是非耐久物品的基岩版 Data Value;耐久消耗读取物品运行时耐久,根 NBT `Damage` 不作为耐久物品的损耗来源。
浏览结果中的物品序号只属于当前玩家,保存 10 分钟。插件在执行购买前还会再次核对该序号对应的 Instrument,防止市场顺序变化后买错物品。序号过期时重新执行 `/market browse`。
## 6. 出售物品
### GUI
`/market` -> 出售物品,选择背包中的精确物品组,再填写数量、单价和期限。
### 命令
命令出售读取玩家当前手持物品:
```text
/market sell <数量> <单价> <期限天>
```
默认可选期限是 `3、7、14、30、60` 天,但还必须不超过当前等级的最长期限。上架物品和发布求购时,表单会显示该物品当前的动态或冷启动价格范围;管理员启用强制模式时单价必须在范围内,关闭时范围只作推荐,仍需填写正整数单价。冷启动默认区间为 1 至 200000。
物品上架后会从背包移入订单托管。订单成交时卖方收到“成交额 - 卖方手续费”;若外部经济暂时无法入账,款项进入待领取货币。
## 7. 快速出售
快速出售会立刻吃掉当前最优的玩家收购订单,不创建长期订单:
```text
/market quicksell <数量> [最低单价]
```
- 读取当前手持物品的精确快照。
- 优先卖给最高价收购订单;同价时先到先得。
- 可选“最低单价”用于保护价格。
- 没有符合条件的收购订单时不会扣物。
- 快速请求一次最多提交 256 个成交分片,未成交数量会安全返回背包;背包明确放不下时转入受保护仓库。
- 快速出售仍收卖方成交手续费,但不收上架费。
## 8. 收购订单与快速购买
先使用 `/market browse` 获取物品序号。
创建长期收购订单:
```text
/market buyorder <物品序号|instrumentId> <数量> <单价> <期限天>
```
立即购买现有出售订单:
```text
/market quickbuy <物品序号|instrumentId> <数量> [最高单价]
```
- 快速购买优先最低价出售订单;同价时先到先得。
- 可选“最高单价”用于保护价格。
- 买方只支付实际成交额,不承担玩家市场成交手续费。
- 玩家自由市场成交后优先交付到背包;背包空间不足或交付无法确认时,物品保留在市场仓库。
- 收购订单按“数量 × 委托单价”托管最大金额;如果实际成交价更低,价差会退回外部余额,失败时进入待领取货币。
## 9. 订单管理
```text
/market orders [active|history] [页码]
/market cancel <订单序号|orderId>
/market replace <订单序号|orderId> <新单价> <新期限天>
/market history [页码]
```
订单每页显示 6 条,默认只显示进行中的公开订单;使用 `/market orders history` 查看已成交、已取消、已过期和已替换订单。订单序号来自当前列表。
- 取消出售订单:剩余物品优先返回背包;背包明确放不下时转入仓库。
- 出售订单到期:剩余物品进入仓库。
- 取消或到期的收购订单:剩余托管货币退回外部余额,无法立即到账时进入待领取货币。
- 订单改价/续期会创建新订单、重新收上架费并重置价格时间优先级。
- 已成交、已取消、已过期、已替换或待人工复核的订单不能再次取消或改价。
## 10. 仓库
背包空间不足或交付无法确认的自由市场购买、订单到期、部分退回和补偿物品可能进入市场仓库:
```text
/market warehouse
/market claim <仓库序号|warehouseEntryId>
```
默认状态和时限:
| 状态 | 含义 | 默认处理 |
| --- | --- | --- |
| 免费领取 | 占用免费记录数和物品数 | 30 天内免费领取 |
| 需付费赎回 | 免费期已过,或入库时免费容量已满 | 按记录收赎回费;默认再保留 30 天 |
| 受保护 | 补偿或管理员补发 | 不收费、不自动过期 |
| 已归档 | 赎回期也已过 | 玩家不能自行领取,联系管理员复核恢复 |
| 待人工复核 | 外部背包或经济结果不确定 | 不要反复操作,等待管理员处理 |
默认免费容量是 50 条记录、合计 4096 件物品。若一次入库只有部分容量,插件会拆成“免费领取”和“需付费赎回”两条记录。
当前仓库限制的是免费区域,不是永久总容量;反复让订单到期可能积累付费或已归档记录。不要把市场仓库当作长期云背包,收到物品后应尽快领取。
领取前会先检查背包是否能放下全部物品。背包不足时不会扣赎回费,也不会改变仓库记录。
## 11. 待领取货币
```text
/market pending
/market claimmoney <货币序号|货币名称|economyId>
```
待领取货币用于保存暂时无法写入外部经济的卖款、退款或补偿。余额为 0 的记录不会显示。玩家界面显示管理员配置的货币名称,不要求玩家记忆经济 ID。
`unknown` 表示外部经济没有明确告诉插件成功或失败。为防止重复发钱,插件会把相关操作冻结为待人工复核,而不是自动重试。
## 12. 系统商店
```text
/market shop list [页码]
/market shop <商店序号|名称|专用命令|ID> [页码]
/market shop <商店> <buy|recycle> [页码]
/market shop <商店> gui
/market shopbuy <商店> <出售报价序号|offerId> <数量>
/market shopsell <商店> <回收报价序号|offerId> <数量>
```
管理员也可以为商店注册专用命令,例如 `/coinshop`。专用命令在服务器启动时注册,商店创建后通常需要重启一次服务器才会出现新命令。
系统商店报价可能有以下限制:商店或报价暂停、库存数量、玩家账户最低等级、每日/每周/每月限额。列表会显示当前可用状态。
系统商店的出售和回收报价可分别设置显示分组。一个方向只有一个分组时直接显示商品;存在多个分组时先选择分组,再浏览组内商品。旧报价自动归入“默认分组”。命令列表会在每条报价上显示分组名。
- 购买:系统商店先检查背包容量,再扣除“单价 × 数量”,然后把精确物品发到背包。
- 回收:玩家手持对应的精确物品,系统移除物品后支付“单价 × 数量”;无法立即入账时进入待领取货币。
- 系统商店不使用玩家自由市场上架费和卖方成交手续费。
## 13. 物品身份与禁售规则
市场按基岩版物品规则保存原始 ItemSnapshot,并用独立 Instrument 身份撮合。以下差异可能使同名物品成为不同报价:
- 自定义名称和原版物品类型名
- Lore
- 附魔及等级
- 药水种类和效果
- 基岩版 Data Value
- 耐久区间和实际已损耗耐久
- 其他参与身份的 NBT 或标签
容器类和疑似容器物品会硬拒绝,包括潜影盒、收纳袋以及带未知容器能力/NBT 的物品,以防嵌套资产或复制问题。默认单件物品 NBT 最大 65536 字节;管理员还可以按物品 ID、命名空间、ID 模式、附魔和结构化 NBT 规则禁售。
## 14. 完整玩家命令速查
| 命令 | 作用 |
| --- | --- |
| `/market` 或 `/market gui` | 打开玩家 GUI |
| `/market help` | 显示帮助 |
| `/market browse [页码]` | 浏览自由市场 |
| `/market browse <筛选词> [页码]` | 筛选自由市场 |
| `/market account` | 查看账户等级、指标和限制 |
| `/market orders [active|history] [页码]` | 默认查看进行中订单;history 查看已结束订单 |
| `/market history [页码]` | 查看自己的成交历史 |
| `/market warehouse` | 查看仓库 |
| `/market pending` | 查看待领取货币 |
| `/market upgrade` | 尝试升级账户 |
| `/market sell <数量> <单价> <期限天>` | 上架当前手持物品 |
| `/market quicksell <数量> [最低单价]` | 快速卖给收购订单 |
| `/market buyorder <物品> <数量> <单价> <期限天>` | 创建收购订单 |
| `/market quickbuy <物品> <数量> [最高单价]` | 快速购买 |
| `/market cancel <订单>` | 取消活动订单 |
| `/market replace <订单> <新单价> <新期限天>` | 改价或续期,创建替换订单 |
| `/market claim <仓库记录>` | 领取仓库物品 |
| `/market claimmoney <货币>` | 领取待领取货币 |
| `/market shop list [页码]` | 列出系统商店 |
| `/market shop <商店> [页码]` | 列出商店报价 |
| `/market shopbuy <商店> <报价> <数量>` | 从系统商店购买 |
| `/market shopsell <商店> <报价> <数量>` | 向系统商店出售手持物品 |
尖括号 `<...>` 表示必填参数,方括号 `[...]` 表示可选参数。物品、订单、仓库、货币、商店和报价都优先支持相应列表中显示的序号。
## 15. 出现异常时
- “没有可成交订单”:当前没有符合物品、价格保护和非本人条件的对手订单。
- “价格超出范围”:管理员启用强制价格模式时,重新打开表单或浏览市场,按当前已发布区间填写价格;仅推荐模式不会出现此限制。
- “重复操作/不可变计划变化”:不要继续点击旧表单;关闭后重新打开,避免重复扣款或扣物。
- “背包容量不足”:清理背包后再购买或领取。
- “待人工复核”:不要重复提交同一操作,联系管理员并提供发生时间、物品、数量和命令。
[/MD]
[MD]
# LSEMultiMarket 管理员使用手册
本文面向服务器管理员,说明权限、配置、费用、等级、系统商店、恢复、数据安全和命令。玩家操作说明见 [玩家使用手册](PLAYER_GUIDE.md)。
文中的数值为默认值。所有会改变配置、角色、商店、报价或玩家资产的管理员操作都必须填写原因,并写入业务数据库和审计 journal。
## 1. 管理入口与权限模型
- `/marketadmin`:打开独立管理员 GUI。
- 管理员必须同时满足“游戏内 OP”和“持久化角色”两个条件。
- `super_admin` 拥有全部角色能力,但仍必须是 OP。
- 控制台不使用 GUI,只支持角色授权和撤销,用于首次授权及紧急恢复。
角色能力:
| 角色 | 主要能力 |
| --- | --- |
| `shop_manager` | 创建、编辑、暂停和归档系统商店及报价 |
| `market_manager` | 修改自由市场、费用、期限、等级、经济、价格、分类、禁售、仓库和 journal 数值配置 |
| `auditor` | 只读查看配置、商店、审计操作和待人工复核记录 |
| `recovery_admin` | 设置玩家等级、冻结账户、恢复归档仓库、补发物品、调整待领取货币、重置报价限额、处理复核 |
| `super_admin` | 全部能力,并可管理管理员角色 |
首次从控制台授权:
```text
marketadmin grant <玩家名|XUID> super_admin <原因>
```
示例:
```text
marketadmin grant AdminPlayer super_admin 首次部署授权
```
控制台也支持 `revoke`。游戏内不能撤销最后一个 `super_admin`;最后一个超级管理员只能由控制台恢复或调整。
玩家名会通过 LSE 玩家数据库解析为 XUID,支持已记录的离线玩家。持久化记录始终以 XUID 为准。
## 2. 管理员 GUI 模块
GUI 只显示当前角色允许的模块:
| 页面 | 内容 |
| --- | --- |
| 系统商店 | 商店列表、出售报价、回收报价、报价创建/复制/编辑、库存、限额和状态 |
| 市场规则 | 自由市场开关和经济、费用与期限、账户等级、经济、价格控制、分类、禁售、仓库、journal 数值 |
| 审计与人工复核 | 持久化操作、状态迁移、失败码、待人工复核记录 |
| 恢复管理 | 设置玩家等级、账户冻结、归档仓库恢复、受保护物品补发、待领取货币调整、报价限额重置 |
| 权限管理 | 角色列表、授权和撤销 |
GUI 和命令调用相同的应用用例,不会绕过权限、审计或资产状态机。所有需要选择目标玩家的管理员操作,包括设置等级、账户状态、归档恢复、受保护物品补发、待领取货币调整和授予角色,统一先显示在线玩家按钮;首项为模糊搜索,搜索可匹配部分玩家名或 XUID,在线列表和搜索结果均每页显示 6 人。
以下启动级配置不通过运行期 GUI 修改:
- `schemaVersion`、插件名称和构建版本
- 存储后端及数据路径
- 玩家/管理员命令名
- 时区
- journal 目录
- 初始 `adminRoles`
- 撮合切片 32 和快速请求切片 256
如必须修改这些字段,应停止服务器、完整备份后编辑 `config.json`,再启动验证。不要在服务器运行时手工修改数据库或 journal。
## 3. 文件、数据和备份
默认路径:
| 内容 | 路径 |
| --- | --- |
| 插件配置 | `./plugins/LSEMultiMarket/config.json` |
| JSON 数据库 | `./plugins/LSEMultiMarket/data/store.json` manifest、`data/store/player/<xuid>-rN.json` 与 index/record 分片 |
| KV 数据库 | `./plugins/LSEMultiMarket/data/database` |
| 审计 journal | `./plugins/LSEMultiMarket/data/logs` |
| 单文件插件 | `./plugins/LSEMultiMarket/LSEMultiMarket.js` |
默认后端是 JSON。JSON 和 KV 使用相同的业务键结构,但物理存储不同:
- `json`:manifest 加按 XUID 聚合的玩家文件、索引和记录分片,适合默认部署和直观检查;写入先生成受影响文件,再原子切换 manifest 并保留上一代备份。
- `kv`:LSE KV 后端,键查找和前缀扫描更适合更大数据量;带写前日志、事务回放和有界键缓存。
不要直接把 `storage.backend` 从 JSON 改为 KV 或反向修改来“迁移”数据;插件不会自动在两个后端之间复制业务记录。切换后会看到另一套空数据。正式迁移必须使用单独、经过校验的迁移工具。
备份步骤:
1. 停止 BDS/LeviLamina。
2. 备份整个 `plugins/LSEMultiMarket` 目录,至少包含 `config.json` 和 `data`。
3. 校验备份可读后再升级 bundle 或修改启动配置。
4. 启动后检查 schema、恢复扫描和 journal 队列日志。
本版本使用隔离的配置、数据库和 journal 路径,不会在部署或启动时覆盖现有运行时数据。
## 4. 默认自由市场规则
```text
自由市场:启用
结算经济:coin(默认显示名:金币)
允许期限:3、7、14、30、60 天
全局最大期限:60 天
冷启动价格范围:1 至 200000
调度撮合:每切片最多 32 个成交分片
快速买卖:每次最多 256 个成交分片
```
活动订单按价格时间优先:
- 出售订单:低价优先;同价时早创建的优先。
- 收购订单:高价优先;同价时早创建的优先。
- 新订单跨价成交时使用挂在订单簿中的 Maker 价格。
- 买方预留价格高于实际成交价时,差价退回外部余额;失败则进入待领取货币。
- 同一 XUID 的买卖订单不会互相成交。
关闭自由市场后,禁止创建新自由市场交易,但玩家仍可取消订单、领取仓库/待领取货币并使用系统商店。
## 5. 费用配置与实际计算
### 5.1 上架费
出售订单和收购订单使用同一公式:
```text
上架费 = ceil(
accountLevels[当前等级].listingBase
+ 数量 × 单价 × fees.listingValueBps / 10000
+ ceil(数量 / 100) × fees.custodyPer100ItemsDay × 期限天数
)
```
默认值:
| 字段 | 默认值 | 含义 |
| --- | ---: | --- |
| `accountLevels[].listingBase` | 100/80/60/50/40 | 各等级实际使用的基础费 |
| `fees.custodyPer100ItemsDay` | 8 | 每 100 件每天预收的托管费 |
| `fees.listingValueBps` | 300 | 委托价值费率,300 bps = 3% |
重要:当前结算实际读取 `accountLevels[].listingBase`。顶层 `fees.listingBase` 仍保留在 schema 和命令中作为兼容字段,但不参与订单费用计算;调整基础费时应修改每个等级的 `listingBase`。旧字段 `custodyPerItemDay`、`custodyPer64UnitDays` 会迁移为 `custodyPer100ItemsDay`,随后删除旧字段。
托管费按完整订单原始数量和期限预收。提前取消、提前完全成交、替换/改价时,按 `usedDays = min(期限, max(1, ceil((结束时间 - 创建时间) / 1 天)))` 计算已使用天数,未使用整天的托管费进入玩家待领取货币;自然到期不退。固定费和价值费不退。旧订单缺少费用拆分快照时不退款。替换订单会另外按新数量、价格和期限收取新订单完整上架费。收购订单首次扣款为“数量 × 委托单价 + 上架费”。
### 5.2 成交手续费
默认 `fees.tradeFeeBps = 1500`,即 15%。自由市场成交手续费始终由卖方承担,买方只支付成交额。
```text
目标累计手续费 = ceil(出售订单累计成交额 × tradeFeeBps / 10000)
本次手续费 = 目标累计手续费 - 该订单已收手续费
```
费率会快照到订单。修改费率只影响之后创建或替换的订单,不回写旧订单。
### 5.3 仓库赎回费
```text
赎回费 = warehouse.redemptionBase
+ warehouse.redemptionPerItem × 该仓库记录数量
```
默认基础费 100、每件费 1。费用和经济 ID 在入库时快照,之后修改配置不会改写已有记录。只有 `redemption_locked` 收费。
### 5.4 系统商店费用
系统商店不套用自由市场上架费或成交手续费:
- 商店出售给玩家:玩家支付 `单价 × 数量`。
- 商店回收玩家物品:玩家收到 `单价 × 数量`。
## 6. 默认等级配置
限制:
| 等级 | 买单数 | 卖单数 | 物品托管 | 货币托管 | 最长期限 | 基础费 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| 1 | 5 | 5 | 2,048 | 100,000 | 7 | 100 |
| 2 | 10 | 10 | 5,000 | 200,000 | 14 | 80 |
| 3 | 20 | 20 | 10,000 | 500,000 | 30 | 60 |
| 4 | 40 | 40 | 16,384 | 1,000,000 | 60 | 50 |
| 5 | 80 | 80 | 50,000 | 5,000,000 | 60 | 40 |
升级目标:
| 目标等级 | 成交笔数 | 不同对手 | 成交额 | 账户天数 | 升级费 |
| --- | ---: | ---: | ---: | ---: | ---: |
| 2 | 20 | 10 | 5,000 | 7 | 10,000 |
| 3 | 100 | 25 | 50,000 | 30 | 50,000 |
| 4 | 500 | 50 | 500,000 | 90 | 200,000 |
| 5 | 2,000 | 100 | 5,000,000 | 180 | 1,000,000 |
所有升级条件必须同时满足。有效指标只在玩家自由市场已完成 Trade 时增加,买卖双方都会增加一笔、一个可能的新对手和对应成交额;系统商店不计入。
等级必须从 1 开始连续配置。删除仍被玩家账户或活动报价等级门槛引用的等级会被拒绝。
## 7. 价格控制
默认参数:
| 字段 | 默认 | 作用 |
| --- | ---: | --- |
| `enforceRange` | `true` | 是否强制限制玩家出售、求购和改价的单价 |
| `coldMin` / `coldMax` | 1 / 200000 | 数据不足时允许的冷启动价格范围 |
| `updateMinutes` | 60 | 价格快照更新周期 |
| `historyDays` | 7 | 成交样本窗口 |
| `maxSamples` | 200 | 每个 Instrument 最大样本数 |
| `minimumTrades` | 10 | 切换动态范围所需最少成交数 |
| `minimumPlayers` | 5 | 所需最少不同玩家数 |
| `trimBpsEachSide` | 1000 | 两侧各裁剪 10% 异常样本 |
| `playerWeightBps` | 2000 | 单一玩家最大有效权重 20% |
| `dynamicMinimumBps` | 5000 | 动态下限为参考价 50% |
| `dynamicMaximumBps` | 20000 | 动态上限为参考价 200% |
| `maximumStepBps` | 1000 | 单次参考价最大变化 10% |
| `maximumDailyBps` | 3000 | 自然日最大累计变化 30% |
| `algorithmVersion` | 1 | 算法版本 |
| `batchSize` | 16 | 每批计算 Instrument 数量 |
价格统计只读取业务数据库中 `status=completed` 且来源为玩家自由市场的 Trade,不读取 JSONL journal。系统商店、未完成交易和异常记录不会进入正常自由市场价格样本。
订单价格在创建、替换和执行快速请求前都按已发布快照重新校验。`enforceRange=true` 时,出售、求购和改价必须落在当前范围;关闭时仍显示动态/冷启动推荐范围,但只要求价格为正整数,不强制限制范围。切换模式后后台每秒分批同步最多 256 张活动订单,完成同步前暂停撮合;关闭时恢复仅因价格被暂停的订单,账户冻结不受影响。更新后的范围可能暂时阻止旧订单撮合,但不会移动或丢失托管资产。旧配置没有 `enforceRange` 时按 `true` 加载,保证升级后保持原有安全行为。
## 8. 经济配置
支持:
- `llmoney`:对接 LLMoney。
- `scoreboard`:对接计分板目标,当前按在线玩家经济处理。
每个经济有稳定 `id` 和玩家可见 `name`。玩家界面显示名称,内部记录仍使用 ID。
限制:
- ID 必须匹配 `[a-z][a-z0-9_-]{0,31}`。
- 经济 ID 必须唯一。
- 同一个底层来源不能重复注册;例如不能用两个 ID 同时指向同一 LLMoney 来源。
- scoreboard objective 只能使用 `[A-Za-z0-9_.-]`,最长 16 个字符。
- 已被订单、仓库或商店引用的经济来源不能随意改变。
修改自由市场结算经济只影响之后创建的订单和之后的升级;已有订单保留自己的经济快照。
## 9. 物品身份、分类和禁售
插件将两类数据分离:
- `ItemSnapshot`:精确保存实际物品及 NBT,用于扣物、托管、交付和补偿。
- `Instrument`:用于订单簿身份、搜索、分类和价格统计。
基岩版 Data Value 与耐久必须分开:非耐久物品的根 NBT `Damage` 是 DV,可形成不同 Instrument;耐久物品的已损耗耐久优先读取 LSE 运行时 `Item.damage`,最大耐久读取 `Item.maxDamage`。运行时值不可用的兼容路径依次回退到 `tag.Damage`、组件 Damage 和根 `Damage`;根 `Damage` 不会覆盖可信的运行时耐久。`tag.RepairCost`、自定义名称、Lore 和附魔均随物品元数据保存并在详情中显示。
默认 `maxItemNbtBytes = 65536`,硬上限 262144。容器、疑似容器、潜影盒、收纳袋和未知容器 NBT/能力始终硬拒绝,不能通过配置放行。`tag.minecraft:item_lock` 为 `1`(锁定槽位)或 `2`(锁定背包)的物品同样始终禁售,不能上架、快速出售或作为系统回收报价。
### 9.1 耐久快照修复与历史记录
新创建的 `ItemSnapshot` 使用 `snapshotVersion: 2`:真实损耗单独保存为 `actualDamage`,交付时通过 LSE `Item.setDamage(actualDamage)` 还原。不要将运行时 `Item.damage` 写入根 NBT `Damage`。
新快照只保存一个 `snbt` 字段,它就是 LSE `item.getNbt().toSNBT()` 返回的文本;插件不对该字段补写、排序或重编码,交付也以它重建物品。规范化 NBT 仅在上架分析期间留在内存中,用于大小检查、指纹和 Instrument 身份,不会写入订单、仓库、Trade 或商店报价。旧记录若只含 `canonicalSnbt`,读取时仅作为兼容回退。
`snapshotVersion: 1` 的历史耐久物品若曾把根 NBT `Damage` 误当作耐久,数据库中没有足够信息反推出原始 `Item.damage`,不得自动改写订单、仓库、Trade 或 journal。部署前先完整备份 `plugins/LSEMultiMarket/data`;对受影响的活动订单先暂停自由市场或冻结相关账户,依据部署前备份、玩家记录和审计证据核对。需要补偿时,由 `recovery_admin` 手持已设置正确耐久的物品执行 `grantitem` 补发,并记录原因和关联操作 ID。
可配置禁售:
- 完整 `typeId`
- 命名空间
- typeId 模式
- 附魔 ID 和最低等级
- 结构化 NBT 路径及值规则
不要用 SNBT 文本包含关系代替结构化规则。插件会解析 NBT 并按类型比较,避免玩家通过字段顺序或字符串伪装绕过。
分类规则支持精确 typeId、命名空间、typeId 模式和结构化标签条件。GUI 还提供原版风格建筑、自然、装备、物品四类浏览。
## 10. 仓库策略
默认:
```text
免费记录数:50
免费物品总数:4096
免费领取期:30 天
付费赎回期:30 天
赎回基础费:100
赎回每件费:1
赎回经济:coin
```
入库时只统计 `available` 记录占用的免费记录数和物品数:
- 容量内进入 `available`。
- 超出部分立即进入 `redemption_locked`,并从入库时开始计算赎回期。
- `available` 免费期结束后转为 `redemption_locked`。
- 赎回期结束后转为 `archived`。
- 补偿和管理员补发进入 `protected`,不收费、不自动过期。
仓库目前限制免费区域,不限制 `redemption_locked` 或 `archived` 的历史总量。因此玩家反复让订单到期可能形成大量付费/归档记录。应通过合理的免费容量、赎回费、期限和运营规则抑制其被当作云背包,并监控归档记录数量。
领取先检查背包容量,再扣费,再交付;明确交付失败会退款,不确定结果进入人工复核。归档记录只能由 `recovery_admin` 恢复为免费受保护记录或指定费用的赎回记录。
## 11. 系统商店设计
商店字段:
- `shopId`:稳定内部 ID。
- `name`:玩家可见名称。
- `economyId`:该商店独立结算经济。
- `command`:专用无参数玩家命令,不含 `/`。
- `sortOrder`:数值越小越靠前。
- `status`:`active`、`paused`、`archived`。
报价方向:
- `sell_to_player`:商店出售物品给玩家,必须保存可交付的精确 ItemSnapshot。
- `buy_from_player`:商店回收玩家物品,按 Instrument 身份核对玩家提交的精确物品。
报价还可配置:显示分组 `groupName`、单价、有限/无限库存、剩余库存、每玩家限额、周期 `none/daily/weekly/monthly`、最低账户等级和状态。分组在每个商店的出售/回收方向内展示;旧报价或未填写时使用“默认分组”。管理员 GUI 可在创建和编辑报价时修改分组。
报价唯一键是:
```text
shopId + instrumentId + direction + period + minAccountLevel
```
完全重复报价会被拒绝;不同周期或不同等级门槛可以使用不同报价。`groupName` 只是显示与导航属性,不进入唯一键,因此不能靠更换分组创建完全重复报价。更新报价可改分组、价格、库存、限额和状态,不改变唯一身份。要改变周期或等级门槛,应复制为新报价。
创建报价时,管理员 GUI 从背包选择物品;命令读取当前手持物品。首次出现的新物品 Instrument 会和报价在同一数据库事务中保存。有限库存只影响商店出售方向;无限库存不会使用特殊大数哨兵。
商店专用命令只在服务器启动阶段注册。新建商店后,报价立即可通过 `/market shop` 使用,但新专用命令需要下次重启才会注册。
## 12. 管理员命令总览
所有 `<原因>` 都是必填,可包含空格。玩家参数可使用已知玩家名或 XUID。
### 12.1 审计、角色和恢复
| 命令 | 角色 | 作用 |
| --- | --- | --- |
| `/marketadmin` | 任一管理员角色 | 打开管理员 GUI |
| `/marketadmin reviews` | auditor / recovery_admin | 列出待人工复核操作 |
| `/marketadmin grant <玩家> <角色> <原因>` | super_admin | 授权角色 |
| `/marketadmin revoke <玩家> <角色> <原因>` | super_admin | 撤销角色 |
| `/marketadmin freeze <玩家> <原因>` | recovery_admin | 冻结交易账户和撮合 |
| `/marketadmin unfreeze <玩家> <原因>` | recovery_admin | 解冻账户 |
| `/marketadmin setlevel <玩家> <等级> <原因>` | recovery_admin | 设置玩家交易账户等级;目标等级必须已配置 |
| `/marketadmin restore <玩家> <entryId> <free|费用> <原因>` | recovery_admin | 恢复归档仓库记录 |
| `/marketadmin grantitem <玩家> <数量> <原因>` | recovery_admin | 按管理员手持物品补发受保护仓库记录 |
| `/marketadmin adjustpending <玩家> <economyId> <增减值> <原因>` | recovery_admin | 调整待领取货币 |
| `/marketadmin quotareset <offerId> <玩家> <periodKey> <原因>` | recovery_admin | 重置某周期报价限额 |
冻结不会删除订单或移动资产,只会阻止账户继续正常交易/撮合,便于调查。不要把冻结当作取消订单。
### 12.2 自由市场、费用和期限
```text
/marketadmin exchangeenabled <on|off> <原因>
/marketadmin exchangeeconomy <economyId> <原因>
/marketadmin fee <tradeFeeBps> <原因>
/marketadmin fees <兼容基础费> <每100件每日托管费> <上架价值bps> <成交bps> <原因>
/marketadmin durations <逗号分隔天数> <最大天数> <原因>
```
`durations` 必须升序、无重复,最后一个值必须等于最大天数。修改等级基础费请使用 `level` 命令或 GUI,不要依赖 `fees` 的第一个兼容字段。
### 12.3 等级
```text
/marketadmin level <等级> <最大买单> <最大卖单> <物品托管> <货币托管> <最长期限> <基础费> <升级费> <成交数> <对手数> <成交额> <账户天数> <原因>
```
命令只编辑已有等级;新增/删除等级建议使用 GUI,因为 GUI 会执行连续等级和引用检查。
`recovery_admin` 可在“恢复管理 -> 设置玩家等级”中先从在线玩家按钮中选择,或点击首项模糊搜索输入部分玩家名或 XUID。匹配玩家和已配置等级均通过按钮选择;在线列表和搜索结果每页显示 6 人。也可使用:
```text
/marketadmin setlevel <玩家名|XUID> <等级> <原因>
```
设置等级会原子写入玩家账户、业务操作和审计 journal,不会移动货币或物品,也不会取消已有订单。若目标尚未创建交易账户,会先创建 Lv.1 账户再设置指定等级;已有交易统计和冻结状态保持不变。
示例:把 Lv.1 卖单上限改为 8,其余保持默认:
```text
/marketadmin level 1 5 8 2048 100000 7 100 0 0 0 0 0 调整新玩家卖单上限
```
### 12.4 经济
```text
/marketadmin economyadd <id> [名称] llmoney <原因>
/marketadmin economyadd <id> [名称] scoreboard <objective> <原因>
```
示例:
```text
/marketadmin economyadd coin 金币 llmoney 注册主经济
/marketadmin economyadd points 活跃点 scoreboard market_point 注册活动积分
```
经济名称用于玩家显示;未填写名称时默认使用 ID。重命名经济使用管理员 GUI。
### 12.5 价格控制
```text
/marketadmin priceinterval <分钟> <原因>
/marketadmin priceconfig <15个数值> <原因>
```
`priceconfig` 的 15 个数值顺序固定为:
```text
coldMin coldMax updateMinutes historyDays maxSamples
minimumTrades minimumPlayers trimBpsEachSide playerWeightBps
dynamicMinimumBps dynamicMaximumBps maximumStepBps maximumDailyBps
algorithmVersion batchSize
```
复杂价格参数优先使用 GUI,减少错位风险。
### 12.6 分类和禁售
```text
/marketadmin categoryadd <categoryId> <type|namespace|pattern> <值> <原因>
/marketadmin restrictions <最大NBT字节> <typeIds|-> <namespaces|-> <patterns|-> <原因>
```
列表参数用逗号分隔,`-` 表示空列表。`restrictions` 命令只修改基础 ID/命名空间/模式规则并保留现有附魔和 NBT 规则;附魔、结构化 NBT 规则以及分类编辑/删除请使用 GUI。
### 12.7 仓库和 journal
```text
/marketadmin warehouse <免费记录> <免费物品> <领取天> <赎回天> <基础费> <每件费> <economyId> <原因>
/marketadmin journal <刷新毫秒> <批事件数> <批字节> <队列事件数> <原因>
```
修改仓库规则只影响之后新建记录的费用和期限快照。journal 目录不能通过命令修改。
### 12.8 系统商店和报价
```text
/marketadmin shopcreate <shopId|auto> <名称> <economyId> <专用命令> <排序> <原因>
/marketadmin shopstatus <shopId> <active|paused|archived> <原因>
/marketadmin offercreate <offerId|auto> <shopId> <sell_to_player|buy_from_player> <单价> <原因>
/marketadmin offerupdate <offerId> <单价> <库存|-> <限额|-> <active|paused|archived> <原因>
/marketadmin offerstatus <offerId> <active|paused|archived> <原因>
/marketadmin offercopy <原offerId> <新offerId|auto> <none|daily|weekly|monthly> <最低等级|-> <原因>
```
`offercreate` 读取管理员当前手持物品,并创建“无限库存、无周期、无最低等级、无每玩家限额”的基础报价。需要有限库存、周期和限额时,使用 GUI 创建,或创建后配合复制/更新。
示例:创建一个系统回收报价:
```text
# 管理员先把目标物品拿在手中
/marketadmin offercreate auto material-shop buy_from_player 5 回收基础材料
```
## 13. 配置有效性限制
运行期保存前会整体验证候选配置;任何一项失败时,主配置和内存策略都保持原值。
常见限制:
- 存储路径必须位于插件隔离目录,不能指向旧测试数据库。
- 后端只能是 `json` 或 `kv`。
- 自由市场结算经济、仓库赎回经济和商店经济必须已注册。
- 允许期限必须唯一升序,且与最大期限一致。
- 费率必须是合法 bps,金额和数量必须是安全非负整数。
- journal 刷新、批大小和队列必须大于最小安全值。
- 系统商店命令不能与市场保留命令或其他商店命令重复。
- 报价 ID、商店 ID、报价唯一键不能重复。
- 有限库存必须是非负整数;无限库存必须使用 `null` 语义。
## 14. 故障恢复和审计
所有物品/货币移动都先保存不可变操作计划,再进入外部操作、取得、提交或补偿状态。关键状态包括:
```text
prepared -> external_pending -> acquired -> committing -> committed
\-> compensating -> compensated
\-> manual_review
```
- `failed`:外部系统明确失败,插件可以安全停止或补偿。
- `unknown`:外部系统结果不确定,插件不会猜测,也不会自动重复扣款/扣物。
- `manual_review`:冻结该操作,管理员核对经济、背包、数据库和 journal 后处理。
启动恢复会扫描未完成操作:未取得资产的计划可取消;已明确取得资产的操作按已保存提交点继续;未知外部结果保持人工复核。
journal 是异步审计副本,不在交易主路径执行同步 JSONL I/O。可在插件目录运行:
```powershell
npm run journal:verify
```
处理人工复核时至少记录:玩家 XUID、操作 ID、发生时间、物品指纹/经济 ID、外部系统实际结果和处理原因。不要删除 operation、event 或 journal 行来“解锁”。
## 15. 发布前和日常检查
构建前:
```powershell
npm install
npm run check
npm run journal:verify
```
最终 bundle 固定为:
```text
dist/LSEMultiMarket/LSEMultiMarket.js
```
上线前:
1. 停止服务器并完整备份插件配置和数据。
2. 在 Windows x64 服务根目录运行:
```powershell
lip install github.com/lwenk/LSEMultiMarket@0.2.0
```
3. LIP 只安装 bundle、manifest 和示例配置;不要删除或覆盖 `config.json`、`data` 或 journal。
4. 启动后检查 schema、数据库路径、恢复扫描、命令注册和系统商店命令日志。
5. 用非管理员账号完成一次小额出售、购买、取消、仓库领取和系统商店买卖。
6. 用管理员账号检查角色过滤、报价状态、审计和人工复核页面。
不要在未停止服务器时升级,也不要手工覆盖用户配置、数据库或 journal。
## 16. 常见管理员错误
- `economy backing sources must be unique`:两个经济配置指向同一底层来源;删除重复设计或改用不同 scoreboard objective。
- `invalid system shop`:商店 ID、名称、经济、专用命令、状态或排序不合法。
- `system shop or Instrument not found`:确认商店仍存在,并使用包含首次物品 Instrument 原子持久化修复的最新 bundle;重新从背包选择物品后创建报价。
- `offer ... identity already exists`:同一商店、物品、方向、周期和最低等级已存在完全重复报价;编辑原报价或改变周期/等级门槛。
- `duration is not available for this account level`:所选期限不在全局允许列表,或超过玩家等级上限。
- `immutable operation plan changed`:重复使用了旧表单/旧操作 ID,或操作参数在提交前变化;关闭表单重新发起,不要重复点击。
- `manual_review`:先查数据库 operation 和 journal,再核对 LLMoney/scoreboard 与玩家背包;不得盲目重试价值移动。
[/MD]
[MD] # LSEMultiMarket ## 使用手册 - [玩家使用手册](docs/PLAYER_GUIDE.md):自由市场、系统商店、费用、等级、仓库、筛选和玩家命令。 - [管理员使用手册](docs/ADMIN_GUIDE.md):权限、配置、费用公式、等级、系统商店、恢复、审计和管理员命令。 - [更新日志](CHANGELOG.md):面向服务器管理员的版本、安装和发布流程变化。 面向 LegacyScriptEngine QuickJS 的可恢复玩家自由市场与系统商店。领域层不依赖 LSE;LSE API 只存在于 `src/hosts/lse`。玩家市场匿名,系统商店和玩家订单簿 使用不同业务入口,但共享账户、经济、仓库、审计和恢复基础设施。 ## 本地验证 ```powershell npm install npm run check npm run journal:verify ``` `npm run check` 依次执行 TypeScript 检查、全部自动化测试、单文件构建和打包后 LSE smoke。最终入口固定为: ```text dist/LSEMultiMarket/LSEMultiMarket.js ``` 构建目录同时包含 `manifest.json` 和 `config.example.json`。bundle 为 IIFE,不含 运行时 `import`、`export` 或 `require`。 ## 数据隔离 - 配置:`./plugins/LSEMultiMarket/config.json` - JSON 数据(默认):`./plugins/LSEMultiMarket/data/store.json` manifest;玩家按 XUID 写入 `data/store/player`,公共索引和记录写入 `data/store/index`、`data/store/record` - KV 数据(可选):`./plugins/LSEMultiMarket/data/database` - journal:`./plugins/LSEMultiMarket/data/logs` - `schemaVersion` 固定为 2;配置、数据库和 journal 使用隔离路径,部署不会覆盖现有运行时数据。 配置首次启动原子创建。管理用例先验证候选配置,再通过 `.new`/`.bak` 激活;写入 或运行时激活失败时保留旧主文件和旧内存策略。 如果新路径尚不存在,启动时会把旧的带版本号配置及其备份原子迁移到 `config.json` 与 `config.json.bak`;如果 `config.json` 已存在,则不会覆盖或改动旧文件。 ## 入口 - `/market`:玩家 GUI,包含自由市场、出售/求购、快速买卖、自己的订单与历史、 账户等级、仓库、待领取货币和系统商店。 - `/market help`:逐条显示彩色分组命令帮助;玩家市场可用 `/market browse <筛选词> [页码]` 匿名筛选浏览,订单、历史、商店和报价列表也支持页码,`/market shop list` 列出商店,玩家可继续 按序号查看报价,并通过 `shopbuy` / `shopsell` 直接购买或回收手持物品。 `shop` 的模式使用 LSE 枚举 `buy|recycle|gui`,数量、单价和期限使用整数参数, 由 BDS 提供自动补全和输入校验。 - `/marketadmin`:独立管理员 GUI;需要 OP,并按持久化组合角色显示模块。 - 控制台 `/marketadmin grant|revoke <xuid> <role> <reason>`:首次授权和恢复。 - 系统商店的持久化专用命令在服务器启动时注册。 命令等价入口见 [集成说明](docs/INTEGRATION.md)。GUI 和命令共同调用 `MarketApplicationFacade` / `AdminApplicationFacade`,不会直接修改余额、物品、 订单或配置文件。 ## 资产安全 - ItemSnapshot 保存原始物品身份,Instrument 单独用于撮合;容器和不确定容器能力 均硬拒绝。 - 所有物品/货币移动使用持久化状态、不可变计划、提交点、幂等恢复和补偿。 - 外部结果为 `unknown` 时进入 `manual_review`,启动恢复不会猜测经济或背包结果。 - 玩家市场手续费始终由卖方承担;买方只支付成交额。 - 调度切片最多 32 个成交分片,快速请求最多 256 个,剩余订单继续留簿。 - journal 只走异步 File append;交易主路径不做同步 JSONL I/O。 - 价格统计从业务数据库已完成 Trade 的持久化索引读取。 ## 安装与升级 在已停止的 Windows x64 BDS/LeviLamina 服务根目录运行: ```powershell lip install github.com/lwenk/LSEMultiMarket@0.2.0 ``` LIP 会解析所需的 LegacyScriptEngine 依赖,并只将 `LSEMultiMarket.js`、`manifest.json` 和 `config.example.json` 安装到 `plugins/LSEMultiMarket`。升级前先备份整个插件目录; 发布包不会包含或覆盖现有 `config.json`、`data` 或 journal,服务器仍必须保持停止直到 安装完成。发布与回滚步骤见 [运维说明](docs/design/OPERATIONS.md),真服项目见 [中文人工测试清单](docs/MANUAL_TEST_CHECKLIST.md)。 [/MD]