Docker 镜像拉取失败:proxyconnect tcp EOF 与国内镜像源配置

proxyconnect tcp: EOF 这个错误不是 Docker Hub 故障,而是 Docker daemon 正在使用一个不可达的代理。 快速诊断 # 查看 Docker 是否配置了代理 docker info | grep -i proxy# 查看环境变量 env | grep -i proxy如果看到: HTTP Proxy: http://127.0.0.1:7890说明 Docker 正在走代理,需要验证代理是否可用: curl -x http://127.0.0.1:7890 https://registry-1.docker.io/v2/返回 {} 或认证信息 → 代理正常 EOF / timeout → 代理链路断了 Connection refused → 代理未启动方案一:清除代理环境变量(临时) unset http_proxy unset https_proxy unset HTTP_PROXY unset HTTPS_PROXYdocker pull redis:latest如果此时能拉成功,说明就是代理问题。 方案二:配置国内镜像源(推荐) sudo mkdir -p /etc/docker sudo nano /etc/docker/daemon.json写入: { "registry-mirrors": [ "https://mirror.ccs.tencentyun.com", "https://registry.docker-cn.com" ] }然后重启: sudo systemctl daemon-reexec sudo systemctl restart docker验证: docker info | grep -A5 "Registry Mirrors"方案三:正确配置 Docker daemon 使用代理 Docker 不会自动读取系统代理(即使浏览器能上网),需要单独配置: sudo mkdir -p /etc/systemd/system/docker.service.d sudo nano /etc/systemd/system/docker.service.d/http-proxy.conf写入: [Service] Environment="HTTP_PROXY=http://127.0.0.1:7890" Environment="HTTPS_PROXY=http://127.0.0.1:7890" Environment="NO_PROXY=localhost,127.0.0.1"注意:代理地址必须用 http://,不能写 https://: HTTPS_PROXY=https://127.0.0.1:7890 ← 错误 HTTPS_PROXY=http://127.0.0.1:7890 ← 正确重新加载: sudo systemctl daemon-reload sudo systemctl restart docker常见坑 Docker 不继承系统代理:Clash/sing-box 开了,Docker 仍然无法自动使用,必须显式配置 /etc/systemd/system/docker.service.d/http-proxy.conf。 proxyconnect tcp: EOF 和 connection refused 的区别:EOF → 代理在监听但链路断了(软件崩溃、配置错误) refused → 代理根本没有在对应端口监听

Python 移动 / 复制目录的正确写法:shutil 全场景速查

