Showing Posts From

tools

Python 跨平台获取 CPU 型号:py-cpuinfo / wmic / /proc/cpuinfo 全方案

问题背景 需要在 Python 代码里获取完整的 CPU 型号字符串,例如: Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz不同平台读取方式不同,以下是各方案的完整写法。 推荐:py-cpuinfo(跨平台) pip install py-cpuinfofrom cpuinfo import get_cpu_infoinfo = get_cpu_info() print(info["brand_raw"]) # Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHzbrand_raw 是最原始、最完整的 CPU 商品名称。该库会自动根据平台选择 Windows 注册表、Linux /proc/cpuinfo、macOS sysctl 等数据源,兼容性最好,适合写跨平台工具。 Windows(无第三方库) 方案一:wmic import subprocesscpu = subprocess.check_output( "wmic cpu get name", shell=True ).decode("gbk").split("\n")[1].strip()print(cpu)注意编码用 gbk,Windows 中文环境下 cmd 输出默认是 GBK。 方案二:PowerShell CimInstance import subprocesscpu = subprocess.check_output( ["powershell", "(Get-CimInstance Win32_Processor).Name"] ).decode("utf-8").strip()print(cpu)PowerShell 输出是 UTF-8,更可靠,推荐用这个。 Linux 直接读 /proc/cpuinfo: with open("/proc/cpuinfo") as f: for line in f: if line.startswith("model name"): print(line.split(":", 1)[1].strip()) break多核机器 /proc/cpuinfo 会重复每个核的信息,读第一条就够了。 macOS import subprocesscpu = subprocess.check_output( ["sysctl", "-n", "machdep.cpu.brand_string"] ).decode().strip()print(cpu)不推荐:platform.processor() import platformprint(platform.processor()) # 很多系统输出:Intel64 Family 6 Model 79 Stepping 1, GenuineIntel # 或者直接为空字符串platform.processor() 不返回 CPU 商品名称,在 Linux 上通常是架构描述或空串,不适合用来显示给用户看的型号。 跨平台封装 from cpuinfo import get_cpu_infodef get_cpu_name() -> str: return get_cpu_info()["brand_raw"]用 py-cpuinfo 一行搞定,不需要为三个平台分别写判断逻辑。

Hexo 替代方案:Astro、Hugo、VitePress 选型对比

Hexo 的问题 Hexo 在 2015–2020 年是最流行的静态博客框架,但现在暴露出几个问题:插件生态老化:很多插件多年未更新,兼容性问题多 大站构建慢:文章超过几百篇后全量构建明显变慢 Markdown 扩展性差:不支持 MDX,组件嵌入需要 hack 主题质量参差不齐:活跃维护的主题越来越少主流替代方案 Astro(个人博客首选) 适合: 个人博客、作品集、内容站 npm create astro@latest核心优势:Islands 架构:默认零 JS,按需激活交互组件 MDX 支持:Markdown 里可以嵌入任意框架组件(React/Vue/Svelte) 构建速度快:增量构建,千篇文章也流畅 SEO 友好:内置 sitemap、RSS、Open Graph 主题生态活跃:官方 theme showcase 持续更新 多框架:同一项目可以混用 React、Vue、Svelte 组件缺点:学习曲线比 Hexo 稍陡(需要理解 Islands 概念) .astro 文件语法独特Hugo(超大站、Go 技术栈) 适合: 文章超过千篇、CI/CD 要求构建秒级完成 brew install hugo hugo new site myblog核心优势:构建速度极快:Go 实现,数千篇文章构建只需几秒 单二进制:不依赖 Node.js,部署环境干净 模板系统强大:Go template,灵活但学习成本高缺点:Go template 语法反直觉,调试困难 不支持组件化(无 MDX) 主题质量不稳定VitePress(Vue 技术文档) 适合: 技术文档、组件库文档 npm add -D vitepress核心优势:Vite 驱动:热更新极快 Vue 生态:文档里可以直接写 Vue 组件做交互 Demo 默认主题精美:开箱即用,对比 Hexo 默认主题体验好很多缺点:专门为文档场景优化,个人博客功能(RSS、标签、归档)需要手写 不适合内容站Next.js(需要服务端功能) 适合: 需要 SSR、API Routes、数据库查询的站点 优点:完整的 React 生态 SSG + SSR + ISR 混用 Vercel 一键部署缺点:对纯静态博客来说过重 构建后体积大Docusaurus(纯文档站) 适合: 开源项目文档、知识库 Meta 出品,React 生态,内置版本管理、多语言、搜索。对博客场景支持一般。 选型建议场景 推荐个人博客、内容站 Astro超大站(千篇以上)或 CI 构建极速要求 HugoVue 组件库文档 VitePress需要 API / 数据库 Next.js开源项目文档 Docusaurus从 Hexo 迁移成本最低的是 Astro:Markdown 文件基本兼容,frontmatter 格式相同,只需调整模板层。

JS 注入标注快捷键面板:数据驱动 radio 分组与可拖拽悬浮窗

