批量导出性能优化 消除 N+1 查询与协程并发
问题病害导出 Excel 的时候,每条病害都得查它的类型颜色、所属层位、雷达文件的里程数组……最早是”遍历病害、一条一条查库”,导出几百上千条的时候,查库次数跟着病害数线性往上涨——典型的 N+1。批量导出耗时的大头全花在查库上,反而不是绘图。
优化分三步走:
批量预查询,干掉 N+1
把循环里那条单查,改成开头一次性批量查、构个 Map 缓起来:
val colorMap = service.batchQueryColors(defects.map { it.typeId })defects.forEach { d -> d.color = colorMap[d.typeId] } // O(1) 查
像病害类型颜色这种”N 条病害其实只有几种类型”的数据,批量查一次,查询次数从 N 降到 1。
按雷达文件分组,共享渲染上下文
原来每条病害导出截图都要重新打开雷达文件、重算里程数组。改成按雷达文件分组,同一个文件的病害共享一次”开文件 + 算里程数组”的上下文,省掉一堆重复 IO。
协程并发各文件
分完组,各雷达文件的导出互相不依赖 ...
JavaFX 后台线程操作 ObservableList 报 IllegalStateException
现象做”导入历史数据”功能的时候,解析 Excel 是个耗时操作,很自然就丢到后台线程。解析完直接往绑了 TableView 的 ObservableList 里 add,结果运行就炸了:
Exception in thread "pool-1-thread-1" java.lang.IllegalStateException: Not on FX application thread; currentThread = pool-1-thread-1
原因JavaFX 有条规矩:所有对 Scene Graph 的改动(包括 ObservableList、Property 的变更)都得在 JavaFX Application Thread 上做。ObservableList 一变就会触发 UI 监听器去刷表格,后台线程改它就破坏了线程安全,所以框架直接抛异常拦下来。
一开始图省事的写法最开始图省事,每解析一条就用 Platform.runLater 包一下 add:
thread { val parsed = parseRow(row) ...
用 Kotlin 协程替代 Thread 并行加载多测线雷达图
背景多测线雷达图加载,原来用 ExecutorService + Thread:一条测线一个任务,结果靠回调或者 Future 收。线程数基本靠拍脑袋,取消靠一个 volatile 标志位,异常处理各写各的,并行度也把握不好。串行加载几条测线的时候界面就干等着,体感很慢。
换成协程换成 Kotlin 协程以后,多测线并行加载就剩这么一段:
suspend fun loadMultiScanLines(files: List<RadarFile>): List<RadarData> = coroutineScope { files.map { file -> async(Dispatchers.IO) { readRadar(file) } // 每条测线一个异步任务 }.awaitAll() // 并行,全部完成后返回}
比 Thread 那套舒服的地方:所有 async 任务都挂在同一个 co ...
MVVM 重构 把数据加载从 View 下沉到 ViewModel
起因项目最早的写法是典型的”胖 Controller”:View(FXML 对应的 Controller)里既管 UI 交互,又直接调 Service 加载数据、刷表格、维护选中状态。功能一多,单个 Controller 几千行,加载数据的逻辑和 UI 操作揉一块儿,几乎没法单测,改一处怕动全身。
拆分按 MVVM 把职责切开:
View 只管 UI 绑定和用户交互,从 UI 组件里抠纯数据(比如表格当前选中的行),不含业务。
ViewModel 持有状态(Property / ObservableList),负责数据加载的调度和参数构建,对 View 暴露可绑定的属性。
Service 是纯逻辑,不碰 JavaFX,能在任意线程跑、能单测。
核心动作就一个:把数据加载从 View 剥到 ViewModel。原来 Controller 里 service.load() 之后跟一大串 UI 刷新,现在改成 ViewModel 加载完更新自己的 ObservableList,View 靠绑定自动刷新:
class RadarViewModel { val fi ...
twikoo找回管理员密码
twikoo找回管理员密码前言距离上一次维护网站,已经过去很长很长一段时间了。中间那阵子,我一直在调整自己的生活状态,后来又搬到了另一个城市生活,更是完全没了心思去管网站的事。一直到去年年底,域名和服务器同时到期——服务器咬咬牙还是续上了,域名却换成了一个便宜的新域名。之后就是一点一点地把网站内容往新域名迁移,又折腾了新域名的备案。最近备案总算通过,网站也终于重新上线。可结果,发现 twikoo 的暗号和管理员密码早已被忘得一干二净。于是,便有了这篇关于「twikoo 找回管理员密码」的记录。
部署方案说明我的 Twikoo 是通过 Vercel 部署的,数据库用的是 MongoDB。接下来的操作步骤和截图,也都是基于这套环境来展开的。
浏览器控制台找回暗号(HIDE_ADMIN_CRYPT)先解决「暗号」的问题。这个其实不用去翻数据库,直接在浏览器控制台就能看到:
打开网站首页,按 F12 打开开发者工具,切到 Network 面板。
刷新页面,等 twikoo 加载完成后,在 Network 的过滤框里搜索 HIDE_ADMIN_CRYPT。
进到 MongoDB 查看 tw ...
用 jlink 给 JavaFX 桌面应用生成精简自定义 JRE
为什么用 jlink桌面应用分发有个痛点:用户机器上不一定有 JDK,让用户自己装个 25 既不现实也挺难受。jlink 可以把”应用真正用到的那些 jmod”拼成一个精简的、独立的 JRE,跟应用一起打包,用户双击就能跑,体积也比塞一个完整 JDK 小很多。
准备需要两套 jmods:
JDK 自带的:在 $JDK_HOME/jmods(openjdk-25 自带)。
JavaFX 的:要单独下 javafx-jmods-25.0.1,SDK 包里没有,得另外找。
两套都得是 25,版本不一致 jlink 会直接拒绝链接。
命令核心就一条 jlink,把 --module-path 指向两个 jmods 目录,--add-modules 列出实际要用的模块:
%JDK_HOME%\bin\jlink ^ --module-path "%JDK_HOME%\jmods;%JAVAFX_JMODS%" ^ --add-modules java.base,java.management,jdk.charsets,javafx.controls,javafx.fx ...
Kotlin 数据类型
Kotlin 数据类型使用var定义变量
var b: Int = 1// val 创建常量val a = 1
所有的数据类型都是对象 Int
public actual class Int private constructor() : Number(), Comparable<Int>public actual abstract class Number { /** * Returns the value of this number as a [Double], which may involve rounding. */ public actual abstract fun toDouble(): Double /** * Returns the value of this number as a [Float], which may involve rounding. */ public actual abstract fun toFloat(): Float /** * Retur ...
Docker 部署 File Browser 文件管理系统
Docker 部署 File Browser 文件管理系统1. File Browser 介绍File Browser 是一款基于 Go 语言开发的开源、轻量级、跨平台的 Web 文件管理工具。
它允许用户通过浏览器访问和管理服务器上的文件,支持 Windows、macOS、Linux 等多种操作系统。
FileBrowser 具备文件的上传、下载、复制、移动、重命名、在线预览及编辑等功能,还提供多用户支持与精细的权限控制,可设置不同用户对文件和目录的访问权限。
此外,它支持连接 FTP、SFTP、S3 等远程存储,方便用户管理远程文件,并且能够通过生成带密码或有效期的外链来实现文件分享。
官网:https://filebrowser.org/GitHub:https://github.com/filebrowser/filebrowser
官网在线体验地址:https://demo.filebrowser.org/用户名: demo密码: demo
2. 安装以下是使用Docker安装的步骤:
创建本地存储目录
# File Browser的数据文件存储目录mkdir -p /my ...
JavaFX初学
JavaFX初学1. Scene Graph(场景图)JavaFX 的场景图是一个树状数据结构,表示应用程序的所有 UI 组件。
层次结构:Stage (舞台) → Scene (场景) → Parent (根节点) → Nodes (子节点)
2. 事件机制JavaFX 使用事件驱动编程模型,基于观察者模式。
主要事件类型:
ActionEvent:按钮点击等
MouseEvent:鼠标相关事件
KeyEvent:键盘事件
WindowEvent:窗口事件
3. 常用容器和布局方法VBox - 垂直排列
VBox vbox = new VBox(10); // 10像素间距vbox.setPadding(new Insets(15)); // 内边距vbox.setAlignment(Pos.CENTER);
HBox - 水平排列
HBox hbox = new HBox(10);hbox.setPadding(new Insets(10));hbox.setAlignment(Pos.CENTER);
BorderPane
BorderPane borderPane = ...
从零搭建 JavaFX 25 与 Kotlin 2.3 桌面应用踩坑记
背景最近在做一个 JavaFX 桌面应用(数据可视化分析类的),想尝鲜下新特性、也考虑长期维护,技术栈直接拉到最新的 JDK 25 + Kotlin 2.3 + JavaFX 25。结果就是踩了一整圈版本对齐和模块系统的坑,记一下。
版本必须严格对齐这套组合对版本非常敏感,哪个对不上都会报各种莫名其妙的错:
组件
版本
为什么必须是这个版本
JDK
25
JVM Target 统一为 25
Kotlin
2.3.0
第一个正式支持生成 Java 25 字节码的版本
Gradle
9.1+
低版本 Gradle 不认 JDK 25
JavaFX
25.0.1
与 JDK 25 配套
IDEA
2025.3+
旧版对 Kotlin 2.3 支持不全
Kotlin 版本低于 2.3 的时候,编译器没法把字节码目标设到 25,直接报编译错误;Gradle 低于 9.1 的时候连 java.toolchain 都识别不了 JDK 25。这俩一旦对不上,报错信息往往指东打西,很难一下定位到是版本问题。
模块系统JavaFX 25 在强模块化下,大量内部包(com.s ...
