浏览器里动态加载 opencv.js:等 onRuntimeInitialized
想在网页里用 OpenCV.js 做图像处理,标准写法是: <script async src="https://docs.opencv.org/4.x/opencv.js" onload="onOpenCvReady();"></script>想改成 JS 动态加载(比如按需加载、或者写扩展 / 油猴脚本),有几个坑。 基础动态加载 const script = document.createElement("script"); script.src = "https://docs.opencv.org/4.x/opencv.js"; script.async = true; script.onload = () => onOpenCvReady(); document.head.appendChild(script);但这个 onload 触发时,OpenCV 还没准备好——window.cv 存在,但里面的 native 函数还没初始化(emscripten 编译的 WASM 需要额外时间)。 正确姿势:等 onRuntimeInitialized script.onload = () => { cv.onRuntimeInitialized = () => { console.log("OpenCV Ready"); onOpenCvReady(); }; };cv.onRuntimeInitialized 是 emscripten 的钩子,WASM 加载并 instantiate 完毕后调用。这时 cv.imread、cv.Mat 才能真正用。 Promise 化封装 function loadOpenCV() { return new Promise((resolve, reject) => { // 已加载过 if (window.cv && cv.Mat) return resolve(cv); // 加载中(多次调用不重复插 script) if (window.cv) { cv.onRuntimeInitialized = () => resolve(cv); return; } const script = document.createElement("script"); script.src = "https://docs.opencv.org/4.x/opencv.js"; script.async = true; script.onload = () => { cv.onRuntimeInitialized = () => resolve(cv); }; script.onerror = reject; document.head.appendChild(script); }); }// 用 loadOpenCV().then(cv => { const mat = new cv.Mat(100, 100, cv.CV_8UC3); console.log(mat); });关键点:if (window.cv && cv.Mat) — 双重判断,cv 存在不代表 ready 支持并发调用(多次 loadOpenCV() 只加载一次真正的 script) 错误传给 reject(网络挂了能捕获)油猴脚本里加载 油猴脚本里 document.createElement("script") 遇到有 CSP(Content-Security-Policy)的网站会失败——unsafe-inline 被禁的情况下,外链 script 加载被阻。 用 @require 声明,油猴会把脚本预先拉下来注入到你的沙箱里: // ==UserScript== // @name Load OpenCV // @match *://*/* // @require https://docs.opencv.org/4.x/opencv.js // @grant none // ==/UserScript==(function () { "use strict"; cv.onRuntimeInitialized = () => { console.log("OpenCV Ready"); const mat = new cv.Mat(100, 100, cv.CV_8UC3); console.log(mat); }; })();@require 走的是 Tampermonkey / Violentmonkey / ScriptCat 自己的加载机制,不走页面 CSP。这是给受 CSP 保护的网站注 OpenCV 最稳的方式。 常见问题 1. cv is not defined script 还没加载完就用 cv。用 Promise 版本,.then 里再用。 2. cv.imread is not a function onRuntimeInitialized 没等——script.onload 触发早于 runtime init。 3. 用了 async/await 但还是异步崩溃 外面 async 函数忘了 await: async function process() { const cv = await loadOpenCV(); // 别忘 await const mat = new cv.Mat(...); }4. 内存泄漏 OpenCV.js 的 Mat 是 WASM 分配的内存,必须手动 delete: const mat = cv.imread("input"); try { // 处理 mat } finally { mat.delete(); }不 delete,跑几张图页面就 OOM。 想省点带宽 opencv.js 从官方 CDN 拉大约 8MB。有几个选择:换镜像:https://cdn.jsdelivr.net/npm/@techstark/opencv-js@4.x.x/dist/opencv.js 裁剪版本:自己 build 只带需要的模块,webnn、videoio 之类不需要就砍 本地缓存:Service Worker 缓存 opencv.js,二次加载秒开 动态判断需求:只有用户点了"图像处理"按钮再加载第一次加载 8MB 是常态,别期望闪电般。 一句话总结 动态加载 opencv.js 要等 cv.onRuntimeInitialized,封成 Promise 用起来最顺。油猴脚本里遇到 CSP 就用 @require。用完 Mat 记得 delete()。
PowerShell 环境变量设置:临时赋值、永久写入与路径追加
临时设置(当前会话有效) $env:ADMIN_PASSWORD = "your-password"关闭终端后失效。读取: echo $env:ADMIN_PASSWORD # 或 Write-Host $env:ADMIN_PASSWORD永久设置(当前用户) [System.Environment]::SetEnvironmentVariable("ADMIN_PASSWORD", "your-password", "User")写入注册表 HKCU\Environment,重新打开终端生效,不影响其他用户。 永久设置(系统级,需管理员权限) [System.Environment]::SetEnvironmentVariable("ADMIN_PASSWORD", "your-password", "Machine")写入 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment,所有用户生效。 读取已保存的环境变量 # 读取用户级别 [System.Environment]::GetEnvironmentVariable("ADMIN_PASSWORD", "User")# 读取机器级别 [System.Environment]::GetEnvironmentVariable("ADMIN_PASSWORD", "Machine")删除环境变量 # 删除用户级别 [System.Environment]::SetEnvironmentVariable("ADMIN_PASSWORD", $null, "User")追加 PATH(避免覆盖) $current = [System.Environment]::GetEnvironmentVariable("PATH", "User") [System.Environment]::SetEnvironmentVariable("PATH", "$current;D:\my-tools", "User")不要直接给 PATH 赋值,否则会覆盖已有路径。 当前会话立即生效 SetEnvironmentVariable 修改的是注册表,当前会话的 $env: 不会自动更新。如果需要立即生效,手动同步一次: $env:ADMIN_PASSWORD = [System.Environment]::GetEnvironmentVariable("ADMIN_PASSWORD", "User")查看所有环境变量 Get-ChildItem Env:
PowerShell 设置环境变量:临时、当前用户、系统级
PowerShell 里改环境变量有三档:当前会话、当前用户永久、系统级永久,权限和作用范围各不相同。搞混了一堆脚本会失效。 临时(仅当前 PowerShell 窗口) $env:MY_TOKEN = "secret" echo $env:MY_TOKEN只影响当前会话 关掉窗口就没了 90% 的临时脚本调试用这个就够对子进程也生效: $env:PYTHONPATH = "D:\repo\src" python main.py # main.py 里能读到永久(当前用户) [System.Environment]::SetEnvironmentVariable("MY_TOKEN", "secret", "User")写到注册表 HKCU\Environment 新开的进程能读到 当前 PowerShell 窗口需要重开 才能通过 $env:MY_TOKEN 看到不重开也想立刻用的话: $env:MY_TOKEN = "secret" # 先本地设一份永久(系统级) 需要管理员 PowerShell: [System.Environment]::SetEnvironmentVariable("MY_TOKEN", "secret", "Machine")写到 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment 所有用户都能读到 用于系统服务、多用户共享配置读取和删除 读: $env:MY_TOKEN [System.Environment]::GetEnvironmentVariable("MY_TOKEN", "User") [System.Environment]::GetEnvironmentVariable("MY_TOKEN", "Machine")删(把值设为空字符串就是删除): [System.Environment]::SetEnvironmentVariable("MY_TOKEN", $null, "User")追加到 PATH 写 PATH 一定要先读旧的再拼,不然会把系统原本的 PATH 覆盖掉: $old = [System.Environment]::GetEnvironmentVariable("Path", "User") $new = "$old;C:\Users\me\bin" [System.Environment]::SetEnvironmentVariable("Path", $new, "User")去重和验证: $new.Split(";") | Sort-Object -Unique三档对比目标 写法 生效范围只当前窗口 $env:X = "v" 当前会话 + 子进程当前用户永久 [Environment]::SetEnvironmentVariable("X","v","User") 新开进程,用户级系统级永久(管理员) [Environment]::SetEnvironmentVariable("X","v","Machine") 新开进程,所有用户老 Windows API:setx 老一辈 Windows 用 setx: setx MY_TOKEN secret # 用户 setx MY_TOKEN secret /M # 系统(管理员)坑:有 1024 字符限制,PATH 老长了会被截断 也不影响当前窗口一般还是推荐 [Environment]::SetEnvironmentVariable。 一句话总结 会话内 $env:X;永久用 [Environment]::SetEnvironmentVariable,第三参数 "User" 或 "Machine" 决定作用域。改 PATH 记得先读再拼。
IBM Db2 的定位与应用领域
IBM Db2 是 IBM 的大型关系型数据库,在互联网公司几乎看不到,但在金融、政府和大型机生态中至今仍是核心基础设施。 核心应用领域 银行 / 金融(最主要场景) Db2 在这个领域的地位最为稳固:银行核心账务系统 信贷审批和清算 证券交易撮合 保险核心系统原因是 Db2 在 IBM 大型机(z/OS)上的 ACID 事务能力经过几十年验证,部分银行核心系统已稳定运行 20~30 年,日均处理数亿笔交易几乎不宕机。很多老牌银行的底层组合是 IBM Mainframe + z/OS + Db2,更换成本极高。 政府 / 大型国企 历史上大量政府和国企采购了 IBM 的全套方案(小型机 + AIX + WebSphere + Db2),典型领域:税务系统 社保和医保系统 电信计费 能源调度 铁路订票这类系统生命周期很长,更倾向于稳定性而非新技术,因此沿用至今。 大型机(Mainframe)生态 这是 Db2 最特殊的优势——大多数开发者接触不到 IBM 大型机,但全球大量关键基础设施仍在 Mainframe 上运行:全球 80% 以上的信用卡交易、几乎所有主要航空公司的订票系统、大量跨国银行的清算网络。Db2 在这个生态里几乎是"官方搭档"。 ERP / 企业系统 传统 Java EE 企业项目的经典组合:WebSphere + Db2 + Oracle,在财务、供应链、仓储管理类系统中有相当存量。 为什么互联网公司很少用 互联网公司的优先考量是:开源:避免商业授权成本 社区活跃:遇到问题能快速找到解决方案 云原生:与 Kubernetes、微服务架构配合 弹性伸缩:流量波动大,需要横向扩展Db2 的定价体系较重,运维门槛高,人才市场小,不符合互联网团队的需求。 与 MySQL / PostgreSQL 的定位差异维度 Db2 MySQL / PostgreSQL授权模式 商业授权 开源主要场景 传统企业、大型机 互联网、云原生运维成本 高 低事务处理 极强 强大型机支持 原生 无新项目采用率 低 高现实价值 虽然新项目几乎不选 Db2,但现有存量系统的价值不可忽视:每天处理几亿笔交易、稳定运行 20+ 年、几乎零宕机的系统,才是传统金融机构最看重的能力。Db2 DBA 在银行/证券/保险行业仍是稀缺岗位。
读懂 webpack 混淆代码:(0, fn)() 模式与 RxJS 链解析
(0, fn)(...) 是什么 这是 webpack / Babel 生成代码中常见的间接调用写法: (0, s.p)(args)等价于: const _fn = s.p; _fn(args); // 普通函数调用,this === undefined(严格模式)为什么不直接 s.p(args)? s.p(args) 会将 this 绑定为 s;逗号运算符写法 (0, s.p) 会把方法"脱离"对象,变成普通函数调用,this 不再指向 s。 这在以下场景有实际意义:s.p 是从模块导入的函数(如 rxjs.from),不应绑 this 打包工具为了兼容 ES Modules 的 strict mode 语义,确保 this === undefined Tree-shaking 友好:逗号写法对某些打包器更易分析常见形式: (0, rxjs.of)(1, 2, 3) // 等价 rxjs.of(1, 2, 3) (0, lodash.map)(arr, fn) // 等价 lodash.map(arr, fn) (0, react.createElement)(...) // 等价 react.createElement(...) (0, o.Z)(list) // 混淆后的模块导出函数读到这种代码,直接把 (0, expr) 去掉,剩下的 expr(...) 就是实际调用。 真实打包代码示例 下面是一段典型的混淆打包代码(来自实际页面逆向): (0, s.p)( (t = window.$).when.apply(t, (0, o.Z)(window.beforeSubmitQueue.map(function(e) { return e(); })) ) ).switchMap( window.MarkLib.submitMarkAnswer.bind(window.MarkLib, !0, e) ).filter(function(t) { var n = t.fire_status, r = void 0 === n || n; var a = t.reject, o = void 0 !== a && a; return !e && w(!1), r && !o; }).map(JSON.stringify.bind(JSON)).do(function(t) { return y({ type: "markpage/saveAnswer", payload: { isTemporary: 1, packetId: Number(p), ... } }); }).subscribe();逐步还原 第一步:去掉 (0, expr) 包装 s.p(...) // s.p 可能是 rxjs.from / rxjs.fromPromise o.Z(list) // o.Z 是某个导出函数,根据上下文判断第二步:提取 window 上的对象引用 const { $, beforeSubmitQueue, MarkLib } = window;第三步:还原 .apply 和 .bind // (t = window.$).when.apply(t, args) → $.when(...args) // fn.bind(obj, true, e) → () => obj.fn(true, e)第四步:改写旧版 RxJS API旧版 新版.do(fn) .tap(fn)fromPromise(p) from(p).map(fn) .map(fn) 不变还原结果: const { $, beforeSubmitQueue, MarkLib } = window;from($.when(...beforeSubmitQueue.map(fn => fn()))) .pipe( switchMap(() => MarkLib.submitMarkAnswer(true, e)), filter(({ fire_status = true, reject = false }) => { if (!e) w(false); return fire_status && !reject; }), map(JSON.stringify), tap(answer => { y({ type: "markpage/saveAnswer", payload: { isTemporary: 1, packetId: Number(p), pageId: Number(a), round: f, stage: k, answer: j(answer, c, u) }, callback() { if (!e) w(false); } }); }) ) .subscribe();逻辑说明 还原后的业务逻辑只有一句话:执行提交前钩子(beforeSubmitQueue)→ 提交答案(submitMarkAnswer)→ 过滤无效结果 → 序列化 → 保存到 Redux store关键点:$.when(...tasks):jQuery Deferred,等待所有 before-hooks 完成 switchMap:前一个 Observable 完成后切换到新的,丢弃之前未完成的 filter:fire_status 不为 false 且 reject 不为 true 才继续 .subscribe():没有 subscribe,RxJS 链不会执行(冷 Observable)其他常见混淆模式 // void 0 === undefined void 0 // → undefined// !! 转布尔 !!value // → Boolean(value)// 逗号运算符返回最后一个值 (a(), b(), c()) // 执行 a、b、c,返回 c 的值// 短路赋值 x = void 0 === x ? defaultVal : x // → x ?? defaultVal(现代写法)读打包代码时,把这些模式认出来后,理解代码逻辑会快很多。