Python 里"移动整个目录"或"把文件全复制到另一处"这类操作,全部靠 shutil。列几个最常用的模板。 移动整个目录 99% 场景直接: import shutilshutil.move(r"C:\source_dir", r"D:\target_dir")行为:目标不存在 → 直接改名/移动 目标已存在 → 源被移到 目标\源目录名 同盘 是秒移(本质是 rename) 跨盘 会退化成复制 + 删除想强制覆盖已有目标: import os, shutilsrc = r"C:\source_dir" dst = r"D:\target_dir\source_dir"if os.path.exists(dst): shutil.rmtree(dst) shutil.move(src, dst)复制整个目录(含子目录) Python 3.8+: import shutilshutil.copytree( r"C:\source_dir", r"D:\target_dir", dirs_exist_ok=True, # 目标已存在时合并写入 )会连带子目录、文件属性一起复制。 只复制当前目录里的文件(跳过子目录) import os, shutilsrc = r"C:\source_dir" dst = r"D:\target_dir" os.makedirs(dst, exist_ok=True)for name in os.listdir(src): s = os.path.join(src, name) if os.path.isfile(s): shutil.copy2(s, os.path.join(dst, name))copy2 会保留 mtime、权限,比 copy 更完整。 只复制某类文件 import os, shutilfor name in os.listdir(src): if name.endswith(".txt"): shutil.copy2( os.path.join(src, name), os.path.join(dst, name), )递归复制文件(扁平化,不保留目录结构) import os, shutilfor root, _, files in os.walk(src): for f in files: shutil.copy2(os.path.join(root, f), os.path.join(dst, f))注意同名文件会互相覆盖,要保结构就直接 copytree。 用 pathlib 版本(更现代) from pathlib import Path import shutilsrc = Path(r"D:\A\deep\folder") dst = Path(r"D:\B\deep\folder")if dst.exists(): shutil.rmtree(dst) dst.parent.mkdir(parents=True, exist_ok=True) shutil.copytree(src, dst)Path 的 parent / mkdir(parents=True) 组合特别适合处理"目标父目录还没创建"的情况。 不要用 os.rename os.rename(src, dst)跨磁盘直接报错 不能覆盖已有目录除非明确是"同盘、无冲突",否则直接上 shutil.move。 一句话总结 移动用 shutil.move,整目录复制用 shutil.copytree(dirs_exist_ok=True)。其它场景在这两个基础上按需组合。

PyInstaller 打包 GUI 程序:无 CMD、文件夹形式

用 PyInstaller 打包一个 Python GUI 程序,想要"启动不弹 CMD 黑框、也不打成单个大文件",一条命令搞定: pyinstaller -w -D main.py参数解释 -w(或 --noconsole):不开控制台窗口。适合 Tkinter、PyQt、PySide、wxPython 之类的 GUI。 -D(或 --onedir):文件夹形式打包。产物结构: dist/ └── main/ ├── main.exe ├── python3x.dll └── ...(依赖 dll、pyd、资源)跟 -F 单文件相比,-D 启动快、崩溃少、易调试,就是需要发一整个文件夹。 加图标 pyinstaller -w -D -i app.ico main.py.ico 文件建议 256×256 或多尺寸 ico。 常见错误组合 GUI 忘了 -w: pyinstaller -D main.py # 会弹黑色 CMD 窗口GUI 只想单文件反而用了 -F -w: pyinstaller -F -w main.py能跑,但每次启动都要把打包体解压到临时目录,启动慢;sys._MEIPASS 拿资源要处理路径。 用 spec 文件更可控 第一次 pyinstaller 会生成 main.spec,之后修改这个文件重新构建: pyinstaller main.specspec 里控制"无 CMD"的字段: exe = EXE( pyz, a.scripts, name='main', console=False, # 无 CMD 窗口 icon='app.ico', ... )有 -w -D 起步足够,需要精细控制 hidden imports、数据文件、加签名时再上 spec。 加静态资源 程序里读了 templates/、assets/ 之类的目录,-D 只会打二进制,不带资源,运行时找不到。加 --add-data: Windows(分隔符 ;): pyinstaller -w -D main.py ^ --add-data "templates;templates" ^ --add-data "assets;assets"macOS / Linux(分隔符 :): pyinstaller -w -D main.py \ --add-data "templates:templates" \ --add-data "assets:assets"代码里读资源要用 PyInstaller 兼容路径: import sys, osdef resource_path(rel): base = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) return os.path.join(base, rel)with open(resource_path("templates/index.html")) as f: ...找不到某个包 装了包但打包运行时抛 ModuleNotFoundError——PyInstaller 静态分析漏了动态导入。补 hidden-import: pyinstaller -w -D main.py --hidden-import=some_dynamic_module大量子模块可以直接 collect-all: pyinstaller -w -D main.py --collect-all fastapi --collect-all uvicorn一句话总结 pyinstaller -w -D main.py 是 GUI 程序打包最省心的一条命令。资源要 --add-data、动态导入要 --hidden-import 或 --collect-all。

Vue 项目国际化(i18n)配置:vue-i18n 基础用法

Vue 项目做多语言支持用 vue-i18n,核心是:把文字抽成 key-value 的语言文件,组件里用 $t('key') 调用。 安装 npm install vue-i18n创建语言文件 src/ ├── locales/ │ ├── zh-CN.json │ └── en-US.json// zh-CN.json { "setup": { "logout": "退出登录", "language": "语言设置", "version": "版本更新", "clearCache": "清理缓存", "darkMode": "暗黑模式" }, "ok": "确定", "cancel": "取消" }// en-US.json { "setup": { "logout": "Logout", "language": "Language", "version": "Version Update", "clearCache": "Clear Cache", "darkMode": "Dark Mode" }, "ok": "OK", "cancel": "Cancel" }配置 i18n 实例 // src/i18n/index.ts import { createI18n } from "vue-i18n"; import zhCN from "../locales/zh-CN.json"; import enUS from "../locales/en-US.json";const savedLocale = localStorage.getItem("lang") || "zh-CN";export const i18n = createI18n({ legacy: false, // 使用 Composition API 模式 locale: savedLocale, fallbackLocale: "zh-CN", messages: { "zh-CN": zhCN, "en-US": enUS, }, });// main.ts import { createApp } from "vue"; import App from "./App.vue"; import { i18n } from "./i18n";createApp(App).use(i18n).mount("#app");组件内使用 Options API <template> <button @click="handleLogout">{{ $t('setup.logout') }}</button> <span>{{ $t('ok') }}</span> </template>Composition API import { useI18n } from "vue-i18n";const { t } = useI18n(); console.log(t("setup.logout")); // "退出登录"动态切换语言 import { useI18n } from "vue-i18n";const { locale } = useI18n();function switchLanguage(lang: string) { locale.value = lang; localStorage.setItem("lang", lang); }// 切换到英文 switchLanguage("en-US");带参数的翻译 // zh-CN.json { "greeting": "你好,{name}!", "items": "共 {count} 个项目" }<template> <p>{{ $t('greeting', { name: 'Soulock' }) }}</p> <p>{{ $t('items', { count: 42 }) }}</p> </template>语言列表组件示例 <template> <div v-for="lang in languageList" :key="lang.id" @click="switchTo(lang)"> <span>{{ lang.label }}</span> <span v-if="currentLang === lang.id">✓</span> </div> </template><script setup lang="ts"> import { ref } from "vue"; import { useI18n } from "vue-i18n";const { locale } = useI18n();const languageList = [ { id: "zh-CN", label: "简体中文" }, { id: "en-US", label: "English" }, { id: "ja-JP", label: "日本語" }, ];const currentLang = ref(locale.value);function switchTo(lang: { id: string; label: string }) { locale.value = lang.id; currentLang.value = lang.id; localStorage.setItem("lang", lang.id); } </script>按需加载语言包 语言多时可以懒加载,避免全部打包: async function loadLocale(lang: string) { const messages = await import(`../locales/${lang}.json`); i18n.global.setLocaleMessage(lang, messages.default); i18n.global.locale.value = lang; }第一次切换到某语言时再加载对应文件,减小初始包体积。

ethers v6 里 provider / signer 怎么拿,以及手写一个 signer

前端 dApp 连 MetaMask 用 ethers.js,v5 和 v6 API 变了。写代码总卡在"provider 怎么初始化"这一步。 ethers v6 的最小骨架 <script src="https://cdn.jsdelivr.net/npm/ethers@6.7.1/dist/ethers.min.js"></script> <script> let provider, signer;if (window.ethereum) { provider = new ethers.BrowserProvider(window.ethereum); } else { alert("请先安装 MetaMask"); }document.getElementById("connect").addEventListener("click", async () => { await provider.send("eth_requestAccounts", []); signer = await provider.getSigner(); const address = await signer.getAddress(); const balance = await provider.getBalance(address); console.log(address, ethers.formatEther(balance), "ETH"); }); </script>要点:v6 用 ethers.BrowserProvider(v5 是 ethers.providers.Web3Provider) getSigner() 现在返回 Promise,要 await ethers.formatEther(v6)替代了 ethers.utils.formatEther(v5)v5 vs v6 对照场景 v5 v6浏览器 Provider new ethers.providers.Web3Provider(eth) new ethers.BrowserProvider(eth)RPC Provider ethers.providers.JsonRpcProvider(url) ethers.JsonRpcProvider(url)formatEther ethers.utils.formatEther(v) ethers.formatEther(v)parseEther ethers.utils.parseEther(s) ethers.parseEther(s)BigNumber ethers.BigNumber.from(x) 原生 BigInt手写一个 signer(不依赖 ethers) 有些教学场景或轻量项目不想引入整个 ethers.js,直接靠 window.ethereum 也能拼出一个 signer: async function myGetSigner() { const accounts = await window.ethereum.request({ method: "eth_requestAccounts", }); const address = accounts[0]; return { address, async getAddress() { return address; }, async signMessage(message) { const hex = typeof message === "string" ? toHex(message) : message; return window.ethereum.request({ method: "personal_sign", params: [hex, address], }); }, async sendTransaction(tx) { const params = { from: address, to: tx.to, value: tx.value ? "0x" + BigInt(tx.value).toString(16) : "0x0", gas: tx.gas ? "0x" + BigInt(tx.gas).toString(16) : undefined, data: tx.data || "0x", }; return window.ethereum.request({ method: "eth_sendTransaction", params: [params], }); }, }; }// 浏览器没有 Buffer,自己写一个字符串转 hex function toHex(str) { let hex = ""; for (const ch of str) { hex += ch.charCodeAt(0).toString(16).padStart(2, "0"); } return "0x" + hex; }常见踩坑 Buffer is not defined 从 Node 教程抄的 Buffer.from(...).toString('hex') 在浏览器不能用。用上面的 toHex 或 TextEncoder: const hex = "0x" + Array.from(new TextEncoder().encode(str)) .map(b => b.toString(16).padStart(2, "0")).join("");BigInt 序列化 v6 起金额和 nonce 都是原生 BigInt,直接 JSON.stringify 会崩: TypeError: Do not know how to serialize a BigInt要么手动转字符串,要么全局 patch: BigInt.prototype.toJSON = function () { return this.toString(); };window.ethereum 是数组 装了多个钱包(MetaMask + Coinbase + OKX)后,window.ethereum 可能变成聚合对象,isMetaMask 判断失效。EIP-6963 出来后的做法: window.addEventListener("eip6963:announceProvider", (event) => { console.log(event.detail.info.name, event.detail.provider); }); window.dispatchEvent(new Event("eip6963:requestProvider"));一句话总结 ethers v6:new BrowserProvider(window.ethereum) + await provider.getSigner();不想引 ethers 时手写 signer 也不难,注意浏览器里没 Buffer 就行。