基础版:数字键触发单个标签 click (function () { const labels = ["路中障碍物", "施工区域", "基本可见", "部分可见", "运动", "静上"]; // 防止重复注入 const old = document.getElementById("label-hotkey-panel"); if (old) old.remove(); const panel = document.createElement("div"); panel.id = "label-hotkey-panel"; panel.style.cssText = "position:fixed;top:20px;right:20px;z-index:999999;background:rgba(0,0,0,.85);" + "color:#fff;padding:12px;border-radius:10px;font-size:14px;min-width:220px;"; function getEl(name) { return document.querySelector(`span.label[title="${name}"]`); } labels.forEach((name, idx) => { const row = document.createElement("div"); row.style.cssText = "display:flex;align-items:center;justify-content:space-between;margin:6px 0;"; const info = document.createElement("span"); info.textContent = `[${idx + 1}] ${name}`; const btn = document.createElement("button"); btn.textContent = getEl(name) ? "点击" : "不存在"; btn.style.cssText = `cursor:pointer;border:none;padding:4px 8px;border-radius:6px; background:${getEl(name) ? "#409eff" : "#666"};color:white;`; btn.onclick = () => { const el = getEl(name); el ? el.click() : console.warn("未找到:", name); }; row.append(info, btn); panel.appendChild(row); }); document.body.appendChild(panel); document.addEventListener("keydown", (e) => { const n = parseInt(e.key); if (!isNaN(n) && n >= 1 && n <= labels.length) { const el = getEl(labels[n - 1]); el ? el.click() : console.warn("未找到:", labels[n - 1]); } }); })();数据驱动 radio 分组版(推荐维护方式) 多个分组各自单选,按反引号 ` 执行当前选择: (function () { if (document.getElementById("label-panel")) return; // 只改这里就能增减标签 const groups = { g1: { title: "第一组", options: ["路中障碍物", "施工区域"] }, g2: { title: "第二组", options: ["基本可见", "部分可见", "基本不可见"] }, g3: { title: "���三组", options: ["运动", "禁止"] } }; const panel = document.createElement("div"); panel.id = "label-panel"; panel.style.cssText = "position:fixed;top:20px;right:20px;z-index:999999;background:#fff;" + "border:1px solid #ccc;border-radius:8px;padding:12px;" + "box-shadow:0 0 10px rgba(0,0,0,.2);font-size:14px;width:240px;"; // 动态渲染 HTML const html = Object.entries(groups) .map(([key, group]) => { const radios = group.options .map((opt, idx) => ` <label style="display:block;margin:2px 0;"> <input type="radio" name="${key}" value="${opt}" ${idx === 0 ? "checked" : ""}>${opt} </label>`) .join(""); return `<div style="margin-bottom:8px;"><b>${group.title}</b><div>${radios}</div></div>`; }) .join(""); panel.innerHTML = ` <div style="font-weight:bold;margin-bottom:10px;">标签快捷选择</div> ${html} <hr> <div style="font-size:12px;color:#666;">反引号 = 执行</div> `; document.body.appendChild(panel); function clickLabel(title) { const el = document.querySelector(`span.label[title="${title}"]`); el ? el.click() : console.warn("未找到:", title); } function execute() { Object.keys(groups).forEach(key => { const selected = document.querySelector(`input[name="${key}"]:checked`); if (selected) clickLabel(selected.value); }); } document.addEventListener("keydown", e => { if (["INPUT", "TEXTAREA"].includes(e.target.tagName)) return; if (e.key === "`") { e.preventDefault(); execute(); } if (e.key === "Tab") { e.preventDefault(); document.querySelector('input[type="checkbox"][value="开启高层过滤"]')?.click(); } }); })();添加拖拽与折叠 // 拖动 let dragging = false, ox = 0, oy = 0; panel.addEventListener("mousedown", e => { if (e.target.tagName === "INPUT") return; dragging = true; ox = e.clientX - panel.offsetLeft; oy = e.clientY - panel.offsetTop; }); document.addEventListener("mousemove", e => { if (!dragging) return; panel.style.left = (e.clientX - ox) + "px"; panel.style.top = (e.clientY - oy) + "px"; panel.style.right = "auto"; }); document.addEventListener("mouseup", () => dragging = false);// 折叠 let collapsed = false; panel.querySelector("#toggle-btn").onclick = () => { collapsed = !collapsed; panel.querySelector(".body").style.display = collapsed ? "none" : "block"; };面板头部加折叠按钮: <div style="display:flex;justify-content:space-between;align-items:center;cursor:pointer"> <b>label</b> <span id="toggle-btn">[-]</span> </div> <div class="body">...</div>Python 批量转义 JS 中的汉字 把 JS 文件里的中文字符替换成 \uXXXX,代码仍然可以在浏���器运行: import redef encode_chinese(js): def repl(m): return ''.join(f'\\u{ord(c):04x}' for c in m.group(0)) return re.sub(r'[一-鿿]+', repl, js)js_code = ''' const title = "路中障碍���"; document.querySelector('span.label[title="施工区域"]'); '''print(encode_chinese(js_code)) # const title = "路中障碍物"; # document.querySelector('span.label[title="施工区域"]');只替换汉字,JS 语法和 ASCII 字符保持不变,浏览器完全等价执行。

Linux journalctl 查询日志:时间范围、上下文与 grep 定位

syslog 日志格式没有年份 Nov 15 13:57:05 VM-0-8-centos spring_ruoyi_admin_jar: at org.springframework...Nov 15 13:57:05 只有月日时分秒,没有年份。如果是当前年度的历史日志,通常是前一年或当年的同月。 查看完整时间戳: journalctl -u spring_ruoyi_admin_jar -o short-iso输出带完整 ISO 时间: 2025-11-15T13:57:05+08:00 VM-0-8-centos spring_ruoyi_admin_jar: ...按时间范围查询 # 精确时间段 journalctl -u spring_ruoyi_admin_jar \ --since "2025-11-15 13:52:00" \ --until "2025-11-15 14:02:00"# 最近 10 分钟 journalctl -u spring_ruoyi_admin_jar --since "10 minutes ago"# 今天的日志 journalctl -u spring_ruoyi_admin_jar --since today查看最近 N 条 # 最近 50 条,带完整时间 journalctl -u spring_ruoyi_admin_jar -n 50 -o short-iso实时追踪(类似 tail -f) journalctl -u spring_ruoyi_admin_jar -f关键词上下文查询 查看匹配行前后各 50 行: journalctl -u spring_ruoyi_admin_jar -n 1000 | grep -C 50 "查询简单视图数据失败"-C 50 等价于 --context=50,显示匹配行前后各 50 行。 普通日志文件(非 systemd) grep -nC 快速查上下文 grep -nC 50 "查询简单视图数据失败" /opt/logs/app.log输出: 123400-2025-11-15 13:56:58 INFO ... ... 123450:2025-11-15 13:57:05 ERROR 查询简单视图数据失败 ... 123500-2025-11-15 13:57:12 INFO ...-n 显示行号,-C 50 前后各 50 行。 先找行号再打印范围 # 先找到行号 grep -n "查询简单视图数据失败" app.log # 123456:2025-11-15 13:57:05 ERROR 查询简单视图数据失败# 打印第 123400 到 123500 行 sed -n '123400,123500p' app.log多关键词过滤 有时候真正原因在后面的 Caused by: grep -nC 20 "Caused by" app.log | grep -A 10 "13:57"先找异常根因,再按时间过滤。 Docker 容器日志 # 按时间范围 docker logs --since "2025-11-15T13:52:00" --until "2025-11-15T14:02:00" 容器名# 最近 100 条 docker logs --tail 100 容器名# 实时 docker logs -f 容器名Spring Boot 应用常见排查流程先用 grep -n "ERROR" app.log 找所有错误行号 用 grep -nC 30 "Caused by" app.log 找根本原因 如果有 requestId/traceId,用 grep "traceId=xxx" app.log 找完整请求链路 对照时间和堆栈确认是 DB 异常、Redis 异常还是下游服务超时

Python pycryptodome、shutil 目录同步与 tqdm 进度条、PyInstaller 打包

pycryptodome:No module named 'Crypto' 安装了 pycryptodome 但运行时报错: ModuleNotFoundError: No module named 'Crypto'原因:pycryptodome 安装后的导入路径是 Crypto(大写C),和旧版 pycrypto 同名,但两者不兼容。虚拟环境里如果同时装了两个就会冲突。 解决: pip uninstall pycrypto pycryptodome pip install pycryptodome导入方式不变: from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad如果想用 Cryptodome 命名空间(与 pycrypto 完全隔离),改装 pycryptodomex: pip install pycryptodomexfrom Cryptodome.Cipher import AES虚拟环境里确认安装: pip list | grep -i crypto # pycryptodome 3.20.0shutil 目录同步加 tqdm 进度条 shutil.copytree 不支持进度回调,需要手动遍历: import shutil from pathlib import Path from tqdm import tqdmdef sync_dir(src: Path, dst: Path): files = [f for f in src.rglob("*") if f.is_file()] dst.mkdir(parents=True, exist_ok=True) for src_file in tqdm(files, desc="复制中", unit="file"): rel = src_file.relative_to(src) dst_file = dst / rel dst_file.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src_file, dst_file)shutil.copy2 会保留原文件的修改时间和权限位。 覆盖前备份,保留最近 N 个 from datetime import datetimedef backup_and_replace(src: Path, dst: Path, keep=5): if dst.exists(): ts = datetime.now().strftime("%Y%m%d_%H%M%S") backup = dst.parent / f"{dst.name}_bak_{ts}" dst.rename(backup) # 清理旧备份,只保留最近 keep 个 baks = sorted(dst.parent.glob(f"{dst.name}_bak_*")) for old in baks[:-keep]: shutil.rmtree(old) sync_dir(src, dst)PyInstaller 打包后路径检测 -F 打包成单文件 exe 后,__file__ 不再可用,需要用 sys.executable: import sys from pathlib import Pathif getattr(sys, "frozen", False): # 打包环境:exe 所在目录 BASE_DIR = Path(sys.executable).parent else: # 开发环境:脚本所在目录 BASE_DIR = Path(__file__).parentCONFIG_PATH = BASE_DIR / "config.json"sys.frozen 在 PyInstaller 打包环境下为 True,普通 Python 解释器下不存在(getattr 返回 False)。 打包命令: pyinstaller -F -n mytool --noconsole main.py-F:单文件 -n mytool:输出名称 --noconsole:不弹出控制台窗口(GUI 工具用)完整示例:插件目录同步工具 import sys import json import shutil from pathlib import Path from datetime import datetime from tqdm import tqdmBASE_DIR = Path(sys.executable).parent if getattr(sys, "frozen", False) else Path(__file__).parent CONFIG = json.loads((BASE_DIR / "config.json").read_text(encoding="utf-8"))SRC = Path(CONFIG["src"]) DST = Path(CONFIG["dst"])def backup(dst: Path, keep=5): if not dst.exists(): return ts = datetime.now().strftime("%Y%m%d_%H%M%S") dst.rename(dst.parent / f"{dst.name}_bak_{ts}") for old in sorted(dst.parent.glob(f"{dst.name}_bak_*"))[:-keep]: shutil.rmtree(old)def copy_with_progress(src: Path, dst: Path): files = [f for f in src.rglob("*") if f.is_file()] dst.mkdir(parents=True, exist_ok=True) for f in tqdm(files, unit="file"): rel = f.relative_to(src) out = dst / rel out.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(f, out)backup(DST) copy_with_progress(SRC, DST) print("同步完成")config.json: { "src": "C:/Users/用户名/AppData/Roaming/adspower_global/cwd_global/chrome/Default", "dst": "D:/backup/chrome_profile" }

Chrome/Edge 浏览器插件数据存放位置与读取方法

Chrome/Edge 插件安装目录 Windows Chrome: C:\Users\用户名\AppData\Local\Google\Chrome\User Data\Default\Extensions\Edge: C:\Users\用户名\AppData\Local\Microsoft\Edge\User Data\Default\Extensions\每个插件一个子目录,目录名是插件 ID(32 位小写字母字符串): Extensions\ ├── nkbihfbeogaeaoehlefnkodbefgpgknn\ ← MetaMask │ └── 12.3.0_0\ │ ├── manifest.json │ ├── background.js │ └── ... ├── cjpalhdlnbpafiamejdnhcphjbkeiagm\ ← uBlock Origin │ └── ...插件存储的数据位置 chrome.storage.local 和 chrome.storage.sync 数据以 LevelDB 格式存在: Default\Local Extension Settings\插件ID\Chrome 示例: C:\Users\用户名\AppData\Local\Google\Chrome\User Data\Default\ Local Extension Settings\ nkbihfbeogaeaoehlefnkodbefgpgknn\ ← MetaMask 存储 LOG CURRENT MANIFEST-000001 000003.ldb这是二进制 LevelDB 数据库,不能直接文本编辑器打开,需要 LevelDB 工具读取。 IndexedDB 存储 部分插件使用 IndexedDB: Default\IndexedDB\ chrome-extension_插件ID.indexeddb.leveldb\Session Storage / Cache Default\Session Storage\ Default\Cache\ Default\Code Cache\查找某个插件的 ID 在浏览器地址栏输入: chrome://extensions/开启开发者模式后,每个插件卡片下方显示 ID(32位字符串)。 在 DevTools 里查看插件存储内容打开 chrome://extensions/ 找到目标插件,点击「详情」 点击「检查视图」(Service Worker 或背景页) 在 Console 里执行:// 查看所有 chrome.storage.local 数据 chrome.storage.local.get(null, console.log);// 或用 async/await const data = await chrome.storage.local.get(); console.log(data);Manifest V3 的 Service Worker 和 V2 的 Background Page 都支持这个 API。 常见插件 ID(Chrome)插件 IDMetaMask nkbihfbeogaeaoehlefnkodbefgpgknnuBlock Origin cjpalhdlnbpafiamejdnhcphjbkeiagmTampermonkey dhdgffkkebhmkfjojejmpbldmpobfkfoSwitchyOmega padekgcemlokbadohgkifijomclgjgifEdge 的同款插件 ID 可能不同(Edge 有自己的扩展商店)。 macOS 位置 ~/Library/Application Support/Google/Chrome/Default/Extensions/ ~/Library/Application Support/Google/Chrome/Default/Local Extension Settings/

Windows 查看端口被哪个进程占用:netstat / PowerShell / 资源监视器

cmd 方式(最常用) 打开命令提示符(管理员权限更好),查看指定端口: netstat -ano | findstr :9000输出示例: TCP 0.0.0.0:9000 0.0.0.0:0 LISTENING 12345末尾的 12345 是进程 PID,再查进程名: tasklist | findstr 12345输出: php-cgi.exe 12345 Console 1 25,200 Kphp-cgi.exe 就是占用 9000 端口的进程。 PowerShell 方式 直接查指定端口: Get-NetTCPConnection -LocalPort 9000输出包含 OwningProcess(PID),再查进程: Get-Process -Id (Get-NetTCPConnection -LocalPort 9000).OwningProcess这一条命令可以直接得到进程名,不需要中间步骤。 资源监视器(图形界面) 运行 resmon,进入「网络」→「监听端口」选项卡,直接看各进程占用的端口列表,也可以在「CPU」→「关联的句柄」里搜索端口号。 释放端口(结束进程) 拿到 PID 后,cmd: taskkill /F /PID 12345PowerShell: Stop-Process -Id 12345 -Force/F 或 -Force 表示强制终止,不等进程自行退出。 常见场景现象 原因启动服务报 Address already in use 端口被其他程序占用多个服务共用同一端口 需要改端口配置php-cgi / node / java 没停干净 上次启动的进程残留找到 PID 之后,先确认进程名再决定是否结束——别误杀系统进程。

停车场场景图像标注 JSON 格式解析与训练模型选择

标注 JSON 结构 自动泊车停车场场景的图像标注通常以折线形式描述结构要素,典型 JSON 结构如下: { "code": 0, "data": { "markData": { "width": 1920, "height": 1536, "marks": [ { "type": "line", "pselect": "墙", "class": { "pname": "wall" }, "point": [ {"x": 117.9, "y": 890.8}, {"x": 164.4, "y": 824.3}, {"x": 210.9, "y": 777.4} ], "attrs": {}, "markId": "y4u-C3GB3x" }, { "type": "line", "pselect": "立柱", "class": { "pname": "pillar" }, "attrs": { "P": ["square"] }, "point": [ {"x": 546.7, "y": 626.9}, {"x": 590.8, "y": 621.1} ], "markId": "y4u-CBB2i3" } ], "totalNums": { "line": 10, "polygon": 0, "rect": 0, "point": 0 } } } }关键字段说明:字段 说明type: "line" 折线标注(polyline)pselect 中文类别名(墙、路沿、立柱、其他类)class.pname 英文类别名(wall / kerb / pillar / other)point 折线坐标数组,(x,y) 为像素坐标,原点左上角attrs.P 立柱形状(square=方形柱)objectId 实例 ID,0 表示无实例关联常见标注类别类别 说明 用途wall(墙) 停车场侧墙和隔断 边界检测kerb(路沿) 车位和道路边缘 导航限制pillar(立柱) 承重柱,通常为方形 障碍物避让other(其他类) 无法分类的边缘或遮挡 训练时通常忽略训练模型选择 这类折线标注本质是 Polyline Detection / Vector Map Learning,不是目标检测。 数据量 < 1 万张:SegFormer(推荐) 把折线标注膨胀成 Mask(每类一个像素值),用 SegFormer 做语义分割: JSON 折线标注 ↓ 生成 Mask(墙=1, 路沿=2, 立柱=3, 背景=0) ↓ SegFormer 训练 ↓ Wall/Pillar/Kerb 分割图SegFormer 训练门槛低、开源实现成熟,几天内可出可用结果。 从 Mask 提取折线坐标(后处理): import cv2# 骨架提取 skeleton = cv2.ximgproc.thinning(mask)# 轮廓提取 contours, _ = cv2.findContours(skeleton, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 折线拟合(减少点数) for cnt in contours: poly = cv2.approxPolyDP(cnt, epsilon=3, closed=False) points = [(int(p[0][0]), int(p[0][1])) for p in poly]数据量 > 10 万张:MapTR(生产级) MapTR 直接学习向量化地图,输出的就是折线坐标序列,不需要 Mask 中间步骤: 输入图像 ↓ MapTR {"wall": [[x1,y1],[x2,y2],...], "pillar": [...]}代价是训练难度高、数据格式转换复杂、对数据量要求更高。 方案对比方案 输出坐标 难度 适合数据量SegFormer + 后处理 间接 低 < 1 万LaneATT / LSTR 直接 中 数万MapTR 直接 高 > 10 万OpenCV 半自动辅助标注 标注工具里用 OpenCV 实现"沿已标线方向自动延伸",可减少 50%~80% 的点击次数: import cv2 import numpy as npdef predict_next_point(image, points): if len(points) < 2: return None last = np.array(points[-1]) prev = np.array(points[-2]) direction = last - prev direction = direction / (np.linalg.norm(direction) + 1e-6) roi_size = 80 cx, cy = int(last[0]), int(last[1]) roi = image[max(0,cy-roi_size):cy+roi_size, max(0,cx-roi_size):cx+roi_size] edges = cv2.Canny(roi, 50, 150) lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=15, minLineLength=10, maxLineGap=5) if lines is None: candidate = last + direction * 30 return tuple(candidate.astype(int)) best_line = None best_score = -1 for line in lines: x1, y1, x2, y2 = line[0] lv = np.array([x2-x1, y2-y1], dtype=float) lv = lv / (np.linalg.norm(lv) + 1e-6) score = abs(np.dot(direction, lv)) if score > best_score: best_score = score best_line = line[0] if best_line is not None and best_score > 0.7: x1, y1, x2, y2 = best_line mid = np.array([cx - roi_size + (x1+x2)//2, cy - roi_size + (y1+y2)//2]) return tuple(mid.astype(int)) return tuple((last + direction * 30).astype(int))用户标完前两个点后,鼠标移动时实时预显示下一个预测点,按 Enter 确认。

Windows 编译 DLL:MSVC cl.exe 与 MinGW g++ 对比

最小示例 dllmain.cpp: #include <windows.h> #include <stdio.h>extern "C" __declspec(dllexport) void HelloFromDll() { MessageBoxA(NULL, "Hello from DLL", "DLL", MB_OK); }BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) { return TRUE; }MSVC cl.exe 编译 cl /LD /EHsc /Y- dllmain.cpp user32.lib参数说明:参数 含义/LD 编译为 DLL(输出 .dll + .lib)/LDd Debug 版 DLL/EHsc 启用 C++ 标准异常处理/Y- 禁用预编译头(避免找不到 pch.h)user32.lib 链接 user32(MessageBox 所在库)必须在正确的命令提示符里运行 普通 cmd 里没有 cl 命令,必须用: 开始菜单 → Visual Studio 20xx → x64 Native Tools Command Prompt for VS 20xx 或在普通 cmd 里手动初始化: call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" cl /LD /EHsc /Y- dllmain.cpp user32.libvcvars64.bat 路径随 VS 版本和安装目录变化,注意替换。 输出文件 成功后生成:dllmain.dll — 动态链接库 dllmain.lib — 导入库(供其他项目链接用) dllmain.exp — 导出表MinGW g++ 编译(不需要 VS) 安装 MinGW 后: g++ -shared -o test.dll dllmain.cpp -luser32或指定目标架构: x86_64-w64-mingw32-g++ -shared -o test.dll dllmain.cpp -luser32MinGW 编译的 DLL 默认导出所有 extern "C" 函数,不需要 .def 文件。 预编译头问题(/Y-) VS 新建项目默认启用预编译头,会生成 pch.h 和 pch.cpp。手写 .cpp 文件如果没有包含 pch.h,编译时报: fatal error C1010: unexpected end of file while looking for precompiled header解决方法:加 /Y- 禁用预编译头(适合独立小文件) 在每个 .cpp 第一行加 #include "pch.h" 在文件属性里设置「不使用预编译头」导出函数声明 C++ 有 name mangling,跨语言调用需要 extern "C" 防止符号被修饰: // 头文件 #ifdef MYDLL_EXPORTS #define MYDLL_API __declspec(dllexport) #else #define MYDLL_API __declspec(dllimport) #endifextern "C" MYDLL_API int Add(int a, int b);用 .def 文件显式指定导出名(不受 extern "C" 影响): LIBRARY mydll EXPORTS Add @1 Hello @2编译时加 /DEF:mydll.def。 验证导出 dumpbin /exports dllmain.dll或用 Dependency Walker、x64dbg 查看导出表。

开源进程联网通知工具:Windows / Linux / macOS 对比

需求场景 想知道哪个进程在悄悄联网、连的是什么地址,并在出现新连接时弹出通知或提示。 Windows:Windows Firewall Notifier(WFN) GitHub: LeBlue/wfn 基于 Windows 内置防火墙的审计日志,当有进程触发出站连接时弹出通知气泡,可以选择「允许」或「拒绝」,行为类似 macOS 的 LuLu。 特点:不需要驱动,利用 Windows 防火墙事件日志(Event ID 5156/5157) 支持创建防火墙规则白名单 .NET 实现,需要 .NET Runtime启动前需要启用 Windows 防火墙的连接日志: # 以管理员身份运行 auditpol /set /subcategory:"Filtering Platform Connection" /success:enable /failure:enableWindows:Sniffnet(Rust 跨平台) GitHub: GyulyVGC/sniffnet 用 Rust 编写的网络流量监控工具,有图形界面,实时显示每个连接的进程、IP、国家、流量统计。 特点:跨平台(Windows / Linux / macOS) 可过滤特定进程或协议 流量图表可视化 不能主动拦截,只做监控和统计适合想直观看到哪些进程在产生流量、流量走向哪里的场景。 Linux:Picosnitch(eBPF) GitHub: elesiuta/picosnitch 基于 eBPF 的进程网络监控,内核级捕获,开销极低。 特点:记录每个进程的 DNS 查询和连接 SQLite 存储历史 支持通知(desktop notification) 需要 Linux 5.8+ 内核和 root 权限安装: pip install picosnitch sudo picosnitch startmacOS:LuLu GitHub: objective-see/LuLu Objective-See 出品的免费防火墙,功能类似商业的 Little Snitch。 特点:新进程首次出站连接时弹出授权窗口 可以创建规则(允许/拒绝) 显示进程路径、签名、目标地址对比工具 平台 技术原理 能拦截 特点WFN Windows 防火墙审计日志 是 弹窗授权Sniffnet 跨平台 pcap 否 流量可视化Picosnitch Linux eBPF 否 低开销,历史记录LuLu macOS 内核扩展 是 弹窗授权,免费想要拦截 + 弹窗授权:Windows 用 WFN,macOS 用 LuLu。只想监控流量走向:Sniffnet 最直观。Linux 生产环境审计:Picosnitch + SQLite 日志。

剪映批量导出自动化:AutoHotkey、draft_content.json 和 FFmpeg 三种方案

三种方案对比方案 适用场景 优点 缺点AutoHotkey 模拟 UI 几十个工程、保留特效 最稳、兼容所有剪映功能 慢、易被 UI 改版搞炸修改 draft_content.json 矩阵号、批量换素材/文案 工业化效率高 剪映 6+ 有加密问题FFmpeg / MoviePy 图片转视频、批量混剪 服务器可跑、不依赖剪映 无法保留剪映特效方案一:AutoHotkey(最稳) 适合"几十个工程挨个导出"的场景,不需要了解剪映内部格式: Loop, Files, D:\jianying_projects\*.draft { Run, "C:\Program Files\JianyingPro\JianyingPro.exe" "%A_LoopFileFullPath%" Sleep, 10000 ; 点击导出按钮(坐标需根据实际分辨率调整) Click, 1800, 1000 Sleep, 3000 ; 点击确认导出 Click, 1700, 950 ; 等待渲染完成 Sleep, 60000 WinClose, 剪映专业版 }坐标需要根据自己的屏幕分辨率和剪映版本调整。可以用 AHK 内置的 MouseGetPos 命令在悬停时获取当前坐标。 方案二:修改 draft_content.json(矩阵号核心) 剪映草稿本质是 JSON,位于草稿目录下的 draft_content.json。批量替换流程: 模板工程 ↓ Python 复制草稿目录 ↓ 修改 draft_content.json(替换素材路径/字幕文本) ↓ 生成 N 个草稿 ↓ AutoHotkey 自动打开逐个导出Python 批量复制并替换字幕示例: import shutil import json import re from pathlib import Pathtemplate = Path("D:/drafts/template") output_base = Path("D:/drafts/output")texts = ["第一集内容", "第二集内容", "第三集内容"]for i, text in enumerate(texts): dst = output_base / f"episode_{i+1}" shutil.copytree(template, dst) draft_file = dst / "draft_content.json" data = json.loads(draft_file.read_text(encoding="utf-8")) # 替换字幕文本(具体字段名以实际工程为准) content_str = json.dumps(data, ensure_ascii=False) content_str = re.sub(r'"占位文本"', f'"{text}"', content_str) draft_file.write_text(content_str, encoding="utf-8")注意:剪映 6+ 加密问题 从剪映专业版 6.x 开始,draft_content.json 部分字段开始加密,很多老方案失效。目前社区常见的做法:锁定在剪映 5.9 不升级 使用开源项目如 pyJianYingDraft 或 capcut-cli方案三:FFmpeg(不经过剪映) 如果只是图片转视频、批量加字幕、批量合并,完全不需要剪映: # 图片+音频合成视频 ffmpeg -loop 1 -i cover.jpg -i bgm.mp3 \ -shortest \ -vf "scale=1920:1080" \ output.mp4# 批量处理(bash) for img in *.jpg; do ffmpeg -loop 1 -i "$img" -i bgm.mp3 -shortest -vf "scale=1920:1080" "${img%.jpg}.mp4" doneFFmpeg 的缺点是没有剪映的智能字幕、卡点、AI 特效,对"剪映味"的视频替代不了。 实际推荐路线(混合方案) Python 改草稿 JSON(批量换内容) + 剪映负责特效渲染 + AutoHotkey 自动导出剪映最有价值的是特效引擎和 AI 功能,时间轴批量生成用代码做,特效用剪映渲染,导出用自动化点击。这是目前短视频矩阵账号的主流技术路线。

动态加载 OpenCV.js:createElement 与油猴 @require 的区别

createElement 动态加载 把原来的 <script> 标签写法: <script async src="https://docs.opencv.org/4.x/opencv.js" onload="onOpenCvReady();" type="text/javascript"></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);避免重复加载 if (!window.cv) { const script = document.createElement("script"); script.src = "https://docs.opencv.org/4.x/opencv.js"; script.async = true; script.onload = () => { console.log("OpenCV loaded"); onOpenCvReady(); }; document.head.appendChild(script); } else { onOpenCvReady(); }onload 不等于 OpenCV 初始化完成 script.onload 只表示 JS 文件下载解析完毕,但 OpenCV.js 内部有一个 WebAssembly 初始化过程,cv 对象此时可能还未就绪。 如果在 onload 里立刻调用 new cv.Mat() 会报错。正确做法是等 cv.onRuntimeInitialized: script.onload = () => { cv["onRuntimeInitialized"] = () => { // 这里才能安全使用 cv API const mat = new cv.Mat(); console.log("OpenCV Ready"); }; };封装成 Promise function loadOpenCV() { return new Promise((resolve, reject) => { if (window.cv) { resolve(window.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) => { console.log("Ready"); const mat = new cv.Mat(100, 100, cv.CV_8UC3); console.log(mat); });油猴脚本:@require 绕过 CSP 有些网页设置了 Content-Security-Policy(CSP),会阻止加载非白名单来源的外部脚本。这时用 createElement 注入 script 会直接被浏览器拦截。 油猴的 @require 是在油猴自身的沙箱里加载,不经过页面的 CSP,可以绕过这个限制: // ==UserScript== // @name Load OpenCV // @namespace http://tampermonkey.net/ // @version 1.0 // @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.rows, mat.cols); }; })();@require 里的脚本在油猴加载阶段执行,cv 全局变量在脚本运行时已经存在。 两种方式对比方式 适用场景 CSP 限制createElement + onload 普通页面、自己控制的网页 受页面 CSP 限制@require 油猴脚本 不受页面 CSP 影响如果网页没有 CSP,两种都能用,@require 写法更简洁;有 CSP 时只能用 @require。

JS 模板字符串含反引号的处理与 Object.defineProperty Hook window 属性

模板字符串里包含反引号 反引号是模板字符串的开始/结束符,中间再出现反引号会直接语法错误: // ❌ 语法错误 ` asdasd`` `方案一:转义(最标准) const str = `asd\`asd`;方案二:String.raw(不处理转义) const str = String.raw`asd\`asd`;String.raw 是标签模板,适合存放正则、shell 命令、SQL、Prompt 等原始文本,不会解释 \n、\t 等转义序列。 方案三:数组 join(不用转义,推荐) const s = [ '`', 'asdasd``', '`' ].join('\n');大型项目里代码生成器常用这种方式,完全不需要考虑转义问题,任意字符都行。 方案四:外部文件 内容包含大量反引号、${} 或换行的情况,最推荐直接存外部文件: // Vite / 现代构建工具 import txt from './prompt.txt?raw';// Node.js const txt = fs.readFileSync('./prompt.txt', 'utf8');AI prompt、HTML 模板、大段 SQL 都适合这种做法。 unsafeWindow 与 Proxy 油猴脚本里直接 window = unsafeWindow 会在某些环境报错,推荐用 Proxy: const windowProxy = new Proxy(unsafeWindow, { get(target, prop) { return target[prop]; }, set(target, prop, value) { target[prop] = value; return true; } });关键限制:new Proxy(window) 只影响局部变量 windowProxy,其他代码里的 window.xxx 仍然访问原生 window,不会走代理。真正要"全局 hook"必须用 Object.defineProperty。 Object.defineProperty Hook window 属性 这是浏览器里最常用的 hook 方式,直接修改全局对象的属性描述符: 监听自定义属性的 get/set let value;Object.defineProperty(window, 'myToken', { configurable: true, get() { console.log('GET myToken'); return value; }, set(v) { console.log('SET myToken', v); value = v; } });其他脚本只要访问 window.myToken 就会触发上面的拦截器。 Hook fetch const rawFetch = window.fetch;Object.defineProperty(window, 'fetch', { configurable: true, get() { return function (...args) { console.log('fetch called:', args[0]); return rawFetch.apply(this, args); }; } });Hook document.cookie const rawSetter = Document.prototype.__lookupSetter__('cookie');Document.prototype.__defineSetter__('cookie', function (val) { console.log('cookie set:', val); rawSetter.call(this, val); });通用 hook 工具函数 function hookGlobal(name) { let value; Object.defineProperty(window, name, { configurable: true, get() { console.log('GET', name); return value; }, set(v) { console.log('SET', name, v); value = v; } }); }hookGlobal('abc');操作前先检查 configurable 部分内置属性(location、top、self、document)不可重定义,直接操作会抛错: const desc = Object.getOwnPropertyDescriptor(window, 'fetch'); console.log(desc.configurable, desc.writable);configurable: true 才能用 defineProperty 重新定义;writable: true 才能直接赋值覆盖。 Proxy vs Object.defineProperty 选哪个场景 推荐方式Hook 单个全局 API(fetch、XHR) Object.defineProperty局部作用域内代理对象 Proxy监控任意属性读写 Proxy(但不适用于真正的 window)跨越沙箱访问页面 window unsafeWindow + Proxy 或 script 注入整体代理 window 在浏览器里基本行不通,原生 window 是宿主对象,有大量不可配置属性。工程上的最佳实践是 hook 关键 API(fetch、XMLHttpRequest、WebSocket、JSON.parse)而不是整体代理。

Edge/Chrome CDP 远程调试与 Node.js Hook TextDecoder、JSON、AES

Edge/Chrome CDP 远程调试 1. 启动浏览器开启调试端口 # Edge msedge.exe --remote-debugging-port=9222 --user-data-dir=D:\edge_debug# Chrome chrome.exe --remote-debugging-port=92222. 获取 WebSocket 调试地址 访问: http://127.0.0.1:9222/json返回 JSON,找 webSocketDebuggerUrl: { "title": "test page", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/ABC123" }3. 用 DevTools 前端连接 把 ws 地址传给 DevTools 前端: devtools://devtools/bundled/devtools_app.html?ws=127.0.0.1:9222/devtools/page/ABC123在 Edge/Chrome 地址栏直接输入即可,打开完整的开发者工具界面。 核心结构: 浏览器(Edge/Chrome) ↓ WebSocket (CDP) DevTools Frontend(网页 UI)Node.js TextDecoder 支持情况 Node.js ≥ 11 支持 TextDecoder,Node ≥ 16/18 更稳定: const { TextDecoder, TextEncoder } = require('util');const decoder = new TextDecoder('utf-8'); const buf = Buffer.from([0xe4, 0xbd, 0xa0]); // "你" console.log(decoder.decode(buf)); // 你Hook TextDecoder.decode 用于拦截 WebSocket / CDP 协议里的二进制转文本: const { TextDecoder } = require('util');const _decode = TextDecoder.prototype.decode;TextDecoder.prototype.decode = function (input, options) { const result = _decode.call(this, input, options); console.log('[TextDecoder.decode]', result); return result; };全局 hook(拦截所有实例): const orig = TextDecoder;global.TextDecoder = function (...args) { const instance = new orig(...args); const _decode = instance.decode; instance.decode = function (...params) { const res = _decode.apply(this, params); console.log('[WS decode]', res); return res; }; return instance; };Hook JSON.parse 并落盘 const fs = require('fs'); const _parse = JSON.parse;JSON.parse = function (text, reviver) { try { const result = _parse.call(this, text, reviver); // 只保存含 "raw" 字段的数据 if (result && typeof result === 'object' && 'raw' in result) { fs.appendFileSync( './json_raw_log.txt', JSON.stringify({ time: Date.now(), data: result }) + '\n' ); } return result; } catch (e) { return _parse.call(this, text, reviver); } };按关键字过滤避免刷屏: // 只记录长度 < 5000、包含关键字的 if (typeof text === 'string' && text.length < 5000 && text.includes('method')) { fs.appendFileSync('./log.txt', text + '\n'); }Hook Node.js AES 加密 Hook crypto.createCipheriv 抓加密前的明文、key、IV: const crypto = require('crypto'); const _createCipheriv = crypto.createCipheriv;crypto.createCipheriv = function (algorithm, key, iv, options) { console.log('[AES createCipheriv]', { algorithm, key: Buffer.isBuffer(key) ? key.toString('hex') : key, iv: Buffer.isBuffer(iv) ? iv.toString('hex') : iv }); const cipher = _createCipheriv.call(this, algorithm, key, iv, options); const _update = cipher.update; const _final = cipher.final; cipher.update = function (data, inputEncoding, outputEncoding) { console.log('[AES encrypt input]', data?.toString?.() ?? data); return _update.apply(this, arguments); }; cipher.final = function (outputEncoding) { const res = _final.apply(this, arguments); console.log('[AES encrypt output]', res); return res; }; return cipher; };解密 hook(对称): const _createDecipheriv = crypto.createDecipheriv;crypto.createDecipheriv = function (algorithm, key, iv) { const decipher = _createDecipheriv.call(this, algorithm, key, iv); const _update = decipher.update; decipher.update = function (data) { const res = _update.apply(this, arguments); console.log('[AES decrypt result]', res.toString()); return res; }; return decipher; };组合 hook(实战抓包) WebSocket + 加密接口逆向的标准三件套:TextDecoder.prototype.decode — 抓二进制转文本 JSON.parse — 抓接口返回 JSON crypto.createCipheriv/createDecipheriv — 抓 AES 明文和密钥部分代码直接用 Buffer.toString('utf-8') 而不经过 TextDecoder,还需要额外 hook: const _toString = Buffer.prototype.toString;Buffer.prototype.toString = function (encoding, ...rest) { const result = _toString.call(this, encoding, ...rest); if (encoding === 'utf8' || encoding === 'utf-8') { console.log('[Buffer.toString]', result.slice(0, 200)); } return result; };

Electron app.asar 解包只有部分资源的原因与逆向方法

为什么解包只有部分资源 1. 文件在 app.asar.unpacked 而不在 asar 里 Electron 会把不适合打包进 asar 的文件放到同级目录: resources/ ├─ app.asar ← 前端代码、JS bundle └─ app.asar.unpacked/ ← 原生模块、大文件通常放在 unpacked 里的有:.node 原生扩展 ffmpeg.dll / ffmpeg.so AI 模型、WASM 文件 浏览器内核组件解包时要把 app.asar 和 app.asar.unpacked 都看一遍。 2. webpack 把所有代码打成了 bundle 现代 Electron 应用几乎都用 webpack 打包,解包后看到的不是: src/pages/index.js src/components/Button.js而是: main.js renderer.js chunk.a1b2c3.js所有模块都合并进了 bundle,没有"缺文件",只是代码被合并了。 3. 资源在运行时从网络下载 部分 AI 软件、IDE 工具首次启动时会在线下载:语言包 插件资源 模型文件 JS chunk下载后存到用户目录,不在安装包里: Windows: %AppData%\应用名\ ← 配置、sqlite、token %LocalAppData%\应用名\ ← 缓存、大文件、下载资源4. JS 被编译为 V8 字节码(.jsc) 商业 Electron 应用常用 bytenode 把 JS 编译成 .jsc: main.js → main.jsc普通 asar 解包后可以看到文件,但无法读取源码。 5. 应用对资源做了加密 运行时解密: // 应用内部流程(无法直接看到) const encrypted = fs.readFileSync('xxx.bin'); const code = decrypt(encrypted, key); eval(code);解包后只能看到加密的 .bin 文件。 解包工具 # 查看 asar 文件列表 npx asar list app.asar# 解包到指定目录 npx asar extract app.asar ./out解包后搜索这些关键字找 webpack 入口: webpackChunk __webpack_require__Electron 应用资源分布位置 通常存放resources/app.asar 前端页面、JS bundle、html、cssresources/app.asar.unpacked/ .node 模块、dll、ffmpeg、WASM%AppData%/应用名/ 用户配置、sqlite、历史记录%LocalAppData%/应用名/ 缓存、下载的大文件、模型%AppData%/.../Code Cache/ V8 字节码缓存%AppData%/.../IndexedDB/ 结构化数据逆向流程 1. 解包 asar npx asar extract app.asar ./out2. 检查 app.asar.unpacked ls resources/app.asar.unpacked/3. 找 preload.js preload 是 Electron 主进程和渲染进程之间的桥梁,通常包含 IPC 通信和权限暴露逻辑: out/main/preload.js out/dist-electron/preload.js4. 找用户数据目录 # Windows dir %AppData%\应用名 dir %LocalAppData%\应用名5. Hook fs 抓解密内容 对于运行时解密的资源,hook fs.readFileSync 把解密后的结果 dump 出来: const fs = require('fs'); const orig = fs.readFileSync;fs.readFileSync = function (path, options) { const result = orig.apply(this, arguments); if (path.includes('.bin') || path.includes('.jsc')) { console.log('[readFileSync]', path, result.toString('hex').slice(0, 64)); } return result; };6. 检查是否是 V8 字节码 .jsc 文件开头有 V8 magic bytes,可以用 bytenode 的 wrap 机制在运行时拿到原始代码,但需要和目标 Node.js/Electron 版本完全匹配。

Windows 安装 PostgreSQL:Installer / ZIP 手动安装 / Docker 三种方式

方式一:官方 EDB 安装包(推荐新手) 从 postgresql.org/download/windows 下载 EDB 安装器,双击 .exe 按向导操作:选择组件:PostgreSQL Server + pgAdmin + Command Line Tools(全选即可) 安装路径默认 C:\Program Files\PostgreSQL\16\ 设置 postgres 超级用户密码(这一步要记好,后面连接必须用) 端口保持默认 5432 Locale 保持默认 等待安装完成安装后验证: # 命令行里输入(要先配好 PATH) psql -U postgres -h localhost# 输入密码后看到: postgres=#或者打开 pgAdmin 4 用图形界面管理。 方式二:ZIP 手动安装 适合不想用安装包、想自定义路径的场景。 1. 解压到目标目录,如 D:\pgsql 2. 初始化数据目录: D:\pgsql\bin\initdb -D D:\pgsql\data -U postgres -W-W 参数会提示设置 postgres 用户密码,必须加,否则后续连接会失败。 3. 启动服务: D:\pgsql\bin\pg_ctl -D D:\pgsql\data -l D:\pgsql\logs\postgresql.log start4. 连接验证: D:\pgsql\bin\psql -U postgres -h localhost5. 把 bin 目录加入 PATH: 系统属性 → 环境变量 → Path → 新增: D:\pgsql\bin加好之后才能直接用 psql、pg_ctl 等命令。 EDB 安装包也会自动加 PATH,ZIP 安装需要手动配。 方式三:Docker(开发推荐) 一条命令,跳过所有安装步骤: docker run --name pgsql \ -e POSTGRES_PASSWORD=123456 \ -p 5432:5432 \ -d postgres连接: psql -U postgres -h localhost # 密码:123456停止/启动: docker stop pgsql docker start pgsql开发环境用 Docker 最省事,环境隔离,随时删掉重建。 常见问题 连不上 5432:防火墙没放行 → 允许 PostgreSQL 入站 端口冲突 → 改 postgresql.conf 里的 port = 5433忘记 postgres 密码: 修改 pg_hba.conf,把认证方式临时改成 trust,重启服务后进去改密码,再改回 scram-sha-256。 psql 找不到命令: PATH 没配好。把 PostgreSQL bin 目录加进去,或者用完整路径调用。 版本选择版本 建议PostgreSQL 17 新项目首选(最新稳定版)PostgreSQL 16 最稳定,生产首选PostgreSQL 15 及以下 老项目兼容,新项目不建议关于生产环境 生产环境强烈建议用 Linux(Ubuntu/Debian/Rocky Linux)跑 PostgreSQL:更好的 I/O 调度和文件系统(ext4/xfs) pgBackRest、Patroni 等高可用工具主要支持 Linux 运维脚本生态更成熟Windows 版 PostgreSQL 可以用,但长期高并发稳定性和性能不如 Linux。开发/测试环境没问题,生产环境能用 Linux 就用 Linux。

Windows 配置 Maven PATH 并用 mvn 运行 Spring Boot

PowerShell 添加 Maven 到 PATH 当前用户(永久) $addPath = "D:\soft\apache-maven-3.9.15\bin" $currentPath = [Environment]::GetEnvironmentVariable("Path", "User")if ($currentPath -notlike "*$addPath*") { [Environment]::SetEnvironmentVariable("Path", "$currentPath;$addPath", "User") Write-Host "已追加: $addPath" } else { Write-Host "PATH 已存在" }当前会话立即生效 $env:Path += ";D:\soft\apache-maven-3.9.15\bin"重新打开终端才能读到写入注册表的值,不想重开就用这行临时追加。 系统级 PATH(需管理员) $addPath = "D:\soft\apache-maven-3.9.15\bin" $currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine")if ($currentPath -notlike "*$addPath*") { [Environment]::SetEnvironmentVariable("Path", "$currentPath;$addPath", "Machine") }cmd 添加 Maven 到 PATH :: 追加到当前用户 PATH(永久,写注册表) setx PATH "%PATH%;D:\soft\apache-maven-3.9.15\bin":: 当前窗口立即可用 set PATH=%PATH%;D:\soft\apache-maven-3.9.15\bin系统 PATH(需管理员): setx /M PATH "%PATH%;D:\soft\apache-maven-3.9.15\bin"setx 会写注册表,当前窗口不生效,重新打开 cmd 后验证: mvn -v用 mvn 运行 Spring Boot 在项目根目录(有 pom.xml 的那层)执行: mvn spring-boot:run常用参数: # 跳过测试(开发时推荐,速度快) mvn spring-boot:run -DskipTests# 指定 profile mvn spring-boot:run -Dspring-boot.run.profiles=dev打包后运行 mvn clean package -DskipTests java -jar target/xxx.jar生产部署通常用这种方式,不依赖 Maven 运行时。 常见错误 mvn 不是内部或外部命令 PATH 没生效。执行 mvn -v 测试,不行就重新打开终端,或确认 Maven bin 目录路径是否正确。 No plugin found for prefix 'spring-boot' pom.xml 缺少 Spring Boot Maven 插件,在 <build> 里加: <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>端口 8080 被占用 netstat -ano | findstr 8080 taskkill /PID 进程ID /F或在 application.yml 改端口: server: port: 8081多模块项目依赖解析失败 ${revision} 未替换说明父 pom 没有先 install,在项目根执行: mvn clean install -N -DskipTests-N 表示只安装父 pom,不递归子模块,之后再正常 mvn spring-boot:run。

JS 动态插入 style 标签与油猴虚拟 input 读取本地文件

动态创建 style 标签 基础写法 const style = document.createElement('style'); style.textContent = ` .my-class { color: red; font-size: 16px; } `; document.head.appendChild(style);缺点是多次调用会重复插入,页面上堆积大量 <style> 标签影响性能。 用 id 防重复插入 function addStyle(id, css) { if (document.getElementById(id)) return; const style = document.createElement('style'); style.id = id; style.textContent = css; document.head.appendChild(style); }addStyle('my-style', ` .box { background: #000; color: #fff; } `);适合油猴脚本或浏览器插件,确保只插入一次。 可覆盖更新(推荐) function setStyle(id, css) { let style = document.getElementById(id); if (!style) { style = document.createElement('style'); style.id = id; document.head.appendChild(style); } style.textContent = css; }已存在则覆盖 textContent,不堆积。 按规则粒度插入 const style = document.createElement('style'); document.head.appendChild(style);style.sheet.insertRule('.title { color: blue; }', 0);适合动态拼接规则的场景,比如根据条件添加不同的选择器。 油猴中直接用 GM_addStyle // @grant GM_addStyle GM_addStyle(` .my-class { color: green; } `);油猴环境推荐用内置 API,不需要手动操作 DOM。 注意事项style 建议放在 head 里,放 body 部分浏览器不会立即生效 频繁整块更新 textContent 会触发页面重排,CSS 内容大时注意 insertRule 插入的规则不会显示在开发者工具的 style 面板里(只在 Computed 里可见)油猴虚拟 input 读取本地文件 浏览器安全限制:文件选择框必须由用户主动触发(点击/按键),不能在页面加载时自动弹出。 封装成 Promise /** * @param {Object} options * @param {string} [options.accept] - 文件类型限制,如 ".json,.txt" * @param {string} [options.readAs] - "text" | "dataURL" | "arrayBuffer" * @param {string} [options.encoding] - 文本编码,默认 "utf-8" * @returns {Promise<{file: File, content: any}>} */ function selectFileAndRead(options = {}) { const { accept = '', readAs = 'text', encoding = 'utf-8' } = options; return new Promise((resolve, reject) => { let input = document.getElementById('__file_input__'); if (!input) { input = document.createElement('input'); input.type = 'file'; input.id = '__file_input__'; input.style.display = 'none'; document.body.appendChild(input); } input.accept = accept; input.onchange = () => { const file = input.files[0]; if (!file) { reject(new Error('未选择文件')); return; } const reader = new FileReader(); reader.onload = (e) => resolve({ file, content: e.target.result }); reader.onerror = () => reject(new Error('读取失败')); switch (readAs) { case 'dataURL': reader.readAsDataURL(file); break; case 'arrayBuffer': reader.readAsArrayBuffer(file); break; default: reader.readAsText(file, encoding); } }; input.value = ''; // 清空,否则同一文件第二次选不会触发 change input.click(); }); }使用示例 读取 JSON 文件: selectFileAndRead({ accept: '.json' }) .then(({ content }) => JSON.parse(content)) .then(data => console.log('解析结果:', data)) .catch(err => console.error(err));读取图片转 base64: selectFileAndRead({ accept: 'image/*', readAs: 'dataURL' }) .then(({ content }) => { const img = document.createElement('img'); img.src = content; document.body.appendChild(img); });Windows 文件中文乱码: selectFileAndRead({ accept: '.txt', encoding: 'gbk' }) .then(({ content }) => console.log(content));关键细节复用 input:用 id 缓存,避免多次点击在 DOM 里堆积隐藏元素 input.value = '':选同一个文件时 change 不会重触发,必须先清空 必须用户主动触发:把 selectFileAndRead() 放在按钮点击或快捷键回调里

Office 查找替换通配符规则与 VBA 正则表达式替换

Office 通配符模式(不是正则) Word / Excel 的查找替换里勾选"使用通配符"后,支持的是简化版匹配规则,和标准正则不同:通配符 含义 示例* 任意多个字符 sh_* 匹配 sh_ 开头的所有内容? 单个任意字符 sh_? 匹配 sh_ 加一个字符[0-9] 数字字符类 匹配一个数字[A-Za-z] 字母字符类 匹配一个字母[!a-z] 排除字符类 匹配不是 a-z 的字符@ 前一元素重复一次或多次(相当于 +) [0-9]@ 匹配连续数字\1 引用第一个捕获组 保留匹配部分** 在 Office 里没有特殊含义,不会"更强匹配",用一个 * 即可。 常用通配符替换示例 提取字母部分(去除数字): 查找: ([A-Za-z]*)([0-9]*) 替换: \1\1 保留第一个括号里的内容(字母部分),丢弃数字。 匹配 sh_ 开头: sh_** 匹配后面任意内容(包括空)。 只匹配 sh_ 后跟数字: sh_[0-9]@@ 在 Word 通配符里相当于正则的 +,表示重复一次或多次。 Word 删除多余空行: 查找: ^13^13 替换: ^13^13 是 Word 里的段落标记(回车符),两个连续段落标记替换成一个即可删除空行。 VBA 调用真正的正则表达式 Excel 原生不支持正则,但可以通过 VBA 调用 VBScript.RegExp 引擎: Function RegReplace(text As String, pattern As String, replacement As String) As String Dim re As Object Set re = CreateObject("VBScript.RegExp") re.Global = True re.IgnoreCase = False re.Pattern = pattern RegReplace = re.Replace(text, replacement) End Function保存后在单元格里直接用: =RegReplace(A1,"\d+","")这才是标准正则(支持 \d、\s、+、? 等)。 打开 VBA 编辑器:Excel → Alt + F11 插入 → 模块 粘贴上面的函数 保存,回到工作表使用Power Query(进阶) Excel 内置 Power Query 的 M 语言支持部分文本处理: // 提取所有数字 Text.Select([Column], {"0".."9"})// 删除数字 Text.Remove([Column], {"0".."9"})不如正则灵活,但不需要写 VBA,适合简单的批量清洗。 选择建议场景 推荐方式简单前缀/后缀匹配 Word/Excel 通配符标准正则(\d、\w、捕获组) VBA + VBScript.RegExp大量数据批量清洗 Power Query复杂逻辑处理后粘回 Excel VS Code / Notepad++ 处理后导入

Word / Excel 查找替换:通配符模式 vs 真正的正则,VBA RegExp 方案

通配符模式 ≠ 正则表达式 Office 自带的查找替换开启"使用通配符"后,支持的是简化版通配符,不是标准正则(PCRE / ECMAScript)。功能 通配符模式 标准正则任意字符 * .*单个字符 ? .数字字符类 [0-9] \d字母字符类 [A-Za-z] \w(有差异)排除字符 [!abc] [^abc]捕获组 () + \1 () + $1懒惰匹配 ❌ 不支持 *?零宽断言 ❌ 不支持 (?=...)\d+ 数字简写 ❌ 不支持 ✅Word 通配符示例 在 Word 查找替换中勾选"使用通配符": # 删除行首的数字编号(如 "1. " "12. ") 查找: [0-9]{1,}. 替换: (留空)# 把 "第X章" 中的数字替换为"第一章"形式——仅捕获数字 查找: (第)[0-9]{1,}(章) 替换: \1一\2# 删除括号内的内容(非嵌套) 查找: \([!)]*\) 替换: (留空)Word 通配符中 {n,m} 表示重复次数,不是标准正则的 {n,m}。Excel 原生通配符(非查找替换) Excel 公式里(COUNTIF、VLOOKUP 等)支持 * 和 ?,不支持字符类 [0-9]: =COUNTIF(A:A,"*错误*") ' 包含"错误"的单元格数 =COUNTIF(A:A,"????") ' 恰好4个字符查找替换对话框(Ctrl+H)同理,勾选"单元格匹配"后才能精确匹配整个单元格。 VBA 实现真正的正则(推荐) 在 Excel 里按 Alt+F11 打开 VBE,插入模块: ' 正则替换函数(可在单元格直接使用) Function RegReplace(text As String, pattern As String, replacement As String) As String With CreateObject("VBScript.RegExp") .Global = True .IgnoreCase = False .Pattern = pattern RegReplace = .Replace(text, replacement) End With End Function' 正则匹配提取函数 Function RegExtract(text As String, pattern As String) As String Dim re As Object Set re = CreateObject("VBScript.RegExp") re.Pattern = pattern re.Global = False If re.Test(text) Then RegExtract = re.Execute(text)(0).Value Else RegExtract = "" End If End Function单元格用法: =RegReplace(A1,"\d+","[数字]") ' 把数字替换为[数字] =RegReplace(A1,"^\s+|\s+$","") ' 去首尾空格(等价于 TRIM) =RegExtract(A1,"\d{11}") ' 提取11位手机号 =RegExtract(A1,"[一-龥]+") ' 提取第一段中文批量替换整列: Sub BatchRegReplace() Dim re As Object Set re = CreateObject("VBScript.RegExp") re.Global = True re.Pattern = "\s+" ' 合并多个空格 Dim cell As Range For Each cell In Range("A1:A100") If cell.Value <> "" Then cell.Value = re.Replace(cell.Value, " ") End If Next cell End SubPower Query 方案 Excel → 数据 → 获取数据 → 启动 Power Query 编辑器,M 语言示例: // 提取列中的数字 = Table.TransformColumns( Source, {{"Column1", each Text.Select(_, {"0".."9"}), type text}} )// 删除非字母数字字符 = Table.TransformColumns( Source, {{"Column1", each Text.Select(_, {"A".."Z","a".."z","0".."9"}), type text}} )Power Query 不支持完整正则,但 Text.Select / Text.Remove 加字符范围可覆盖大多数清洗需求。 Word 宏调用正则 Sub WordRegexReplace() Dim re As Object Set re = CreateObject("VBScript.RegExp") re.Global = True re.Pattern = "\b\d{4}-\d{2}-\d{2}\b" ' 匹配日期格式 YYYY-MM-DD Dim doc As Document Set doc = ActiveDocument Dim para As Paragraph For Each para In doc.Paragraphs para.Range.Text = re.Replace(para.Range.Text, "[日期]") Next para End Sub选择建议场景 方案简单通配(含*/?/字符类) 查找替换 + 通配符\d+ 等 PCRE 语法 VBA + VBScript.RegExp大批量数据清洗 Power Query需要留存为可复用公式 RegReplace 自定义函数对正则有复杂要求 导出 CSV → Python / Node.js 处理后导回

Windows bat 脚本清理 Temp 目录:%TEMP% 变量与 del/rmdir 用法

用 %TEMP% 清理当前用户临时目录 @echo off setlocalecho 当前用户: %USERNAME% echo Temp目录: %TEMP%:: 删除文件(/f 强制 /s 子目录 /q 静默) del /f /s /q "%TEMP%\*" >nul 2>&1:: 删除空文件夹 for /d %%p in ("%TEMP%\*") do rmdir /s /q "%%p" >nul 2>&1echo 清理完成 pause%TEMP% 是 Windows 内建变量,直接指向当前用户的临时目录(通常是 C:\Users\用户名\AppData\Local\Temp),不需要手动拼路径。 同时清理系统 Temp(需管理员) @echo off setlocal:: 用户 temp del /f /s /q "%TEMP%\*" >nul 2>&1 for /d %%p in ("%TEMP%\*") do rmdir /s /q "%%p" >nul 2>&1:: 系统 temp(需要管理员权限运行) del /f /s /q "C:\Windows\Temp\*" >nul 2>&1 for /d %%p in ("C:\Windows\Temp\*") do rmdir /s /q "%%p" >nul 2>&1echo 清理完成 pause手动拼接路径写法 如果需要明确用户名,也可以用 %USERNAME% 拼路径: set USER_TEMP=C:\Users\%USERNAME%\AppData\Local\Tempdel /f /s /q "%USER_TEMP%\*" >nul 2>&1 for /d %%p in ("%USER_TEMP%\*") do rmdir /s /q "%%p" >nul 2>&1两种写法效果一致,推荐用 %TEMP% 更简洁。 常见问题 部分文件删不掉 正常现象。系统正在使用的文件(如浏览器缓存、日志)不会被删,>nul 2>&1 会把错误输出吞掉,脚本继续执行,不影响其他文件的清理。 清理完大小没变化 有些文件夹包含只读属性文件。可以在 del 前加 /a 参数(包含只读): del /f /s /q /a "%TEMP%\*" >nul 2>&1不要手动清 C:\Windows\System32\config\systemprofile\AppData\Local\Temp,这是系统服务的临时目录。 设为计划任务定期执行把脚本保存为 clean_temp.bat 打开"任务计划程序"(Task Scheduler) 创建基本任务 → 触发器选"登录时"或"每周" 操作选"启动程序" → 指向 clean_temp.bat 权限选"使用最高权限运行"这样每次登录自动清理,免手动执行。

ScriptCat / Tampermonkey 用 GM_xmlhttpRequest 调用 AI 视觉接口

为什么必须用 GM_xmlhttpRequest 用户脚本环境里调用第三方 API,普通 fetch 会被浏览器 CORS 限制卡死。GM_xmlhttpRequest 是油猴环境提供的跨域请求接口,可以绕过这个限制: // ❌ 这样会报 CORS 错误 fetch("https://open.bigmodel.cn/api/paas/v4/chat/completions", {...})// ✅ 用 GM_xmlhttpRequest GM_xmlhttpRequest({ method: "POST", url: "https://open.bigmodel.cn/api/paas/v4/chat/completions", ... })脚本头部必须声明权限: // @grant GM_xmlhttpRequest完整示例:调用视觉模型分析页面图片 // ==UserScript== // @name AI 图片分析 // @namespace http://tampermonkey.net/ // @version 1.0 // @match *://*/* // @grant GM_xmlhttpRequest // ==/UserScript==(function () { const API_KEY = "your-api-key"; // 换成你的 key function getImagesFromPage() { const imgs = document.querySelectorAll("img"); return Array.from(imgs) .slice(0, 3) .map(img => ({ type: "image_url", image_url: { url: img.src || img.dataset.src // 处理懒加载 } })) .filter(item => item.image_url.url); } function requestAI(imageItems) { GM_xmlhttpRequest({ method: "POST", url: "https://open.bigmodel.cn/api/paas/v4/chat/completions", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + API_KEY }, data: JSON.stringify({ model: "glm-4v", messages: [ { role: "user", content: [ ...imageItems, { type: "text", text: "请分析这些图片的内容" } ] } ] }), onload: function (res) { try { const data = JSON.parse(res.responseText); const answer = data?.choices?.[0]?.message?.content; console.log("AI 回答:", answer); alert(answer); } catch (e) { console.error("解析失败", e); } }, onerror: function (err) { console.error("请求失败", err); } }); } const images = getImagesFromPage(); if (images.length > 0) { requestAI(images); } })();图片来源处理 指定区域内的图片 function getQuestionImages() { const container = document.querySelector(".question-area"); if (!container) return []; return Array.from(container.querySelectorAll("img")).map(img => ({ type: "image_url", image_url: { url: new URL(img.getAttribute("src") || "", location.href).href } })); }new URL(src, location.href).href 可以把相对路径补全为绝对 URL。 canvas 图片转 base64 页面是 canvas 渲染(反爬、PDF 等)时,需要截图再传: function getCanvasImage() { const canvas = document.querySelector("canvas"); if (!canvas) return []; return [{ type: "image_url", image_url: { url: canvas.toDataURL("image/png") // data:image/png;base64,... } }]; }大多数视觉模型 API 同时支持 URL 和 base64 格式的图片。 常见问题 GM_xmlhttpRequest is not a function脚本头部没有声明 @grant GM_xmlhttpRequest。 图片 URL 404 / 模型报错 图片 URL 必须能公网访问,localhost、blob:、file:// 都不行 带签名的临时 URL(如 S3 presigned URL)容易过期,建议转 base64@grant none 和 @grant GM_xmlhttpRequest 冲突用了 GM API 就不能同时 @grant none,两者互斥。 绑定到按钮 把 AI 调用绑定到页面按钮,点击时分析当前可见的图片: const btn = document.createElement("button"); btn.textContent = "AI 分析"; Object.assign(btn.style, { position: "fixed", right: "20px", bottom: "20px", padding: "10px 18px", background: "#2563eb", color: "#fff", border: "none", borderRadius: "8px", cursor: "pointer", zIndex: 9999 }); btn.onclick = () => requestAI(getImagesFromPage()); document.body.appendChild(btn);