- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
本文围绕 MooTool 仓库next-compose目录下的 DIFF-662 切片文档展开,讲清 F11 网络/IP 工具中 IPv4↔Long 两个转换按钮的可点击状态是如何被NetWiringPresentation的两个纯函数(ipv4ToLongActionEnabled/longToIpv4ActionEnabled)派生出来的,以及它们如何委托给NetEngine做校验、在NetScreen中完成接线、并被 desktopTest 单测覆盖。读完你能掌握这套“展示层纯函数 + 引擎委托校验”的按钮状态控制写法,并能复现该切片的验证命令。
切片定位与验证基线
DIFF 系列文档是next-compose/docs/diff/下逐片记录的增量开发日志,每片只聚焦一个最小变更面。DIFF-662 文档 原文极简,但信息完整:
- 基线:DIFF-661(工作区),即本切片建立在 DIFF-661 之后的工作树上;后续切片 DIFF-663 又以 DIFF-662 为基线,形成链式演进。
- 范围:F11 的 IPv4↔Long 转换钮
ipv4ToLongActionEnabled/longToIpv4ActionEnabled(委托NetEngine校验)+NetScreen接线。 - 验证(JDK 21 环境):
./gradlew :composeApp:desktopTest --offline --no-daemon ./scripts/verify-product-evidence-prep.sh其中--offline表示不访问网络解析依赖,--no-daemon避免 Gradle 守护进程残留;证据准备脚本对应仓库中的 verify-product-evidence-prep.sh。验收台账 acceptance.md 的 F11 行也明确把「IPv4/Long 转换ipv4ToLongActionEnabled/longToIpv4ActionEnabled」标注为引用 DIFF-662 的已接线能力,说明该切片在 F11 工具(左侧命令输出区 + 右侧功能区的网络/IP 面板)中属于 IPv4/Long 转换小节的核心状态控制。
NetWiringPresentation:把按钮 enabled 派生成可单测的纯函数
本切片新增的两个函数位于 NetWiringPresentation.kt:
fun ipv4ToLongActionEnabled(ipv4: String): Boolean = runCatching { NetEngine.ipv4ToLong(ipv4.trim()) true }.getOrDefault(false) fun longToIpv4ActionEnabled(longText: String): Boolean = runCatching { NetEngine.longToIpv4(longText.trim()) true }.getOrDefault(false)写法上有三个值得注意的点:
- 校验逻辑零重复。
NetWiringPresentation本身不重新实现“什么算合法 IPv4 / 合法 Long”的判断,而是直接调用NetEngine.ipv4ToLong/NetEngine.longToIpv4并捕获其抛出的NetConvertException:转换成功返回true(按钮可点),抛异常返回false(按钮置灰)。这样 UI 的可用态与引擎的真实转换规则永远同源,不会出现“按钮可点但点下去报错”的分裂状态。 trim()先行。两个函数都先去除首尾空白再校验,与同文件中portScanStart等入口对输入统一 trim 的处理风格一致(见 NetWiringPresentation.kt)。- 纯函数、无 Compose 依赖。
NetWiringPresentation是一个object,内部只放fun(不含@Composable),因此可以被 desktopTest 直接调用断言,这正是该切片的可测试性来源。同文件还定义了PortScanStart/HostCommandStart这类sealed interface(L5-L13),把“可启动/被拦截 + 错误码”建模为数据,供 UI 分支渲染;转换钮场景更简单,只需一个Boolean。
NetEngine 侧的转换规则与边界
两个按钮最终委托到的引擎实现位于 NetEngine.kt:
fun ipv4ToLong(value: String): Long { val parts = value.trim().split('.') if (parts.size != 4 || parts.any { !it.matches(Regex("""^\d{1,3}$""")) || it.toInt() > 255 }) { throw NetConvertException("Invalid IPv4 address") } return parts.fold(0L) { acc, part -> acc * 256 + part.toInt() } } fun longToIpv4(value: String): String { val number = value.trim().toDoubleOrNull() ?: throw NetConvertException("Invalid IPv4 number") if (number % 1.0 != 0.0 || number < 0 || number > 0xffffffffL.toDouble()) { throw NetConvertException("Invalid IPv4 number") } return longToIpv4(number.toLong()) } fun longToIpv4(value: Long): String { if (value < 0 || value > 0xffffffffL) throw NetConvertException("Invalid IPv4 number") return listOf(24, 16, 8, 0).joinToString(".") { shift -> ((value shr shift) and 0xffL).toString() } }规则可以从源码直接读出:
- IPv4 → Long:必须恰好 4 段、每段 1~3 位纯数字且不超过 255;按
acc * 256 + 段值折叠成无符号 32 位整数。注意前导零是被接受的——测试断言了ipv4ToLong("127.0.0.01") == 2130706433L(NetEngineTest.kt),因为正则^\d{1,3}$不禁止01。 - Long → IPv4:字符串入口先
toDoubleOrNull(),再用number % 1.0 != 0.0排除小数(如1.5),并用0..0xffffffffL限制在 32 位无符号区间;Long 重载则按 24/16/8/0 位移取低 8 位拼成点分十进制。 - 失败语义:非法输入统一抛
NetConvertException(NetEngine.kt),它区别于命令类错误的NetException(后者携带NetworkErrorCode),让 UI 能对“转换失败”与“命令失败”分别给出提示。
对应的失败边界用例集中在 NetEngineTest.kt:256.0.0.1(段越界)、1.2.3/1.2.3.4.5(段数错误)、::1(IPv6 字面量)、127.0.0.(空段)应全部抛出NetConvertException;反向则-1、4294967296(上界 +1)、1.5(小数)均被拒绝,同时0.0.0.0 ↔ 0、255.255.255.255 ↔ 4294967295两个端点可往返。
NetScreen 接线:按钮 enabled 绑定与点击后的完整副作用
UI 侧接线在 NetScreen.kt 的Section(container.t("net.ipv4Long"))内,布局为「IPv4 输入框 → 两个转换按钮 → Long 输入框」:
Section(container.t("net.ipv4Long")) { LabeledField("IPv4", session.ipv4) { session.ipv4 = it; persist() } Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { MooButton( "${container.t("common.convert")} ↓", p5Toolbar = true, enabled = NetWiringPresentation.ipv4ToLongActionEnabled(session.ipv4), onClick = { runCatching { NetEngine.ipv4ToLong(session.ipv4).toString() } .onSuccess { value -> session.longValue = value session.error = "" container.history.save( ToolId.Net.id, NetHistoryMetadata.OP_IPV4_TO_LONG, value, session.ipv4, value, ) persist() } .onFailure { notifyNetFailure(container, session, convertMessage(container, it)) { persist() } } }) MooButton( "↑ ${container.t("common.convert")}", p5Toolbar = true, enabled = NetWiringPresentation.longToIpv4ActionEnabled(session.longValue), onClick = { /* 对称:longToIpv4 → session.ipv4,历史操作 OP_LONG_TO_IPV4 */ }) } LabeledField("Long", session.longValue) { session.longValue = it; persist() } }这条链路的完整行为是:
- enabled 实时派生:
MooButton的enabled直接调用本切片新增的NetWiringPresentation.*ActionEnabled。由于session.ipv4/session.longValue是 Compose 可观察状态,用户每次改输入框(onChange触发persist())都会引发重组,按钮置灰/点亮随之自动更新,无需手动刷新。 - 点击后写回对侧字段:转换成功时把结果写入另一端(
session.longValue或session.ipv4),并清空session.error。 - 写入通用历史:通过
container.history.save(ToolId.Net.id, ...)以NetHistoryMetadata.OP_IPV4_TO_LONG/OP_LONG_TO_IPV4操作类型记录本次转换的输入与输出,纳入 F11 工具的历史恢复体系。 - 失败兜底:虽然按钮已按纯函数置灰,但点击与点击前的状态仍可能存在竞态(例如用户在 IME 合成过程中状态未更新),因此
onClick内部仍保留runCatching,失败时经notifyNetFailure+convertMessage给出真实失败原因,而不是静默或伪造结果——这与 acceptance.md 对 F11「失败显示真原因」的总体要求一致。
单测覆盖与回归验证
针对本切片,NetWiringPresentationTest.kt 新增了ipv4LongConvertActionEnabled用例,覆盖按钮态的四个关键输入:
assertTrue(NetWiringPresentation.ipv4ToLongActionEnabled("127.0.0.1")) assertFalse(NetWiringPresentation.ipv4ToLongActionEnabled("")) assertFalse(NetWiringPresentation.ipv4ToLongActionEnabled("256.0.0.1")) assertTrue(NetWiringPresentation.longToIpv4ActionEnabled("2130706433")) assertFalse(NetWiringPresentation.longToIpv4ActionEnabled("")) assertFalse(NetWiringPresentation.longToIpv4ActionEnabled("4294967296"))即:合法地址/合法数值 → 可点;空串、段越界(256.0.0.1)、超上界(4294967296,比 32 位无符号最大值大 1)→ 置灰。与 NetEngineTest 的引擎级用例配合,形成「引擎转换规则 + 展示层派生规则」双层验证。
回归方式沿用原切片文档给出的两条命令(JDK 21):
./gradlew :composeApp:desktopTest --offline --no-daemon ./scripts/verify-product-evidence-prep.sh从源码结构看,*ActionEnabled这类命名与纯函数化接线在同仓库 DIFF-654 至 DIFF-669 的多个切片中反复出现(如时间、加密、Cron、计算器等工具各有对应的*ActionEnabled切片),可以推断这是next-compose在逐工具对齐 Electron 行为时统一采用的展示层状态控制模式:把“按钮何时可点”的判断从@Composable体内剥离到可单测的纯函数中,并以对应工具引擎作为唯一校验来源。本文所讲的 F11 IPv4/Long 转换,正是该模式在网络工具上的具体落地。
- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
相关推荐
openclaw-cn 浏览器自动化 CLI 完全指南:clawdbot browser 的配置、标签页管理、快照操作与远程控制
openclaw cn 浏览器自动化 CLI 完全指南:clawdbot browser 的配置、标签页管理、快照操作与远程控制 clawdbot browse
桌面应用开发工具cppcheck incompatibleFileOpen 检查详解:同一文件被读写双流并发打开
cppcheck incompatibleFileOpen 检查详解:同一文件被读写双流并发打开 导读 本文深入讲解 Cppcheck 静态分析工具中的 inc
桌面应用开发工具Zabbix 8.0 "Nextcloud by HTTP" 模板完全指南:基于 OCS API 的无脚本监控实战
Zabbix 8.0 "Nextcloud by HTTP" 模板完全指南:基于 OCS API 的无脚本监控实战 导读 本文围绕 Zabbix 官方模板仓库中
桌面应用开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考