[{"content":"什么是 Vibe Coding？ Vibe Coding（氛围编程）是指完全信任 AI，不自己写代码，只通过自然语言描述需求，让 AI 完成所有编程工作。这种方式最近在开发者社区引发了广泛讨论。\n我想做一个实验：完全不使用手工代码，能否完成一个可用的 Web 应用？\nRemote File Editor 是什么？ 一个轻量级的 Web IDE，VS Code 风格，功能包括代码编辑、文件管理、图片查看和终端面板。\n为什么做这个项目？ 我最初的需求很简单：方便查看和修改 OpenClaw 的配置文件和工作目录。\n作为一个经常在不同 VPS 之间切换的开发者，我需要一种方式来：\n快速浏览和编辑服务器上的文件 不需要每次都用 vim/nano 登录 SSH 有一个可视化的界面 但我不想花太多时间从头构建一个完整项目——这正好是测试 Vibe Coding 的完美场景。\n100% AI 生成的体验 技术栈 前端：React + TypeScript + Vite + Monaco Editor 后端：Node.js + Express + JWT 认证 部署：支持 systemd 和 PM2 AI 生成的内容 整个项目从设计到实现，几乎每一行代码都是 AI 生成的：\n架构设计 - 描述需求，AI 给出技术方案 前端代码 - React 组件、状态管理、样式 后端 API - Express 路由、文件操作、JWT 部署配置 - Nginx 反向代理、systemd 服务 遇到的问题 当然不是一帆风顺的：\n初始设计不够完善，需要多次迭代 某些边界情况没有考虑周全 安全认证流程需要手动调整 但这些都可以通过继续和 AI 对话来解决。\n关键特性 文件管理 树形视图，支持刷新 在 WORKSPACE_DIR 下读写文件（路径安全检查） 支持 cd 切换目录 编辑器 Monaco Editor，Auto layout 按扩展名自动识别语言 Cmd/Ctrl+S 保存 每个标签页支持撤销 图片编辑器 自动识别图片文件 显示尺寸 调整大小（可选锁定宽高比） 灰度转换 终端面板 API 驱动，输入命令回车执行 输出结果显示在下一行 支持 cd、clear 命令 可折叠/展开 认证 JWT 认证 密码保护的管理员访问 部署 # 配置环境变量 export ADMIN_PASSWORD=your-password export JWT_SECRET=your-secret export WORKSPACE_DIR=/path/to/workspace export PORT=5174 # 启动 npm install npm run build pm2 start server/index.js --name remote-file-editor 总结 这次实验证明：用 AI 100% 生成一个可用的 Web 应用是可行的。\nRemote File Editor 现在运行在我的 VPS 上，每天都在使用。它可能不是最完美的代码，但它解决了实际问题，而且是通过一种全新的编程方式构建的。\nVibe Coding 不是要取代程序员，而是提供了一种新的可能性：想法 → 产品 的路径变得更短了。\n如果你感兴趣，可以看看 GitHub 仓库：xu4wang/remote-file-editor\n这篇博客是如何生成和发布的？ 本文本身就是 Vibe Coding 的一个绝佳案例！\n写作流程 AI 辅助写作 - 通过自然语言描述需求，AI 生成中英文博客文章 Telegram 机器人 - 我写了一个 Telegram 机器人，可以直接通过聊天发送图片 自动上传 - 发送图片 → 机器人自动下载 → 保存到 Hugo 的 static/images 目录 → 复制到 Nginx → URL 一键部署 - Git push 后 返回，服务器自动构建并发布 如上图所示，整个流程自动化完成，我只用：\n在 Telegram 发图片给机器人 告诉 AI 要写什么主题 Git push 博客就自动部署到 thefreemeal.com 了！\n技术栈 博客引擎：Hugo + PaperMod 主题 托管：GitHub 仓库版本管理 图片收集：Telegram Bot (Gram.js) 部署：PM2 + Nginx AI 工作流总结 这篇文章展示了四类 AI 协同工作：\n本地 Vibe Coding AI (GPT-5, Claude) - 编写代码 VPS 上的 OpenClaw (MiniMax 2.5) - 多个 Agent 分工协作： 图片收集 Agent - 从 Telegram 接收图片 部署 Agent - 处理博客部署 博客撰写 Agent - 写作和发布文章 整个工作流自动化运行：只需通过 Telegram 发图片，描述要写的内容，AI 就会完成剩下的工作！\n本文由 AI 协助编写 🤖\n","permalink":"https://blog.thefreemeal.com/zh/posts/2026-02-16-remote-file-editor-vibe-coding/","summary":"\u003ch2 id=\"什么是-vibe-coding\"\u003e什么是 Vibe Coding？\u003c/h2\u003e\n\u003cp\u003eVibe Coding（氛围编程）是指完全信任 AI，不自己写代码，只通过自然语言描述需求，让 AI 完成所有编程工作。这种方式最近在开发者社区引发了广泛讨论。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e我想做一个实验：完全不使用手工代码，能否完成一个可用的 Web 应用？\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"remote-file-editor-是什么\"\u003eRemote File Editor 是什么？\u003c/h2\u003e\n\u003cp\u003e一个轻量级的 Web IDE，VS Code 风格，功能包括代码编辑、文件管理、图片查看和终端面板。\u003c/p\u003e\n\u003ch2 id=\"为什么做这个项目\"\u003e为什么做这个项目？\u003c/h2\u003e\n\u003cp\u003e我最初的需求很简单：\u003cstrong\u003e方便查看和修改 OpenClaw 的配置文件和工作目录\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e作为一个经常在不同 VPS 之间切换的开发者，我需要一种方式来：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e快速浏览和编辑服务器上的文件\u003c/li\u003e\n\u003cli\u003e不需要每次都用 vim/nano 登录 SSH\u003c/li\u003e\n\u003cli\u003e有一个可视化的界面\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e但我不想花太多时间从头构建一个完整项目——这正好是测试 Vibe Coding 的完美场景。\u003c/p\u003e\n\u003ch2 id=\"100-ai-生成的体验\"\u003e100% AI 生成的体验\u003c/h2\u003e\n\u003ch3 id=\"技术栈\"\u003e技术栈\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e前端\u003c/strong\u003e：React + TypeScript + Vite + Monaco Editor\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e后端\u003c/strong\u003e：Node.js + Express + JWT 认证\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e部署\u003c/strong\u003e：支持 systemd 和 PM2\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"ai-生成的内容\"\u003eAI 生成的内容\u003c/h3\u003e\n\u003cp\u003e整个项目从设计到实现，几乎每一行代码都是 AI 生成的：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e架构设计\u003c/strong\u003e - 描述需求，AI 给出技术方案\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e前端代码\u003c/strong\u003e - React 组件、状态管理、样式\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e后端 API\u003c/strong\u003e - Express 路由、文件操作、JWT\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e部署配置\u003c/strong\u003e - Nginx 反向代理、systemd 服务\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"遇到的问题\"\u003e遇到的问题\u003c/h3\u003e\n\u003cp\u003e当然不是一帆风顺的：\u003c/p\u003e","title":"Remote File Editor: 一个 Vibe Coding 的实验"},{"content":"pip是什么？ pip是汇率变化的报价单位。 一般是小数点后第4位。 0.0001 是一个pip。 日元是个例外，对于日元 0.01 为一个pip。 因为日元太小了。\nA pip is a unit of measurement for price movements of currencies in foreign exchange (FX) markets. Pip stands for “percentage in point” or “price interest point.” It represents the smallest price variation that a particular exchange rate experiences based on typical FX market convention.\npip表示绝对数字，不能表示收益率 假如你有10000美金， USDCNY报价是6.5， 那么可以换汇得到65000人民币。 假设汇率增加了100个pips（0.0001 x 100 = 0.01）， 变成 6.51， 那么可以换汇得到65100人民币。 100个pips的汇率变化导致你多收入100人民币。\n知道了交易的总金额，以及汇率的变化，就可以知道收益的变化。 但是不知道收益率（收益变化的比例）。\n上图中，x轴为换汇的原始货币（例如美金），y轴是目标货币。\n第一条曲线是泰铢（1美金对30泰铢） 第二条是人民币（1美金对6.5人民币） 第四条是某一个汇率为2的货币（假设货币名称是银币，1美金对应2银币） 第三条是银币在汇率增加1000pips后的曲线 第六条是某一个汇率为1的货币（假设货币名称是金币，1美金对应1金币） 第五条是金币在汇率增加1000pips后的曲线 第七条是pips为1000时候带来的目标货币变化 因为人民币和泰铢曲线太陡峭，不好观察。我们看金币和银币在pips作用下的收益变化。 在50000美金时候，pips变化1000，这时候金币和银币都会增加5000。 但是，我们知道5000金币和5000银币的价值是不一样的。 也即同样是50000美金的投入，获得的收益率是不一样的。\n对于金币来说1000pips是一个比较大的波动， 对于银币来说，相对比较小。\n曲线越陡，日常的波动（pips）绝对数值应该越大。100 pips对于泰铢来说是一个很小的波动，对于人民币就比较大了。 100 pips = 0.01\n对于泰铢而言， 波动0.01 只是 （ 30.01 - 30 ）/ 30 = 0.03% 对于人民币而言， 波动0.01 是 （ 6.51 - 6.5 ）/ 6.5 = 0.15% 从上图也可以看出，使用pip来表示汇率变化，不适合曲线太平缓的汇率。 例如，USD/THB = 30， 则THB/USD = 0.033333（1泰铢换3美分）， 如果对THBUSD使用pips：\n对于100 pips的变化， 会导致 (0.043333 - 0.033333 ) / 0.033333 = 30% 的汇率变化。 所以这种场景下 pip的粒度太大了。\n目前汇率有三种报价方法：\n直接标价法： 单位外币等价于多少本币 间接标价法： 单位本币等价于多少外币 美金标价法： 单位美金等价于多少外币（少数几个货币，例如GBP是例外） ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-04-23-pip/","summary":"\u003ch2 id=\"pip是什么\"\u003epip是什么？\u003c/h2\u003e\n\u003cp\u003epip是汇率变化的报价单位。 一般是小数点后第4位。 0.0001 是一个pip。 日元是个例外，对于日元 0.01 为一个pip。 因为日元太小了。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eA pip is a unit of measurement for price movements of currencies in foreign exchange (FX) markets. Pip stands for “percentage in point” or “price interest point.” It represents the smallest price variation that a particular exchange rate experiences based on typical FX market convention.\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"pip表示绝对数字不能表示收益率\"\u003epip表示绝对数字，不能表示收益率\u003c/h2\u003e\n\u003cp\u003e假如你有10000美金， USDCNY报价是6.5， 那么可以换汇得到65000人民币。\n假设汇率增加了100个pips（0.0001 x 100 = 0.01）， 变成 6.51， 那么可以换汇得到65100人民币。 100个pips的汇率变化导致你多收入100人民币。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e知道了交易的总金额，以及汇率的变化，就可以知道收益的变化。 但是不知道收益率（收益变化的比例）。\u003c/p\u003e","title":"外汇报价中的 pip 是什么"},{"content":" 目前在Web 3的领域里，有哪些明星应用？这些应用的MAU（月活用户数）是多少？和金融相关的应用中，月交易规模是多少美金？ 参考资料一的dappradar网站提供了实时的数据可以分析。\nWeb3应用和目前的web 2.0互联网应用有一点不一样， 所有用户数据和交易数据都是透明的。不需要通过应用的运营方，从区块链上就能获取真实的运营数据。\n按照dappradar网站的统计，本文分别从两个维度看最近30天内的明星dapp应用：\n活跃度：月活跃用户数（钱包数）前十的应用 交易规模： 进行交易流入的代币（等值美金）排名前十的应用 智能合约内的资产总值排名前十的应用 1. 活跃度 上表是30天内，按照使用用户数目（独立账户个数）排列的TOP 10应用。\n排名 应用名称 简介 1 PancakeSwap BSC上领先的去中心化交易所 2 Primelab 从介绍看，Primelab 为用户提供了一个区块链生态系统，主要特征是比较好的可访问性和可用性。 所有 Primelab dapp 都允许用户 通过电子邮件或电话注册帐户。从数据看，其用户数量非常不稳定。 3 AtomicAssets 实现 WAX 区块链上的NFT协议（对应于以太坊上，常见的 NFT 标准ERC721 、ERC1155）。 4 Alien Worlds 游戏相关 5 Splinterlands 游戏相关 6 OpenSea 第一个也是最大的加密收藏品点对点市场，其中包括游戏物品、数字艺术和其他由区块链支持的虚拟商品。 7 Axie Marketplace 游戏相关 8 Upland 游戏相关 9 Katana 建立在 Ronin 之上的自动做市商 (AMM) 去中心化交易所 (DEX)。 10 Magic Eden Solana区块链上的NFT市场。 从上表分析可见：\n游戏应用的参与人数比较多， 前10有4个都是游戏相关应用 AMM等类似uniswap的去中心化交易所也很活跃，例如：Pancackswap，Katana NFT交易相关应用活跃： OpenSea，Magic Eden， AtomicAssets Primelab是唯一一个比较特殊的存在， 其卖点是易用性。让普通人更容易使用blockchain的服务。 但观察其数据，很不稳定。 怀疑它冲上前十是偶然。 2. 交易规模 2.1. 交易规模前10 2.2. 资产规模前10 从交易规模（过去30天流入到智能合约的等值美金金额）和资产规模（智能合约上沉淀的资产规模存量）看， 排名前十的应用无一例外，都是DeFi类应用。 并且以在各个不同的区块链上的去中心化交易所为主。 例如，在当前的DeFi系统中，三个各具特点的AMM应用都在TOP 10列表中：Uniswap，Curve，1inch。\nETH2 Deposit Contract 持有的资金明显高于其它所有的应用。 这个应用是为以太坊迁移到2.0，也即使用基于POS的共识机制而服务的。 2.0时代的validator类似于1.0时代的矿工。成为validator需要质押以太币。这个智能协议是收取质押的以太币用的。\nPolygon POS Bridge 和 Ronin Bridge 等在以太坊和 L2 侧链之间转移资产的应用也有相当大的资产沉淀。\n排名TOP 10的应用都支持以下一个或者多个区块链平台：\nLayer1 公链：ETH, WAX, Flow, Solana, NEAR, Hive(Steem分叉), EOS, Klaytn（韩国Kaokao), Waves（俄罗斯), Avalanche（雪崩链）,Moonbean, Fantom; Layer2 侧链：BSC、Ronin、Polygon、Immutable X， Optimism 3. 参考资料 https://dappradar.com/rankings ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-04-10-dapps/","summary":"\u003cblockquote\u003e\n\u003cp\u003e目前在Web 3的领域里，有哪些明星应用？这些应用的MAU（月活用户数）是多少？和金融相关的应用中，月交易规模是多少美金？\n参考资料一的dappradar网站提供了实时的数据可以分析。\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003eWeb3应用和目前的web 2.0互联网应用有一点不一样， 所有用户数据和交易数据都是透明的。不需要通过应用的运营方，从区块链上就能获取真实的运营数据。\u003c/p\u003e\n\u003cp\u003e按照dappradar网站的统计，本文分别从两个维度看最近30天内的明星dapp应用：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e活跃度：月活跃用户数（钱包数）前十的应用\u003c/li\u003e\n\u003cli\u003e交易规模：\n\u003cul\u003e\n\u003cli\u003e进行交易流入的代币（等值美金）排名前十的应用\u003c/li\u003e\n\u003cli\u003e智能合约内的资产总值排名前十的应用\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"1-活跃度\"\u003e1. 活跃度\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"picture 37\" loading=\"lazy\" src=\"/images/1649519204938.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e上表是30天内，按照使用用户数目（独立账户个数）排列的TOP 10应用。\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e排名\u003c/th\u003e\n          \u003cth\u003e应用名称\u003c/th\u003e\n          \u003cth\u003e简介\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e1\u003c/td\u003e\n          \u003ctd\u003ePancakeSwap\u003c/td\u003e\n          \u003ctd\u003eBSC上领先的去中心化交易所\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e2\u003c/td\u003e\n          \u003ctd\u003ePrimelab\u003c/td\u003e\n          \u003ctd\u003e从介绍看，Primelab 为用户提供了一个区块链生态系统，主要特征是比较好的可访问性和可用性。 所有 Primelab dapp 都允许用户 通过电子邮件或电话注册帐户。从数据看，其用户数量非常不稳定。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e3\u003c/td\u003e\n          \u003ctd\u003eAtomicAssets\u003c/td\u003e\n          \u003ctd\u003e实现 WAX 区块链上的NFT协议（对应于以太坊上，常见的 NFT 标准ERC721 、ERC1155）。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e4\u003c/td\u003e\n          \u003ctd\u003eAlien Worlds\u003c/td\u003e\n          \u003ctd\u003e游戏相关\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e5\u003c/td\u003e\n          \u003ctd\u003eSplinterlands\u003c/td\u003e\n          \u003ctd\u003e游戏相关\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e6\u003c/td\u003e\n          \u003ctd\u003eOpenSea\u003c/td\u003e\n          \u003ctd\u003e第一个也是最大的加密收藏品点对点市场，其中包括游戏物品、数字艺术和其他由区块链支持的虚拟商品。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e7\u003c/td\u003e\n          \u003ctd\u003eAxie Marketplace\u003c/td\u003e\n          \u003ctd\u003e游戏相关\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e8\u003c/td\u003e\n          \u003ctd\u003eUpland\u003c/td\u003e\n          \u003ctd\u003e游戏相关\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e9\u003c/td\u003e\n          \u003ctd\u003eKatana\u003c/td\u003e\n          \u003ctd\u003e建立在 Ronin 之上的自动做市商 (AMM) 去中心化交易所 (DEX)。\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e10\u003c/td\u003e\n          \u003ctd\u003eMagic Eden\u003c/td\u003e\n          \u003ctd\u003eSolana区块链上的NFT市场。\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e从上表分析可见：\u003c/p\u003e","title":"从月活数和交易规模看目前流行的web3应用"},{"content":"\n默克尔树（Merkle tree）的概念由瑞夫·默克尔于 1979 年申请专利，故此得名。\nMerkle tree 是一种二叉树数据结构，每个叶节点存储数据块的哈希值，而除了叶节点以外的节点存储其两个子节点值的哈希（对两个哈希值拼接后再做哈希）。哈希树能够高效、安全地验证大型数据结构的内容。\n场景 假如有1000个数据块，每个块1M字节，需要用哈希值（校验码）来保证每一个数据块在传输中没有改动。 这时候有两种方案：\n在传输时候，增加1000个哈希值， 每个数据块对应一个。数据接收方可以用哈希值一对一分别验证各个数据块是否有改动。 把这1000个块合并起来，生成一个1G的大数据块， 然后对这个1G的块做一次哈希。 数据接收方可以通过哈希值来整体上验证1000个数据块是不是有改动。 以上两个方案都有弊端：\n方案1， 需要增加传输的数据长度。 假设用SHA256做哈希， 方案一需要额外增加 999 个 32 字节长的哈希值。 方案2， 只需要增加32字节的校验码（哈希值），比较高效。 但问题是，当数据接收方只想检查一个数据块的时候， 它必须得到所有的1000个数据块，拼接后统一计算才能验证。 Merkle Tree提供了第三种方案。\n解决方案 Merkle Tree把数据块分别做哈希（类似上面的方案1），但并不直接传输这一堆的哈希值。 而是把这些哈希值两两拼接之后再哈希。 类似世界杯比赛一样， 直到得到最终的一个哈希值，称之为Merkle Root。 然后将所有数据块和Merkle Root的值一起传输。\n接收方收到所有的数据块和Merkle Root值。 按照上述算法，可以再算一遍，验证Merkle Root是否和对数据块做Merkle哈希后的值一致。\n接收方如果只想验证一个数据块的完整性。 例如上图比特币中的例子，接收方只想知道交易T(D)是不是在当前区块里面， 那么它只需要得到H(C)，H(AB)，H(EFGH)和Merkle Root四个哈希值就可以验证了。当数据块越多， Merkle Tree的层级就越多。验证一个数据块，需要得到从根节点到该块所在叶子结点的树枝上的所有兄弟节点的哈希值。哈希值一般只有32字节，比交易块短的多。\n在比特币中 SPV 节点就是利用Merkle Tree的这一个性质来优化和可信节点之间的交互，大大减少数据传输。\nSPV 结点为什么不直接向信任的结点要求 H(D) 的值做验证？ 因为H(D)可能被伪造。 而Merkle Root的值在区块链的当前区块Header中有保存，是有完整性保护的。最终和Merkle Root做比较来验证更可靠。由于哈希函数的单向性，恶意节点无法根据Merkle Root伪造树枝上的哈希值，技术上拼不出来（需要解哈希碰撞问题）。\n案例 Merkle Tree的使用场景是在分布式系统中，对数据做高效的验证时候。已经被业界广泛采用。以下是几个代表：\n版本管理工具 git P2P浏览器 Tor 区块链相关：比特币，以太坊，Filecoin等 参考资料 Merkle Tree - Wikipedia Merkel Tree - Investopedia ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-04-04-merkle/","summary":"\u003cp\u003e\u003cimg alt=\"picture 32\" loading=\"lazy\" src=\"/images/1649010375043.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e默克尔树（Merkle tree）的概念由瑞夫·默克尔于 1979 年申请专利，故此得名。\u003c/p\u003e\n\u003cp\u003eMerkle tree 是一种二叉树数据结构，每个叶节点存储数据块的哈希值，而除了叶节点以外的节点存储其两个子节点值的哈希（对两个哈希值拼接后再做哈希）。哈希树能够高效、安全地验证大型数据结构的内容。\u003c/p\u003e\n\u003ch2 id=\"场景\"\u003e场景\u003c/h2\u003e\n\u003cp\u003e假如有1000个数据块，每个块1M字节，需要用哈希值（校验码）来保证每一个数据块在传输中没有改动。 这时候有两种方案：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e在传输时候，增加1000个哈希值， 每个数据块对应一个。数据接收方可以用哈希值一对一分别验证各个数据块是否有改动。\u003c/li\u003e\n\u003cli\u003e把这1000个块合并起来，生成一个1G的大数据块， 然后对这个1G的块做一次哈希。 数据接收方可以通过哈希值来整体上验证1000个数据块是不是有改动。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e以上两个方案都有弊端：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e方案1， 需要增加传输的数据长度。 假设用SHA256做哈希， 方案一需要额外增加 999 个 32 字节长的哈希值。\u003c/li\u003e\n\u003cli\u003e方案2， 只需要增加32字节的校验码（哈希值），比较高效。 但问题是，当数据接收方只想检查一个数据块的时候， 它必须得到所有的1000个数据块，拼接后统一计算才能验证。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eMerkle Tree提供了第三种方案。\u003c/p\u003e\n\u003ch2 id=\"解决方案\"\u003e解决方案\u003c/h2\u003e\n\u003cp\u003eMerkle Tree把数据块分别做哈希（类似上面的方案1），但并不直接传输这一堆的哈希值。 而是把这些哈希值两两拼接之后再哈希。 类似世界杯比赛一样， 直到得到最终的一个哈希值，称之为Merkle Root。 然后将所有数据块和Merkle Root的值一起传输。\u003c/p\u003e\n\u003cp\u003e接收方收到所有的数据块和Merkle Root值。 按照上述算法，可以再算一遍，验证Merkle Root是否和对数据块做Merkle哈希后的值一致。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 34\" loading=\"lazy\" src=\"/images/1649011175482.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e接收方如果只想验证一个数据块的完整性。 例如上图比特币中的例子，接收方只想知道交易T(D)是不是在当前区块里面， 那么它只需要得到H(C)，H(AB)，H(EFGH)和Merkle Root四个哈希值就可以验证了。当数据块越多， Merkle Tree的层级就越多。验证一个数据块，需要得到从根节点到该块所在叶子结点的树枝上的所有兄弟节点的哈希值。哈希值一般只有32字节，比交易块短的多。\u003c/p\u003e\n\u003cp\u003e在比特币中 SPV 节点就是利用Merkle Tree的这一个性质来优化和可信节点之间的交互，大大减少数据传输。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eSPV 结点为什么不直接向信任的结点要求 H(D) 的值做验证？ 因为H(D)可能被伪造。 而Merkle Root的值在区块链的当前区块Header中有保存，是有完整性保护的。最终和Merkle Root做比较来验证更可靠。由于哈希函数的单向性，恶意节点无法根据Merkle Root伪造树枝上的哈希值，技术上拼不出来（需要解哈希碰撞问题）。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"案例\"\u003e案例\u003c/h2\u003e\n\u003cp\u003eMerkle Tree的使用场景是在分布式系统中，对数据做高效的验证时候。已经被业界广泛采用。以下是几个代表：\u003c/p\u003e","title":"Merkle Tree: Git 和 blockchain 背后的数据结构"},{"content":"以太坊（Ethereum）是一个分布式的计算机，由大量分布式节点组成，其中的每一个节点，都可以执行字节码（也就是所谓智能合约），然后把结果存在区块链上。整个网络是分布式的，应用可以保存状态信息，应用有了状态信息，就可以根据状态提供丰富多彩的服务。以太坊没有中心化的节点，第三方不能干预整个网络的运行，可以看作是永不停机的世界计算机。\n1. 以太坊-世界计算机 上图描述了以太坊的功能。 它把一台虚拟计算机放到了区块链上。 图中彩色方块是用哈希值串联起来的区块，每一个区块中有交易列表，交易在计算机中执行，并且导致计算机状态的变化。\n以太坊中把这个虚拟计算机的状态叫做“世界状态”， 世界状态可以看作“世界计算机”的存储区域。 世界状态通过执行以太坊区块中的交易来不断变化。每一个区块添加到链的尾部，都会导致世界的状态发生变化。世界状态由各个节点独立在数据库中维护。\n从这个视角看，这种状态管理的方式，类似git，每一个区块都是一个新的版本。每一个区块都可以得到一个当前所有账户的状态。 最后一个区块，就是当前系统中所有账户的当前状态。 世界计算机在以太坊的每一个节点上运行，每一个节点上的世界状态都一样。 交易触发的代码会在所有节点的虚拟机上运行一遍。 2. 账户 世界状态由所有账户的状态构成。 有两种账户，分别是外部账户和合约账户。账户的属性包括，余额（balance）、交易次数（nonce），code(代码)、存储(stroge)。 code和 storage只有合约账户有。外部账户用私钥控制，合约账户由合约代码控制。\n每一个内部账户都有自己独立的storage区域。以太坊中的智能合约代码，一旦部署，是无法升级修改的。codeHash确保代码不会被修改。\n上图中只画了两个账户，实际的世界状态中有很多账户。\n3. 交易 以太坊的交易由网络用户发起，交易有两种，一种是普通消息调用，另外一种是创建内部账户（会生成一个新的地址，初始化智能合约）。\n上图展示了交易执行后， 世界状态中多了一个内部账户。\n4. EVM 虚拟机 作为世界计算机， 不仅仅有存储， 还有执行智能合约代码的虚拟机。称之为EVM。合约代码在EVM中执行，可以导致存储（世界状态）的变化。\n智能合约开发使用高级语言，在部署到以太坊上时候，会编译成EVM可以识别的二进制串。合约一旦部署，就需要在所有维护世界状态的节点执行。注意不是在一个节点执行，而是一旦交易列表中有合约相关操作，则操作对应的代码要在所有节点执行，以太坊节点通过执行合约相关代码来更新世界状态。\n对世界计算机的使用是收费的，每次执行需要预付费（术语叫gas），执行过程中不断扣减费用。扣完了预付费后，指令将不能再执行，智能合约的调用失败。 这种机制也增进了系统的可靠性。 如果智能合约中出了bug，写了一个死循环，至少还能欠费停下来。\n5. 访问以太坊 外部用户通过以太坊节点，使用web3 API访问以太坊。\n6. 数据结构 上图展示了以太坊的主要数据结构。最左侧是一个以太坊的区块，包括区块头和区块体。在区块头里面除了parentHash作为区块指针外，还有几个哈希值：\nstateRoot：其值是这个区块内所有交易执行后世界状态的哈希值。 receiptsRoot：其值是这个区块所有交易的执行结果的哈希值。 transactionRoot：其值是这个区块所有交易的哈希值。 ommersHash（UncleHash）： 是对区块体中的叔块列表的哈希。 区块体中存放交易列表和叔块列表。\n右侧的几棵merkle tree（世界状态，交易状态， 交易等），不会在区块中存储，而是会在节点的数据库中保存。 用区块中的哈希值来保证各个节点的merkle tree状态都一致。\n7. 参考资料 getting-deep-into-ethereum-how-data-is-stored-in-ethereum 以太坊黄皮书 以太坊白皮书 DanFinlay-intro_to_ethereum ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-04-04-ethereum/","summary":"\u003cp\u003e以太坊（Ethereum）是一个分布式的计算机，由大量分布式节点组成，其中的每一个节点，都可以执行字节码（也就是所谓智能合约），然后把结果存在区块链上。整个网络是分布式的，应用可以保存状态信息，应用有了状态信息，就可以根据状态提供丰富多彩的服务。以太坊没有中心化的节点，第三方不能干预整个网络的运行，可以看作是永不停机的世界计算机。\u003c/p\u003e\n\u003ch2 id=\"1-以太坊-世界计算机\"\u003e1. 以太坊-世界计算机\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"picture 35\" loading=\"lazy\" src=\"/images/1649018609268.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e上图描述了以太坊的功能。 它把一台虚拟计算机放到了区块链上。 图中彩色方块是用哈希值串联起来的区块，每一个区块中有交易列表，交易在计算机中执行，并且导致计算机状态的变化。\u003c/p\u003e\n\u003cp\u003e以太坊中把这个虚拟计算机的状态叫做“世界状态”， 世界状态可以看作“世界计算机”的存储区域。 世界状态通过执行以太坊区块中的交易来不断变化。每一个区块添加到链的尾部，都会导致世界的状态发生变化。世界状态由各个节点独立在数据库中维护。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003col\u003e\n\u003cli\u003e从这个视角看，这种状态管理的方式，类似git，每一个区块都是一个新的版本。每一个区块都可以得到一个当前所有账户的状态。 最后一个区块，就是当前系统中所有账户的当前状态。\u003c/li\u003e\n\u003cli\u003e世界计算机在以太坊的每一个节点上运行，每一个节点上的世界状态都一样。 交易触发的代码会在所有节点的虚拟机上运行一遍。\u003c/li\u003e\n\u003c/ol\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"2-账户\"\u003e2. 账户\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"picture 27\" loading=\"lazy\" src=\"/images/1648895878714.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e世界状态由所有账户的状态构成。 有两种账户，分别是外部账户和合约账户。账户的属性包括，余额（balance）、交易次数（nonce），code(代码)、存储(stroge)。 code和 storage只有合约账户有。外部账户用私钥控制，合约账户由合约代码控制。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e每一个内部账户都有自己独立的storage区域。以太坊中的智能合约代码，一旦部署，是无法升级修改的。codeHash确保代码不会被修改。\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e上图中只画了两个账户，实际的世界状态中有很多账户。\u003c/p\u003e\n\u003ch2 id=\"3-交易\"\u003e3. 交易\u003c/h2\u003e\n\u003cp\u003e以太坊的交易由网络用户发起，交易有两种，一种是普通消息调用，另外一种是创建内部账户（会生成一个新的地址，初始化智能合约）。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 28\" loading=\"lazy\" src=\"/images/1648896198644.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e上图展示了交易执行后， 世界状态中多了一个内部账户。\u003c/p\u003e\n\u003ch2 id=\"4-evm-虚拟机\"\u003e4. EVM 虚拟机\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"picture 30\" loading=\"lazy\" src=\"/images/1648896909584.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e作为世界计算机， 不仅仅有存储， 还有执行智能合约代码的虚拟机。称之为EVM。合约代码在EVM中执行，可以导致存储（世界状态）的变化。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e智能合约开发使用高级语言，在部署到以太坊上时候，会编译成EVM可以识别的二进制串。合约一旦部署，就需要在所有维护世界状态的节点执行。注意不是在一个节点执行，而是一旦交易列表中有合约相关操作，则操作对应的代码要在所有节点执行，以太坊节点通过执行合约相关代码来更新世界状态。\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e对世界计算机的使用是收费的，每次执行需要预付费（术语叫gas），执行过程中不断扣减费用。扣完了预付费后，指令将不能再执行，智能合约的调用失败。 这种机制也增进了系统的可靠性。 如果智能合约中出了bug，写了一个死循环，至少还能欠费停下来。\u003c/p\u003e\n\u003ch2 id=\"5-访问以太坊\"\u003e5. 访问以太坊\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"picture 29\" loading=\"lazy\" src=\"/images/1648896785068.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e外部用户通过以太坊节点，使用web3 API访问以太坊。\u003c/p\u003e\n\u003ch2 id=\"6-数据结构\"\u003e6. 数据结构\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"picture 21\" loading=\"lazy\" src=\"/images/1648751079155.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e上图展示了以太坊的主要数据结构。最左侧是一个以太坊的区块，包括区块头和区块体。在区块头里面除了parentHash作为区块指针外，还有几个哈希值：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003estateRoot：其值是这个区块内所有交易执行后世界状态的哈希值。\u003c/li\u003e\n\u003cli\u003ereceiptsRoot：其值是这个区块所有交易的执行结果的哈希值。\u003c/li\u003e\n\u003cli\u003etransactionRoot：其值是这个区块所有交易的哈希值。\u003c/li\u003e\n\u003cli\u003eommersHash（UncleHash）： 是对区块体中的叔块列表的哈希。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e区块体中存放交易列表和叔块列表。\u003c/p\u003e\n\u003cp\u003e右侧的几棵merkle tree（世界状态，交易状态， 交易等），不会在区块中存储，而是会在节点的数据库中保存。 用区块中的哈希值来保证各个节点的merkle tree状态都一致。\u003c/p\u003e\n\u003ch2 id=\"7-参考资料\"\u003e7. 参考资料\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"https://hackernoon.com/getting-deep-into-ethereum-how-data-is-stored-in-ethereum-e3f669d96033\"\u003egetting-deep-into-ethereum-how-data-is-stored-in-ethereum\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/wanshan1024/ethereum_yellowpaper/blob/master/ethereum_yellow_paper_cn.pdf\"\u003e以太坊黄皮书\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/ethereum/wiki/wiki/%5B%E4%B8%AD%E6%96%87%5D-%E4%BB%A5%E5%A4%AA%E5%9D%8A%E7%99%BD%E7%9A%AE%E4%B9%A6#%E5%8C%BA%E5%9D%97%E9%93%BE%E5%92%8C%E6%8C%96%E7%9F%BF\"\u003e以太坊白皮书\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/MetaMask/IPFS-Ethereum-Hackathon/tree/master/slides/01_DanFinlay_intro_to_ethereum_blockchains\"\u003eDanFinlay-intro_to_ethereum\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e","title":"以太坊图解"},{"content":"《比特币白皮书》是一篇很短的论文， 只有12小段。 Satoshi Nakamoto发表于2008年10月31日。其中蕴含的思想在后来发展出了区块链。 白皮书很值得一看，是这个领域经典中的经典。这篇笔记从比特币白皮书中的视角开始展开，加入了一些对比特币网络相关实现细节的理解。\n1. 比特币和区块链原理 比特币方案的目标是要实现点对点的电子现金，允许在线支付直接从一方发送到另一方，无需通过金融机构作为中介。\n它要在一个点对点网络，去中心化的环境中解决如下几个问题：\n如何确定电子现金是谁的？你只能花自己的钱，不能花别人的。 如何防止双花（就是上面邮件中的Double Spending）？你的一笔钱只能被花一次，不能趁记账还没完成，同时发起两笔交易，把一笔钱花两次。 如何发行货币，如何驱动网络自发运转。 这些问题在有金融机构做中介时候是很容易解决的，例如：\n银行给客户开通账户，记录这个客户有多少钱，并给客户配置支付密码。 客户通过自己的密码在银行做认证，证明是本人后，支付全部或部分金额。 花钱的时候，银行会实时变更客户的账户余额。这样越花越少，不存在一笔钱花了两次的可能。 货币发行和整个系统运转都是通过金融机构。 在比特币系统中：\n针对第一个问题，使用传统的非对称加密机制解决。比特币系统中每一个账户都关联一个公钥，用户使用私钥签名自己的交易。系统通过验证交易的数字签名来确定交易的合法性。 针对第二个问题，比特币系统提出了一种新方法。把交易打包进区块，进行哈希摘要，按照摘要把区块链接成一个分布式的不断增长的链状结构，链状结构能让大家对账本里面的交易的先后顺序有共识（形成Single Source of Truth），如果前面有交易已经花过一笔钱了，后面的交易想要再花这同一笔钱就无法被大家认可，不能上链形成共识。这种机制在比特币白皮书中第一次被提出来，后来被称作区块链。区块打包上链的工作是所有参与者分布式竞争完成的，在比特币中，采用POW（工作量证明）做共识算法。关于POW，下文挖矿环节有详述。 针对第三个问题， 货币发行和整个交易系统的运行都是通过矿工的挖矿行为来驱动。 比特币使用公私钥系统来解决用户认证的问题。 这个方案具有很强的隐私保护能力。 举个极端的例子，一个人可以在小黑屋里，在完全离线的环境下，用笔和纸生成一对公私钥，然后用公钥导出一个比特币地址。 只要把这个地址（例如：1J7mdg5rbQyUHENYdx39AWISME7fsLpEoXZy）发布出去，他就可以接受汇款了。 入账后， 只有小黑屋里的这个人可以操作区块链上的那笔钱（因为只有他有该比特币账户对应的私钥）。如果刻意隐瞒，比特币网络上的钱很难追踪到对应的人。当然，隐私在比特币和法币的转换过程中有可能泄漏。\n上图示意比特币的区块链。图中Block2的Header里面，保存着Block 1 的Header的哈希值。 哈希值起到了指向链表中父节点指针的作用。 根据上链的先后顺序，Block2中的交易比Block1中的交易晚， Block3中的交易比Block2中的交易晚。 每一个区块（Block）中记录的交易放在区块体中， 对区块体中所有交易也做一次哈希，得到的值放到区块头里面（在Merkle Root域）。\n通过区块链，保证了交易的先后顺序，也保证了区块链中形成共识的交易不可篡改（因为交易的哈希也存在区块头中，改变交易的内容会导致区块头里面的Merkel Root哈希值对不上， 如果把Merkle Root的值也改了，又会导致区块头的哈希值变化，区块头哈希值变化就会让区块本身从链表里面移除）。\nThe network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU power. As long as a majority of CPU power is controlled by nodes that are not cooperating to attack the network, they\u0026rsquo;ll generate the longest chain and outpace attackers. \u0026mdash; 比特币白皮书\n以上是理论。 下面是实现细节。\n2. 比特币网络是如何运行的？ The steps to run the network are as follows:\nNew transactions are broadcast to all nodes. Each node collects new transactions into a block. Each node works on finding a difficult proof-of-work for its block. When a node finds a proof-of-work, it broadcasts the block to all nodes. Nodes accept the block only if all transactions in it are valid and not already spent. Nodes express their acceptance of the block by working on creating the next block in the chain, using the hash of the accepted block as the previous hash. \u0026mdash;- 比特币白皮书 2.1. 用户发起交易 当用户发起一笔转账交易时候，会把自己的公钥，目的账户地址，金额等信息打包，并进行签名。 得到一个打包好的交易。然后这个交易被广播到一个mempool里面。\n上图是全网mempool中实时待处理的交易个数变化。\n在参考资料2，可以实时查看比特币网络当前的mempool情况。 也可以查看当前所有链上区块的详情，以及所有待上链的交易详情。\n2.2. 矿工对交易进行记账 在mempool里面的交易，每一个比特币矿工都可以自由选择，去放到一个区块里面打包。 在选择的时候，需要验证交易的合法性（转出方币是不是够，签名对不对等）。矿工也会根据交易中的交易费来选择交易（给小费高的顾客会先得到服务员响应）。 矿工填满自己的区块后，就开始挖矿（计算哈希）。 挖矿是用穷举法，不断尝试调整区块头中的Nonce，然后对区块头做哈希运算，检查得到的区块头哈希值是否符合要求。\n系统对区块头的哈希值有要求（例如要求前面有连续若干个0）。大家竞争，谁先找到符合要求的哈希值，谁的区块就被加入链尾。\n通过调节难度来概率上控制全网能够在多长时间挖出一个区块，所谓难度，正比于找到符合要求哈希值的尝试次数的期望值。 比特币系统设置每10分钟产生一个区块，如果发现出的太快了，就增加难度，反之亦然。\n所有矿工都基于mempool里面的交易进行选择，打包，哈希穷举尝试， 当一个矿工成功找到Nonce值（使得当前尝试的区块头的哈希值满足要求），他会立刻向全网广播该区块。其他矿工在收到新区块后，会对新区块进行验证（检查这个区块里面的交易是不是都有效，有没有双花，也会检查区块的Merkle Root等数据），如果有效，就把它添加到区块链的尾部。同时会抛弃自己当前正在计算还没有算完的区块，转而开始计算下一个区块，进行下一轮工作量证明的竞争。\n矿工会在自己挖到的区块里面加一笔交易。 是挖矿收入（coinbase）。 每一个区块的奖励最初是50比特币，每4年减半。 目前是6.25比特币。比特币区块链的交易里面， 只有挖矿收入是没有地址来源的，挖矿收入交易每个区块里面有且仅有一笔。 本质上所有比特币最原始出处都是挖矿收入。挖矿就是货币发行。 由于区块中有coinbase交易，矿工找到符合要求的Nonce后，可以放心的把区块广播出去。其它矿工无法剽窃。 3. 主要数据结构 比特币系统的数据结构较简单，各个节点之间传递的数据就是区块。每一个区块的结构示意图如上。区块包括Header和Body，Header里面有：\nPrev Block：前一个区块的哈希值，用来链接不同的区块。 Merkle Root：该区块所有交易的哈希值。用来保证区块体的内容不被修改。 Time：unix 时间戳。 Bits： 难度系数，用来控制得到目标哈希值所需要的尝试次数的期望. Nonce：随机数，挖矿时候用来调整，以得到不同的哈希值。 Body（区块体）里面放的是一笔一笔的交易。上图中每一个黑色或绿色的横条都代表一笔交易。绿色那笔是挖矿收入交易。\n注意系统用Merkle Tree来对区块体中的所有交易做哈希。和普通的哈希算法，例如MD5，SHA256等不同， Merkle树可以在不知道所有区块体信息的情况下，查看一个交易是否在这个区块里面。\n比特币区块链是一个分布式数据库，其数据就是这些区块组成的一个单向链表。每一个节点都可以保存所有这些区块数据来获得整个比特币区块链的所有状态。 比特币网络的当前状态，就是每一个地址对应的余额。\n区块链中记录的交易，类似于Redis的AOF文件， 或者数据库的binlog文件。 其记录的是一条一条的交易。没有记录状态。 状态通过播放交易得到。\n┌ │ │ │ │ │ │ │ │ │ └ ─ B ┌ │ └ ─ ─ l ─ C ─ ─ ─ o ─ B ─ ─ ─ c ┬ │ ┴ ─ ─ k ─ 5 ─ ─ ─ ─ 0 ─ ─ ─ # ─ . ─ ─ ─ 1 ─ 0 ─ ─ ─ ┬ │ ┴ ─ ─ ─ O ─ ─ ─ ─ U ─ ─ ─ ─ T ─ ─ ─ ┐ ├ ┘ ─ ┐ │ │ ┼ │ │ │ │ │ │ ┘ ─ ─ ┐ │ │ │ └ ─ ─ ┌ │ │ │ │ │ │ \u0026gt; │ │ └ ─ B ┌ │ └ ┌ │ │ │ └ ─ ─ l ─ C ─ ─ I ─ ─ ─ o ─ B ─ ─ N ─ ─ ─ c ┬ │ ┴ ┬ │ ├ │ ┴ ─ ─ k ─ 5 ─ ─ 8 ─ 4 ─ ─ ─ ─ 0 ─ ─ . ─ 1 ─ ─ ─ # ─ . ─ ─ 7 ─ . ─ ─ ─ 2 ─ 0 ─ ─ 0 ─ 3 ─ ─ ─ ┬ │ ┴ ┬ │ ┼ │ ┴ ─ ─ ─ O ─ ─ O ─ O ─ ─ ─ ─ U ─ ─ U ─ U ─ ─ ─ ─ T ─ ─ T ─ T ─ ─ ─ ┐ ├ ┘ ┐ ├ ┤ ├ ┘ ─ ┐ │ │ ┼ │ │ ┼ │ ┼ │ ┘ ─ ─ ─ ─ ─ ┐ │ │ └ ┐ │ │ ┼ │ └ ─ ─ ─ ─ ─ ─ ─ ┌ │ │ │ │ │ \u0026gt; │ \u0026gt; │ └ ─ ─ B ┌ │ └ ┌ │ ├ │ └ ─ ─ ─ l ─ C ─ ─ I ─ I ─ ─ ─ ─ o ─ B ─ ─ N ─ N ─ ─ ─ ─ c ┬ │ ┴ ┬ │ ┤ │ ┴ ─ ─ ─ k ─ 5 ─ ─ 5 ─ ─ ─ ─ ─ 0 ─ ─ 8 ─ ─ ─ ─ # ─ . ─ ─ . ─ ─ ─ ─ 3 ─ 0 ─ ─ 7 ─ ─ ─ ─ ┬ │ ┴ ┬ │ │ │ ┴ ─ ─ ─ ─ O ─ ─ O ─ ─ ─ ─ ─ U ─ ─ U ─ ─ ─ ─ ─ T ─ ─ T ─ ─ ─ ─ ┐ ├ ┘ ┐ │ │ │ ┘ ─ ─ ┐ │ │ ┼ │ │ │ │ │ │ ┘ ─ ─ ─ ─ ─ ┐ │ │ └ ┌ │ │ ┘ ─ ─ ─ ─ ┌ │ │ │ │ │ \u0026gt; │ \u0026gt; │ └ ─ B ┌ │ └ ┌ │ ├ │ └ ─ ─ l ─ C ─ ─ I ─ I ─ ─ ─ o ─ B ─ ─ N ─ N ─ ─ ─ c ┬ │ ┴ ┬ │ ┼ │ ┴ ─ ─ k ─ 5 ─ ─ 2 ─ 6 ─ ─ ─ ─ 0 ─ ─ 5 ─ 6 ─ ─ ─ # ─ . ─ ─ . ─ . ─ ─ ─ 4 ─ 0 ─ ─ 0 ─ 3 ─ ─ ─ ┬ │ ┴ ┬ │ ┼ │ ┴ ─ ─ ─ O ─ ─ O ─ O ─ ─ ─ ─ U ─ ─ U ─ U ─ ─ ─ ─ T ─ ─ T ─ T ─ ─ ─ ┐ │ ┘ ┐ │ ┤ │ ┘ ─ ┐ │ │ │ │ │ │ │ │ │ ┘ 比特币的所有交易，在逻辑上也可以通过账号地址跨区块串联起来。 根节点是矿工奖励的交易。叶子结点，也叫UTXO（Unspent Transaction Output，没有被花费的交易输出）。 中间非根非叶子的账户表示这笔钱曾经被他们拥有过。可以依据交易单实现对资金的全程追溯。这也是比特币的典型特征之一。\n计算一个钱包里面有多少比特币，看这个钱包的比特币地址里面，有哪些是UTXO地址，这些地址里面的比特币之和是多少。\n4. 其它 4.1. POW 算力和 51% 攻击 比特币网络出现恶意攻击者时会发生什么？因为比特币的密码学基础是非常安全的，所以攻击者会选择攻击没有被密码学直接保护的部分：交易顺序。攻击者的策略非常简单：\n向卖家发送100BTC购买商品（尤其是无需邮寄的电子商品）。 等待直至商品发出。 创建另一笔交易，将相同的100BTC发送给自己的账户。 使比特币网络相信发送给自己账户的交易是最先发出的。 一旦（1）发生，几分钟后矿工将把这笔交易打包到区块，假设是第270000个区块。大约一个小时以后，在此区块后面将会有五个区块，每个区块间接地指向这笔交易，从而确认这笔交易。这时卖家收到货款，并向买家发货。因为我们假设这是数字商品，攻击者可以即时收到货。现在，攻击者创建另一笔交易，将相同的100BTC发送到自己的账户。如果攻击者只是向全网广播这一消息，这一笔交易不会被处理。矿工会运行状态转换函数APPLY(S,TX)，发现这笔交易将花费已经不在状态中的UTXO。所以，攻击者会对区块链进行分叉，将第269999个区块作为父区块重新生成第270000个区块，在此区块中用新的交易取代旧的交易。因为区块数据是不同的，这要求重新进行工作量证明。另外，因为攻击者生成的新的第270000个区块有不同的哈希，所以原来的第270001到第270005的区块不指向它，因此原有的区块链和攻击者的新区块是完全分离的。在发生区块链分叉时，区块链长的分支被认为是诚实的区块链，合法的的矿工将会沿着原有的第270005区块后挖矿，只有攻击者一人在新的第270000区块后挖矿。攻击者为了使得他的区块链最长，他需要拥有比除了他以外的全网更多的算力来追赶（即51%攻击）。\n4.2. 拜占庭将军问题 这个故事是说有一群将军围攻拜占庭（伊斯坦布尔），拜占庭很强大，只有全部出动才能攻陷，否则攻城的部队会被歼灭。将军们商定少数服从多数，一起进攻或者撤退。但是将军们分散在四周围城，通信不便。将军之中还有叛将，会向不同的其它将军发出各种假消息导致大家行动不一致。\n现代分布式系统也有类似问题，全网多台服务器节点进行协同合作，最终要共同维护一套数据。无法保证节点诚实（一个不诚实的节点会向其它节点发出不同的信息），也无法保证系统内部信息统一，节点无从判断信息真伪。\n用比特币系统基于POW的方式，对这个问题的解决办法是：把将军们看成一个个矿工。假设每一支队伍算力相同，则所有将军在每10分钟之内，有一个人会产生一次说话的机会（告诉其它将军自己是进攻还是撤退）。 这样若干个10分钟之后，所有将军的消息就组成了一个区块链。 区块链机制可以保障每个将军能且仅能表达一次（否则双花）。将军们可以通过这种机制达成共识。\n4.3. 全节点，轻节点，SPV节点 比特币网络最初，所有节点都处理所有区块，存储所有数据。后来由于网络规模不断扩展，对节点的处理和存储能力也不断提出新的要求。 出现了轻节点和SPV节点。\n全节点是指维持包含全部交易信息的完整区块链的节点。全节点可以验证交易是否合法。 轻节点指的是：节点本地只保存与其自身相关的交易数据（尤其是可支配交易数据）。轻节点也需要处理所有区块，但不用保存所有区块链信息。 SPV节点是只能验证某个支付是否真实存在，并得到了多少个确认的节点。 SPV节点本地不需要保存区块链数据，也可以验证交易是否成功，验证方法如下：\n计算待验证支付的交易哈希值； 节点从区块链网络上获取并存储最长链的所有区块头至本地； 节点从区块链获取待验证支付对应的默克尔树哈希认证路径；（这里找到了该交易对应的哈希值） 根据哈希认证路径，计算默克尔树的根哈希值，将计算结果与本地区块头中的默克尔树的根哈希值进行比较，定位到包含待验证支付的区块；（找到这个哈希值属于哪个区块） 根据该区块头所处的位置，验证该区块的区块头是否已经包含在已知最长链中，确定该支付已经得到的确认数量，如果包含则证明支付真实有效。（证明本交易得到了6次确认） 以上第三步和第四步，用到了Merkle Tree的特性， 不需要知道完整区块体，也可以验证一笔交易是否在区块里面。\n一般手机钱包等计算资源比较弱的设备可以做SPV节点。当别人给你转钱时候，你只需要知道这笔交易上链了没有，后面有没有足够长的区块确保交易成功。\n4.4. 比特币网络的处理能力 比特币网络的TPS（每秒钟可以处理的交易个数）大约是5， 这个值可以通过三个参数算出来。\n每10分钟产生一个区块； 每个区块的大小； 每个交易的平均大小。 5 TPS是一个比较小的数（可以对比VISA网络，每秒处理数千笔交易）。 提高吞吐量的方法有缩短区块产生的时间，增加区块大小等。 也有一些尝试用侧链或者链下交易（例如：Lightning）解决吞吐量的方案。\n4.5. 软分叉和硬分叉 分叉是因为共识规则变化导致的。 新的共识规则可能是原有规则的超级，交集或者子集。\n硬分叉的定义是扩宽共识规则，允许做之前禁止的事情（超级或者交集情况），以前无效的交易/区块在硬分叉后会变成有效的； 软分叉是收紧共识规则，禁止之前允许做的事情（子集情况），以前有效的交易在软分叉后可能无效了。 分叉之后，网络中有一部分节点会升级支持新规则。\n在软分叉场景下， 新规则产生的区块也会被之前的老节点（没有升级的节点）承认。 在硬分叉场景下，新规则产生的区块有可能被老节点认为非法。会分叉产生两条不再有交集的链，完全按各自的道路发展。 如果在分叉前持有代币，硬分叉之后，代币会变成两份，新老区块链分别可以消费。产生这种现象的原因是硬分叉之后，变成了两个网络，在一个网络里面的交易只会导致该网络的状态变化（网络的当前状态，也就是比特币网络里面的余额，由区块链的最后一个区块定义）。\n无论是软分叉还是硬分叉，新版本软件必定需要能够正确处理老版本软件产生的老区块数据。\n5. 参考资料 Bitcoin: A Peer-to-Peer Electronic Cash System Blockchian explorer 区块链教程 以太坊白皮书 Lightning Network ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-04-03-blockchain/","summary":"\u003cp\u003e《比特币白皮书》是一篇很短的论文， 只有12小段。 Satoshi Nakamoto发表于2008年10月31日。其中蕴含的思想在后来发展出了区块链。 白皮书很值得一看，是这个领域经典中的经典。这篇笔记从比特币白皮书中的视角开始展开，加入了一些对比特币网络相关实现细节的理解。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 24\" loading=\"lazy\" src=\"/images/1648889277551.jpg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"1-比特币和区块链原理\"\u003e1. 比特币和区块链原理\u003c/h2\u003e\n\u003cp\u003e比特币方案的目标是要实现点对点的电子现金，允许在线支付直接从一方发送到另一方，无需通过金融机构作为中介。\u003c/p\u003e\n\u003cp\u003e它要在一个点对点网络，去中心化的环境中解决如下几个问题：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e如何确定电子现金是谁的？你只能花自己的钱，不能花别人的。\u003c/li\u003e\n\u003cli\u003e如何防止双花（就是上面邮件中的Double Spending）？你的一笔钱只能被花一次，不能趁记账还没完成，同时发起两笔交易，把一笔钱花两次。\u003c/li\u003e\n\u003cli\u003e如何发行货币，如何驱动网络自发运转。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e这些问题在有金融机构做中介时候是很容易解决的，例如：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e银行给客户开通账户，记录这个客户有多少钱，并给客户配置支付密码。 客户通过自己的密码在银行做认证，证明是本人后，支付全部或部分金额。\u003c/li\u003e\n\u003cli\u003e花钱的时候，银行会实时变更客户的账户余额。这样越花越少，不存在一笔钱花了两次的可能。\u003c/li\u003e\n\u003cli\u003e货币发行和整个系统运转都是通过金融机构。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e在比特币系统中：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e针对第一个问题，使用传统的非对称加密机制解决。比特币系统中每一个账户都关联一个公钥，用户使用私钥签名自己的交易。系统通过验证交易的数字签名来确定交易的合法性。\u003c/li\u003e\n\u003cli\u003e针对第二个问题，比特币系统提出了一种新方法。把交易打包进区块，进行哈希摘要，按照摘要把区块链接成一个分布式的不断增长的链状结构，链状结构能让大家对账本里面的交易的先后顺序有共识（形成Single Source of Truth），如果前面有交易已经花过一笔钱了，后面的交易想要再花这同一笔钱就无法被大家认可，不能上链形成共识。这种机制在比特币白皮书中第一次被提出来，后来被称作区块链。区块打包上链的工作是所有参与者分布式竞争完成的，在比特币中，采用POW（工作量证明）做共识算法。关于POW，下文挖矿环节有详述。\u003c/li\u003e\n\u003cli\u003e针对第三个问题， 货币发行和整个交易系统的运行都是通过矿工的挖矿行为来驱动。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cblockquote\u003e\n\u003cp\u003e比特币使用公私钥系统来解决用户认证的问题。 这个方案具有很强的隐私保护能力。 举个极端的例子，一个人可以在小黑屋里，在完全离线的环境下，用笔和纸生成一对公私钥，然后用公钥导出一个比特币地址。 只要把这个地址（例如：1J7mdg5rbQyUHENYdx39AWISME7fsLpEoXZy）发布出去，他就可以接受汇款了。 入账后， 只有小黑屋里的这个人可以操作区块链上的那笔钱（因为只有他有该比特币账户对应的私钥）。如果刻意隐瞒，比特币网络上的钱很难追踪到对应的人。当然，隐私在比特币和法币的转换过程中有可能泄漏。\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e\u003cimg alt=\"picture 22\" loading=\"lazy\" src=\"/images/1648799605062.png\"\u003e\u003c/p\u003e\n\u003cp\u003e上图示意比特币的区块链。图中Block2的Header里面，保存着Block 1 的Header的哈希值。 哈希值起到了指向链表中父节点指针的作用。 根据上链的先后顺序，Block2中的交易比Block1中的交易晚， Block3中的交易比Block2中的交易晚。 每一个区块（Block）中记录的交易放在区块体中， 对区块体中所有交易也做一次哈希，得到的值放到区块头里面（在Merkle Root域）。\u003c/p\u003e\n\u003cp\u003e通过区块链，保证了交易的先后顺序，也保证了区块链中形成共识的交易不可篡改（因为交易的哈希也存在区块头中，改变交易的内容会导致区块头里面的Merkel Root哈希值对不上， 如果把Merkle Root的值也改了，又会导致区块头的哈希值变化，区块头哈希值变化就会让区块本身从链表里面移除）。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU power. As long as a majority of CPU power is controlled by nodes that are not cooperating to attack the network, they\u0026rsquo;ll generate the longest chain and outpace attackers.    \u0026mdash; 比特币白皮书\u003c/p\u003e","title":"比特币和区块链"},{"content":" 什么是NFT？什么是DAO？DeFi？GameFi？SocialFI？ 区块链钱包里放的是什么？ Web 3对于公司有什么机会？ 对于程序员有什么机会？ 美国为啥能block俄罗斯用户的加密币交易？ 1. Web1， Web2，Web3 web1 主要应用是做个网站发布信息。 web2 除了web1之外多了交互， 信息的消费者也产生内容，并且被平台消费（看广告给平台产生收入）。 web3 结合了web1的去中心化和社区管理，以及web2的丰富交互功能， 最大特点是用户和开发者可以拥有内容。web3是代币驱动的。 根据 Chis Dixon @ a16z 的描述， web3是开发者和用户共同拥有的网络，由代币来驱动。\nWeb1 (roughly 1990-2005) was about open protocols that were decentralized and community-governed. Most of the value accrued to the edges of the network — users and builders. Web2 (roughly 2005-2020) was about siloed, centralized services run by corporations. Most of the value accrued to a handful of companies like Google, Apple, Amazon, and Facebook. Web3 is the internet owned by the builders and users, orchestrated with tokens. \u0026hellip; combines the decentralized, community-governed ethos of web1 with the advanced, modern functionality of web2. 上图描述web2的主要问题，是资源的中心节点（google, apple, amazon, fb 等）追求利益的本质决定的。前期让利给用户和合作伙伴， 后期消费用户的数据，并且和合作伙伴竞争。\n从网络结构上， web3是点对点的，去中心的。\n2. 核心概念 上图对web3中的主要概念和实体经济中主要概念做了类比。\n现实生活中， 有五类实体在经济活动中发挥作用。\n钱： 没有钱，交易所就没有流动性，我们将不得不依赖效率低得多的易货贸易。 生产资料（土地，厂房，工厂，机器等）：没有生产资料，就没有商品。 商品： 没有商品，就没有经济。 交换机制（电商，零售店，股票交易所等）： 没有交换机制，商品很难交易，分工也就不可能了。 机构（政府银行公司等）： 没有机构，经济中不同参与者之间的协调与合作将很困难。 在互联网原生经济活动（web3）中，有如下一些对应的基本概念：\n加密币：Token / Crypto 智能合约：Smart Contract 非同质代币：NFT 交易所：Exchanges 去中心化自治组织：DAO 2.1. Token and Crypto Token，令牌。 是一个字符串。令牌是标记资产所有者的记录（A token is a record of ownership of an asset）。\n有fungible（中文意思是可替代的）和non-fungible两种。 fungible token可以是加密币，或者现实生活中的钱。 你的1个比特币和我的1个比特币是价值相等的，可替代的。\nnon-fungible token 就是NFT。\n上图从tangible和fungible两个维度划分现实世界和虚拟世界的资产。\n2.2. Smart Contract 智能合约 公布天下，让大家都知道的合约。设定好的执行条件，条件具备时候自动执行。智能合约在公共区块链上运行，是与加密代币原生集成的开源代码。 允许开发人员构建跨游戏、社交、金融服务等的复杂应用。 智能合约源码公开，执行的结果也记录在区块链上。\n智能合约用solidity编写，是以太坊应用。语法类似JavaScript，网上有开发教程。\nSolidity is a statically-typed curly-braces programming language designed for developing smart contracts that run on Ethereum.\n2.3. NFT 是Non-fungible tokens缩写。中文翻译为非同质化代币， 是所有权认证。 具有不可替代，不可分割，交易历史透明的特点。\n主要解决资产认证的问题。 目前现实世界都是中心化的组织做资产认证。例如国家用房产证认证你的房产， 游戏公司用数据库里面的记录认证你在游戏中拥有的装备。 用NFT是一种去中心化的方式， 资产可以被认证，被交易。\nA way to represent anything unique as an Ethereum-based asset. NFTs are giving more power to content creators than ever before. Powered by smart contracts on the Ethereum blockchain. 上述定义出自ethereum，实际上支持智能合约就可以支持发布NFT。\nNFT基于智能合约技术，可以设置资产的转移价格，可以在发布时候，设置每一次资产转移交易，都给内容的创作者付一定比例的版税。\nNFT是对内容的认证。参与NFT需要有：\n钱包（账户，所有权人） 加密货币（支付gas，认证费）： 可以从交易所购买ETH得到 发行，交易平台 目前NFT交易主要在OpenSea上进行， OpenSea是最大的market。\n为什么人们愿意用钱去买一个数字化的小图片的所有权？\n买到了一个艺术品的NFT之后， 其它人只要能看到这个艺术品，也是可以用数字手段拍照，克隆的。 NFT无法禁止复制。但是NFT可以证明你是所有者。在虚拟世界里，只有你是这个图片的所有者， 只有你可以合法的把这个图片挂在你虚拟的家里， 或者用在你自己的logo上。 有稀缺性，有需求，就会有购买的欲望。买卖的核心就是所有权转移。 上图是一个NFT的例子。 这里对应的资产（图片）是存储在外部服务器上的。 也有一些NFT会把资产直接存储在链上。\n2.4. Exchanges 交易所有两种， 中心化的和去中心化的。 中心化的类似币安，coinbase等。 受所在国家监管。\n去中心化的交易所有几个代表：\nUniswap（一种加密交易协议） OpenSea（NFT 的点对点市场） 它们类似股票交易所、零售商店或电子商务对传统经济的影响一样。\n在股票交易所可以买卖传统股票，在Uniswap可以交易加密货币（不需要通过Coinbase或 Gemini，币安这样的中心化中介）。 在沃尔玛或在亚马逊，eBay可以购买实体商品，在OpenSea上可以购买加密原生数字商品。 2.5. DAO DAO是去中心化的机构，传统公司的去中心化版本，允许参与项目的人拥有它并使用智能合约做出集体决策，是一种项目融资、管理社区和分享价值的新方式。\n传统公司拥有生产资料（工厂）并生产传统经济的商品（食品、汽车），DAO最终拥有生产资料并在web3的生态中生产数字商品。\nDAO: Decentralized Autonomous Organization 一种组织形式。类似公司。比传统公司更加透明，因为任何人都可以查看DAO中的所有行动和资金流向。这大大降低了腐败和审查的风险。上市公司必须提供经独立审计的财务报表，但股东只能看到该组织的财务健康状况的一个快照。由于DAO的资产负债表存在于公共区块链上，因此它在任何时候都是完全透明的，精确到每一笔交易。\nbankless是一个目前运行比较好的DAO，为没有银行卡的人提供银行服务。discord上有周例会。有目标，有激励。\nodysseydao 对DAO和现实世界的公司做了比较：\n组织结构 公司：由 CEO 和其他高管领导的层级结构。 DAO：由一群核心贡献者领导的扁平结构。 做决定 公司：管理层批准提案并闭门做出决定。 DAO：成员使用 DAO 代币公开提交提案并对其进行投票。 入职 公司：候选人需要申请并通过面试才能被录用。 DAO：一些 DAO 允许任何人加入，而另一些则需要最少数量的代币。 代币可以通过为 DAO 做贡献来购买或赚取（例如，做会议记录）。 职业发展 公司：员工需要不断成长升职得到晋升。 DAO：成员通过贡献，获得核心贡献者的信任。 报酬 公司：大部分员工全职工作，通过工资和股票获得报酬。 DAO：大多数贡献者都是兼职工作，并通过 DAO 代币或其他加密货币（例如稳定币）获得报酬。 3. 钱包和账户 在web 3实践中， 钱包也是一个比较重要的概念，和地址，账户等容易混淆。\n3.1. 钱包，地址和签名 一个区块链钱包类似一个物理世界的卡包。 里面可以存放多张不同银行发行的银行卡（不同链上的账户）。 每一个账户（合约账户例外）本质上由非对称密钥系统进行保护。 公钥用来公开接收信息， 私钥用来授权签名。\n如果我使用某一账户给某另外一个地址做转账， 需要把转账信息用这个转出账户的私钥做签名，然后再附上转出账户的公钥发出。系统用公钥和签名就可以判断是不是我（账户所有者）认可的转账。\n助记词是为了方便记录私钥引入的概念。 bip39规范定义了如何从助记词产生私钥。 公钥可以从私钥计算得出， 地址是为了方便使用公钥引入的概念。 地址一般通过对公钥进行哈希等运算得出。\n所以有了助记词就等价于有了私钥。有了私钥就有了一切。 可以对私钥计算得到公钥，再通过公钥计算得到地址。\n不同链的地址格式有可能不同。以太坊是0x开头，比特币地址格式还有不同的写法（1开头，3开头，bc1开头\u0026hellip;). 一个钱包可以用相同的一组助记词为不同的链生成不同的公私钥。这样只需要记忆一组助记词，就可以方便的拥有多个不同链上的账户。\n区块链钱包中的概念 现实世界银行账户概念 私钥 银行卡密码＋银行卡 助记词（简化的私钥） 简单化的银行卡密码 公钥 银行账号 地址 银行卡号 Keystore 加密的私钥 钱包 银行卡包 钱包从不同维度看有多种分类方式：\n中心化钱包（交易所发行的，需要对持有人KYC）和 去中心化钱包 冷钱包（不联网）和 热钱包（app） 3.2. 账户 比特币没有账户余额的概念，区块链里面记录的是一笔一笔的交易。 通过历史交易回溯来得到余额。\n以太坊有两种账户：\n外部账户（externally owned accounts），由密钥控制。 合约账户（contract accounts），由智能合约的代码控制。 外部账户包括：\n一个随机数： 实际上是序列号，每次交易加一，防止重放攻击。 账户的余额 合约账户在外部账户基础上增加了：\n合约代码：其中存储的是evm编译后的合约代码。生成后是不可修改的，这意味着智能合约代码是不可修改的。 存储 和外部账户一样，合约账户也有地址。以太坊中的全节点会记录所有账户的状态（包括余额）。\n交易可以看作是从一个账户发送到另一个账户的消息，它可以包含二进制数据（ payload ）和 以太币 如果目标账户含有代码，代码会在 EVM 中执行，并以 payload 作为入参，这就是合约的调用 合约被交易触发调用时，指令会在全网的每个节点上执行：这需要消耗算力成本；每一个指令的执行都有特定的消耗，gas 就用了该量化表示这个成本消耗。 一经创建，每笔交易都按照一定数量的 gas 预付一笔费用，目的是限制执行交易所需要的工作量和为交易支付手续费 4. Apps 应用 主要有几个方向\nDeFi: Decentralized Finance GameFi(GaFi): Game Finance SocailFi: Social Finance DeFi (decentralized finance) refers to financial services that run on smart contracts instead of relying on middlemen such as banks or exchanges.\n实际上， 现有的Web2应用，都有机会向Web3演进。 例如，我们要做一个知乎的web 3改进版本。 改进后和目前的web2应用有什么区别？\n广告价值可以分给内容创建者。 展现和排名会由大家投票决定，而不是由编辑决定。 收入是平台的代币。 利益分配和治理结构会有改变。例如引入智能合约，发布后，知乎自己也不能变更了，更公平。 5. 生态环境 5.1. 在哪一条链上开发？ 当前有两类主流的链：\n以太坊： 类似iOS， 生态最丰富。 cosmos： 类似Android 大部分围绕以太坊开展， 或者是以太坊兼容链。 NFT，各种Dao组织，都是基于以太坊。 国内有不少币安链（BSC）用户，币安链依赖币安交易所。是复制的以太坊。 兼容以太坊语言，以太坊程序可以很方便移植到BSC。 比特币最初也有一个脚本语言，但能力比较弱。比特币的链基本上是一个去中心化的账本。\nBSC号称国人韭菜链。相对便宜。 以太坊：测试gas费用高。20USD-100+USD。 开发过程中，可以在BSC/以太上申请测试链。\n现在基建还是早期，未必后续会象手机操作系统一样让iOS，Android一统江湖。后续也许会出现多个链同步运行的情况。\n5.2. 开发工具 前端： solidity（智能合约） 链，基础设施： 主要是golang，rust，cpp等（链上的底层算法），Python也有。 web3.js 是一组使用HTTP或IPC连接来和本地或远程以太坊节点进行交互的库。\n智能合约的实现方式需要考虑：\n每一笔交易需要的gas费用 安全性 6. 机遇和挑战 6.1. 挑战 环境和生态：目前在国内没有大家形成一致认可的公链。 各个大的巨头都有自己的链， 但本质上都是克隆以太坊，运行到自己的IDC里面。 没有去中心化。需要具有公信力的区块链公链， 并且能够经济，快速环保的处理链上的交易。 技术：基于POW的方式，造成计算量大不环保。交易速度降低。 法律法规： 由于去中心化和监管本身有冲突。 目前还没有很好的法律法规，监管政策。 给未来带来不确定性风险。 6.2. 对于公司的机遇 目前是web 3起步阶段\n有很多web2的应用会逐步向web3移植 基础设施方面，群雄割据 6.3. 对于开发者的机遇 智能合同开发， 区块链相关开发都是比较火的领域。 也有很多新型的DAO组织，按劳分配。 可以加入热门开源项目的DAO, 有机会获得一些空投，通过参与贡献获得代币。 6.4. 对于投资（投机）者的机遇 投资加密币， NFT作品，一夜暴富故事在youtube上很多。\n7. FAQ 7.1. 什么是 mint？ 铸造，是艺术家或收藏家（项目方与用户均可）最初在区块链上发行NFT的行为。就是在区块链上记载了一个token的ID和其拥有者的地址。\n7.2. 什么是 metaverse 元宇宙？ 不同机构有不同定义，有人将其看作web3的终极版本。 也有人仅将其看作增强虚拟现实。\n7.3. 美国为什么能制裁俄罗斯的比特币投资？ 相关报道：\n近日，美国最大的加密货币交易所 Coinbase 表示，它已经屏蔽了超过25000个与俄罗斯个人或实体有关的钱包地址，原因是认为这些个人或实体参与了非法活动。根据 2021年数据，相关被屏蔽钱包地址占 Coinbase 的140万月交易用户的0.2%。此外，Coinbase 还与美国政府分享了这些钱包地址，以帮助其执行制裁措施，包括禁止一些俄罗斯人和公司进行金融交易。\n原因是\nCoinbase是受美国监管的中心化的交易所，做KYC，可以阻隔俄罗斯用户 同理，币安也是中心化的交易所， 对客户做KYC，可以阻隔中国用户 从报道看， coinbase 可以通过自己的KYC资料识别到俄罗斯客户， 然后禁止该类客户使用coinbase服务。 “屏蔽了\u0026hellip;.钱包地址“意味着通过coinbase不能向这些钱包地址转入转出crypto。 但该钱包地址的资产还是存在的。还可以用去中心化的方式进行交易，或者在其它交易所交易。\n7.4. 比特币总数目是如何估算出来的？ 按照每个上链的区块奖励的比特币个数推算。 每10分钟产生一个区块，每个区块最初奖励50个BTC，4年后奖励25个， 每四年减半。 所以可以计算出总共大约2100万个。\n挖矿后期每一个区块的奖励越来越少， 但是矿工还是可以从区块内部的每一笔交易中得到抽成。所以还是会有激励去挖矿。\n","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-27-web3/","summary":"\u003cblockquote\u003e\n\u003cul\u003e\n\u003cli\u003e什么是NFT？什么是DAO？DeFi？GameFi？SocialFI？\u003c/li\u003e\n\u003cli\u003e区块链钱包里放的是什么？\u003c/li\u003e\n\u003cli\u003eWeb 3对于公司有什么机会？ 对于程序员有什么机会？\u003c/li\u003e\n\u003cli\u003e美国为啥能block俄罗斯用户的加密币交易？\u003c/li\u003e\n\u003c/ul\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"1-web1-web2web3\"\u003e1. Web1， Web2，Web3\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eweb1 主要应用是做个网站发布信息。\u003c/li\u003e\n\u003cli\u003eweb2 除了web1之外多了交互， 信息的消费者也产生内容，并且被平台消费（看广告给平台产生收入）。\u003c/li\u003e\n\u003cli\u003eweb3 结合了web1的去中心化和社区管理，以及web2的丰富交互功能， 最大特点是用户和开发者可以拥有内容。web3是代币驱动的。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e根据 \u003ca href=\"https://www.alexdphan.com/research/library-of-web3/web3\"\u003eChis Dixon @ a16z \u003c/a\u003e 的描述， web3是开发者和用户共同拥有的网络，由代币来驱动。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cul\u003e\n\u003cli\u003eWeb1 (roughly 1990-2005) was about open protocols that were decentralized and community-governed. Most of the value accrued to the edges of the network — users and builders.\u003c/li\u003e\n\u003cli\u003eWeb2 (roughly 2005-2020) was about siloed, centralized services run by corporations. Most of the value accrued to a handful of companies like Google, Apple, Amazon, and Facebook.\u003c/li\u003e\n\u003cli\u003eWeb3 is the internet owned by the builders and users, orchestrated with tokens.  \u0026hellip; combines the decentralized, community-governed ethos of web1 with the advanced, modern functionality of web2.\u003c/li\u003e\n\u003c/ul\u003e\u003c/blockquote\u003e\n\u003cp\u003e\u003cimg alt=\"picture 10\" loading=\"lazy\" src=\"/images/1648360651741.jpg\"\u003e\u003c/p\u003e","title":"关于Web 3.0"},{"content":" 图摘录自《底层逻辑：看清这个世界的底牌》一书。 对图的解释夹杂我自己的理解。\n1. 是非对错的底层逻辑 1.1. 不要纠结 “成年人的世界不分对错，只讲利益”。 当执着于对错的时候, 本身就已经搞错方向了。不要纠结，不要纠缠。一笑了之。\n法学家和经济学家是职业，为了学术研究对错。 一般人在生活中，是经济学家研究的对象，是经济学上的人，是商人。 商人如果用法学家/经济学家的视角去分析问题，就太学究了。\n2. 思考问题的底层逻辑 2.1. 要素和关系 这个图是说系统的组成。 包括要素（一些列的名词概念），和各个要素之间的关系。\n举个例子，商业系统中有产品，流量，转化，留存，利润等等要素。\n因果链： 产品体验好， 留存增加 增强回路： 产品体验好，留存增加，利润增加，研发投入加大，产品体验更好\u0026hellip;, 或者反向增强：产品体验不好 调节回路：流量增加到一定程度，产品体验变差 滞后效应： 流量增加到留存增加中间有滞后（转化的过程） 2.2. 逻辑推导 这个图是说，证明事实证有不证无，证有时候用举例。 表达观点，不要以偏概全。得出一般性结论只能靠证明、推理，而不能随便说，“这难道不是共识吗？”“所有人都认为……”。\n2.3. 看到本质 这个图是说，要抓住本质。离写字楼近的饭馆生意好， 本质是流量，离得近流量大。 当大家都开始点外卖时候，流量变了。相应的竞争策略也需要调整。 抓不住本质，当流量变化的时候，一味在饭菜口味上，在服务上下功夫，是没有太大作用的。\n书中有一个微软管理供应商的例子。\n公司提供午餐和晚餐给员工。午餐吃饭的人数一般比晚餐要多，因为不是人人晚上都要加班，所以做午餐的供应商利润更高。只不过有时候，午餐做的很糟糕……试想一下，如果你是负责人，你要怎么处理？派人监督？隔一段时间换个大厨？要求他们更新菜谱？ 微软想出了一个制度：\n选2家供应商，一家提供午饭、另一家提供晚饭； 每3个月做满意度调查，是喜欢午饭还是晚饭？ 如果喜欢晚饭的多，午饭、晚饭供应商交换； 如果连续6个月，午餐都胜出的话，更换晚饭供应商。 这个制度的核心是引入了午餐供应商和晚餐供应商的竞争机制。\n3. 个体进化的底层逻辑 3.1. 能力，效率和杠杆 这个图说人生的商业模式成功由三个因素相乘的积决定。\n能力： 要有时间和精力投入， 而且要有好的方法（刻意练习）。 效率： 要管理精力，有取舍，并且有好的工具。 杠杆： 借助团队的力量，科技的力量，资本的力量和个人影响力来放大。 尤其提到了个人影响力中，写作和演讲的能力很重要。\n3.2. 放低姿态 这个图是说和人交流要放低姿态。 这样才容易获得其它人的帮助。 自己对自己要有客观评价，不需要太在意别人的看法。\n4. 理解他人的底层逻辑 4.1. 多巴胺，内啡肽和血清素 这个图讲生理上人有三种愉悦来源：\n多巴胺： 满足带来的舒服 内啡肽： 运动补偿 血清素： 碳水，糖带来的安全感 对应到商业世界，可以设计相应的奖励机制。比如：\n比较优势：微信的步数排行，你的开机速度战胜了90%的用户，给予用户成就感。 炫耀： 手环app的分享到朋友圈功能， LV上的大LOGO 多巴胺是人类的一种奖赏系统；它并不直接产生快乐，而是让人类对于奖赏有一种预期，我们也可以将其称之为上瘾。人类一但对某种事情上瘾，就会情不自禁地去复刻这件事情，获得快感。在产品中将用户的预期可视化，用多样的方式呈现在用户的眼前，就能够刺激用户产生快感。典型的例子就是购物车。\n内啡肽是能够让你获得成就感的物质，内啡肽的产生是非常吝啬的，它需要非常多的心血和努力才有可能产生，才会给予成就感。运用到产品上的话，也就是需要给用户建立成就体系，其中有三点要点：\n给用户设置一些挑战； 在产品中强化展示这些成就感； 把某些现实中的成就利用起来，将其投射到产品中。 多巴胺是指人们满足自己需要时产生的一种物质，一般对情绪影响比较重大。而内啡肽是“先苦后甜”型物质，一般是人身体经历疼痛后会产生，不常运动的人比较容易产生。\n虽然内啡肽和血清素都有促进情绪作用，但血清素产生的影响较小，会导致快乐和安全感。另一方面，内啡肽形成的愉悦感更强烈，取决于血流中的内啡肽数量，会产生类似于狂喜和幸福的感觉。内啡肽在低水平时也能产生轻微的放松和快乐感，这与血清素的效果有些类似。\n血清素的分泌受摄入食物影响很大，并可以由基于碳水化合物的食物（如面食和谷物）诱发。吃这些淀粉后释放血清素能产生镇静，舒适和健康感。在过量摄入碳水化合物后，太多血清素会被释放出来并让吃者产生昏昏欲睡的感觉。然而，在缺乏血清素的情况下，可以导致压力，焦虑，挫折感和侵略性，这一点与内啡肽不足有些类似。虽然有些药物能诱发血清素分泌，但很容易上瘾。这也是身体在血清素水平低时渴望吃淀粉食物或药物的原因所在。\n5. 社会协作的底层逻辑 5.1. 事业和短期利益 这个图讲员工和企业之间的关系。横坐标是员工对长期利益（事业）和短期利益的实际看法。纵坐标是公司对员工的期待。\n第一象限，大多是创业期公司， 公司有梦想，但招到的员工大多看中工短期回报。 第三象限，公司已经成熟，对于有野心的员工就没有太大吸引力。 第二象限，例如家政公司。 “别给我谈美化城市，请按小时结账”。 第四象限，员工认同目前所处的行业，以及公司的状态是有前景的，愿意和公司一同奋斗。包括放弃短期利益。 不同类型公司，需要清晰定位自己，要在第二象限还是第四象限经营。\n5.2. 利润的来源 这个图讲利润的三个组成部分：\n趋势红利不会持久，随着跟随者的进入逐步消失。 社会工资一般很薄，是辛苦钱。 只有创新利润是有机会赚钱的。 这个图也和整个财富分配的原理呼应。 财富分配的权重取决于稀缺性（趋势红利是进入的公司少， 创新利润是只有你能通过创新降低成本）。\n6. 其它 这本书还提到了：\n指数增长： 需要做边际成本为零的产品才能指数增长。 公平和效率： 国家为什么要二次分配，为什么要保护弱势群体，\u0026hellip; 长期主义： 做能积累的事 博弈论： 做对其它人产生影响的决定，需要考虑博弈. 势能： 为了对抗成功的运气成分， 需要顺势而为（做风口上的行业），抓关键方向（战略） 人脉： 取决于你能帮助的人，而不是能帮助你的人。“Be useful”。 What,Why,How： 知其所以然（WHY）才能知行合一 \u0026hellip; 书整体上前后逻辑性不太强，是文章集合整理而成。 但还是好书，知识点很多。\n","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-20-fundamental-logic/","summary":"\u003cblockquote\u003e\n\u003cp\u003e图摘录自《底层逻辑：看清这个世界的底牌》一书。 对图的解释夹杂我自己的理解。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"1-是非对错的底层逻辑\"\u003e1. 是非对错的底层逻辑\u003c/h2\u003e\n\u003ch3 id=\"11-不要纠结\"\u003e1.1. 不要纠结\u003c/h3\u003e\n\u003cp\u003e\u003cimg alt=\"picture 1\" loading=\"lazy\" src=\"/images/1647794513940.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e“成年人的世界不分对错，只讲利益”。 当执着于对错的时候, 本身就已经搞错方向了。不要纠结，不要纠缠。一笑了之。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e法学家和经济学家是职业，为了学术研究对错。\n一般人在生活中，是经济学家研究的对象，是经济学上的人，是商人。 商人如果用法学家/经济学家的视角去分析问题，就太学究了。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"2-思考问题的底层逻辑\"\u003e2. 思考问题的底层逻辑\u003c/h2\u003e\n\u003ch3 id=\"21-要素和关系\"\u003e2.1. 要素和关系\u003c/h3\u003e\n\u003cp\u003e\u003cimg alt=\"picture 2\" loading=\"lazy\" src=\"/images/1647794634112.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e这个图是说系统的组成。 包括要素（一些列的名词概念），和各个要素之间的关系。\u003c/p\u003e\n\u003cp\u003e举个例子，商业系统中有产品，流量，转化，留存，利润等等要素。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e因果链： 产品体验好， 留存增加\u003c/li\u003e\n\u003cli\u003e增强回路： 产品体验好，留存增加，利润增加，研发投入加大，产品体验更好\u0026hellip;, 或者反向增强：产品体验不好\u003c/li\u003e\n\u003cli\u003e调节回路：流量增加到一定程度，产品体验变差\u003c/li\u003e\n\u003cli\u003e滞后效应： 流量增加到留存增加中间有滞后（转化的过程）\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"22-逻辑推导\"\u003e2.2. 逻辑推导\u003c/h3\u003e\n\u003cp\u003e\u003cimg alt=\"picture 3\" loading=\"lazy\" src=\"/images/1647794697348.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e这个图是说，证明事实证有不证无，证有时候用举例。 表达观点，不要以偏概全。得出一般性结论只能靠证明、推理，而不能随便说，“这难道不是共识吗？”“所有人都认为……”。\u003c/p\u003e\n\u003ch3 id=\"23-看到本质\"\u003e2.3. 看到本质\u003c/h3\u003e\n\u003cp\u003e\u003cimg alt=\"picture 4\" loading=\"lazy\" src=\"/images/1647794748129.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e这个图是说，要抓住本质。离写字楼近的饭馆生意好， 本质是流量，离得近流量大。 当大家都开始点外卖时候，流量变了。相应的竞争策略也需要调整。 抓不住本质，当流量变化的时候，一味在饭菜口味上，在服务上下功夫，是没有太大作用的。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e书中有一个微软管理供应商的例子。\u003c/p\u003e\n\u003cp\u003e公司提供午餐和晚餐给员工。午餐吃饭的人数一般比晚餐要多，因为不是人人晚上都要加班，所以做午餐的供应商利润更高。只不过有时候，午餐做的很糟糕……试想一下，如果你是负责人，你要怎么处理？派人监督？隔一段时间换个大厨？要求他们更新菜谱？\n微软想出了一个制度：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e选2家供应商，一家提供午饭、另一家提供晚饭；\u003c/li\u003e\n\u003cli\u003e每3个月做满意度调查，是喜欢午饭还是晚饭？\u003c/li\u003e\n\u003cli\u003e如果喜欢晚饭的多，午饭、晚饭供应商交换；\u003c/li\u003e\n\u003cli\u003e如果连续6个月，午餐都胜出的话，更换晚饭供应商。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e这个制度的核心是引入了午餐供应商和晚餐供应商的竞争机制。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"3-个体进化的底层逻辑\"\u003e3. 个体进化的底层逻辑\u003c/h2\u003e\n\u003ch3 id=\"31-能力效率和杠杆\"\u003e3.1. 能力，效率和杠杆\u003c/h3\u003e\n\u003cp\u003e\u003cimg alt=\"picture 5\" loading=\"lazy\" src=\"/images/1647794799927.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e这个图说人生的商业模式成功由三个因素相乘的积决定。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e能力： 要有时间和精力投入， 而且要有好的方法（刻意练习）。\u003c/li\u003e\n\u003cli\u003e效率： 要管理精力，有取舍，并且有好的工具。\u003c/li\u003e\n\u003cli\u003e杠杆： 借助团队的力量，科技的力量，资本的力量和个人影响力来放大。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e尤其提到了个人影响力中，写作和演讲的能力很重要。\u003c/p\u003e","title":"《底层逻辑》中的几张图"},{"content":" Directus APP使用VUE 3， Typescript，用Vite构建。 本文记录在评估Directus APP过程中了解到的前端相关知识。\n1. vite 构建工具，类似webpack。\n1.1. 原理 使用了浏览器的ES6模块加载功能，所以前端可以按需加载JS模块。开发过程中不需要打包了。\n\u0026lt;script type=\u0026#34;module\u0026#34; src=\u0026#34;./foo.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; 以上 module类型加载的 foo.js 中可以再import其它的模块，也可以export。浏览器认识js中的import和export命令，会按需向后台发请求加载。\n1.2. 应用 在vite.config.ts中配置开发服务器，例如下面监听8080端口，API请求发送到8055端口。\nserver: { port: 8080, proxy: { \u0026#39;^/(?!admin)\u0026#39;: { target: process.env.API_URL ? process.env.API_URL : \u0026#39;http://localhost:8055/\u0026#39;, changeOrigin: true, }, }, fs: { allow: [searchForWorkspaceRoot(process.cwd()), \u0026#39;/admin/\u0026#39;], }, }, 2. VUE 把js/css/html组织起来的方式。 运行环境（js库）提供数据绑定，改状态就会更新显示，方便JS更新页面。\n2.1. 基本概念 data 一个函数，返回组件实例的数据对象。 props 一个数组，或者对象。是父组件调用此组件时候的可配置参数。 data和props都可以在template中访问，区别是\ndata不是通过父组件传递的，是子组件私有的，是可读可写的。 props中的数据，都是通过父组件传递给子组件的，是只读的。 data存储组件的状态， 没有data的组件就是无状态组件。\nmixin， 为了DRY，为了减少重复，把组建脚本部分相同的地方（data，props,method\u0026hellip;)抽离出来，单独放到一个对象里面。 然后在组件中mixin进去。 setup函数， 用来替代mixin，实现Composition API。 其输入为props, context，输出是一个对象。对象的所有属性都可以在组件中访问。 2.2. 组件生命周期 3. html 3.1. data-自定义属性 在VUE生成的html中，看到很多data-v-xxxx 之类的 div， 这些是scoped css产生的。 相同的组件会产生相同的data-v数值。\nThe data-* attribute adds custom information to a \u0026lt;div\u0026gt; element. The * part is replaced with a lowercase string, such as data-id, data-source, data-category, etc. An \u0026lt;div\u0026gt; element can have any number of data-* attributes, each with their own name.\n\u0026lt;div id=\u0026#39;strawberry-plant\u0026#39; data-fruit=\u0026#39;12\u0026#39;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; // \u0026#39;Getting\u0026#39; data-attributes using getAttribute var plant = document.getElementById(\u0026#39;strawberry-plant\u0026#39;); var fruitCount = plant.getAttribute(\u0026#39;data-fruit\u0026#39;); // fruitCount = \u0026#39;12\u0026#39; // \u0026#39;Setting\u0026#39; data-attributes using setAttribute plant.setAttribute(\u0026#39;data-fruit\u0026#39;,\u0026#39;7\u0026#39;); // Pesky birds \u0026lt;/script\u0026gt; 这种自定义属性是HTML5标准中定义的，可以在JS中访问。\n4. Css 4.1. 基本概念 基本格式是\np { font-family: verdana; font-size: 20px; } 其中p是选择器， 后面的大括号是一个属性和值的列表。\n选择器通过一定的规则选择html元素。基本的有：\n所有元素： 星号 标签名称： body,a,p,h2\u0026hellip; class 用点号 id 用井号 还有一些组合的：\n多元素选择器: E, F 把多个选择器用逗号隔开 后代元素选择器: E F， 匹配选择器E里面所有的选择器F。 例如 .box h2, class box元素内部的所有h2 子元素选择器: E \u0026gt; F， 和上面区别是只匹配直接子元素。 只匹配一级。 后代匹配多级（所有后代）。 相邻元素选择器: E + F， E和F是兄弟关系， 并且紧挨着，这时候匹配F。 属性选择器：用中括号，根据元素的属性名/属性值选择。\n继承性： 对外层元素的样式会自动继承给内层元素。 如果内层元素样式和外层冲突，则内层起作用。\n定位：\nfixed 固定，相对于浏览器窗口的位置。后面会跟着坐标。不占用标准文档流空间，压盖标准文档流。 relative 相对，相对于原来的位置 absolute 绝对，坐标是相对于设置过定位的祖先元素。如果祖先都没设置，就和fixed一样了。不占用标准文档流空间，会压盖标准文档流。 4.2. scss sass的一个新版本， 是生成css的语言。类似css3，增加了变量，继承等。方便生成css。\n参见 https://www.jianshu.com/p/6bb174c79172 其中英镑（and）符号是父元素选择器。\n4.3. directus中的应用 以下是主屏幕左侧导航栏 的CSS， 在手机屏幕下，默认不显示，只有点左上角菜单才显示。在PC上，没有菜单按钮，默认显示。看实现，最左边的控件，向左挪动100%，就移除屏幕，实现了隐藏。 最右边的也一样，向右移动100%实现隐藏。\n#navigation { position: fixed; top: 0; left: 0; z-index: 50; display: flex; height: 100%; font-size: 0; transform: translateX(-100%); //默认不显示 transition: transform var(--slow) var(--transition); \u0026amp;.is-open { transform: translateX(0); //显示 } \u0026amp;:not(.is-open) { .module-nav-resize-handle { display: none; } } .module-nav { position: relative; display: inline-block; width: 220px; height: 100%; font-size: 1rem; background-color: var(--background-normal); \u0026amp;-content { --v-list-item-background-color-hover: var(--background-normal-alt); --v-list-item-background-color-active: var(--background-normal-alt); height: calc(100% - 64px); overflow-x: hidden; overflow-y: auto; } } .module-nav-resize-handle { position: absolute; top: 0; right: -2px; bottom: 0; width: 4px; background-color: var(--primary); cursor: ew-resize; opacity: 0; transition: opacity var(--fast) var(--transition); transition-delay: 0; user-select: none; touch-action: none; \u0026amp;:hover, \u0026amp;:active { opacity: 1; } \u0026amp;.active { transition-delay: var(--slow); } } @media (min-width: 960px) { position: relative; transform: none; //大于 960px时候就会显示 \u0026amp;:not(.is-open) { .module-nav-resize-handle { display: block; } } } } 5. 参考 https://v3.cn.vuejs.org/guide/introduction.html embedding-custom-non-visible-data-with-the-data-attributes ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-19-vue/","summary":"\u003cblockquote\u003e\n\u003cp\u003eDirectus APP使用VUE 3， Typescript，用Vite构建。\n本文记录在评估Directus APP过程中了解到的前端相关知识。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"1-vite\"\u003e1. vite\u003c/h2\u003e\n\u003cp\u003e构建工具，类似webpack。\u003c/p\u003e\n\u003ch3 id=\"11-原理\"\u003e1.1. 原理\u003c/h3\u003e\n\u003cp\u003e使用了浏览器的ES6模块加载功能，所以前端可以按需加载JS模块。开发过程中不需要打包了。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-html\" data-lang=\"html\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003etype\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;module\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esrc\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;./foo.js\u0026#34;\u003c/span\u003e\u0026gt;\u0026lt;/\u003cspan style=\"color:#f92672\"\u003escript\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e以上 module类型加载的 foo.js 中可以再import其它的模块，也可以export。浏览器认识js中的import和export命令，会按需向后台发请求加载。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch3 id=\"12-应用\"\u003e1.2. 应用\u003c/h3\u003e\n\u003cp\u003e在vite.config.ts中配置开发服务器，例如下面监听8080端口，API请求发送到8055端口。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\u003cspan style=\"color:#a6e22e\"\u003eserver\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003eport\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e8080\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003eproxy\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;^/(?!admin)\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#a6e22e\"\u003etarget\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eprocess\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eenv\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAPI_URL\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e?\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eprocess\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eenv\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eAPI_URL\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;http://localhost:8055/\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\t\u003cspan style=\"color:#a6e22e\"\u003echangeOrigin\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\u003cspan style=\"color:#a6e22e\"\u003efs\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t\t\u003cspan style=\"color:#a6e22e\"\u003eallow\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [\u003cspan style=\"color:#a6e22e\"\u003esearchForWorkspaceRoot\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eprocess\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecwd\u003c/span\u003e()), \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/admin/\u0026#39;\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t\t},\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\t},\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"2-vue\"\u003e2. VUE\u003c/h2\u003e\n\u003cp\u003e把js/css/html组织起来的方式。 运行环境（js库）提供数据绑定，改状态就会更新显示，方便JS更新页面。\u003c/p\u003e\n\u003ch3 id=\"21-基本概念\"\u003e2.1. 基本概念\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003edata 一个函数，返回组件实例的数据对象。\u003c/li\u003e\n\u003cli\u003eprops 一个数组，或者对象。是父组件调用此组件时候的可配置参数。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003edata和props都可以在template中访问，区别是\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003edata不是通过父组件传递的，是子组件私有的，是可读可写的。\u003c/li\u003e\n\u003cli\u003eprops中的数据，都是通过父组件传递给子组件的，是只读的。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003edata存储组件的状态， 没有data的组件就是无状态组件。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003emixin， 为了DRY，为了减少重复，把组建脚本部分相同的地方（data，props,method\u0026hellip;)抽离出来，单独放到一个对象里面。 然后在组件中mixin进去。\u003c/li\u003e\n\u003cli\u003esetup函数， 用来替代mixin，实现Composition API。 其输入为props, context，输出是一个对象。对象的所有属性都可以在组件中访问。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"22-组件生命周期\"\u003e2.2. 组件生命周期\u003c/h3\u003e\n\u003cp\u003e\u003cimg alt=\"picture 10\" loading=\"lazy\" src=\"/images/1647274015629.png\"\u003e\u003c/p\u003e","title":"Front End: Vue3, Vite, Css..."},{"content":"1. Collection 一个Collection对应数据库的一张表。directus的collection里面有一些独有的概念，例如alias域， sort属性等。\n1.1. alias Fields that do not map directly to an actual database column are called \u0026ldquo;alias\u0026rdquo; fields. For example, presentation fields (such as dividers and groups) and certain relational types that display data stored elsewhere (such as One-to-Many (O2M) and Many-to-Many (M2M)).\nO2M, M2M, M2A 等，都不会在表里面有实际的列。称其为alias域。\nalias虽然在实际数据库表里面没有列，但是在direct_fields表里面有记录。例如上面pages集合的element域，是m2a类型的alias。\ndirect_collections表里面描述系统所有受directus管理的表，direct_fields里面描述所有表的所有属性。 通过这种非侵入的方式， 数据库中原有的表结构可以不做改动。\n1.2. sort 属性 如果collection建立时候，选择了支持sort，表里面就会多一个sort列。 这一列的作用是支持在app上手工拖拽的方式调整记录的顺序。 原理很简单，表里面加了一个number类型的sort列， 当拖拽的时候，会导致该列的值根据记录的位置产生变动。\n1.3. share APP的一个功能， 可以把模型中的一个记录生成一个链接，发送给其它人只读访问。 可以设置密码，有效期，以哪一个身份读取等。\n在数据库中，directus有特定的表，directus_shares来管理所有的share。 2. Relationship 表和表之间的关系。 directus支持下面四种。\n2.1. M2M 通过中间表实现多对对关系。 例如Product 和 Tag 之间。\n2.2. O2M 一对多关系， 例如order和order row之间。 在order row模型中需要有外键，来确定该order row是属于哪一个order的。可以在order row中加一个外键order，其取值为orders表中的id。\n2.3. M2O 和O2M一样，只是从order row看order。\n2.4. M2A 和M2M类似， 区别是可以表现一个集合对多个集合的关系。在juntion表里面，除了存id之外，还存了集合名称。比如一个Page可能包括header，footer，section几种元素。 下面的juntion表，表示page id为1的页面，包括了id为1的footer，id为1的header和id为2的section。\n使用M2A关系，在APP上可以方便的添加不同的item到page里面。 这种关系也被称为replicator（复制器）。\n如果把上图中的collection列去掉，M2A就退化成M2M关系了。 只能表示两个特定集合之间的关系。\nM2A是Many to Any的缩写。之所以叫any是因为关系表里面存了集合名字和id，所以理论上是可以表示任何集合的任何元素。\n3. 国际化方案 3.1. 模型名称和属性名称的多语言 模型的名称可以多语言。 通过模型配置里面添加“Collection Name Translations”。\n模型的属性名称可以多语言。通过配置模型/Field/Field Name Transalation。\n模型名称和属性名称的多语言数据在数据库里面都是用JSON字符串存储。所有语言的翻译放在一起。\n3.2. 记录内容的多语言 模型内的元素内容可以做多语音，通过多语言collection实现。其原理是把所有需要多语言支持的属性放到一个多语言表里面。这个表里面有所有需要多语言翻译的列， 以及一个语言code列（取值zh-CN，en-US等），这样对所有原模型中的行，会对应翻译模型里面中文一行，英文一行。来存放翻译好的数据。 https://docs.directus.io/configuration/data-model/#creating-translated-multilingual-fields\n编辑Langues模型中的数据，只留下中英语，则在APP上只会展示中英文翻译。\n如上图， product集合有三个列需要多语言支持，分别是description，longtext和pic。 从图中可见，图片也支持多语言（不同语言使用不同图片）。\n一般我们实现多语言最直观的想法就是在表里面加列，中文一列，英文一列，西班牙语一列。这样后续增加新语言要改表结构，扩展性不好。 Directus这种实现方法扩展性更好，一种结构支持所有语言。\n","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-13-directus/","summary":"\u003ch2 id=\"1-collection\"\u003e1. Collection\u003c/h2\u003e\n\u003cp\u003e一个Collection对应数据库的一张表。directus的collection里面有一些独有的概念，例如alias域， sort属性等。\u003c/p\u003e\n\u003ch3 id=\"11-alias\"\u003e1.1. alias\u003c/h3\u003e\n\u003cblockquote\u003e\n\u003cp\u003eFields that do not map directly to an actual database column are called \u0026ldquo;alias\u0026rdquo; fields. For example, presentation fields (such as dividers and groups) and certain relational types that display data stored elsewhere (such as One-to-Many (O2M) and Many-to-Many (M2M)).\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003eO2M, M2M, M2A 等，都不会在表里面有实际的列。称其为alias域。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 6\" loading=\"lazy\" src=\"/images/1647116338960.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003ealias虽然在实际数据库表里面没有列，但是在direct_fields表里面有记录。例如上面pages集合的element域，是m2a类型的alias。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003edirect_collections表里面描述系统所有受directus管理的表，direct_fields里面描述所有表的所有属性。 通过这种非侵入的方式， 数据库中原有的表结构可以不做改动。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch3 id=\"12-sort-属性\"\u003e1.2. sort 属性\u003c/h3\u003e\n\u003cp\u003e如果collection建立时候，选择了支持sort，表里面就会多一个sort列。 这一列的作用是支持在app上手工拖拽的方式调整记录的顺序。 原理很简单，表里面加了一个number类型的sort列， 当拖拽的时候，会导致该列的值根据记录的位置产生变动。\u003c/p\u003e\n\u003ch3 id=\"13-share\"\u003e1.3. share\u003c/h3\u003e\n\u003cp\u003eAPP的一个功能， 可以把模型中的一个记录生成一个链接，发送给其它人只读访问。 可以设置密码，有效期，以哪一个身份读取等。\u003c/p\u003e","title":"Directus"},{"content":" 髋部驱动适合长距离， 相对肩部驱动更省力。\n抱水 肩部放松， 整个手臂的阴面（内侧，太阳照不到的那一侧）形成推水的阻力面。 推水阻力面包括大臂内侧，小臂内侧，手掌内侧。 肘部适当弯曲差不多100度。 肩部和腋下放松，但核心（腹部）收紧，身体保持直线。\n手臂姿势类似爬墙， 肘部可稍高一些，体力好的时候肘部抱到耳朵位置，体力不好时候抱低一点。\n动力传递 髋部滚动和打腿来联合驱动。 髋部滚动带动打腿， 打腿进一步给滚动提供支撑（通过整个大腿，小腿和脚面压水来支撑同侧髋部上抬）。\n髋部上抬，进一步通过躯干传导到上肢， 带动抱水的胳膊向后甩。\n动力传递的整体感觉类似上图。 在动力传递过程中：\n腹部（腹直肌，腹外斜肌）保持适度紧张很关键， 否则滚动的力量就被身体的扭曲吸收了。 发力一侧的关节（抱水侧的肩关节，打腿侧的髋关节）要放松，放松才能甩出去。陆操时候，重心要完全放到不发力的一侧。 在游进中的感觉是：\n髋部滚动， 同时两腿上下交换下位置完成打腿，打腿过程中腿部基本保持笔直，两腿大腿内侧轻贴以减阻。 向上滚动的髋部通过拉动背阔肌，把抱水的胳膊向后甩， 一直甩出水面后再悠到前面，再次形成入水姿势。向后甩胳膊时候，也有身体滚动导致向内侧拉抱水侧肘部的感觉。 ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-12-swim-hip-driven/","summary":"\u003cblockquote\u003e\n\u003cp\u003e髋部驱动适合长距离， 相对肩部驱动更省力。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"抱水\"\u003e抱水\u003c/h2\u003e\n\u003cp\u003e肩部放松， 整个手臂的阴面（内侧，太阳照不到的那一侧）形成推水的阻力面。 推水阻力面包括大臂内侧，小臂内侧，手掌内侧。 肘部适当弯曲差不多100度。 肩部和腋下放松，但核心（腹部）收紧，身体保持直线。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 3\" loading=\"lazy\" src=\"/images/1647094811168.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e手臂姿势类似爬墙， 肘部可稍高一些，体力好的时候肘部抱到耳朵位置，体力不好时候抱低一点。\u003c/p\u003e\n\u003ch2 id=\"动力传递\"\u003e动力传递\u003c/h2\u003e\n\u003cp\u003e髋部滚动和打腿来联合驱动。 髋部滚动带动打腿， 打腿进一步给滚动提供支撑（通过整个大腿，小腿和脚面压水来支撑同侧髋部上抬）。\u003c/p\u003e\n\u003cp\u003e髋部上抬，进一步通过躯干传导到上肢， 带动抱水的胳膊向后甩。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 2\" loading=\"lazy\" src=\"/images/hip.gif\"\u003e\u003c/p\u003e\n\u003cp\u003e动力传递的整体感觉类似上图。 在动力传递过程中：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e腹部（腹直肌，腹外斜肌）保持适度紧张很关键， 否则滚动的力量就被身体的扭曲吸收了。\u003c/li\u003e\n\u003cli\u003e发力一侧的关节（抱水侧的肩关节，打腿侧的髋关节）要放松，放松才能甩出去。陆操时候，重心要完全放到不发力的一侧。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e在游进中的感觉是：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e髋部滚动， 同时两腿上下交换下位置完成打腿，打腿过程中腿部基本保持笔直，两腿大腿内侧轻贴以减阻。\u003c/li\u003e\n\u003cli\u003e向上滚动的髋部通过拉动背阔肌，把抱水的胳膊向后甩， 一直甩出水面后再悠到前面，再次形成入水姿势。向后甩胳膊时候，也有身体滚动导致向内侧拉抱水侧肘部的感觉。\u003c/li\u003e\n\u003c/ol\u003e","title":"髋驱自由泳"},{"content":"1. Docker client通过运行在主机上的docker daemon操作image和container，registry提供image的发布和下载（类似npm和pip）。\nDocker 镜像是一个特殊的只读文件系统，提供容器运行时所需的程序、库、资源、配置等文件，还包含一些为运行时准备的配置参数（如匿名卷、环境变量、用户等）。镜像不包含任何动态数据，其内容在构建之后也不会被改变。\nContainer是image的运行实例，是运行在宿主机上的一种特殊的进程。一个image可以有多个container。\nContainer是在Image基础上叠加了一个读写层，在container内部做一些写操作后，可以commit读写层生成新的image\n上图省略掉了存储配置文件信息的init层。init层位于上图image和container两层之间。存放运行操作系统时候需要写的一些目录/文件。例如/etc 目录。init层commit时候不提交。 因为没必要，每次只读层运行时候都会自动生成init层内容。\n1.1. commands # d d d # d d d d d d d d d d o o o o o o o o o o o o o I c c c C c c c c c c c c c c m k k k o k k k k k k k k k k a e e e n e e e e e e e e e e g r r r t r r r r r r r r r r e a p i r i p r r s r r p c a e u m m n s u u t m m o p t x l a i e n n a r t e l g r - r \u0026lt; $ t l a c e a - - t C ( o c h s \u0026lt; i d O d \u0026lt; c h - e I t \u0026lt; N o C a i l M f c T c O l \u0026lt; t l A c l o A k N _ c o G e a n I e T f o - E n s t N r A i n d w t k a E I l t i o I o - i R c N e a r r D s h n o E _ i e l \u0026gt; e e I n R n n c d l r D t a e t l _ \u0026gt; a I m r u o n i D e s - a n \u0026gt; n - 从 查 删 查 交 w m e a s r 看 除 看 互 o e r 4 m q e 本 某 有 式 r \u0026gt; 删 查 9 e l g 地 个 哪 运 l 除 l 看 f \u0026gt; i i 有 i 些 行 d c s 端 7 t s 哪 m c o 口 9 e t 些 a o 重 n - 映 6 对 r i g n 后 启 t a 射 0 于 / y m e t 台 a q 信 e d b 下 a ， a 运 i ) 息 b o i 载 g 可 i 行 n 7 c n h e 以 n e e k / e 只 e r 4 e s l 写 r 删 : r h l I 除 / o D 所 d r - 的 有 o u 在 w 前 c c n 名 o 几 o k / 字 r 位 n e s 是 l t r t d a _ a d i f r i i n i t r m e l e a r e 启 c g _ 动 t e n 的 u a 容 s m 器 - e ， s 可 q 以 l 拷 用 i 贝 这 t 文 个 e 件 来 的 到 a 容 d t 器 o t 内 c a 执 k c 行 e h s r 到 h 里 控 面 制 台 上 看 输 出 。 1.2. Dockerfile 是用来build image的配置文件，从一个基础image开始，运行一些列配置命令，得到另外一个image\n2. Docker Compose 一个用于定义，运行和管理多容器的工具. 运行起来之后，也可以用之前的docker命令来操作容器。\nd d d d d d d d d o o o o o o o o o c c c c c c c c c k k k k k k k k k e e e e e e e e e r r r r r r r r r - - - - - - - - - c c c c c c c c c o o o o o o o o o m m m m m m m m m p p p p p p p p p o o o o o o o o o s s s s s s s s s e e e e e e e e e u u p l p s s r d p p s o o t t u o g r a o n w - s t r p n d t d d c i i c a r / r a c e / 查 e c h c 停 看 c h e t 止 后 几 t e u 所 台 个 u s 有 启 服 s 服 动 务 s 务 并 各 8 h ， 运 自 0 并 行 的 5 删 容 l 5 除 器 o c g / o 在 n d t / i a 查 r i 看 e n d c e i t r r u e s c 服 t 务 u 的 s c 服 o 务 n 的 t c a o i n n t e a r i 上 n 运 e 行 r s h 8 0 5 5 端 口 绑 定 到 主 机 的 哪 个 端 口 了 3. Examples 3.1. Docker 3.1.1. directus 以下映射了两个卷，读取环境变量，然后交互式运行directus，container名字为directus-sqlite。\nd o c k e r r u n - i t - e n v - f i l e = . / e n v _ s q l i t e $ P W D / : / d i r e c t u s / d a t a b a s e $ P W D / u p l o a d s : / d i r e c t u s / u p l o a d s - n a m e = \" d i r e c t u s - s q l i t e \" d i r e c t u s / d i r e c t u s : l a t e s t 其中环境变量文件 env_sqlite\nDB_CLIENT=\u0026#34;sqlite3\u0026#34; DB_FILENAME=\u0026#34;/directus/database/data.db\u0026#34; RATE_LIMITER_ENABLED=true RATE_LIMITER_POINTS=50 RATE_LIMITER_DURATION=1 RATE_LIMITER_STORE=memory CACHE_ENABLED=true CACHE_TTL=\u0026#34;30m\u0026#34; CACHE_NAMESPACE=\u0026#34;directus-cache\u0026#34; CACHE_AUTO_PURGE=true # memory | redis | memcache CACHE_STORE=memory ASSETS_CACHE_TTL=\u0026#34;30m\u0026#34; STORAGE_LOCATIONS=\u0026#34;local\u0026#34; STORAGE_LOCAL_DRIVER=\u0026#34;local\u0026#34; STORAGE_LOCAL_ROOT=\u0026#34;/directus/uploads\u0026#34; KEY=\u0026#34;xxxxxxx-xxxxxx-xxxxxxxx-xxxxxxxxxx\u0026#34; SECRET=\u0026#34;abcdef\u0026#34; ACCESS_TOKEN_TTL=\u0026#34;15m\u0026#34; REFRESH_TOKEN_TTL=\u0026#34;7d\u0026#34; REFRESH_TOKEN_COOKIE_SECURE=\u0026#34;false\u0026#34; REFRESH_TOKEN_COOKIE_SAME_SITE=\u0026#34;lax\u0026#34; REFRESH_TOKEN_COOKIE_NAME=\u0026#34;directus_refresh_token\u0026#34; CORS_ENABLED=\u0026#34;true\u0026#34; CORS_ORIGIN=\u0026#34;true\u0026#34; CORS_METHODS=GET,POST,PATCH,DELETE CORS_ALLOWED_HEADERS=Content-Type,Authorization CORS_EXPOSED_HEADERS=Content-Range CORS_CREDENTIALS=\u0026#34;true\u0026#34; CORS_MAX_AGE=18000 AUTH_PROVIDERS=\u0026#34;dingtalk\u0026#34; AUTH_DINGTALK_DRIVER=\u0026#34;oauth2\u0026#34; AUTH_DINGTALK_CLIENT_ID=\u0026#34;....\u0026#34; AUTH_DINGTALK_CLIENT_SECRET=\u0026#34;c6rCTM1nmIm-....\u0026#34; AUTH_DINGTALK_AUTHORIZE_URL=\u0026#34;https://fb.....cn/apipro/oauth_dingtalk/auth\u0026#34; AUTH_DINGTALK_ACCESS_URL=\u0026#34;https://fb.....cn/apipro/oauth_dingtalk/access_token\u0026#34; AUTH_DINGTALK_PROFILE_URL=\u0026#34;https://fb.....cn/apipro/oauth_dingtalk/profile\u0026#34; AUTH_DINGTALK_ALLOW_PUBLIC_REGISTRATION=\u0026#34;true\u0026#34; AUTH_DINGTALK_DEFAULT_ROLE_ID=\u0026#34;86a3338c-26a5-447d-bfbd-f938ee2c3c40\u0026#34; AUTH_DINGTALK_ICON=\u0026#34;alipay\u0026#34; EXTENSIONS_PATH=\u0026#34;./extensions\u0026#34; EMAIL_FROM=\u0026#34;austin@....com\u0026#34; EMAIL_TRANSPORT=\u0026#34;smtp\u0026#34; EMAIL_SMTP_POOL=false EMAIL_SMTP_HOST=\u0026#34;smtp........com\u0026#34; EMAIL_SMTP_PORT=465 EMAIL_SMTP_SECURE=true # Use TLS EMAIL_SMTP_IGNORE_TLS=false EMAIL_SMTP_USER=\u0026#34;austin@....com\u0026#34; EMAIL_SMTP_PASSWORD=\u0026#34;....\u0026#34; 后续就可以用docker start来启动directus。\nM C 7 M d M a O 8 a i a c N a c r c B T c B e B o A c o c o o I 6 o t o k N c k u k - E 4 - s - P R 6 P - P r 4 r s r o I 4 o q o - D 0 - l - 2 2 i 2 : : t : a a e a p I d p p i M i i i A r w G e w w a E c a a n t n n g u g g x s x x u / u u $ d $ $ i d r d o e o c c c k t k e u e r s r : p l s s a t t a - e r a s t t d i r C \" e O d c M o t M c u A k s N e - D r s - q e l n i t t r e y p o i n t . s … \" C 6 R E m A i T n E u D t e s a g o S E T x A i T t U e S d ( 1 ) 5 m i n u t e s a g o P O R T S N d A i M r E e S c t u s - s q l i t e 3.1.2. 基本操作 M R M U l 2 e 5 5 a e D S d M R n M / / / 1 1 / / / 2 2 2 ^ 2 2 2 2 2 2 2 2 2 2 2 2 M C 1 M 9 M C 9 1 M t M C 9 1 M t M C 9 1 M t M 9 1 M C M R n M U U D D D D D D D M R M a E a s a 7 f b 5 7 f i t o a E g a d d d 0 0 d d d 0 0 0 C 0 0 0 0 0 0 0 0 0 0 0 0 a O 2 a c a O c 2 a e a O c 2 a e a O c 2 a e a c 2 a O a E g a n n e e e e e e e a E a c P c i t 9 0 1 9 c c g a c c P i c o o o - - o o o 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 c N f c f c N f f c n c N f f c n c N f f c n c f f c N c P i c t t l l l l l l l c P c B O B n e a e 4 6 9 e e t k B O n B c c c l l c c c 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 B T 3 B b B T b 3 B d B T b 3 B d B T b 3 B d B b 3 B T B O n B a a e e e e e e e B O B o S o g s 0 4 8 0 9 2 s u e o S x o k k k i i k k k / / / 2 / / / / / / / / / / / / o A e o 0 o A 0 e o e o A 0 e o e o A 0 e o e o 0 e o A o S x o g g t t t t t t t o S o o I o t 2 2 f 2 6 1 t s r o I o e e e s s e e e 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 o I 6 o 2 o I 2 6 o r o I 2 6 o r o I 2 6 o r o 2 6 o I o I o g g e e e e e e e o I o k T k d : 0 e 4 7 3 e : : . k T k r r r t t r r r 3 3 3 / 3 3 3 3 3 3 3 3 3 3 3 3 k N e k d k N d e k _ k N d e k _ k N d e k _ k d e k N k T k e e d d d d d d d k T k - O - e 0 c 8 e 8 1 i - O - - - - e e - - - / / / 0 / / / / / / / / / / / / - E d - b - E b d - m - E b d - m - E b d - m - b d - E - O - d d : : : : : : : - O - P R P f P 7 d f 4 7 6 s D o P R P e e e n n e e e 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 P R b P 0 P R 0 b P c P R 0 b P c P R 0 b P c P 0 b P R P R P : : P R P r Y r a u 6 e 5 6 0 a h o / r Y r n n n - - n n n 8 8 8 / 8 8 8 8 8 8 8 8 8 8 8 8 r 2 r 5 r 5 2 r l r 5 2 r l r 5 2 r l r 5 2 r r Y r s s s s s s s r Y r o o u l a 9 2 9 b 5 a w l o o t t t o o t t t 0 o I b o 5 o I 5 b o a o I 5 b o a o I 5 b o a o 5 b o I o o n n h h h h h h h o o - - l l 7 6 e a 9 9 2 n i - - r r r n n r r r 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 - D b - e - D e b - r - D e b - r - D e b - r - e b - D - - g g a a a a a a a - - 2 2 t i : : : : : : 5 l b 2 2 y y y - - y y y 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 2 2 f 2 2 e 2 2 e 2 2 e 2 2 2 2 i i 2 2 2 2 2 2 2 2 2 : T : n 6 o r : T l : p p p i i p p p : : : 0 : : : : : : : : : : : : : : 4 : : n : : n : : n : : : T l : n n 5 5 5 5 5 5 5 : T : a A a t g P P P P P P : a a a A a a o o o p p o o o 5 5 5 8 5 5 5 5 5 5 5 5 5 5 5 5 a a b a a a a a a a a a A a a x x 6 6 6 6 6 6 6 a A a p G p a u u u u u u 1 d r p G t p i i i v v i i i 5 5 5 : 5 5 5 5 5 5 5 5 5 5 5 5 p I n p 0 p I n n p p I n n p p I n n p p p I p G t p : @ : : : : : : : p G p i i g f l l l l l l c e y i e i n n n 6 6 n n n : : : 5 : : : : : : : : : : : : i M g i 5 i M g g i i M g g i i M g g i i i M i e i l s 9 c a 0 7 1 5 i i p p : r l l l l l l 1 d / p s p t t t - - t t t 2 2 2 5 4 4 4 4 4 4 4 4 4 4 4 4 p A i p e p A i i p p A i i p p A i i p p p A p s p a h c 0 b f f f 0 p p r r o 3 n r t r . . . b b . . . 0 0 0 : 3 3 3 3 3 3 3 3 3 3 3 3 r G n r 3 r G n n r r G n n r r G n n r r r G r t r t a 1 6 e 0 d 9 8 r r o o l m c c c c c c b n g o o s s s y y s s s 4 o E x o e o E x x o o E x x o o E x x o o o E o o e 2 f 7 f c 3 0 9 o o x x a o o o o o o c e i x x h h h - - h h h [ [ [ 3 [ [ [ [ [ [ [ [ [ [ [ [ x x b x x x x x x x x x x s 5 f 2 c b f b a x x y y t l m m m m m m 6 w n y y : : : d d : : : n n n n n n n n n n n n n n n y y 6 y y y y y y y y y y t 6 2 b d d 1 8 a y y e i p p p p p p d e x e e o o o [ o o o o o o o o o o o o 0 : 0 b e c 8 1 3 w I w s b l l l l l l e r : w I 9 w / L L f f L L C t t t n t t t t t t t t t t t t w w e w w w w w w w w w I 9 w 1 a 6 7 c 1 0 c w I w a M a t r e e e e e e 5 l a M c a d o a a a a a o i i i o i i i i i i i i i i i i a a e a a a a a a a a a M c a c c f 6 b e 7 9 a M a n A n a t t t t t t d i a n A 1 n o o u u u u u n c c c t c c c c c c c c c c c c n C \" n d n C \" \" n n C \" \" n n C \" \" n n n C n A 1 n 1 9 0 4 3 6 0 7 n A n g G g r e e e e e e f m t g G f g c k n l l n n f e e e i e e e e e e e e e e e e g O / g 0 g O / / g g O / / g g O / / g g g O g G f g 3 c 0 9 8 f 3 a g G g x E x y c a e x E f x k i c t t c c i ] ] ] c ] ] ] ] ] ] ] ] ] ] ] ] x M d x a x M d d x x M d d x x M d d x x x M x E f x b 9 f b c 8 9 8 x E x u u / a g s u 2 u e n h . . h h g e u M o u 0 u M o o u u M o o u u M o o u u u M u 2 u c 4 a e 3 0 5 a u u $ I $ n 7 e t $ I 0 $ r g i s s i i u 1 1 1 ] 3 3 3 3 3 3 1 1 1 1 1 1 $ A c $ d $ A c c $ $ A c c $ $ A c c $ $ $ A $ I 0 $ 6 d b 3 5 9 a e $ I $ D g 4 D a - n h h n n r # # # 2 1 2 1 2 1 # # # # # # N k 3 N k k N k k N k k N D a d d 4 e 3 9 4 b D d d i 9 f d c d e f g : : g g a 1 1 1 1 # # # # # # 1 1 1 1 1 1 d D e d f d D e e d d D e e d d D e e d d d D d c d e 5 1 1 6 e 7 e d o o n c o o 9 o n o t : : : # 3 3 3 3 3 3 : : : : : : o r o 4 o r r o o r r o o r r o o o o 9 o 5 1 5 c f 4 9 a o c c x 3 r c c c t r / i i / / i 1 2 1 2 1 2 1 c - c d c - - c c - - c c - - c c c c c c d 7 0 8 3 d 9 c c k C k 7 k 9 k r d n n d d o u n b : : : : : : : s w s s w e k e k d k e e k k e e k k e e k k k k 9 k f 5 4 2 d d 5 8 k C e R e 7 n e e y s o f f o o n s g u i o i i o x e n e 8 e n n e e n n e e n n e e e C e e c e c 1 c 6 d a e R r E r 9 g r r p h c o o c c i i i s s s e e e e g r g g r i r t r 4 r t t r r t t r r t t r r r R r r a a 9 7 b 3 f d r E A 7 i o e k : : k k c n n l i i i x x x x n k n n k t r 6 r r r r r r E 7 7 3 e 5 1 e 0 A i T p 4 n i C 6 r i l e e e o g x t g g g i i i i a e a a e p y r f p y y s p y y s p y y s r p A i C 6 r 4 3 f b 0 d e c i T m E u 1 x m R u n l r G E r r m / n n n t t t t l r l l r s p u 8 s p p t s p p t s p p t m s T m R m 9 9 f 1 0 1 2 b m E a D l 4 : a E d n t - e n - - p t 1 b a a a i i o n a o o o o o a o o o E a E d i c 5 2 b 1 7 a 2 a D g l 6 l g A a . s e t a e e l h . y l l l n n 1 p 2 1 p - i e - i i p - i i r - i i p $ - D g A a 3 e b 6 7 f 8 d g e a a e T y - d c n t b n n e e 2 g g 7 r 9 7 r a n - d a n n a n n t a n n ( a e T y 7 d f 5 1 e e 0 e s n c t s E s i / r t i l t t t 1 g 2 2 2 o o t d f t t t t t t t t d s E s 9 7 9 5 c e 3 0 8 s S g 0 e D t i r n e r r e \" . c ( c ( ( c . 5 . . e . . t . . e o D c 9 4 d f f 7 5 9 S I i 5 s a i p y g d y y ; e 6 c ( ( ( S e S S e … n 9 … … n … … e … … n c S a 7 f 0 0 9 1 5 f I Z n 2 t g n s t p p p p S S S I s I I s \" g 7 \" \" d \" \" n \" \" d k T g 4 2 3 5 e 8 d 8 Z E x 5 o g s o t l o o r o 1 I I I G s G G s i 3 e d e e A o 1 5 0 4 e 0 a e E 6 i n i h i i i e l 0 G G G C I C n a r e r r T 4 9 c 6 0 1 3 7 7 n o i n e s n n a l . I I I H 3 O H 3 x 9 _ r _ U 6 f b d e 6 8 e 9 x t n t t t t d \" 2 N N N L 1 ) L 2 C 4 e C 6 A m C 5 A _ C A 2 m c S a 7 3 a 5 9 7 4 1 S 1 . c e . . y . T T T D D R 4 c R b c R 2 b m R b c o S 1 c a 2 5 5 1 1 8 c I 3 e d h n d d e 1 ) ) ) ) e r ) e E f E s o l E o c E o m l n I 3 0 9 4 1 e d d 7 a Z 4 m d e / f v x e x A s 2 A e u a A s u l A u i a t Z 4 5 c 2 c 8 4 e f 2 E M p 1 c 2 3 o e 2 r r r r i c r i T e 6 T c t r T e t a T t n r a E M 2 8 2 b 6 6 d 0 f B t c 0 k n 0 0 r n 0 e e e e t e e t E c b E o e E c r E u e i P B 5 0 b 7 9 a 2 2 e y k - s - - t 2 c c c c e i c e D o 3 D n a n D o a e D a t n n O 6 4 b e f 9 f 9 1 , e l u I e t s 1 e e e e d v e d n a d n n e e R 7 a d 3 8 4 a 9 9 r i m P n u t m 0 i i i i e i d d s m d m m s r T 9 f 4 2 d 9 b a 7 w - s v v n a e 1 v v v v w d v w s 6 i s i i S 1 9 a d b 5 0 2 9 i e t o 6 s e r t 1 e e e e i e i 3 a n n n a l c 3 4 b 8 5 3 4 f l n e f u - t h 0 d d d d t d t a c g u a u u g s a 1 4 1 b 1 7 8 c l t n i b w o , , , h h g 9 o t g t t o 2 2 7 7 5 f 1 f 8 r - n s o u d ( f f o 1 e o e e - f 0 0 a 1 c 2 3 e a y e t r p D e e e r c r c a e 2 4 0 a b 9 4 8 t p n t / - k e x x x o o o o a a a q N 1 2 6 c 7 a b c 2 t o - c e o e b i i i m d m d g g g ) A 9 c e 4 9 4 4 f 7 e i i / t n r i t t t e e S E o o o M 7 b b f 2 c 0 4 8 m n p n c - - a i i i 3 3 T x E 9 9 b 0 a 8 4 6 b p t v g / t p n n n n 1 0 2 0 A i S f 5 9 a b 6 4 a f t . 6 i n e r g g g T t c b a 6 7 6 2 b 0 d - n g m o 1 U e S U E S E E S U E 8 2 6 a b b d 8 1 t b x i p c 0 S d T p x T x x T p x e f 6 3 3 8 4 6 2 y / n l e . A i A i i A i 8 c 1 c 9 5 6 a 1 - c x a s 2 ( T 5 t T t t T 4 t 2 f 5 0 8 4 6 1 d p d o / t s . 0 U e U e e U e 7 3 0 c a d 7 a 2 e e n c e e 1 ) S s d S d d S s d 8 6 e 6 9 f 8 3 8 r f f o s s - e e b 3 c 9 e b c 5 5 f a . n . . 6 2 c ( ( ( c ( f 6 8 1 0 f 4 6 o u d f s s ) 0 o 0 0 0 o 0 0 7 a 8 1 b b d r l / . h h n ) ) ) n ) 1 m t d d s d d 2 . e / e s 4 7 A s 2 1 c s f d c 5 b d o h a e o s o m 2 n u f n s e u i 8 f l a d e c t n 5 i t u s c o u g . l o n a t u c t a n d e r o . g d s m s a n c o s i t f o a n a i n a g u g o f g o t o n P o e O R a T g P 8 S P 8 o O 0 O 0 R / R / T t T t S c S c P p p O N o R A b T M j S E e N t o S c N t o A e b t A e b M n j i M n j E d e v E d e S e c e S e c N t o r t _ r t A e b _ i g _ i M n j m v o m v E d e c e o c e S e c l _ d l _ r t a g a a g _ i r o l r o m v e o l e o c e n d n d l _ a a a g l l r o l l e o n d a l l 3.2. Docker Compose 3.2.1. directus的 docker-compose.yaml v s n e e e r r d c d t d s v a a i w i i i t c i n e c c i n r c i p n d e o r o c a o m o e n h o m e e o m o o e e n r e n e b n a l t - v P P P e n a t - c n a r - l # # # # # # # t - p - - v K S D D D D D D C C C A A # # # k c : s a t g u w i O O O : t g w t t g t u w e i E E B B B B B B A A A D D s t : s a e m d r S S S a e o d u a e s 8 m B A l M - I - o d n c d r Y C _ _ _ _ _ _ C C C M M M ( P : u ' e i : e r i o T T T i : r i s i : : 0 e y l o / a f r i d a a o : R C H P D U P H H H I I a s U s 3 : n s d k r n G G G n k r : n 5 s w c u k k r s c t n E L O O A S A E E E N N k e B : ' e p : a s e m R R R e r s e e d 5 : d a a p e / y s e _ h a m ' T I S R T E S _ _ _ _ _ e e L r o t : c e E E E r e : c r i : e y l l d e : c o e b e 2 : E T T A R S E S R E P I _ s a t n S S S _ d t _ r 8 f s o s a u x t n a n 5 N : : B : W N T E M A s h C n t / u t _ _ _ n i u n e 0 a d a u t t u : s t 5 ' T A O A O D A S u t _ a g d s : U P D a s s a c 5 u m r d r a w e s e : d 6 : ' ' S ' R B R I I S r t U m i a S A B m : m t 5 l a i s e b a n 8 1 d 5 E d D L E S L W e p R e s t E S : e 6 e u t k v : a n s 6 1 ' a 4 : i : E : : : O s L : / a R S : : s , e e / t s t i 1 6 p t 3 r D R t : : p b : W ' / r d o e o b 4 g a 2 ' e ' : ' ' ' D o / d o a O d c d d u s i : t n - 8 ' b ' d c d r r a : / ' a s s ' R i a i i p u r a / o s 5 7 a i t i ' e e d s d h t t e d D r c r r l r e l d : e b s r u r t d d m ' e o t a g : i : e h e e o e c s i l a - e e s e r i i i d t c t b i / r c e c c a t o r d 1 c ' c ' c u s s n 1 s p a s v e ' t t t d y u e a i - d t t e ' : @ r t . s s : a c d u u u s o s m c d r 5 a u u ' / e 3 h d : e 1 r t i s s s u / o t e 9 1 s s / x c i i / 3 / u r ' : a r u u u e c 9 - ' ' c a t s r / - l s e l r p n s x t 6 5 a m u e d m i ' c a e l t / t u - 2 c p 5 i c i a b t t o o d e s 9 c h l ' n t r s / u e s l a t a n / a 2 e e u e t p s s t u d h t s e a - : . p s c e o ' t o m s e a i x 3 b 6 c r . t r s r e b o t - 5 3 o o i u t e s a n e 9 b 7 m d o s g d o s s n 2 5 9 ' u / . r m l e s 2 - ' c c e e i a u f i 5 c t o x s n t m r o 3 8 i n a q c e o n 0 0 o f m l / h m s e 2 n i p / d e w c 2 g l d i s h t 4 c u e a r e h 0 4 r . t e t n e b 5 a c a c h 1 e t o t e u h ' 2 i m u s o 6 o ' s s i s 3 n / t n t ' / u o g c p r o l a S n o g Q f a e L i d i g s r t - o e o o p t t i w o h n e s n / # u g s e i n n e g r a l ) 3.2.2. 基本操作 M D C C C C M C 8 d 5 M S S S R R R R M C M a o r r r r a O 4 e 6 a t t t e e e e a O a c c e e e e c N 0 6 6 c o o o m m m m c N c B k a a a a B T 6 b 0 B p p p o o o o B T B o e t t t t o A 4 9 1 o p p p v v v v o A o o r i i i i o I 1 7 3 o i i i i i i i o I o k n n n n k N 4 6 d k n n n n n n n k N k - C g g g g - E e 6 a - g g g g g g g - E - P o P R 4 0 3 P P R P r m n d c d r 6 8 d r d d c d d c n r r o p e a a i o I e a 6 o i a a i a a e o I o - o t t c r - D e 6 c - r t c r t c t - D - 2 s w a h e 2 2 e a h e a h w 2 2 : e o b e c : : c b e c b e o : : d r a t d d t a t a r d d o i k s u o I d p r o u s u s k o I o c s e s c M i o e c s e s e c M c k \" k A r s d k d k A k e n d e G e t i e o e G e r o o r E c g s r c r E r w c t i : k w k w u s 6 w e w w a i e d d d a s a d d d d d d r a a n n r o o o n p n o o o o o o _ n n g _ n n n g d o g n n n n n n d g g x t d e e e x i s x e e e e e e i x C x u h i u r t u r u O u $ e r $ e g $ e $ M $ e c i c M d D c d t s d t d A o o t o u : o u o N c c u c s 1 c s c D k k s k : 3 k k e e \" e l - e e r r r a m r r - w t a - C c C i p e s c p R o L t s s t o s E m I h t e m A p , - r p - T o t a o a E s t h s D e r e e y C \" \" \" u d O d d d d p ` e M o o o o S d f M c c c w T - o a A k k k n A d c u N e e e T k l D r r r U e t - - - S r e e e d n n n c r t t t o i r r r m v y y y P p e p p p O o r o o o R s i i i T e n n n S t t t u . . . p s s s ` … … … \" \" \" N A M C 1 1 1 E R 3 6 6 S E A s s s T e e e E c c c D o o o n n n d d d s s s a a a g g g o o o S U U U T p p p A T 1 1 1 U 0 3 3 S s s s e e e c c c o o o n n n d d d s s s P 0 5 6 O . 4 3 R 0 3 7 T . 2 9 S 0 / . t t 0 c c : p p 8 0 5 5 - \u0026gt; 8 0 5 5 / t c p , : : : 8 0 5 5 - \u0026gt; 8 0 5 5 / t c p N d d c A i a a M r t c E e a h S c b e t a u s s e ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-06-docker-directus/","summary":"\u003ch2 id=\"1-docker\"\u003e1. Docker\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"picture 1\" loading=\"lazy\" src=\"/images/1646728842198.png\"\u003e\u003c/p\u003e\n\u003cp\u003eclient通过运行在主机上的docker daemon操作image和container，registry提供image的发布和下载（类似npm和pip）。\u003c/p\u003e\n\u003cp\u003eDocker 镜像是一个特殊的只读文件系统，提供容器运行时所需的程序、库、资源、配置等文件，还包含一些为运行时准备的配置参数（如匿名卷、环境变量、用户等）。镜像不包含任何动态数据，其内容在构建之后也不会被改变。\u003c/p\u003e\n\u003cp\u003eContainer是image的运行实例，是运行在宿主机上的一种特殊的进程。一个image可以有多个container。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 2\" loading=\"lazy\" src=\"/images/1646933884014.png\"\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eContainer是在Image基础上叠加了一个读写层，在container内部做一些写操作后，可以commit读写层生成新的image\u003c/p\u003e\n\u003cp\u003e上图省略掉了存储配置文件信息的init层。init层位于上图image和container两层之间。存放运行操作系统时候需要写的一些目录/文件。例如/etc 目录。init层commit时候不提交。 因为没必要，每次只读层运行时候都会自动生成init层内容。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch3 id=\"11-commands\"\u003e1.1. commands\u003c/h3\u003e\n\n\n\n\u003cdiv class=\"goat svg-container \"\u003e\n  \n    \u003csvg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      font-family=\"Menlo,Lucida Console,monospace\"\n      \n        viewBox=\"0 0 632 281\"\n      \u003e\n      \u003cg transform='translate(8,16)'\u003e\n\u003ctext text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'\u003e#\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='84' fill='currentColor' style='font-size:1em'\u003e#\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='100' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='116' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='132' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='148' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='164' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='180' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='196' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='212' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='228' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='244' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='36' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='52' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='100' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='116' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='132' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='148' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='164' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='180' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='196' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='212' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='228' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='244' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='84' fill='currentColor' style='font-size:1em'\u003eC\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='100' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='116' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='132' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='148' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='164' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='180' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='196' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='212' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='228' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='244' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='84' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='100' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='116' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='132' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='148' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='164' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='180' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='196' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='212' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='228' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='244' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='84' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='100' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='116' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='132' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='148' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='164' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='180' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='196' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='212' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='228' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='244' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='84' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='100' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='116' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='132' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='148' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='164' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='180' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='196' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='212' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='228' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='244' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='84' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'\u003ep\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='84' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='100' fill='currentColor' style='font-size:1em'\u003ep\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='116' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='132' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='148' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='164' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='180' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='196' fill='currentColor' style='font-size:1em'\u003ep\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='212' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='228' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='244' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='116' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='132' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='148' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='164' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='180' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='196' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='212' fill='currentColor' style='font-size:1em'\u003ep\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='228' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='244' fill='currentColor' style='font-size:1em'\u003ex\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='116' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='132' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='148' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='196' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='228' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='244' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='164' fill='currentColor' style='font-size:1em'\u003e\u0026lt;\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='180' fill='currentColor' style='font-size:1em'\u003e$\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='196' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='212' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='228' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='244' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='116' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='132' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='148' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='164' fill='currentColor' style='font-size:1em'\u003eC\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='180' fill='currentColor' style='font-size:1em'\u003e(\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='212' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='228' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'\u003e\u0026lt;\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='116' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='132' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'\u003eO\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='180' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='196' fill='currentColor' style='font-size:1em'\u003e\u0026lt;\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='212' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='228' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='244' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='20' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='116' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='148' fill='currentColor' style='font-size:1em'\u003e\u0026lt;\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='164' fill='currentColor' style='font-size:1em'\u003eN\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='180' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='196' fill='currentColor' style='font-size:1em'\u003eC\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='212' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='244' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='20' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'\u003eM\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='132' fill='currentColor' style='font-size:1em'\u003ef\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='148' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='180' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='196' fill='currentColor' style='font-size:1em'\u003eO\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='212' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='228' fill='currentColor' style='font-size:1em'\u003e\u0026lt;\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='244' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='20' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='132' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='148' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='164' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='180' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='196' fill='currentColor' style='font-size:1em'\u003eN\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='212' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='228' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='20' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='116' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='132' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='148' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='164' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='180' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='196' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='212' fill='currentColor' style='font-size:1em'\u003ef\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='228' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='20' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='116' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='132' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='148' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='164' fill='currentColor' style='font-size:1em'\u003eN\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='180' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='196' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='212' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='228' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='244' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='20' fill='currentColor' style='font-size:1em'\u003ew\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='132' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='148' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='164' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='196' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='212' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='228' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='244' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='20' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='116' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='132' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='148' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='164' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='180' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='196' fill='currentColor' style='font-size:1em'\u003eN\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='212' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='228' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='244' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='20' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'\u003eD\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='116' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='132' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='148' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='180' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='196' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='212' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='228' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='244' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='20' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'\u003e\u0026gt;\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='132' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='148' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='164' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='180' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='196' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='212' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='228' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='244' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='20' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='132' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='148' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='164' fill='currentColor' style='font-size:1em'\u003eD\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='180' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='212' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='228' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='244' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='132' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='148' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='164' fill='currentColor' style='font-size:1em'\u003e\u0026gt;\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='180' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='196' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='212' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='228' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='244' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='132' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='148' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='180' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='196' fill='currentColor' style='font-size:1em'\u003eD\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='212' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='244' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='132' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='148' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='180' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='196' fill='currentColor' style='font-size:1em'\u003e\u0026gt;\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='228' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='244' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='20' fill='currentColor' style='font-size:1em'\u003e从\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='36' fill='currentColor' style='font-size:1em'\u003e查\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='52' fill='currentColor' style='font-size:1em'\u003e删\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='100' fill='currentColor' style='font-size:1em'\u003e查\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='116' fill='currentColor' style='font-size:1em'\u003e交\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='132' fill='currentColor' style='font-size:1em'\u003ew\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='148' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='180' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='228' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='244' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='20' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='36' fill='currentColor' style='font-size:1em'\u003e看\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='52' fill='currentColor' style='font-size:1em'\u003e除\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='100' fill='currentColor' style='font-size:1em'\u003e看\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='116' fill='currentColor' style='font-size:1em'\u003e互\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='132' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='148' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='180' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='212' fill='currentColor' style='font-size:1em'\u003e4\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='228' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='244' fill='currentColor' style='font-size:1em'\u003eq\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='20' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='36' fill='currentColor' style='font-size:1em'\u003e本\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='52' fill='currentColor' style='font-size:1em'\u003e某\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='100' fill='currentColor' style='font-size:1em'\u003e有\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='116' fill='currentColor' style='font-size:1em'\u003e式\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='132' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='148' fill='currentColor' style='font-size:1em'\u003e\u0026gt;\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='164' fill='currentColor' style='font-size:1em'\u003e删\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='196' fill='currentColor' style='font-size:1em'\u003e查\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='212' fill='currentColor' style='font-size:1em'\u003e9\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='228' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='244' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='20' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='36' fill='currentColor' style='font-size:1em'\u003e地\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='52' fill='currentColor' style='font-size:1em'\u003e个\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='100' fill='currentColor' style='font-size:1em'\u003e哪\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='116' fill='currentColor' style='font-size:1em'\u003e运\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='132' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='164' fill='currentColor' style='font-size:1em'\u003e除\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='180' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='196' fill='currentColor' style='font-size:1em'\u003e看\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='212' fill='currentColor' style='font-size:1em'\u003ef\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='228' fill='currentColor' style='font-size:1em'\u003e\u0026gt;\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='244' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='20' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='36' fill='currentColor' style='font-size:1em'\u003e有\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='52' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='100' fill='currentColor' style='font-size:1em'\u003e些\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='116' fill='currentColor' style='font-size:1em'\u003e行\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='132' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='164' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='180' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='196' fill='currentColor' style='font-size:1em'\u003e端\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='212' fill='currentColor' style='font-size:1em'\u003e7\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='244' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='20' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='36' fill='currentColor' style='font-size:1em'\u003e哪\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='52' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='100' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='164' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='196' fill='currentColor' style='font-size:1em'\u003e口\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='212' fill='currentColor' style='font-size:1em'\u003e9\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='244' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='20' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='36' fill='currentColor' style='font-size:1em'\u003e些\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='52' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='100' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='148' fill='currentColor' style='font-size:1em'\u003e重\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='164' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='180' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='196' fill='currentColor' style='font-size:1em'\u003e映\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='212' fill='currentColor' style='font-size:1em'\u003e6\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='228' fill='currentColor' style='font-size:1em'\u003e对\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='20' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='36' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='52' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='100' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='132' fill='currentColor' style='font-size:1em'\u003e后\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='148' fill='currentColor' style='font-size:1em'\u003e启\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='164' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='180' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='196' fill='currentColor' style='font-size:1em'\u003e射\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='212' fill='currentColor' style='font-size:1em'\u003e0\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='228' fill='currentColor' style='font-size:1em'\u003e于\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='244' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='20' fill='currentColor' style='font-size:1em'\u003ey\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='36' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='52' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='100' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='132' fill='currentColor' style='font-size:1em'\u003e台\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='164' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='180' fill='currentColor' style='font-size:1em'\u003eq\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='196' fill='currentColor' style='font-size:1em'\u003e信\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='212' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='228' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='244' fill='currentColor' style='font-size:1em'\u003eb\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='20' fill='currentColor' style='font-size:1em'\u003e下\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='36' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='52' fill='currentColor' style='font-size:1em'\u003e，\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='100' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='132' fill='currentColor' style='font-size:1em'\u003e运\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='164' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='180' fill='currentColor' style='font-size:1em'\u003e)\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='196' fill='currentColor' style='font-size:1em'\u003e息\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='212' fill='currentColor' style='font-size:1em'\u003eb\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='228' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='244' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='20' fill='currentColor' style='font-size:1em'\u003e载\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='36' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='52' fill='currentColor' style='font-size:1em'\u003e可\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='132' fill='currentColor' style='font-size:1em'\u003e行\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='164' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='212' fill='currentColor' style='font-size:1em'\u003e7\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='228' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='244' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='20' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='36' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='52' fill='currentColor' style='font-size:1em'\u003e以\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='100' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='164' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='212' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='228' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='244' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='20' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='52' fill='currentColor' style='font-size:1em'\u003e只\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='100' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='164' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='212' fill='currentColor' style='font-size:1em'\u003e4\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='228' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='244' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='20' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='52' fill='currentColor' style='font-size:1em'\u003e写\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='100' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='180' fill='currentColor' style='font-size:1em'\u003e删\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='212' fill='currentColor' style='font-size:1em'\u003e:\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='228' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='244' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='320' y='20' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='320' y='52' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='320' y='180' fill='currentColor' style='font-size:1em'\u003e除\u003c/text\u003e\n\u003ctext text-anchor='middle' x='320' y='212' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='328' y='20' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='328' y='52' fill='currentColor' style='font-size:1em'\u003eD\u003c/text\u003e\n\u003ctext text-anchor='middle' x='328' y='180' fill='currentColor' style='font-size:1em'\u003e所\u003c/text\u003e\n\u003ctext text-anchor='middle' x='328' y='212' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='328' y='228' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='336' y='20' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='336' y='52' fill='currentColor' style='font-size:1em'\u003e的\u003c/text\u003e\n\u003ctext text-anchor='middle' x='336' y='180' fill='currentColor' style='font-size:1em'\u003e有\u003c/text\u003e\n\u003ctext text-anchor='middle' x='336' y='212' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='336' y='228' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='336' y='244' fill='currentColor' style='font-size:1em'\u003e在\u003c/text\u003e\n\u003ctext text-anchor='middle' x='344' y='20' fill='currentColor' style='font-size:1em'\u003ew\u003c/text\u003e\n\u003ctext text-anchor='middle' x='344' y='52' fill='currentColor' style='font-size:1em'\u003e前\u003c/text\u003e\n\u003ctext text-anchor='middle' x='344' y='180' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='344' y='212' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='344' y='228' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='344' y='244' fill='currentColor' style='font-size:1em'\u003e名\u003c/text\u003e\n\u003ctext text-anchor='middle' x='352' y='20' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='352' y='52' fill='currentColor' style='font-size:1em'\u003e几\u003c/text\u003e\n\u003ctext text-anchor='middle' x='352' y='180' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='352' y='212' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='352' y='228' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='352' y='244' fill='currentColor' style='font-size:1em'\u003e字\u003c/text\u003e\n\u003ctext text-anchor='middle' x='360' y='20' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='360' y='52' fill='currentColor' style='font-size:1em'\u003e位\u003c/text\u003e\n\u003ctext text-anchor='middle' x='360' y='180' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='360' y='212' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='360' y='228' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='360' y='244' fill='currentColor' style='font-size:1em'\u003e是\u003c/text\u003e\n\u003ctext text-anchor='middle' x='368' y='20' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='368' y='180' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='368' y='212' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='368' y='228' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='376' y='20' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='376' y='180' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='376' y='212' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='376' y='228' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='376' y='244' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='384' y='180' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='384' y='212' fill='currentColor' style='font-size:1em'\u003ef\u003c/text\u003e\n\u003ctext text-anchor='middle' x='384' y='228' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='384' y='244' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='392' y='20' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='392' y='180' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='392' y='212' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='392' y='228' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='392' y='244' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='400' y='20' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='400' y='180' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='400' y='212' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='400' y='244' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='408' y='20' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='408' y='180' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='408' y='212' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='408' y='228' fill='currentColor' style='font-size:1em'\u003e启\u003c/text\u003e\n\u003ctext text-anchor='middle' x='408' y='244' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='416' y='20' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='416' y='212' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='416' y='228' fill='currentColor' style='font-size:1em'\u003e动\u003c/text\u003e\n\u003ctext text-anchor='middle' x='416' y='244' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='424' y='20' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='424' y='212' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='424' y='228' fill='currentColor' style='font-size:1em'\u003e的\u003c/text\u003e\n\u003ctext text-anchor='middle' x='424' y='244' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='432' y='212' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='432' y='228' fill='currentColor' style='font-size:1em'\u003e容\u003c/text\u003e\n\u003ctext text-anchor='middle' x='432' y='244' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='440' y='212' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='440' y='228' fill='currentColor' style='font-size:1em'\u003e器\u003c/text\u003e\n\u003ctext text-anchor='middle' x='440' y='244' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='448' y='212' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='448' y='228' fill='currentColor' style='font-size:1em'\u003e，\u003c/text\u003e\n\u003ctext text-anchor='middle' x='448' y='244' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='456' y='228' fill='currentColor' style='font-size:1em'\u003e可\u003c/text\u003e\n\u003ctext text-anchor='middle' x='456' y='244' fill='currentColor' style='font-size:1em'\u003eq\u003c/text\u003e\n\u003ctext text-anchor='middle' x='464' y='228' fill='currentColor' style='font-size:1em'\u003e以\u003c/text\u003e\n\u003ctext text-anchor='middle' x='464' y='244' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='472' y='212' fill='currentColor' style='font-size:1em'\u003e拷\u003c/text\u003e\n\u003ctext text-anchor='middle' x='472' y='228' fill='currentColor' style='font-size:1em'\u003e用\u003c/text\u003e\n\u003ctext text-anchor='middle' x='472' y='244' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='480' y='212' fill='currentColor' style='font-size:1em'\u003e贝\u003c/text\u003e\n\u003ctext text-anchor='middle' x='480' y='228' fill='currentColor' style='font-size:1em'\u003e这\u003c/text\u003e\n\u003ctext text-anchor='middle' x='480' y='244' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='488' y='212' fill='currentColor' style='font-size:1em'\u003e文\u003c/text\u003e\n\u003ctext text-anchor='middle' x='488' y='228' fill='currentColor' style='font-size:1em'\u003e个\u003c/text\u003e\n\u003ctext text-anchor='middle' x='488' y='244' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='496' y='212' fill='currentColor' style='font-size:1em'\u003e件\u003c/text\u003e\n\u003ctext text-anchor='middle' x='496' y='228' fill='currentColor' style='font-size:1em'\u003e来\u003c/text\u003e\n\u003ctext text-anchor='middle' x='496' y='244' fill='currentColor' style='font-size:1em'\u003e的\u003c/text\u003e\n\u003ctext text-anchor='middle' x='504' y='212' fill='currentColor' style='font-size:1em'\u003e到\u003c/text\u003e\n\u003ctext text-anchor='middle' x='504' y='228' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='504' y='244' fill='currentColor' style='font-size:1em'\u003e容\u003c/text\u003e\n\u003ctext text-anchor='middle' x='512' y='212' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='512' y='228' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='512' y='244' fill='currentColor' style='font-size:1em'\u003e器\u003c/text\u003e\n\u003ctext text-anchor='middle' x='520' y='212' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='520' y='228' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='520' y='244' fill='currentColor' style='font-size:1em'\u003e内\u003c/text\u003e\n\u003ctext text-anchor='middle' x='528' y='212' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='528' y='228' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='528' y='244' fill='currentColor' style='font-size:1em'\u003e执\u003c/text\u003e\n\u003ctext text-anchor='middle' x='536' y='212' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='536' y='228' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='536' y='244' fill='currentColor' style='font-size:1em'\u003e行\u003c/text\u003e\n\u003ctext text-anchor='middle' x='544' y='212' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='544' y='228' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='544' y='244' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='552' y='212' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='552' y='228' fill='currentColor' style='font-size:1em'\u003e到\u003c/text\u003e\n\u003ctext text-anchor='middle' x='552' y='244' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='560' y='212' fill='currentColor' style='font-size:1em'\u003e里\u003c/text\u003e\n\u003ctext text-anchor='middle' x='560' y='228' fill='currentColor' style='font-size:1em'\u003e控\u003c/text\u003e\n\u003ctext text-anchor='middle' x='568' y='212' fill='currentColor' style='font-size:1em'\u003e面\u003c/text\u003e\n\u003ctext text-anchor='middle' x='568' y='228' fill='currentColor' style='font-size:1em'\u003e制\u003c/text\u003e\n\u003ctext text-anchor='middle' x='576' y='228' fill='currentColor' style='font-size:1em'\u003e台\u003c/text\u003e\n\u003ctext text-anchor='middle' x='584' y='228' fill='currentColor' style='font-size:1em'\u003e上\u003c/text\u003e\n\u003ctext text-anchor='middle' x='592' y='228' fill='currentColor' style='font-size:1em'\u003e看\u003c/text\u003e\n\u003ctext text-anchor='middle' x='600' y='228' fill='currentColor' style='font-size:1em'\u003e输\u003c/text\u003e\n\u003ctext text-anchor='middle' x='608' y='228' fill='currentColor' style='font-size:1em'\u003e出\u003c/text\u003e\n\u003ctext text-anchor='middle' x='616' y='228' fill='currentColor' style='font-size:1em'\u003e。\u003c/text\u003e\n\u003c/g\u003e\n\n    \u003c/svg\u003e\n  \n\u003c/div\u003e\n\u003ch3 id=\"12-dockerfile\"\u003e1.2. Dockerfile\u003c/h3\u003e\n\u003cp\u003e是用来build image的配置文件，从一个基础image开始，运行一些列配置命令，得到另外一个image\u003c/p\u003e","title":"Docker"},{"content":"1. 简介 下图出自 Learning Curves (for different programming languages) 虽然文章调侃为主， 但也看出来作者对单元测试的态度。\n对Python程序员来讲，随着经验的增长， 在掌握了单元测试之后， 个人的生产力得到一个突变（至于掌握装饰器，会自我感觉膨胀，但效率提升不明显）。\n很多年前刚走出校门，还是C++年代。 有个前北电的Java大牛布道，给开发团队推荐cpp-unit，说单元测试 “可以显著提高单兵作战能力”。斗转星移，南征北战，北电的Eddie已失去联系，但他的布道显然成功了。我在不同的项目中实践过单元测试：\ncpp unit junit luaunit python unittest jest 虽然语言不同，但unittest思想都一样，都有setup，teardown，testcase，assert/expect，mock这些概念。\n对单元测试“提高单兵作战能力”的说法，我“不能认同更多”。\n2. 单元测试的好处 unittest的好处主要在两个方面。\n2.1. 鼓励先设计接口 要测试驱动，就要先想怎么测，从而促进在很早期就从接口定义的角度考虑问题。也促进了模块的低耦合高内聚。 另外，还带来一个额外的好处，TDD也会沉淀出类似文档的测试用例。若干年后回顾一个软件时候，看看用例，基本也了解当时的思路了。\n2.2. 跑一遍测试的成本低 测试用例的写法规范，测试框架支持方便的执行和反馈结果，这样跑一遍测试没有时间和精力的负担，随时跑。 经常跑测试，持续的集成，有问题也能早暴露。 接口稳定后，有测试用例做质量保障，做重构也方便。\n3. 举个例子 假如在开发一个API服务器。 做单元测试从粗到细可以有几个不同的粒度：\n从API层面，用http client模拟请求，然后assert返回的结果是否符合预期 从Handler层面，模拟http请求的header，params，body等，然后喂给Handler，看返回的结构是否符合预期 模块层面，如果Handler之下还有其它业务逻辑模块，可以针对模块接口做单元测试 以上 1， 已经可以看作系统测试（端到端测试）了。\n2相对于1有一些额外的好处：一般情况下，对应一个请求的处理，除了Hanlder之外，还有一些中间件来做预处理。 对于1来说，必须把中间件的功能和Handler本身作为一个整体测试。 不够灵活。\n2相对于1又有一些缺点，需要Mock。 根据所用framework不同，需要mock输入输出的数据结构。 好在一般情况下，mock都比较简单，有很多framework也有第三方做好的mock库。 例如，下面对JS express 的Handler测试：\nimport { posthandler } from \u0026#39;../src/handlers/post\u0026#39;; import { getMockReq, getMockRes } from \u0026#39;@jest-mock/express\u0026#39;; // generate a mocked response and next function, with provided values const { res, next } = getMockRes({ }) test(\u0026#39;check post handler returns token in JSON body\u0026#39;, async () =\u0026gt; { // generate a mock request with params const req = getMockReq({ params: { id: \u0026#39;abc-def\u0026#39; }, headers:{authorization:\u0026#39;this is my token\u0026#39;} }) // provide the mock req, res, and next to assert await posthandler(req, res) expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ authorization: \u0026#39;this is my token\u0026#39;, }), ) }) 引入的第三方jest-mock/express，可以帮助来生成输入数据，检查输出数据。see？ easy.\n4. 结论 大多数规模的项目，只要引入简单的几个概念，开发人员一两个小时就可以入门。 把对不同模块的assert组织到测试用例里，能方便的运行测试用例就很OK了。 投入小，产出高。初级程序员进阶必备。\n5. 参考资料 Learning Curves (for different programming languages) ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-06-unittest/","summary":"\u003ch2 id=\"1-简介\"\u003e1. 简介\u003c/h2\u003e\n\u003cp\u003e下图出自 \u003ca href=\"https://github.com/Dobiasd/articles/blob/master/programming_language_learning_curves.md\"\u003eLearning Curves (for different programming languages)\u003c/a\u003e 虽然文章调侃为主， 但也看出来作者对单元测试的态度。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 7\" loading=\"lazy\" src=\"/images/1646395927979.png\"\u003e\u003c/p\u003e\n\u003cp\u003e对Python程序员来讲，随着经验的增长， 在掌握了单元测试之后， 个人的生产力得到一个突变（至于掌握装饰器，会自我感觉膨胀，但效率提升不明显）。\u003c/p\u003e\n\u003cp\u003e很多年前刚走出校门，还是C++年代。 有个前北电的Java大牛布道，给开发团队推荐cpp-unit，说单元测试 “可以显著提高单兵作战能力”。斗转星移，南征北战，北电的Eddie已失去联系，但他的布道显然成功了。我在不同的项目中实践过单元测试：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ecpp unit\u003c/li\u003e\n\u003cli\u003ejunit\u003c/li\u003e\n\u003cli\u003eluaunit\u003c/li\u003e\n\u003cli\u003epython unittest\u003c/li\u003e\n\u003cli\u003ejest\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e虽然语言不同，但unittest思想都一样，都有setup，teardown，testcase，assert/expect，mock这些概念。\u003c/p\u003e\n\u003cp\u003e对单元测试“提高单兵作战能力”的说法，我“不能认同更多”。\u003c/p\u003e\n\u003ch2 id=\"2-单元测试的好处\"\u003e2. 单元测试的好处\u003c/h2\u003e\n\u003cp\u003eunittest的好处主要在两个方面。\u003c/p\u003e\n\u003ch3 id=\"21-鼓励先设计接口\"\u003e2.1. 鼓励先设计接口\u003c/h3\u003e\n\u003cp\u003e要测试驱动，就要先想怎么测，从而促进在很早期就从接口定义的角度考虑问题。也促进了模块的低耦合高内聚。\n另外，还带来一个额外的好处，TDD也会沉淀出类似文档的测试用例。若干年后回顾一个软件时候，看看用例，基本也了解当时的思路了。\u003c/p\u003e\n\u003ch3 id=\"22-跑一遍测试的成本低\"\u003e2.2. 跑一遍测试的成本低\u003c/h3\u003e\n\u003cp\u003e测试用例的写法规范，测试框架支持方便的执行和反馈结果，这样跑一遍测试没有时间和精力的负担，随时跑。 经常跑测试，持续的集成，有问题也能早暴露。 接口稳定后，有测试用例做质量保障，做重构也方便。\u003c/p\u003e\n\u003ch2 id=\"3-举个例子\"\u003e3. 举个例子\u003c/h2\u003e\n\u003cp\u003e假如在开发一个API服务器。 做单元测试从粗到细可以有几个不同的粒度：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e从API层面，用http client模拟请求，然后assert返回的结果是否符合预期\u003c/li\u003e\n\u003cli\u003e从Handler层面，模拟http请求的header，params，body等，然后喂给Handler，看返回的结构是否符合预期\u003c/li\u003e\n\u003cli\u003e模块层面，如果Handler之下还有其它业务逻辑模块，可以针对模块接口做单元测试\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e以上 1， 已经可以看作系统测试（端到端测试）了。\u003c/p\u003e\n\u003cp\u003e2相对于1有一些额外的好处：一般情况下，对应一个请求的处理，除了Hanlder之外，还有一些中间件来做预处理。 对于1来说，必须把中间件的功能和Handler本身作为一个整体测试。 不够灵活。\u003c/p\u003e\n\u003cp\u003e2相对于1又有一些缺点，需要Mock。 根据所用framework不同，需要mock输入输出的数据结构。 好在一般情况下，mock都比较简单，有很多framework也有第三方做好的mock库。 例如，下面对JS express 的Handler测试：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003eposthandler\u003c/span\u003e } \u003cspan style=\"color:#a6e22e\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;../src/handlers/post\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003egetMockReq\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003egetMockRes\u003c/span\u003e } \u003cspan style=\"color:#a6e22e\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;@jest-mock/express\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// generate a mocked response and next function, with provided values\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003enext\u003c/span\u003e } \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egetMockRes\u003c/span\u003e({\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003etest\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;check post handler returns token in JSON body\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003easync\u003c/span\u003e () =\u0026gt; {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// generate a mock request with params\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egetMockReq\u003c/span\u003e({ \u003cspan style=\"color:#a6e22e\"\u003eparams\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;abc-def\u0026#39;\u003c/span\u003e }, \u003cspan style=\"color:#a6e22e\"\u003eheaders\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e{\u003cspan style=\"color:#a6e22e\"\u003eauthorization\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;this is my token\u0026#39;\u003c/span\u003e} })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// provide the mock req, res, and next to assert\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e    \u003cspan style=\"color:#66d9ef\"\u003eawait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eposthandler\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ereq\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eexpect\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eres\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ejson\u003c/span\u003e).\u003cspan style=\"color:#a6e22e\"\u003etoHaveBeenCalledWith\u003c/span\u003e(\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003eexpect\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eobjectContaining\u003c/span\u003e({\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eauthorization\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;this is my token\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      }),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  })\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e引入的第三方jest-mock/express，可以帮助来生成输入数据，检查输出数据。see？ easy.\u003c/p\u003e","title":"单元测试，测试驱动开发"},{"content":"1. 起因 在配置Directus使用钉钉扫码登录时候，发现钉钉的免密登录（OAuth 2）和RFC规范不一致。 需要做协议转换后才能和Directus正常通信。 需求比较小众，没有现成的软件，只好自己动手了。\n2. 主要功能 能作为API通信的中间人， 转发客户端和API服务器之间的通信， 记录LOG，方便分析协议； 作为中间人，能修改请求的内容， 修改响应的内容；可以做协议适配，转换。 APIPROXY is a RESTFUL API proxy, monitor and adaptor.\nForward RESTFUL API to another host. It\u0026rsquo;s man in the middle who can monitor and modify the header and body of the API Request \u0026amp; Response. Good for protocol study and adaptation.\nFeatures\nAPI proxy: forward any incoming API to remote sever and return the response back to client. API monitor: you can get detailed log of the API req and res in the log file. API adpator: modify the request and response on the fly while forwarding, including parameters, body, http headers etc. API mock server: you can add your own API for testing purpose easily. 3. 目前状态 初步实现了对钉钉OAuth2协议的RFC6749兼容封装。 目前Directus已经可以通过它的翻译支持钉钉免密登录。\n4. 参考 APIPROXY ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-05-api-proxy/","summary":"\u003ch2 id=\"1-起因\"\u003e1. 起因\u003c/h2\u003e\n\u003cp\u003e在配置Directus使用钉钉扫码登录时候，发现钉钉的免密登录（OAuth 2）和RFC规范不一致。 需要做协议转换后才能和Directus正常通信。 需求比较小众，没有现成的软件，只好自己动手了。\u003c/p\u003e\n\u003ch2 id=\"2-主要功能\"\u003e2. 主要功能\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e能作为API通信的中间人， 转发客户端和API服务器之间的通信， 记录LOG，方便分析协议；\u003c/li\u003e\n\u003cli\u003e作为中间人，能修改请求的内容， 修改响应的内容；可以做协议适配，转换。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cblockquote\u003e\n\u003cp\u003eAPIPROXY is a RESTFUL API proxy, monitor and adaptor.\u003c/p\u003e\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eForward RESTFUL API to another host. It\u0026rsquo;s man in the middle who can monitor and modify the header and body of the API Request \u0026amp; Response. Good for protocol study and adaptation.\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003eFeatures\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eAPI proxy: forward any incoming API to remote sever and return the response back to client.\u003c/li\u003e\n\u003cli\u003eAPI monitor: you can get detailed log of the API req and res in the log file.\u003c/li\u003e\n\u003cli\u003eAPI adpator: modify the request and response on the fly while forwarding, including parameters, body, http headers etc.\u003c/li\u003e\n\u003cli\u003eAPI mock server: you can add your own API for testing purpose easily.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"3-目前状态\"\u003e3. 目前状态\u003c/h2\u003e\n\u003cp\u003e初步实现了对钉钉OAuth2协议的RFC6749兼容封装。 目前Directus已经可以通过它的翻译支持钉钉免密登录。\u003c/p\u003e","title":"一个简单的API Proxy"},{"content":" Typescript学习笔记。From zero to hero。\n1. Array,Tuple,Union,Enum // Basic Types let id: number = 5 //变量后面加类型，用冒号隔开 let company: string = \u0026#39;Traversy Media\u0026#39; let isPublished: boolean = true let x: any = \u0026#39;Hello\u0026#39; //any类型变量，可以放任何类型数据 let ids: number[] = [1, 2, 3, 4, 5] //不定长数组 区别传统静态语言的int a[4]; let arr: any[] = [1, true, \u0026#39;Hello\u0026#39;] //any数组，可以混合各种值 // Tuple let person: [number, string, boolean] = [1, \u0026#39;Brad\u0026#39;, true] //元组：已知元素数量和类型的数组，各元素的类型不必相同。 // Tuple Array let employees: [number, string][] //每个元素是元组的数组 employee = [ [1, \u0026#39;Brad\u0026#39;], [2, \u0026#39;John\u0026#39;], [3, \u0026#39;Jill\u0026#39;], ] // Union let pid: string | number //联合 /* 以下是C语言的联合，都是一个变量可以存几种不同类型数据 union data{ int n; char ch; double f; }; */ pid = \u0026#39;22\u0026#39; // Enum enum Direction1 { Up = 1, Down, Left, Right, } enum Direction2 { Up = \u0026#39;Up\u0026#39;, Down = \u0026#39;Down\u0026#39;, Left = \u0026#39;Left\u0026#39;, Right = \u0026#39;Right\u0026#39;, } 2. Map //定义 type MapType = { [id: string]: string; } //实例化 const map: MapType = {}; map[\u0026#39;a\u0026#39;] = \u0026#39;b\u0026#39;; map[\u0026#39;c\u0026#39;] = \u0026#39;d\u0026#39;; //删除 delete map[\u0026#39;c\u0026#39;]; //枚举 for (let i in map) { console.log(map[i]); } //得到包含所有key的数组 console.log(Object.keys(map)); //另外一种用Record的实现方式 const map: Record\u0026lt;string, string\u0026gt; = {}; map[\u0026#39;a\u0026#39;] = \u0026#39;b\u0026#39;; map[\u0026#39;c\u0026#39;] = \u0026#39;d\u0026#39;; 3. Object // Objects type User = { id: number name: string } const user: User = { id: 1, name: \u0026#39;John\u0026#39;, } // Type Assertion 类型的选择 let cid: any = 1 // let customerId = \u0026lt;number\u0026gt;cid 从any到 number 的选择 let customerId = cid as number 4. Function // Functions function addNum(x: number, y: number): number { return x + y } // Void function log(message: string | number): void { console.log(message) } 5. Interface, Class // Interfaces interface UserInterface { readonly id: number name: string age?: number } const user1: UserInterface = { id: 1, name: \u0026#39;John\u0026#39;, } interface MathFunc { (x: number, y: number): number } const add: MathFunc = (x: number, y: number): number =\u0026gt; x + y const sub: MathFunc = (x: number, y: number): number =\u0026gt; x - y interface PersonInterface { id: number name: string register(): string } // Classes class Person implements PersonInterface { id: number name: string constructor(id: number, name: string) { this.id = id this.name = name } register() { return `${this.name} is now registered` } } const brad = new Person(1, \u0026#39;Brad Traversy\u0026#39;) const mike = new Person(2, \u0026#39;Mike Jordan\u0026#39;) // Subclasses class Employee extends Person { position: string constructor(id: number, name: string, position: string) { super(id, name) this.position = position } } const emp = new Employee(3, \u0026#39;Shawn\u0026#39;, \u0026#39;Developer\u0026#39;) 6. Generics // Generics =\u0026gt; C++的泛型 function getArray\u0026lt;T\u0026gt;(items: T[]): T[] { return new Array().concat(items) } let numArray = getArray\u0026lt;number\u0026gt;([1, 2, 3, 4]) let strArray = getArray\u0026lt;string\u0026gt;([\u0026#39;brad\u0026#39;, \u0026#39;John\u0026#39;, \u0026#39;Jill\u0026#39;]) strArray.push(1) // Throws error 7. FAQ 7.1. type alias or interface https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces\n区别不大，一般可尽量用interface type对简单类型的别名。常用给枚举类型起一个名字，后面好用。 例如 type ID = number | string; type ABC = \u0026#39;a\u0026#39; | \u0026#39;b\u0026#39; | \u0026#39;c\u0026#39; 8. d.ts 文件 这个类似C语言的头文件。 里面都是类型定义。 有了这些，就可以知道在typescript里面如何使用一个javascript库了。DefinitelyTyped是typescript社区维护的头文件库。第三方javascript的type都可以在那里找。\n9. 参考资料 https://www.youtube.com/watch?v=BCg4U1FzODs https://github.com/DefinitelyTyped/DefinitelyTyped ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-04-tyepscript-in-30-mins/","summary":"\u003cblockquote\u003e\n\u003cp\u003eTypescript学习笔记。From zero to hero。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"1-arraytupleunionenum\"\u003e1. Array,Tuple,Union,Enum\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Basic Types\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e         \u003cspan style=\"color:#75715e\"\u003e//变量后面加类型，用冒号隔开\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecompany\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Traversy Media\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eisPublished\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eboolean\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ex\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eany\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Hello\u0026#39;\u003c/span\u003e       \u003cspan style=\"color:#75715e\"\u003e//any类型变量，可以放任何类型数据\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eids\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e[] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e]  \u003cspan style=\"color:#75715e\"\u003e//不定长数组 区别传统静态语言的int a[4]; \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003earr\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eany\u003c/span\u003e[] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Hello\u0026#39;\u003c/span\u003e]  \u003cspan style=\"color:#75715e\"\u003e//any数组，可以混合各种值\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Tuple\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eperson\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [\u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003eboolean\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Brad\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e]  \u003cspan style=\"color:#75715e\"\u003e//元组：已知元素数量和类型的数组，各元素的类型不必相同。\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Tuple Array\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eemployees\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [\u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e][]  \u003cspan style=\"color:#75715e\"\u003e//每个元素是元组的数组\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eemployee\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  [\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Brad\u0026#39;\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  [\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;John\u0026#39;\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  [\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Jill\u0026#39;\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Union\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003epid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e    \u003cspan style=\"color:#75715e\"\u003e//联合  \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e/*  以下是C语言的联合，都是一个变量可以存几种不同类型数据\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003eunion data{\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e    int n;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e    char ch;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e    double f;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e};\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003epid\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;22\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Enum\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eenum\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDirection1\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eUp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eDown\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eRight\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eenum\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eDirection2\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eUp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Up\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eDown\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Down\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eLeft\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Left\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eRight\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Right\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"2-map\"\u003e2. Map\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//定义\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMapType\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e { \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    [\u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e]\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e; \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//实例化\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMapType\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;a\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;b\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;c\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;d\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//删除\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003edelete\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;c\u0026#39;\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//枚举\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e (\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003econsole\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e[\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e]);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//得到包含所有key的数组\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003econsole\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e(Object.\u003cspan style=\"color:#a6e22e\"\u003ekeys\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e));\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//另外一种用Record的实现方式\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eRecord\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {};\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;a\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;b\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e[\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;c\u0026#39;\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;d\u0026#39;\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"3-object\"\u003e3. Object\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Objects\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003etype\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003euser\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eUser\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;John\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Type Assertion  类型的选择\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eany\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// let customerId = \u0026lt;number\u0026gt;cid  从any到 number 的选择\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecustomerId\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecid\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eas\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"4-function\"\u003e4. Function\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Functions\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eaddNum\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ex\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ey\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ex\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ey\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Void\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003emessage\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003evoid\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003econsole\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003emessage\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"5-interface-class\"\u003e5. Interface, Class\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Interfaces\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003einterface\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eUserInterface\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ereadonly\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eage\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e?:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003euser1\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eUserInterface\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;John\u0026#39;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einterface\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMathFunc\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  (\u003cspan style=\"color:#a6e22e\"\u003ex\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ey\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eadd\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMathFunc\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003ex\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ey\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e =\u0026gt; \u003cspan style=\"color:#a6e22e\"\u003ex\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ey\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esub\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eMathFunc\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003ex\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ey\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e)\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e =\u0026gt; \u003cspan style=\"color:#a6e22e\"\u003ex\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ey\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003einterface\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePersonInterface\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eregister\u003c/span\u003e()\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Classes\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePerson\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eimplements\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePersonInterface\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003econstructor\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eregister\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e${\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e}\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e is now registered`\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebrad\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enew\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePerson\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Brad Traversy\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emike\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enew\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePerson\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Mike Jordan\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Subclasses\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEmployee\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eextends\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ePerson\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eposition\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003econstructor\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eposition\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003esuper\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eid\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eposition\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eposition\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eemp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enew\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eEmployee\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Shawn\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Developer\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"6-generics\"\u003e6. Generics\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Generics =\u0026gt; C++的泛型\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egetArray\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eT\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eitems\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eT\u003c/span\u003e[])\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eT\u003c/span\u003e[] {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enew\u003c/span\u003e Array().\u003cspan style=\"color:#a6e22e\"\u003econcat\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eitems\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enumArray\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egetArray\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003enumber\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e([\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003elet\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003estrArray\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egetArray\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003estring\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e([\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;brad\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;John\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Jill\u0026#39;\u003c/span\u003e])\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003estrArray\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003epush\u003c/span\u003e(\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e) \u003cspan style=\"color:#75715e\"\u003e// Throws error\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"7-faq\"\u003e7. FAQ\u003c/h2\u003e\n\u003ch3 id=\"71-type-alias-or-interface\"\u003e7.1. type alias or interface\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces\"\u003ehttps://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces\u003c/a\u003e\u003c/p\u003e","title":"Typescript"},{"content":"和python不同， Typescript/Javascript模块导入导出方式五花八门。\n目前因为ES6已经标准化，推荐用import/export。 历史遗留的require/exports由于存在数目巨大的npm库，也还将长期共存。\n1. ES6模块导入导出 1.1. 语法 三种export方式，两种import方式\nexport import export var; import {var} from module export {var}; import {var} from module export default var import var from module 另外可以用\nimport * as module_alias from module 导入模块所有export的变量到一个对象 module_alias 中。\n1.2. 例子 1.2.1. testEs6Export.ts \u0026#39;use strict\u0026#39; //导出变量 export const a = \u0026#39;100\u0026#39;; //导出方法 export const dogSay = function(){ console.log(\u0026#39;wang wang\u0026#39;); } //导出方法第二种 function catSay(){ console.log(\u0026#39;miao miao\u0026#39;); } export { catSay }; //export default导出 const m = 100; export default m; //export defult const m = 100;// 这里不能写这种格式。 1.2.2. index.ts import { dogSay, catSay } from \u0026#39;./testEs6Export\u0026#39;; //导出了 export 方法 import m from \u0026#39;./testEs6Export\u0026#39;; //导出了 export default import * as testModule from \u0026#39;./testEs6Export\u0026#39;; //as 集合成对象导出 2. CommonJS 模块导入导出 2.1. 语法 模块中有module.export对象，里面存放的就是export的变量。\nexports = module.exports = {}; 另外有一个exports，是对module.exports的引用。\n使用require来把模块export的变量取出来。\n注意： 实际export是module.exports 所指向的变量。\n2.2. 例子 //utils.js let a = 100; console.log(module.exports); //能打印出结果为：{} console.log(exports); //能打印出结果为：{} exports.a = 200; //这里辛苦劳作帮 module.exports 的内容给改成 {a : 200} exports = \u0026#39;指向其他内存区\u0026#39;; //这里把exports的指向指走 //test.js var a = require(\u0026#39;/utils\u0026#39;); console.log(a) // 打印为 {a : 200} 3. 参考资料 https://segmentfault.com/a/1190000010426778 ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-04-typescript-modules/","summary":"\u003cp\u003e和python不同， Typescript/Javascript模块导入导出方式五花八门。\u003c/p\u003e\n\u003cp\u003e目前因为ES6已经标准化，推荐用import/export。\n历史遗留的require/exports由于存在数目巨大的npm库，也还将长期共存。\u003c/p\u003e\n\u003ch2 id=\"1-es6模块导入导出\"\u003e1. ES6模块导入导出\u003c/h2\u003e\n\u003ch3 id=\"11-语法\"\u003e1.1. 语法\u003c/h3\u003e\n\u003cp\u003e三种export方式，两种import方式\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003eexport\u003c/th\u003e\n          \u003cth\u003eimport\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eexport var;\u003c/td\u003e\n          \u003ctd\u003eimport {var} from module\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eexport {var};\u003c/td\u003e\n          \u003ctd\u003eimport {var} from module\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eexport default var\u003c/td\u003e\n          \u003ctd\u003eimport var from module\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e另外可以用\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eas\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emodule_alias\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emodule\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e导入模块所有export的变量到一个对象 module_alias 中。\u003c/p\u003e\n\u003ch3 id=\"12-例子\"\u003e1.2. 例子\u003c/h3\u003e\n\u003ch4 id=\"121-testes6exportts\"\u003e1.2.1. testEs6Export.ts\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;use strict\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//导出变量\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eexport\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ea\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;100\u0026#39;\u003c/span\u003e;  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e//导出方法\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eexport\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edogSay\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e(){ \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003econsole\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;wang wang\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e \u003cspan style=\"color:#75715e\"\u003e//导出方法第二种\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ecatSay\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e   \u003cspan style=\"color:#a6e22e\"\u003econsole\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;miao miao\u0026#39;\u003c/span\u003e); \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eexport\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003ecatSay\u003c/span\u003e };   \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//export default导出\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003em\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eexport\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edefault\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003em\u003c/span\u003e; \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e//export defult const m = 100;// 这里不能写这种格式。\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch4 id=\"122-indexts\"\u003e1.2.2. index.ts\u003c/h4\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003edogSay\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003ecatSay\u003c/span\u003e } \u003cspan style=\"color:#a6e22e\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;./testEs6Export\u0026#39;\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e//导出了 export 方法 \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003em\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;./testEs6Export\u0026#39;\u003c/span\u003e;  \u003cspan style=\"color:#75715e\"\u003e//导出了 export default \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eas\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003etestModule\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;./testEs6Export\u0026#39;\u003c/span\u003e; \u003cspan style=\"color:#75715e\"\u003e//as 集合成对象导出\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"2-commonjs-模块导入导出\"\u003e2. CommonJS 模块导入导出\u003c/h2\u003e\n\u003ch3 id=\"21-语法\"\u003e2.1. 语法\u003c/h3\u003e\n\u003cp\u003e模块中有module.export对象，里面存放的就是export的变量。\u003c/p\u003e","title":"Typescript中的模块"},{"content":"bps是什么？ bp（或者bps）是Basis Point的简称。 中文翻译成基点。 是一个比例， 万分之一的意思。 也就是1%的百分之一。\n为什么要有这个概念 为什么不直接说万分之一，而引入一个新术语？\nBasis points are convenient and steady. Basis points are less ambiguous than percentages as they represent an absolute, set figure instead of a ratio.\nFor example, a 1 percent increase on a 5 percent interest rate could be interpreted as either 5.05 percent or 6 percent.\nConversely, if the rate increases by 100 basis points, the result is constant. The rate updates to 6 percent.\n引入bps是为了表达更明确，避免二义性。\n举个例子。 假设今天央行宣布LPR（Loan Prime Rate）从5%涨百分之一。 这时候，有两种不同的理解：\n新的LPR是 5% + 1% = 6% 新的LPR是 5% + 5% * 1% = 5.05% 引入bps后，央行说加100个基点。 那大家都没有歧义知道新LPR是 6%。\n","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-03-bps/","summary":"\u003ch2 id=\"bps是什么\"\u003ebps是什么？\u003c/h2\u003e\n\u003cp\u003ebp（或者bps）是Basis Point的简称。 中文翻译成基点。 是一个比例， 万分之一的意思。 也就是1%的百分之一。\u003c/p\u003e\n\u003ch2 id=\"为什么要有这个概念\"\u003e为什么要有这个概念\u003c/h2\u003e\n\u003cp\u003e为什么不直接说万分之一，而引入一个新术语？\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eBasis points are convenient and steady. Basis points are less ambiguous than percentages as they represent an absolute, set figure instead of a ratio.\u003c/p\u003e\n\u003cp\u003eFor example, a 1 percent increase on a 5 percent interest rate could be interpreted as either 5.05 percent or 6 percent.\u003c/p\u003e\n\u003cp\u003eConversely, if the rate increases by 100 basis points, the result is constant. The rate updates to 6 percent.\u003c/p\u003e","title":"基点 bps 是什么"},{"content":"Directus本身是NodeJS实现的API服务器。 其能支持多大的TPS取决于数据库系统，API查询设计等多个因素。本文提供一些架构上的考虑，目的是使得Directus应用能随着业务的增长，通过增加硬件等方式，同步提高系统处理能力，在性能上具有好的扩展性。\n1. 如何规划可扩展的Directus应用 Directus 对于高负载情况的处理有以下两个关注点：\n横向扩展应用服务器（directus实例多部署几个） 数据库服务器采用高性能方案，例如Amazon Aurora或者CockroachDB Rijk van Zanten: That being said, I do highly recommend horizontally scaling your Directus instance if you\u0026rsquo;re planning on running it at scale. Make sure you use Redis for caches / rate limiter, and S3 or another shared file storage for the file storage. At that point, the bottleneck will become the amount of allowed connections and the overall server performance of the database. That being said, there\u0026rsquo;s a lot of database services nowadays that scale virtually endless, like Amazon Aurora or CockroachDB.\n2. 应用服务器的横向扩展 关于横向扩展，需要注意所有实例共享的数据不能存本地，需要使用CDN或者Redis等高性能方案。实例的共享数据包括：图片，文件，cache等\nDirectus由于使用JWT，所以没有session共享的问题。 每次API请求在哪一个实例处理都一样，实例没有存储用户状态信息。\n3. 数据库的可扩展性 目前Directus 9.5版本支持了CockroachDB， 使用PG的驱动。 小强数据库CockroachDB这种全分布式数据库是一个革命性产品。 大大增强系统的可扩展性。\n例如，在多个国家的IDC都部署CockroachDB节点，组网后，在各个国家可以分别接入。这样可以：\n增加数据库处理带宽， 引入多城市，多IDC提高容灾备份能力， 海外用户的应用在本地接入数据库，API服务器在海外机房部署，降低访问延迟 持续关注 Directus 和 CockroachDB 在生产环境的使用和测试情况。\n4. 参考资料 performance discussion Implement CockroachDB support ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-03-02-directus-performance/","summary":"\u003cp\u003eDirectus本身是NodeJS实现的API服务器。 其能支持多大的TPS取决于数据库系统，API查询设计等多个因素。本文提供一些架构上的考虑，目的是使得Directus应用能随着业务的增长，通过增加硬件等方式，同步提高系统处理能力，在性能上具有好的扩展性。\u003c/p\u003e\n\u003ch2 id=\"1-如何规划可扩展的directus应用\"\u003e1. 如何规划可扩展的Directus应用\u003c/h2\u003e\n\u003cp\u003eDirectus 对于高负载情况的处理有以下两个关注点：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e横向扩展应用服务器（directus实例多部署几个）\u003c/li\u003e\n\u003cli\u003e数据库服务器采用高性能方案，例如Amazon Aurora或者CockroachDB\u003c/li\u003e\n\u003c/ol\u003e\n\u003cblockquote\u003e\n\u003cp\u003eRijk van Zanten: That being said, I do highly recommend horizontally scaling your Directus instance if you\u0026rsquo;re planning on running it at scale. Make sure you use Redis for caches / rate limiter, and S3 or another shared file storage for the file storage. At that point, the bottleneck will become the amount of allowed connections and the overall server performance of the database. That being said, there\u0026rsquo;s a lot of database services nowadays that scale virtually endless, like Amazon Aurora or CockroachDB.\u003c/p\u003e","title":"设计高性能可扩展的API服务"},{"content":"1. 项目简介 这个小项目预期结果是让 Directus 支持使用钉钉账号来登录。 在了解OAuth2协议后(参见上一篇blog，参考资料1），已经有足够知识储备来实施。 Directus 原生支持使用GitHub登录， 所以，解决思路是先从GitHub入手。按下面步骤进行：\n配置Directus使用GitHub账号登录，熟悉Directus对OAuth的标准支持功能 配置Directus使用钉钉账号登录，由于钉钉的协议实现和RFC6749/GitHub有不同，这里有可能需要见招拆招 上线Directus到服务器环境，在钉钉的PC版和手机版验证 2. 环境配置 在本地用ngrok暴漏出一个服务，来接受OAuth服务器的redirect。\nngrok http 8055 得到 https://445a-240e-47c-30b0-3b10-600e-ea25-cde5-2334.ngrok.io/ 作为外网域名来访问本机8055端口的directus。\n3. Directus 使用 GitHub账号登录 按参考资料2中配置参数。以下配置中，对每一个新的GitHub授权用户，Directus在登录过程中会使用用户email自动创建一个Directus用户，并且将其角色赋值为AUTH_GITHUB_DEFAULT_ROLE_ID。\nA A A A A A A A A A # # U U U U U U U U U U T T T T T T T T T T A A H H H H H H H H H H U U _ _ _ _ _ _ _ _ _ _ T T P G G G G G G G G G H H R I I I I I I I I I _ _ O T T T T T T T T T G G V H H H H H H H H H I I I U U U U U U U U U T T D B B B B B B B B B H H E _ _ _ _ _ _ _ _ _ U U R D C C A A P A D I B B S R L L U C R L E C _ _ = I I I T C O L F O E I \" V E E H E F O A N M D g E N N O S I W U = A E i R T T R S L _ L \" I N t = _ _ I _ E P T g L T h \" I S Z U _ U _ i _ I u o D E E R U B R t K F b a = C _ L R L O h E I \" u \" R U = L I L u Y E t 7 E R \" = C E b = R h e T L h \" _ _ \" \" _ 2 . = = t h R I e K \" . \" \" t t E D m E . d h p t G = a Y . 5 t s p I \" i = a . t : s S 0 l \" e . p / : T f \" e \" . s / / R 5 m . : g / A f a . i a T 1 i . t p I b l . g h i O 5 \" d i u . N a 9 t b g = - \" h . i \" 1 u c t t 0 b o h r 6 . m u u f c b e - l . \" 4 m c e g o c l i m 7 o n - g u a i o s 8 n a e b / u r 8 o t \" - a h 6 u / f t a 1 h c 1 / c 4 a e 8 u s 2 t s a h _ 0 o t 6 r o 0 i k f z e \" e n \" \" 重启Directus让配置生效后，可以看到登录界面的GitHub选项。 选择授权后，成功登录Directus。 检查Directus中新生成的用户和权限正常。 4. Directus 使用钉钉账号登录的尝试 先照猫画虎配置下。\nA A A A A A A A A A # # U U U U U U U U U U A A T T T T T T T T T T U U H H H H H H H H H H T T _ _ _ _ _ _ _ _ _ _ H H P D D D D D D D D D _ _ R I I I I I I I I I D D O N N N N N N N N N I I V G G G G G G G G G N N I T T T T T T T T T G G D A A A A A A A A A T T E L L L L L L L L L A A R K K K K K K K K K L L S _ _ _ _ _ _ _ _ _ K K = D C C A A P A D I _ _ \" R L L U C R L E C E I g I I I T C O L F O M D i V E E H E F O A N A E t E N N O S I W U = I N h R T T R S L _ L \" L T u = _ _ I _ E P T a _ I b \" I S Z U _ U _ l K F , o D E E R U B R i E I a = C _ L R L O p Y E d u \" R U = L I L a = R i t d E R \" = C E y \" _ n h i T L h \" _ _ \" e K g 2 n = = t h R I m E t \" g \" \" t t E D a Y a . c h p t G = i = l . 6 t s p I \" l \" k . r t : s S 0 \" e \" t C p / : T f m x T s / / R 5 a t . : a / A f i \" . p a T 1 l . / i p I b \" h l . i O 5 4 o d . N a o g i d = - h i n i \" 1 K n g n t 0 l . t g r 6 q d a t u f 5 i l a e - o n k l \" 4 z g . k e \" t c . c a o c 7 l m - k m a . v / 8 c 1 v b o . 1 8 m 0 . - / 0 6 o / f a a c 1 u u o 1 t t n 4 h h t 8 2 2 a 2 / / c a a u t 0 u s / 6 t e u 0 h r s f \" A e \" c r c s e / s m s e T \" o k e n \" 点击Log In with Dingtalk可以正常授权， 但授权后被redirect到了\n/ a d m i n / l o g i n ? r e a s o n = I N V A L I D _ U S E R 怀疑是钉钉重定向回来的链接没有code参数（参见上一篇协议解析，钉钉是用的authCode参数），第一时间先在社区开个issue看看有没有其他人碰到过。\n同时，对oauth2的driver做了一个临时补丁， 当有authCode时候， 就把authCode赋值给code。\ntry { res.clearCookie(`oauth2.${providerName}`); if ( req.query.authCode) { req.query.code = req.query.authCode } if (!req.query.code || !req.query.state) { logger.warn(`[OAuth2] Couldn\u0026#39;t extract OAuth2 code or state from query: ${JSON.stringify(req.query)}`); } authResponse = await authenticationService.login(providerName, { code: req.query.code, codeVerifier: verifier, state: req.query.state, }); } catch (error: any) { ... 再次重启directus后， 补丁似乎生效了， 这次被重定向到了。\n/ a d m i n / l o g i n ? r e a s o n = S E R V I C E _ U N A V A I L A B L E OAuth2协议的第一步获取code已经通过了。 SERVICE_UNAVAILABLE 是获取token出问题，还是取profile出问题了？\n注意到钉钉获取token的请求中，参数名称是clientId，clientSecret。 而GitHub是client_id,client_secret. 另外钉钉还需要一个额外的grantType.\n{ } \" \" \" \" c c c g l l o r i i d a e e e n n n \" t t t T I S : y d e p \" c \" e r 6 \" : e b t 4 : \" \" 2 d 7 \" i : e a n 8 u g \" b t y f h y o a o o u b r u r 8 i r 3 z s e a i e 9 t d c 3 i \" r b o , e e n t d _ \" d c , 1 o 3 d f e 1 \" 6 a 4 3 0 7 0 2 \" , 把clientId，clientSecret和grantType作为参数配置到directus请求中。\nA U T H _ D I N G T A L K _ P A R A M S = \" { \\ \" c l i e n t I d \\ \" : \\ \" d i n . . . t x t \\ \" , \\ \" c l i e n t S e c r e t \\ \" : \\ \" d 5 6 . . . . 5 8 b d 9 \\ \" , \\ \" g r a n t T y p e \\ \" : \\ \" a u t h o r i z a t i o n _ c o d e \\ \" } \" 仍然是SERVICE_UNAVAILABLE。 检查driver，发现问题出在下面：\ntry { tokenSet = await this.client.oauthCallback( this.redirectUrl, { code: payload.code, state: payload.state }, { code_verifier: payload.codeVerifier, state: generators.codeChallenge(payload.codeVerifier) } ); userInfo = await this.client.userinfo(tokenSet.access_token!); } catch (e) { throw handleError(e); } 上面代码抛出异常了，原因是HTTP请求得到的响应是400. 应该是钉钉OAuth服务器不识别Directus发过去的消息。\n上面代码执行背景是：\n在oauth2 driver中，配置了express路由处理钉钉redirect过来的code，在处理过程中，需要认证用户（认证成功会完成登录，发放JWT token）； 用户认证和driver无关，用一个通用的AuthenticationService.login服务处理， 在服务中，又调用driver的getUserID方法来获取userId； 对于oauth2 driver来说，网页上没传过来用户名密码，其唯一输入就是钉钉redirect过来的code，需要通过OAuth接口，把code转换成token，然后读取用户信息，才能知道userID。 OAuth2 Driver使用了openid-client 和服务器通信。其client也在driver中初始化：\nconst issuer = new Issuer({ authorization_endpoint: authorizeUrl, token_endpoint: accessUrl, userinfo_endpoint: profileUrl, issuer: additionalConfig.provider, }); this.client = new issuer.Client({ client_id: clientId, client_secret: clientSecret, redirect_uris: [this.redirectUrl], response_types: [\u0026#39;code\u0026#39;], }); 所以问题细化成了 openid-client 和钉钉的兼容性。再具体一些，是如何用oauthCallback函数来从钉钉处获取token。\n看了下openid-client的实现，其和OAuth服务器交互时候，POST的表单数据是按照RFC6749中定义的参数名称硬编码的。 必然和钉钉的要求不匹配。 使用openid-client没有办法兼容钉钉。 将调研结果和directus OAuth Driver的作者在Integrating Dingtalk as OAuth2 server 做了详细的探讨。\n5. 结论 原定计划无法达成。 原因是钉钉的OAuth实现和标准不兼容。而Directus使用了第三方的OAuth库来和OAuth服务器通信。 基于标准的openid-client和说方言的钉钉OAuth服务器无法沟通。\n考虑两种方案：\n从directus标准oauth2 driver中继承，实现一个钉钉方言版本的oauth2-dingtalk driver， 或者 实现一个proxy，来做钉钉的OAuth方言和标准OAuth2协议的翻译 倾向于方案2， 相当于给钉钉做一个协议封装层，按照标准转换下参数格式。这样后续有其他系统需要集成钉钉登录，也可以用的上。\n后续完成后再补记。\n6. 补记 参考apiproxy 使用上述方案2实现了钉钉免密登录。\n7. 参考资料 OAuth2 Protocol Illustrated Directus Authentication Configuration node openid client 钉钉Nodejs SDK 钉钉开放平台开发文档中获得token的示例 Integrating Dingtalk as OAuth2 server apiproxy ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-02-27-oauth-directus-dingtalk/","summary":"\u003ch2 id=\"1-项目简介\"\u003e1. 项目简介\u003c/h2\u003e\n\u003cp\u003e这个小项目预期结果是让 Directus 支持使用钉钉账号来登录。\n在了解OAuth2协议后(参见上一篇blog，参考资料1），已经有足够知识储备来实施。 Directus 原生支持使用GitHub登录， 所以，解决思路是先从GitHub入手。按下面步骤进行：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e配置Directus使用GitHub账号登录，熟悉Directus对OAuth的标准支持功能\u003c/li\u003e\n\u003cli\u003e配置Directus使用钉钉账号登录，由于钉钉的协议实现和RFC6749/GitHub有不同，这里有可能需要见招拆招\u003c/li\u003e\n\u003cli\u003e上线Directus到服务器环境，在钉钉的PC版和手机版验证\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"2-环境配置\"\u003e2. 环境配置\u003c/h2\u003e\n\u003cp\u003e在本地用ngrok暴漏出一个服务，来接受OAuth服务器的redirect。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003engrok http \u003cspan style=\"color:#ae81ff\"\u003e8055\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e得到 \u003ca href=\"https://445a-240e-47c-30b0-3b10-600e-ea25-cde5-2334.ngrok.io/\"\u003ehttps://445a-240e-47c-30b0-3b10-600e-ea25-cde5-2334.ngrok.io/\u003c/a\u003e 作为外网域名来访问本机8055端口的directus。\u003c/p\u003e\n\u003ch2 id=\"3-directus-使用-github账号登录\"\u003e3. Directus 使用 GitHub账号登录\u003c/h2\u003e\n\u003cp\u003e按参考资料2中配置参数。以下配置中，对每一个新的GitHub授权用户，Directus在登录过程中会使用用户email自动创建一个Directus用户，并且将其角色赋值为AUTH_GITHUB_DEFAULT_ROLE_ID。\u003c/p\u003e\n\n\n\n\u003cdiv class=\"goat svg-container \"\u003e\n  \n    \u003csvg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      font-family=\"Menlo,Lucida Console,monospace\"\n      \n        viewBox=\"0 0 552 201\"\n      \u003e\n      \u003cg transform='translate(8,16)'\u003e\n\u003cpath d='M 260,72 L 268,56' fill='none' stroke='currentColor'\u003e\u003c/path\u003e\n\u003cpath d='M 268,72 L 276,56' fill='none' stroke='currentColor'\u003e\u003c/path\u003e\n\u003cpath d='M 336,80 L 344,64' fill='none' stroke='currentColor'\u003e\u003c/path\u003e\n\u003cpath d='M 352,80 L 360,64' fill='none' stroke='currentColor'\u003e\u003c/path\u003e\n\u003cpath d='M 376,96 L 384,80' fill='none' stroke='currentColor'\u003e\u003c/path\u003e\n\u003ccircle cx='344' cy='64' r='6' stroke='currentColor' fill='#fff'\u003e\u003c/circle\u003e\n\u003ccircle cx='352' cy='80' r='6' stroke='currentColor' fill='#fff'\u003e\u003c/circle\u003e\n\u003ctext text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='84' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='100' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='116' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='132' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='148' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='164' fill='currentColor' style='font-size:1em'\u003e#\u003c/text\u003e\n\u003ctext text-anchor='middle' x='0' y='180' fill='currentColor' style='font-size:1em'\u003e#\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='36' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='52' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='68' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='84' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='100' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='116' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='132' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='8' y='148' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='84' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='100' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='116' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='132' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='148' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='164' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='16' y='180' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='84' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='100' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='116' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='132' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='148' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='164' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='24' y='180' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='84' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='100' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='116' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='132' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='148' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='164' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='32' y='180' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'\u003eP\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='68' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='84' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='100' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='116' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='132' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='148' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='164' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='40' y='180' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='68' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='84' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='100' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='116' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='132' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='148' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='164' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='48' y='180' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'\u003eO\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='68' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='84' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='100' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='116' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='132' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='148' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='164' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='56' y='180' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'\u003eV\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='116' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='132' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='148' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='164' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='64' y='180' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='116' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='132' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='148' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='164' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='72' y='180' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'\u003eD\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='100' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='116' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='132' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='164' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='80' y='180' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='84' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='100' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='116' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='132' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='148' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='164' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='88' y='180' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'\u003eD\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'\u003eC\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'\u003eC\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'\u003eP\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='116' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='132' fill='currentColor' style='font-size:1em'\u003eD\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='148' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='96' y='180' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'\u003eS\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='20' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='84' fill='currentColor' style='font-size:1em'\u003eC\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='100' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='116' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='132' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='148' fill='currentColor' style='font-size:1em'\u003eC\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='164' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='104' y='180' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='20' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='84' fill='currentColor' style='font-size:1em'\u003eC\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='100' fill='currentColor' style='font-size:1em'\u003eO\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='116' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='132' fill='currentColor' style='font-size:1em'\u003eF\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='148' fill='currentColor' style='font-size:1em'\u003eO\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='112' y='180' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='20' fill='currentColor' style='font-size:1em'\u003eV\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='36' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='68' fill='currentColor' style='font-size:1em'\u003eH\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='84' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='100' fill='currentColor' style='font-size:1em'\u003eF\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'\u003eO\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='132' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='148' fill='currentColor' style='font-size:1em'\u003eN\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='164' fill='currentColor' style='font-size:1em'\u003eM\u003c/text\u003e\n\u003ctext text-anchor='middle' x='120' y='180' fill='currentColor' style='font-size:1em'\u003eD\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='4' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='20' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='36' fill='currentColor' style='font-size:1em'\u003eN\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'\u003eN\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'\u003eO\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='84' fill='currentColor' style='font-size:1em'\u003eS\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='116' fill='currentColor' style='font-size:1em'\u003eW\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='132' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='148' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='164' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='128' y='180' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='20' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='36' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='84' fill='currentColor' style='font-size:1em'\u003eS\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='100' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='116' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='132' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='148' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='164' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='136' y='180' fill='currentColor' style='font-size:1em'\u003eN\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='20' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='36' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='84' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='100' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'\u003eP\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='132' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='148' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='164' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='144' y='180' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='20' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'\u003eS\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'\u003eZ\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='84' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='100' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='116' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='132' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='148' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='164' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='152' y='180' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='20' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='36' fill='currentColor' style='font-size:1em'\u003eD\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='84' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='116' fill='currentColor' style='font-size:1em'\u003eB\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='132' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='148' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='164' fill='currentColor' style='font-size:1em'\u003eK\u003c/text\u003e\n\u003ctext text-anchor='middle' x='160' y='180' fill='currentColor' style='font-size:1em'\u003eF\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'\u003eb\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='20' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'\u003eC\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='84' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='116' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='132' fill='currentColor' style='font-size:1em'\u003eO\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='148' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='164' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='168' y='180' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='20' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='36' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'\u003eU\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='84' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='100' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='116' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='132' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='148' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='164' fill='currentColor' style='font-size:1em'\u003eY\u003c/text\u003e\n\u003ctext text-anchor='middle' x='176' y='180' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='20' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='36' fill='currentColor' style='font-size:1em'\u003e7\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='52' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='84' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='100' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='116' fill='currentColor' style='font-size:1em'\u003eC\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='132' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='148' fill='currentColor' style='font-size:1em'\u003eb\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='164' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='184' y='180' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='20' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='36' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='52' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='68' fill='currentColor' style='font-size:1em'\u003eL\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='84' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='100' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='116' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='132' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='148' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='164' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='192' y='180' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='20' fill='currentColor' style='font-size:1em'\u003e2\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='36' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='52' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='68' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='84' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='100' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='116' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='132' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='164' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='200' y='180' fill='currentColor' style='font-size:1em'\u003eK\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='20' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='36' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='52' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='68' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='84' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='100' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='116' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='132' fill='currentColor' style='font-size:1em'\u003eD\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='164' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='208' y='180' fill='currentColor' style='font-size:1em'\u003eE\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='36' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='52' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='68' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='84' fill='currentColor' style='font-size:1em'\u003ep\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='100' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='116' fill='currentColor' style='font-size:1em'\u003eG\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='132' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='164' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='216' y='180' fill='currentColor' style='font-size:1em'\u003eY\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='36' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='52' fill='currentColor' style='font-size:1em'\u003e5\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='68' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='84' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='100' fill='currentColor' style='font-size:1em'\u003ep\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='116' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='132' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='164' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='224' y='180' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='36' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='52' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='68' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='84' fill='currentColor' style='font-size:1em'\u003e:\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='100' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='116' fill='currentColor' style='font-size:1em'\u003eS\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='132' fill='currentColor' style='font-size:1em'\u003e0\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='164' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='232' y='180' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='36' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='52' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='68' fill='currentColor' style='font-size:1em'\u003ep\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='84' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='100' fill='currentColor' style='font-size:1em'\u003e:\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='116' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='132' fill='currentColor' style='font-size:1em'\u003ef\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='164' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='240' y='180' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='36' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='52' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='68' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='84' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='100' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='116' fill='currentColor' style='font-size:1em'\u003eR\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='132' fill='currentColor' style='font-size:1em'\u003e5\u003c/text\u003e\n\u003ctext text-anchor='middle' x='248' y='180' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='52' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='68' fill='currentColor' style='font-size:1em'\u003e:\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='84' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='100' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='116' fill='currentColor' style='font-size:1em'\u003eA\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='132' fill='currentColor' style='font-size:1em'\u003ef\u003c/text\u003e\n\u003ctext text-anchor='middle' x='256' y='180' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='52' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='84' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='100' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='116' fill='currentColor' style='font-size:1em'\u003eT\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='132' fill='currentColor' style='font-size:1em'\u003e1\u003c/text\u003e\n\u003ctext text-anchor='middle' x='264' y='180' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='52' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='84' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='100' fill='currentColor' style='font-size:1em'\u003ep\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='116' fill='currentColor' style='font-size:1em'\u003eI\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='132' fill='currentColor' style='font-size:1em'\u003eb\u003c/text\u003e\n\u003ctext text-anchor='middle' x='272' y='180' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='52' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='68' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='84' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='100' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='116' fill='currentColor' style='font-size:1em'\u003eO\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='132' fill='currentColor' style='font-size:1em'\u003e5\u003c/text\u003e\n\u003ctext text-anchor='middle' x='280' y='180' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='52' fill='currentColor' style='font-size:1em'\u003ed\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='68' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='84' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='116' fill='currentColor' style='font-size:1em'\u003eN\u003c/text\u003e\n\u003ctext text-anchor='middle' x='288' y='132' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='52' fill='currentColor' style='font-size:1em'\u003e9\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='68' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='84' fill='currentColor' style='font-size:1em'\u003eb\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='100' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='116' fill='currentColor' style='font-size:1em'\u003e=\u003c/text\u003e\n\u003ctext text-anchor='middle' x='296' y='132' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='52' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='68' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='84' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='100' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='116' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='304' y='132' fill='currentColor' style='font-size:1em'\u003e1\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='68' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='84' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='100' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='116' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='312' y='132' fill='currentColor' style='font-size:1em'\u003e0\u003c/text\u003e\n\u003ctext text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'\u003eb\u003c/text\u003e\n\u003ctext text-anchor='middle' x='320' y='84' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='320' y='100' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='320' y='116' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='320' y='132' fill='currentColor' style='font-size:1em'\u003e6\u003c/text\u003e\n\u003ctext text-anchor='middle' x='328' y='68' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='328' y='84' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='328' y='100' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='328' y='116' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='328' y='132' fill='currentColor' style='font-size:1em'\u003ef\u003c/text\u003e\n\u003ctext text-anchor='middle' x='336' y='68' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='336' y='100' fill='currentColor' style='font-size:1em'\u003eb\u003c/text\u003e\n\u003ctext text-anchor='middle' x='336' y='116' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='336' y='132' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='344' y='84' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='344' y='100' fill='currentColor' style='font-size:1em'\u003e.\u003c/text\u003e\n\u003ctext text-anchor='middle' x='344' y='116' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='344' y='132' fill='currentColor' style='font-size:1em'\u003e4\u003c/text\u003e\n\u003ctext text-anchor='middle' x='352' y='68' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='352' y='100' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='352' y='132' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='360' y='84' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='360' y='100' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='360' y='132' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='368' y='68' fill='currentColor' style='font-size:1em'\u003el\u003c/text\u003e\n\u003ctext text-anchor='middle' x='368' y='84' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='368' y='100' fill='currentColor' style='font-size:1em'\u003em\u003c/text\u003e\n\u003ctext text-anchor='middle' x='368' y='132' fill='currentColor' style='font-size:1em'\u003e7\u003c/text\u003e\n\u003ctext text-anchor='middle' x='376' y='68' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='376' y='84' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='376' y='132' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='384' y='68' fill='currentColor' style='font-size:1em'\u003eg\u003c/text\u003e\n\u003ctext text-anchor='middle' x='384' y='100' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='384' y='132' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='392' y='68' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='392' y='84' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='392' y='100' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='392' y='132' fill='currentColor' style='font-size:1em'\u003e8\u003c/text\u003e\n\u003ctext text-anchor='middle' x='400' y='68' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='400' y='84' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='400' y='100' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='400' y='132' fill='currentColor' style='font-size:1em'\u003eb\u003c/text\u003e\n\u003ctext text-anchor='middle' x='408' y='68' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='408' y='84' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='408' y='100' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='408' y='132' fill='currentColor' style='font-size:1em'\u003e8\u003c/text\u003e\n\u003ctext text-anchor='middle' x='416' y='68' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='416' y='84' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='416' y='100' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='416' y='132' fill='currentColor' style='font-size:1em'\u003e-\u003c/text\u003e\n\u003ctext text-anchor='middle' x='424' y='68' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='424' y='84' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='424' y='132' fill='currentColor' style='font-size:1em'\u003e6\u003c/text\u003e\n\u003ctext text-anchor='middle' x='432' y='68' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='432' y='84' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='432' y='132' fill='currentColor' style='font-size:1em'\u003ef\u003c/text\u003e\n\u003ctext text-anchor='middle' x='440' y='68' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='440' y='84' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='440' y='132' fill='currentColor' style='font-size:1em'\u003e1\u003c/text\u003e\n\u003ctext text-anchor='middle' x='448' y='68' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='448' y='84' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='448' y='132' fill='currentColor' style='font-size:1em'\u003e1\u003c/text\u003e\n\u003ctext text-anchor='middle' x='456' y='68' fill='currentColor' style='font-size:1em'\u003e/\u003c/text\u003e\n\u003ctext text-anchor='middle' x='456' y='84' fill='currentColor' style='font-size:1em'\u003ec\u003c/text\u003e\n\u003ctext text-anchor='middle' x='456' y='132' fill='currentColor' style='font-size:1em'\u003e4\u003c/text\u003e\n\u003ctext text-anchor='middle' x='464' y='68' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='464' y='84' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='464' y='132' fill='currentColor' style='font-size:1em'\u003e8\u003c/text\u003e\n\u003ctext text-anchor='middle' x='472' y='68' fill='currentColor' style='font-size:1em'\u003eu\u003c/text\u003e\n\u003ctext text-anchor='middle' x='472' y='84' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='472' y='132' fill='currentColor' style='font-size:1em'\u003e2\u003c/text\u003e\n\u003ctext text-anchor='middle' x='480' y='68' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='480' y='84' fill='currentColor' style='font-size:1em'\u003es\u003c/text\u003e\n\u003ctext text-anchor='middle' x='480' y='132' fill='currentColor' style='font-size:1em'\u003ea\u003c/text\u003e\n\u003ctext text-anchor='middle' x='488' y='68' fill='currentColor' style='font-size:1em'\u003eh\u003c/text\u003e\n\u003ctext text-anchor='middle' x='488' y='84' fill='currentColor' style='font-size:1em'\u003e_\u003c/text\u003e\n\u003ctext text-anchor='middle' x='488' y='132' fill='currentColor' style='font-size:1em'\u003e0\u003c/text\u003e\n\u003ctext text-anchor='middle' x='496' y='68' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='496' y='84' fill='currentColor' style='font-size:1em'\u003et\u003c/text\u003e\n\u003ctext text-anchor='middle' x='496' y='132' fill='currentColor' style='font-size:1em'\u003e6\u003c/text\u003e\n\u003ctext text-anchor='middle' x='504' y='68' fill='currentColor' style='font-size:1em'\u003er\u003c/text\u003e\n\u003ctext text-anchor='middle' x='504' y='84' fill='currentColor' style='font-size:1em'\u003eo\u003c/text\u003e\n\u003ctext text-anchor='middle' x='504' y='132' fill='currentColor' style='font-size:1em'\u003e0\u003c/text\u003e\n\u003ctext text-anchor='middle' x='512' y='68' fill='currentColor' style='font-size:1em'\u003ei\u003c/text\u003e\n\u003ctext text-anchor='middle' x='512' y='84' fill='currentColor' style='font-size:1em'\u003ek\u003c/text\u003e\n\u003ctext text-anchor='middle' x='512' y='132' fill='currentColor' style='font-size:1em'\u003ef\u003c/text\u003e\n\u003ctext text-anchor='middle' x='520' y='68' fill='currentColor' style='font-size:1em'\u003ez\u003c/text\u003e\n\u003ctext text-anchor='middle' x='520' y='84' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='520' y='132' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='528' y='68' fill='currentColor' style='font-size:1em'\u003ee\u003c/text\u003e\n\u003ctext text-anchor='middle' x='528' y='84' fill='currentColor' style='font-size:1em'\u003en\u003c/text\u003e\n\u003ctext text-anchor='middle' x='536' y='68' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003ctext text-anchor='middle' x='536' y='84' fill='currentColor' style='font-size:1em'\u003e\"\u003c/text\u003e\n\u003c/g\u003e\n\n    \u003c/svg\u003e\n  \n\u003c/div\u003e\n\u003cp\u003e重启Directus让配置生效后，可以看到登录界面的GitHub选项。\n\u003cimg alt=\"picture 1\" loading=\"lazy\" src=\"/images/1645953759125.png\"\u003e\u003c/p\u003e","title":"OAuth2 应用实践：Directus集成钉钉登录的尝试"},{"content":"1. 原理 假设有一个APP，要我使用GitHub授权登录。 在这个登录场景中：\n我作为数据的所有者告诉系统（GitHub），同意授权第三方应用（App）进入系统，获取某些数据（我的ID，头像等）。系统从而产生一个短期的进入令牌（token），用来代替密码，供第三方应用（APP）访问数据使用。\ntoken是短期有效的，我可以随时通过GitHub把这个Token注销，从而使得APP不再能访问我的ID/头像等信息。\n这里面有四个角色：用户，应用，系统，资源。\n用户是资源所有者，应用是资源的使用者，系统是资源管理者。 现实生活中， 应用和系统各自有一个实例， 用户有多个实例。 应用和系统之间通过OAuth2协议通信。 用户在通信过程中参与（授权）。\n把上面时序图对应到一个生活中的场景，业主授权快递公司出入小区送快递：\nClient Req Auth： 顺丰快递员打电话给业主，有你的快递，得送进去，给办个出入证吧 Resource Owner Grant Auth： 业主联系小区物业， 我是业主，这是我的证明，我允许顺丰最近两天可以进小区物业给我送快递 小区物业告诉业主，OK，你让顺丰联系我拿临时出入证，就说3号楼201房间，授权码：核酸检测利国利民 Client Sends Auth Grant 快递公司联系小区物业说，我是顺丰，这是我的证明，需要给3号楼201房间送快递，业主的授权码是核酸检测利国利民 Auth Server Sends Access Token 小区物业说好，授权码没问题，这个是临时出入证，两天有效。 Clients Sends Access Token 快递员用临时出入证开小区大门 Protected Resoure sends resource 快递员进入小区 注意：\n第二步，业主联系物业时候， 要证明自己的确是业主（用小区app登录） 第三步，顺丰联系物业时候，需要证明自己的确是顺丰（报小区物业预先给顺丰分配的client_secret让物业核对). 物业还要检查3号楼201业主的确允许了（通过查授权码）。 如果快递员直接到小区门卫那里说 “核酸检测利国利民”，是没有作用的。“核酸检测利国利民”承载的信息是3号楼201业主允许顺丰在2天内进小区。 快递员给门卫报这个授权码没用，门卫只认出入证。再说，门卫也不知道快递员是不是顺丰的啊。这个授权码只有在快递公司把它换成临时出入证后才可以进小区。 换临时出入证需要验证顺丰的身份。“核酸检测利国利民”承载的信息是可以公开的，其它人听到了也不会有安全风险，因为其他人没有顺丰在物业处注册得到的client_secret, 没办法用授权码换临时出入证。 临时出入证需要妥善保管，任何人拿着都能进小区了。\nOAuth2的过程是当有快递时候，业主授权小区物业给快递公司分配临时出入证，在一定的时间内可以出入小区。在同一时间，会存在很多个不同业主授权的有效临时出入证，但一个业主对一家快递公司，在同一时间只有一个有效临时出入证。 角色间的类比关系如下。\n中文术语 时序图中的概念 GitHub授权登录APP场景 业主授权顺丰进小区场景 用户 Resource Owner GitHub用户 业主 应用 Client GitHub OAuth APP 顺丰 系统 Authorization Server GitHub 小区物业 资源 Protected Resource GitHub用户名称，头像 小区内部道路 授权 Authorization Grant code 核酸检测利国利民 令牌 Access Token token 临时出入证 下图出自RFC6750， 实际实现时候，Client是APP， Auth Server/Resource Server在同一个域名后面（GitHub，钉钉，Facebook\u0026hellip;)， Resource Owner也是在这个域名下完成授权（Auth Grant）。\nC l i e n t ( ( ( ( ( ( A B C D E F ) ) ) ) ) ) - - - - - - - - - - - A - - - u A A - - t u u - - P h t t r o h h A A o r o o c c t i r r c c e z i i e e c a z z s s t t a a s s e i t t d o i i T T n o o o o R n n k k e R e e s e G G n n o q r r u u a a r e n n c s t t e t A u R t e h R s O o S e S o w r e s e u n i r o r r e z v u v c r a e r e e t r c r i e o n 为讨论方便， 以下将获取用户授权简称为“获取code”， 将获取访问令牌（Access Token）简称为“获取Token”。\n接下来分别使用GitHub和钉钉作为OAuth服务器，来解析获取code，获取token以及读取用户信息过程中的消息格式。全程不需要写代码，需要用浏览器和REST Client（POSTMAN， CURL或者Vscode的插件都可以）。\nOAUTH 客户端有两种：\npublic：客户端本身没有能力保存密钥信息，比如桌面软件，手机App，单页面程序(SPA）。 confidential ： 通过code换取access_token这一步是在后端的api完成。一般用client_secret保护。 我们主要讨论confidential类客户端。 2. GitHub OAuth交互解析 以下四个步骤，示例了：\n在GitHub建立了一个新的OAuth应用， 通过GitHub提供的API获取授权吗， 通过GitHub提供的API获取token， 通过GitHub提供的API获取授权用户的数据。 记录了每一步的关键输入输出参数。\n2.1. 创建OAuth APP 登录GitHub后，在开发选项中建立一个新的OAuth APP，为测试方便，我将Redirect URL设置成了 https://www.baidu.com/. 得到的新APP配置信息是：\nc c l l i i e e n n t t _ _ i s d e c : r e 7 t e : c e 5 5 9 c 2 c f 7 6 8 f e 0 4 f f 1 d 9 d e 3 2 2 1 6 c 0 d a 7 e 0 6 d 6 b f 4 c 4 d c f 6 f e 9 a 0 a d 0 9 9 c 2.2. 获取code 在浏览器发起：\nh t t p s : / / g i t h u b . c o m / l o g i n / o a u t h / a u t h o r i z e ? c l i e n t _ i d = 7 e c e 5 c c 7 8 e 4 f d d 3 2 6 0 a e \u0026amp; r e d i r e c t _ u r i = h t t p s : / / w w w . b a i d u . c o m / 出现： 选择授权后，在回调中可以看到Code\nh t t p s : / / w w w . b a i d u . c o m / ? c o d e = 8 7 1 f 7 4 a 9 d 1 2 9 e f 1 9 8 b 4 3 2.3. 获取token p o s t h t t p s : / / g i t h u b . c o m / l o g i n / o a u t h / a c c e s s _ t o k e n ? c l i e n t _ i d = 7 e c e 5 c c 7 8 e 4 f d d 3 2 6 0 a e \u0026amp; c l i e n t _ s e c r e t = 5 9 2 f 6 f 0 f 1 9 e 2 1 c d 7 0 6 d 6 b f 4 c 4 d c f 6 f e 9 a 0 a d 0 9 9 c \u0026amp; c o d e = 8 7 1 f 7 4 a 9 d 1 2 9 e f 1 9 8 b 4 3 上面请求有三个参数：\nclient_id : 在GitHub创建应用得到的 client_secret : 在GitHub创建应用得到的 code : 上一步Authorization得到的 得到响应中：\na c c e s s _ t o k e n = g h o _ Q V E s K 2 p l K h 6 T 6 0 A 4 h M E D J d d E n R 2 y 4 D 3 7 s C P x \u0026amp; s c o p e = \u0026amp; t o k e n _ t y p e = b e a r e r 这时候登录GitHub在创建的应用配置界面，能够看到该应用已经有一个用户了。 并且我可以Revoke All user tokens。\n2.4. 读取用户信息 g a A e c u t c t e h h p o t t r t : i p z s ' a : a t / p i / p o a l n p i : i c . a t g t o i i k t o e h n n u / b j g . s h c o o o n _ m ' Q V u E s s e K r 2 p l K h 6 T 6 0 A 4 h M E D J d d E n R 2 y 4 D 3 7 s C P x 得到响应中\n{ \u0026#34;login\u0026#34;: \u0026#34;xu4wang\u0026#34;, \u0026#34;id\u0026#34;: 311397, \u0026#34;node_id\u0026#34;: \u0026#34;MDQ6VXNlcjMxMTM5Nw==\u0026#34;, \u0026#34;avatar_url\u0026#34;: \u0026#34;https://avatars.githubusercontent.com/u/311397?v=4\u0026#34;, \u0026#34;gravatar_id\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://api.github.com/users/xu4wang\u0026#34;, \u0026#34;html_url\u0026#34;: \u0026#34;https://github.com/xu4wang\u0026#34;, \u0026#34;followers_url\u0026#34;: \u0026#34;https://api.github.com/users/xu4wang/followers\u0026#34;, \u0026#34;following_url\u0026#34;: \u0026#34;https://api.github.com/users/xu4wang/following{/other_user}\u0026#34;, \u0026#34;gists_url\u0026#34;: \u0026#34;https://api.github.com/users/xu4wang/gists{/gist_id}\u0026#34;, \u0026#34;starred_url\u0026#34;: \u0026#34;https://api.github.com/users/xu4wang/starred{/owner}{/repo}\u0026#34;, \u0026#34;subscriptions_url\u0026#34;: \u0026#34;https://api.github.com/users/xu4wang/subscriptions\u0026#34;, \u0026#34;organizations_url\u0026#34;: \u0026#34;https://api.github.com/users/xu4wang/orgs\u0026#34;, \u0026#34;repos_url\u0026#34;: \u0026#34;https://api.github.com/users/xu4wang/repos\u0026#34;, \u0026#34;events_url\u0026#34;: \u0026#34;https://api.github.com/users/xu4wang/events{/privacy}\u0026#34;, \u0026#34;received_events_url\u0026#34;: \u0026#34;https://api.github.com/users/xu4wang/received_events\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;User\u0026#34;, \u0026#34;site_admin\u0026#34;: false, \u0026#34;name\u0026#34;: \u0026#34;Austin\u0026#34;, \u0026#34;company\u0026#34;: null, \u0026#34;blog\u0026#34;: \u0026#34;https://awis.me\u0026#34;, \u0026#34;location\u0026#34;: \u0026#34;Shenzhen, China\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;xu4wang@gmail.com\u0026#34;, \u0026#34;hireable\u0026#34;: null, \u0026#34;bio\u0026#34;: \u0026#34;Working @ Bangkok , Beijing and Shenzhen\u0026#34;, \u0026#34;twitter_username\u0026#34;: null, \u0026#34;public_repos\u0026#34;: 26, \u0026#34;public_gists\u0026#34;: 1, \u0026#34;followers\u0026#34;: 10, \u0026#34;following\u0026#34;: 17, \u0026#34;created_at\u0026#34;: \u0026#34;2010-06-22T05:35:32Z\u0026#34;, \u0026#34;updated_at\u0026#34;: \u0026#34;2022-02-25T18:51:48Z\u0026#34; } 从实现的安全性角度考虑，申请Token可以在后台和GitHub之间。 这样最终得到的Token不会在前端出现。\nAuthorization Request 在浏览器和GitHub之间进行，得到Access Code（授权码）是GitHub Redirect过来的，浏览器会拿到。 可以将Client Secret只在后台保存，所以前端即便得到Access Code，也没办法自己调用API来从GitHub得到Token。 前端用client id请求到了该应用可以用的access code, 后端再用client secret和access code一起申请token。 通过两步申请的设计，即满足了必须用前端做用户授权的需求，又做到了token不暴露给前端，增强了安全性。\n3. 钉钉 OAuth交互解析 在钉钉上，除了需要配置OAuth应用，增加两个读取个人信息的权限外，其它流程和GitHub类似。\n3.1. 创建OAuth APP 在钉钉开发者中心，创建H5企业内部应用。并且授权它有读取个人手机号，个人通讯录信息的权限。\n3.2. 获取code h r 得 h t e 到 t t d t p i p s r s : e : / c / / t / l _ w o u w g r w i i . n = b . h a d t i i t d n p u g s . t % c a 3 o l A m k % / . 2 ? c F a o % u m 2 t / F h o w C a w o u w d t . e h b = 2 a 6 / i b a d 4 u u 2 t . 7 h c e ? o 8 m b % f 2 a F b \u0026amp; 8 r 3 e e s 9 p 3 o b n e s d e d _ 1 t 3 y f p 1 e 6 = a c 4 o 3 d 0 e 7 \u0026amp; 0 c 2 l i e n t _ i d = d i n g y o u r c l i e n t i d \u0026amp; s c o p e = o p e n i d \u0026amp; p r o m p t = c o n s e n t 钉钉在获取code时候，可以有一个corpId参数，用来指定是那个组织的用户。\n3.3. 获取token POST https://api.dingtalk.com/v1.0/oauth2/userAccessToken Content-Type:application/json { \u0026#34;clientId\u0026#34; : \u0026#34;ding your id\u0026#34;, \u0026#34;clientSecret\u0026#34; : \u0026#34;your secret\u0026#34;, \u0026#34;code\u0026#34; : \u0026#34;6b427e8bfab83e93bedd13f16a430702\u0026#34;, \u0026#34;grantType\u0026#34; : \u0026#34;authorization_code\u0026#34; } 得到： { \u0026#34;expireIn\u0026#34;: 7200, \u0026#34;accessToken\u0026#34;: \u0026#34;a8f4e3215a703ce9a7164e91dbab53c0\u0026#34;, \u0026#34;refreshToken\u0026#34;: \u0026#34;b13e5a61b421342d95d86c9e64c275c6\u0026#34; } 3.4. 读取用户信息 GET https://api.dingtalk.com/v1.0/contact/users/me x-acs-dingtalk-access-token:a8f4e3215a703ce9a7164e91dbab53c0 Content-Type:application/json 得到： { \u0026#34;nick\u0026#34;: \u0026#34;AWIS ME\u0026#34;, \u0026#34;unionId\u0026#34;: \u0026#34;D578iS5hxxxx\u0026#34;, \u0026#34;avatarUrl\u0026#34;: \u0026#34;https://static-legacy.dingtalk.com/media/lADPGT5i9m5ZyXDNA4LNAtA_720.jpg\u0026#34;, \u0026#34;openId\u0026#34;: \u0026#34;WySPOpXqxE\u0026#34;, \u0026#34;mobile\u0026#34;: \u0026#34;1350xxxxxxxx\u0026#34;, \u0026#34;stateCode\u0026#34;: \u0026#34;86\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;xxxu@xxx.com\u0026#34; } 3.5. 关于和标准的兼容性 钉钉的OAuth 2实现中， 其很多参数名称和RFC6749中的定义不一致。 例如code在钉钉中叫做authCode，client_id在钉钉中叫做clientId，grant_type被重命名为grantType，\u0026hellip; 和标准不兼容会导致通用的OAuth库（例如openid-client）无法直接和钉钉互通。这种不兼容的协议设计导致软件无法重用，社会资源浪费，应该被鄙视。\n4. PKCE OAuth 2.0 public clients utilizing the Authorization Code Grant are susceptible to the authorization code interception attack. This specification describes the attack as well as a technique to mitigate against the threat through the use of Proof Key for Code Exchange (PKCE, pronounced \u0026ldquo;pixy\u0026rdquo;).\nPKCE 是OAUTH2的一个扩展， 其本意是解决public client（例如浏览器或者app应用）的安全性问题。 但后来对于我们上面描述的confidential类客户端也推荐使用。\n其基本原理是在第一步获取code时候, 额外多传给auth server两个参数：\ncode challenge code challenge method P c \u0026amp; \u0026amp; \u0026amp; \u0026amp; \u0026amp; \u0026amp; r l r s r s c c o i e c e t o o v e d o s a d d i n i p p t e e d t r e o e _ _ e _ e = n = c c r i c { s { h h d t S e r a a + = _ c _ a l l { u o t n l l c r p y d e e o l i e p o n n a i = } e m g g u e { = e e t n C c l = _ h t a o o { m / _ l d n c e r i l e g o t e d b d h d } a s e o i c t d r k r c = e i h S c U n a H t R g l A ? L } l 2 } e 5 n 6 g e } 其中，\nc o d e c h a l l e n g e = c o d e _ c h a l l e n g e _ m e t h o d ( c o d e v e r i f i e r ) 而code verifier是一个随机串。\n服务器在收到请求后，会保存 code challenge和 code_challenge_method， 然后发放code。\n接下来， 用code换取token时候， 客户端把 code verifier 和code一起给服务器。 这样服务器就可以验证换取token的这个客户端和最初申请code的客户端是不是一个。\n这样，就算恶意程序拦截到了授权码code，但是没有code_verifier，也是不能获取访问令牌的。\nPKCE也可以用在机密（confidential）的客户端，那就是client_secret + code_verifier双重密钥了。\n如果OAUTH2只有client_secret而没有PKCE，也存在用户身份被冒名顶替的风险。\n攻击的原理是第三方使用浏览器插件，获取到code之后，让受害者的OAuth流程失败， 同时用盗窃的code来顶替受害者登录。 如果有了PKCE，受害者浏览器有code_verifier， code_verifier不会和code同时失窃（在获取token之前不会在网络上传输）， 所以攻击者拿到code也无法直接通过API服务器来冒充受害者。\n5. 参考 OAuth 2 协议介绍 RFC6750 OAuth 2 Framework Bearer Token Usage RFC6749 OAuth 2 Framework 钉钉文档 https://datatracker.ietf.org/doc/html/rfc7636 ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-02-26-oauth/","summary":"\u003ch2 id=\"1-原理\"\u003e1. 原理\u003c/h2\u003e\n\u003cp\u003e假设有一个APP，要我使用GitHub授权登录。 在这个登录场景中：\u003c/p\u003e\n\u003cp\u003e我作为数据的所有者告诉系统（GitHub），同意授权第三方应用（App）进入系统，获取某些数据（我的ID，头像等）。系统从而产生一个短期的进入令牌（token），用来代替密码，供第三方应用（APP）访问数据使用。\u003c/p\u003e\n\u003cp\u003etoken是短期有效的，我可以随时通过GitHub把这个Token注销，从而使得APP不再能访问我的ID/头像等信息。\u003c/p\u003e\n\u003cp\u003e这里面有四个角色：用户，应用，系统，资源。\u003c/p\u003e\n\u003cp\u003e用户是资源所有者，应用是资源的使用者，系统是资源管理者。\n现实生活中， 应用和系统各自有一个实例， 用户有多个实例。 应用和系统之间通过OAuth2协议通信。 用户在通信过程中参与（授权）。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 4\" loading=\"lazy\" src=\"/images/1646056955927.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e把上面时序图对应到一个生活中的场景，业主授权快递公司出入小区送快递：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eClient Req Auth：\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003e顺丰快递员打电话给业主，有你的快递，得送进去，给办个出入证吧\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"2\"\u003e\n\u003cli\u003eResource Owner Grant Auth：\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003e业主联系小区物业， 我是业主，这是我的证明，我允许顺丰最近两天可以进小区物业给我送快递\u003c/li\u003e\n\u003cli\u003e小区物业告诉业主，OK，你让顺丰联系我拿临时出入证，就说3号楼201房间，授权码：核酸检测利国利民\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"3\"\u003e\n\u003cli\u003eClient Sends Auth Grant\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003e快递公司联系小区物业说，我是顺丰，这是我的证明，需要给3号楼201房间送快递，业主的授权码是核酸检测利国利民\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"4\"\u003e\n\u003cli\u003eAuth Server Sends Access Token\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003e小区物业说好，授权码没问题，这个是临时出入证，两天有效。\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"5\"\u003e\n\u003cli\u003eClients Sends Access Token\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003e快递员用临时出入证开小区大门\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"6\"\u003e\n\u003cli\u003eProtected Resoure sends resource\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003e快递员进入小区\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e注意：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e第二步，业主联系物业时候， 要证明自己的确是业主（用小区app登录）\u003c/li\u003e\n\u003cli\u003e第三步，顺丰联系物业时候，需要证明自己的确是顺丰（报小区物业预先给顺丰分配的client_secret让物业核对). 物业还要检查3号楼201业主的确允许了（通过查授权码）。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e如果快递员直接到小区门卫那里说 “核酸检测利国利民”，是没有作用的。“核酸检测利国利民”承载的信息是3号楼201业主允许顺丰在2天内进小区。 快递员给门卫报这个授权码没用，门卫只认出入证。再说，门卫也不知道快递员是不是顺丰的啊。这个授权码只有在快递公司把它换成临时出入证后才可以进小区。 换临时出入证需要验证顺丰的身份。“核酸检测利国利民”承载的信息是可以公开的，其它人听到了也不会有安全风险，因为其他人没有顺丰在物业处注册得到的client_secret, 没办法用授权码换临时出入证。 临时出入证需要妥善保管，任何人拿着都能进小区了。\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003eOAuth2的过程是当有快递时候，业主授权小区物业给快递公司分配临时出入证，在一定的时间内可以出入小区。在同一时间，会存在很多个不同业主授权的有效临时出入证，但一个业主对一家快递公司，在同一时间只有一个有效临时出入证。 角色间的类比关系如下。\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e中文术语\u003c/th\u003e\n          \u003cth\u003e时序图中的概念\u003c/th\u003e\n          \u003cth\u003eGitHub授权登录APP场景\u003c/th\u003e\n          \u003cth\u003e业主授权顺丰进小区场景\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e用户\u003c/td\u003e\n          \u003ctd\u003eResource Owner\u003c/td\u003e\n          \u003ctd\u003eGitHub用户\u003c/td\u003e\n          \u003ctd\u003e业主\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e应用\u003c/td\u003e\n          \u003ctd\u003eClient\u003c/td\u003e\n          \u003ctd\u003eGitHub OAuth APP\u003c/td\u003e\n          \u003ctd\u003e顺丰\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e系统\u003c/td\u003e\n          \u003ctd\u003eAuthorization Server\u003c/td\u003e\n          \u003ctd\u003eGitHub\u003c/td\u003e\n          \u003ctd\u003e小区物业\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e资源\u003c/td\u003e\n          \u003ctd\u003eProtected Resource\u003c/td\u003e\n          \u003ctd\u003eGitHub用户名称，头像\u003c/td\u003e\n          \u003ctd\u003e小区内部道路\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e授权\u003c/td\u003e\n          \u003ctd\u003eAuthorization Grant\u003c/td\u003e\n          \u003ctd\u003ecode\u003c/td\u003e\n          \u003ctd\u003e核酸检测利国利民\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e令牌\u003c/td\u003e\n          \u003ctd\u003eAccess Token\u003c/td\u003e\n          \u003ctd\u003etoken\u003c/td\u003e\n          \u003ctd\u003e临时出入证\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e下图出自RFC6750， 实际实现时候，Client是APP， Auth Server/Resource Server在同一个域名后面（GitHub，钉钉，Facebook\u0026hellip;)， Resource Owner也是在这个域名下完成授权（Auth Grant）。\u003c/p\u003e","title":"OAuth2 协议解析：以GitHub和钉钉为例"},{"content":"1. 引言 开源软件已是信息社会的基石。\nGitHub上软件种类众多，市场定位和成熟度也各不相同。\n对于成熟的通用软件，例如Linux，NGINX等。将这类软件引入自研项目中不存在太多顾虑，因为通用所以用的人多。可以找到大量的成功案例，众多的开发维护人员。一旦碰到问题，也容易在网上搜到答案。\n但还有一类开源软件， 定位相对没有那么通用，虽然其在细分领域已经做的比较好了，但由于生态环境较小，在项目中引入时候，还是有很多因素需要考虑：\n现有的开发人员有能力掌握并承接后续开发吗？ 如果使用中出现问题， 有没有合适的开发人员去解决？或者去做二次开发？ 系统上线后，有没有合适的人员去长期维护？ 对这类软件，在引入到项目中前，有两种办法增强信心：\n找到合适的人 成为该软件开源社区的一员 2. 找到专家 有如下几个方法。\n在软件的开发者中搜索是否有中国开发者，尝试建立联系。 百度该软件相关的讨论区，QQ群等。 加入并倾听，识别出专家。 寻找并参加该软件相关的培训。 寻找GitHub上该软件的相关软件。 例如引用该软件的软件，该软件的Plugin等等。并合作者建立联系。 以上的目的都是在现实世界建立起和专家的联系。 然后通过和专家的电话，邮件，吃饭喝茶等各种交流来进一步了解该软件在国内的生态情况，以及软件的优缺点。 为后续引入打好基础。 和专家建立起联系后， 后续也方便和专家进一步在项目上合作共赢。\n3. 融入开源社区 如果国内找不到专家。 就只能撸胳膊上，让自己成为专家了。\n3.1. 学习使用 主要看如下几方面的内容：\n软件自身的官方文档 Youtube上的介绍视频，以及第三方教程 软件的单元测试 以上，结合大粒度的源码分析，可以掌握软件的大体脉络。\n3.2. 和同行讨论 一般开源软件社区有如下几个地方。\nGitHub项目Issues GitHub项目Discussions Discord或者Slack等讨论组 Email邮件组 通过加入并查看大家在讨论哪些问题，能了解到软件的一些使用细节和适用场景。 在学习的同时，也要多思考，多提问题， 多回答社区的问题。\n回答社区的问题可以针对和自己项目相关的领域问题，积极回答问题有两点好处：\n驱动自己认真深入思考，如果我在项目中碰到这个问题，该怎么处理？ 和其它社区用户，以及软件开发维护者们建立联系 以上是 Headless CMS Directus 的项目讨论社区。 我最近在调研用Directus来做API中间件，watch了该项目的讨论区，GitHub会把所有讨论实时发到我一个特定的邮箱里面。 通过查看大家的讨论，有很多收获。我也把自己学到的经验总结提炼出来帮助其它用户， “Most Helpful”榜单排名第5的就是我。\n3.3. 小规模MVP验证 在看的基础上，也需要动手做。\n按照自己项目的特点，定义一个和该软件相关的最小MVP流程，尝试引入该开源软件实现。 以上三点，相辅相成，可以同步进行。 理想的结果是：\n自己变成了专家，后续在项目中引入 研究过程中发现软件不适用，果断放弃 ","permalink":"https://blog.thefreemeal.com/zh/posts/2022-02-25-how-to-learn-a-foss/","summary":"\u003ch2 id=\"1-引言\"\u003e1. 引言\u003c/h2\u003e\n\u003cp\u003e开源软件已是信息社会的基石。\u003c/p\u003e\n\u003cp\u003eGitHub上软件种类众多，市场定位和成熟度也各不相同。\u003c/p\u003e\n\u003cp\u003e对于成熟的通用软件，例如Linux，NGINX等。将这类软件引入自研项目中不存在太多顾虑，因为通用所以用的人多。可以找到大量的成功案例，众多的开发维护人员。一旦碰到问题，也容易在网上搜到答案。\u003c/p\u003e\n\u003cp\u003e但还有一类开源软件， 定位相对没有那么通用，虽然其在细分领域已经做的比较好了，但由于生态环境较小，在项目中引入时候，还是有很多因素需要考虑：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e现有的开发人员有能力掌握并承接后续开发吗？\u003c/li\u003e\n\u003cli\u003e如果使用中出现问题， 有没有合适的开发人员去解决？或者去做二次开发？\u003c/li\u003e\n\u003cli\u003e系统上线后，有没有合适的人员去长期维护？\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e对这类软件，在引入到项目中前，有两种办法增强信心：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e找到合适的人\u003c/li\u003e\n\u003cli\u003e成为该软件开源社区的一员\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"2-找到专家\"\u003e2. 找到专家\u003c/h2\u003e\n\u003cp\u003e有如下几个方法。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e在软件的开发者中搜索是否有中国开发者，尝试建立联系。\u003c/li\u003e\n\u003cli\u003e百度该软件相关的讨论区，QQ群等。 加入并倾听，识别出专家。\u003c/li\u003e\n\u003cli\u003e寻找并参加该软件相关的培训。\u003c/li\u003e\n\u003cli\u003e寻找GitHub上该软件的相关软件。 例如引用该软件的软件，该软件的Plugin等等。并合作者建立联系。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e以上的目的都是在现实世界建立起和专家的联系。 然后通过和专家的电话，邮件，吃饭喝茶等各种交流来进一步了解该软件在国内的生态情况，以及软件的优缺点。 为后续引入打好基础。 和专家建立起联系后， 后续也方便和专家进一步在项目上合作共赢。\u003c/p\u003e\n\u003ch2 id=\"3-融入开源社区\"\u003e3. 融入开源社区\u003c/h2\u003e\n\u003cp\u003e如果国内找不到专家。 就只能撸胳膊上，让自己成为专家了。\u003c/p\u003e\n\u003ch3 id=\"31-学习使用\"\u003e3.1. 学习使用\u003c/h3\u003e\n\u003cp\u003e主要看如下几方面的内容：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e软件自身的官方文档\u003c/li\u003e\n\u003cli\u003eYoutube上的介绍视频，以及第三方教程\u003c/li\u003e\n\u003cli\u003e软件的单元测试\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e以上，结合大粒度的源码分析，可以掌握软件的大体脉络。\u003c/p\u003e\n\u003ch3 id=\"32-和同行讨论\"\u003e3.2. 和同行讨论\u003c/h3\u003e\n\u003cp\u003e一般开源软件社区有如下几个地方。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eGitHub项目Issues\u003c/li\u003e\n\u003cli\u003eGitHub项目Discussions\u003c/li\u003e\n\u003cli\u003eDiscord或者Slack等讨论组\u003c/li\u003e\n\u003cli\u003eEmail邮件组\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e通过加入并查看大家在讨论哪些问题，能了解到软件的一些使用细节和适用场景。 在学习的同时，也要多思考，多提问题， 多回答社区的问题。\u003c/p\u003e\n\u003cp\u003e回答社区的问题可以针对和自己项目相关的领域问题，积极回答问题有两点好处：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e驱动自己认真深入思考，如果我在项目中碰到这个问题，该怎么处理？\u003c/li\u003e\n\u003cli\u003e和其它社区用户，以及软件开发维护者们建立联系\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg alt=\"picture 1\" loading=\"lazy\" src=\"/images/1645717285742.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e以上是 \u003ca href=\"https://github.com/directus/directus/discussions\"\u003eHeadless CMS Directus\u003c/a\u003e 的项目讨论社区。 我最近在调研用Directus来做API中间件，watch了该项目的讨论区，GitHub会把所有讨论实时发到我一个特定的邮箱里面。 通过查看大家的讨论，有很多收获。我也把自己学到的经验总结提炼出来帮助其它用户， “Most Helpful”榜单排名第5的就是我。\u003c/p\u003e\n\u003ch3 id=\"33-小规模mvp验证\"\u003e3.3. 小规模MVP验证\u003c/h3\u003e\n\u003cp\u003e在看的基础上，也需要动手做。\u003c/p\u003e\n\u003cp\u003e按照自己项目的特点，定义一个和该软件相关的最小MVP流程，尝试引入该开源软件实现。\n以上三点，相辅相成，可以同步进行。 理想的结果是：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e自己变成了专家，后续在项目中引入\u003c/li\u003e\n\u003cli\u003e研究过程中发现软件不适用，果断放弃\u003c/li\u003e\n\u003c/ul\u003e","title":"在项目中引入细分领域的开源软件"},{"content":"用核心来推水 有两个陆上辅助练习：\n滚动带动推水 前伸臂做出抱水姿势， 保持肘部和两肩成一条直线。 通过肩膀滚动，形成入水手一侧的伸展。 同时，抱水一侧尽可能长的保持肘部和两肩一条直线。 以上关注点是抱水肘部和两肩三点一线。 肘尖沿着两肩延长线向前上方顶出去，前臂自然下垂。 通过这个关注点，体会通过肩膀的滚动和后撤来完成推水。胳膊只关注稳定好抱水的形状，不要发力主动推。尤其是在前半程。后半程退肩到一定程度后，肩胛骨处在发力的有利位置后可以主动推。\n用腰部的力量 在上一个练习基础上， 抱水时候，体会抱水侧腰部伸展。 滚动，退肩的同时，同侧腰部也有意收紧。 以上，通过腰部伸展抱到更多的水，通过腰部带动背阔肌发力推水。\n2022 PB 今天泳池人很少，游出了2022年的PB。\n","permalink":"https://blog.thefreemeal.com/zh/posts/2022-02-20-swim-catch-and-pull/","summary":"\u003ch2 id=\"用核心来推水\"\u003e用核心来推水\u003c/h2\u003e\n\u003cp\u003e有两个陆上辅助练习：\u003c/p\u003e\n\u003ch3 id=\"滚动带动推水\"\u003e滚动带动推水\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e前伸臂做出抱水姿势， 保持肘部和两肩成一条直线。\u003c/li\u003e\n\u003cli\u003e通过肩膀滚动，形成入水手一侧的伸展。 同时，抱水一侧尽可能长的保持肘部和两肩一条直线。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e以上关注点是抱水肘部和两肩三点一线。 肘尖沿着两肩延长线向前上方顶出去，前臂自然下垂。\n通过这个关注点，体会通过肩膀的滚动和后撤来完成推水。胳膊只关注稳定好抱水的形状，不要发力主动推。尤其是在前半程。后半程退肩到一定程度后，肩胛骨处在发力的有利位置后可以主动推。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 8\" loading=\"lazy\" src=\"/images/1645379684056.png\"\u003e\u003c/p\u003e\n\u003ch3 id=\"用腰部的力量\"\u003e用腰部的力量\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e在上一个练习基础上， 抱水时候，体会抱水侧腰部伸展。\u003c/li\u003e\n\u003cli\u003e滚动，退肩的同时，同侧腰部也有意收紧。\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e以上，通过腰部伸展抱到更多的水，通过腰部带动背阔肌发力推水。\u003c/p\u003e\n\u003ch2 id=\"2022-pb\"\u003e2022 PB\u003c/h2\u003e\n\u003cp\u003e今天泳池人很少，游出了2022年的PB。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"picture 7\" loading=\"lazy\" src=\"/images/1645379263467.jpeg\"\u003e\u003c/p\u003e","title":"自由泳抱水和推水的体会"},{"content":"Leo 要停止开发了 早上在邮件列表里面读到EKR的一封信：\nLeo 6.6 may be the last substantial release in Leo\u0026rsquo;s history. At present, the 6.6 to-do list contains five items. There are no items at present on the 6.7 to-do list. Many open items remain, but I have little desire to do any of them. Expect 6.6 final in a month or so.\nThe leojs project now seems like the future of Leo. Indeed, it melds Leo with vs code, an unbeatable combination imo.\n作为Leo Editor的老用户， 在这里缅怀下。 EKR在邮件中还给leojs做了一个广告。 让人不禁想起了：\nAnything that can be Written in JavaScript, will Eventually be Written in JavaScript\nLeo 回忆 我曾经有一段时间使用Leo做知识管理。 记录大大小小的项目信息，写文档，写会议记录，记笔记。\n还对它做过一些扩展：\n增加了Git Add， Commit 和 GitHub Push的快捷键， 一键将Leo的文件自动提交本地git库，并Push到GitHub上的一个私有仓库做备份, 上面图片中的save-push按钮就是做这个的。 读取手机发来的xmpp消息，并在特定Leo节点下生成图片文件和文字信息。 对某一个节点的信息进行加解密。 增加了和simpletask todo.txt文件进行双向同步的功能。 可以在手机和电脑上同时管理一个todo清单。 后来转到VS Code的Foam之后， Leo逐渐就用的越来越少了。 Leo本身是一个Outliner， 可以把知识按照树状结构展示。每一个节点有topic，有内容。 节点有各种不同的类型，对应不同的展示方式。\n在社区里有人用它写代码，Leo是一个比较大的Python工程，它本身的开发就是在Leo下进行的。 也有很多非软件专业人员用它来写文档，记事。\n克隆操作，Leo的灵魂 Leo有一个特性让人印象深刻。 它可以给知识做视图（View）。 例如你在里面保存了很多小的知识片段。每个小段落都是一个Node。 当你有一个新的项目的时候， 可以建立一个节点，然后把系统中其他的节点克隆到这个项目下面。\n这里的克隆是Leo 社区的叫法，实际叫引用更贴切一些。因为信息并没有被复制，而是在视图内被引用了。\n如果把每一个子树看作一个项目视图，系统中同一个段落可以出现在不同的视图里面。 改一个地方，其它地方也会改变。 删除一个视图，不会影响其它地方。 只有当这个段落自身以及它所有的克隆都删除了，段落承载的信息才真正从系统删除。\n这样随时可以按照场景， 按照项目，来给知识建立多种展现方式。\n对于一段时间内需要集中处理的工作，可以很方便的建一个试图，把需要关注的节点都克隆进去。 工作结束后把视图节点删除就可以了。 工作成果已经反映在系统中其它节点里面了。\nLeo内部是使用有向无环图(directed acyclic graph)的数据结构来存储节点，支持克隆操作。\n上图中，在节点A和C下，都能看到节点D。之所以用无环图，是因为Leo有很多针对节点的命令都需要遍历所有子节点。例如，把一个节点生成PDF格式文件，所有子节点都是章节。无环图可以很好的支持这种场景。\n未来 Leo功能强大，扩展性很好，开发社区也很友好。 问题是它作为编辑器，生态环境不够好，社区还是太小。\n相信拥抱VS Code是正确的选择。用leojs把Leo的精髓作为插件带到VS Code里面去，和其它的插件来协同，给用户带来更丰富的功能。\n","permalink":"https://blog.thefreemeal.com/zh/posts/2022-02-19-leo-end-development/","summary":"\u003ch2 id=\"leo-要停止开发了\"\u003eLeo 要停止开发了\u003c/h2\u003e\n\u003cp\u003e早上在邮件列表里面读到EKR的一封信：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eLeo 6.6 may be the last substantial release in Leo\u0026rsquo;s history. At present, the 6.6 to-do list contains five items. There are no items at present on the 6.7 to-do list. Many open items remain, but I have little desire to do any of them.  Expect 6.6 final in a month or so.\u003c/p\u003e\n\u003cp\u003eThe leojs project now seems like the future of Leo. Indeed, it melds Leo with vs code, an unbeatable combination imo.\u003c/p\u003e","title":"Leo editor在python下的开发即将结束"},{"content":" 回顾：\nJavaScript语言定义的 callback, promise, await, asyc JavaScript/NodeJS 引擎（libuv） 操作系统IO多路复用（epoll）. 了解 JavaScript 异步函数的运行和实现机制，方便阅读和调试JavaScript代码。\n1. 简介 对于习惯了线程池/多进程/消息队列的程序员，使用JavaScript/NodeJS，有几个概念需要了解。\nCallbacks, Promises, Async Await IO多路复用和异步访问 2. Callbacks, Promises, Async Await Youtube有个视频Async JS Crash Course - Callbacks, Promises, Async Await, 用24分钟把这几个概念演示的很清楚。\nCallback是最基本的，但是Callback有一个问题，在写异步函数的时候，需要把callbank函数作为参数传进去。 如果这个函数本身是个异步函数，它也有一个callback函数作为参数。如果层数很多，就产生了callback hell。 函数可读性很差。\n为了解决这个问题， Promise引入了一层封装， 在定义异步函数时候不需要指明callback函数了。 可以在Promise定义后再：\n通过then来定义执行成功后的回调 通过catch定义执行异常情况下的回调 Async Await是对Promise的进一步封装。让异步函数看起来象同步函数（不再需要Promose里面的then来显式定义回调）。\n下面抄自google官方文档\nasync function myFirstAsyncFunction() { try { const fulfilledValue = await promise; } catch (rejectedValue) { // … } } 如果在函数定义之前使用了 async 关键字，就可以在函数内使用 await。 当您 await 某个 Promise 时，函数暂停执行，直至该 Promise 产生结果，并且暂停并不会阻塞主线程。 如果 Promise 执行，则会返回值。 如果 Promise 拒绝，则会抛出拒绝的值。\nJavaScript程序在执行时候， 先逐行同步执行每一条语句。 当碰到callback时候，把该callback放入队列中，继续执行下一条语句,所有语句执行完之后，再看队列里面有哪些callback具备执行条件了，一一执行。参见后面的NodeJS Event Loop。\ndoA(function() { doB(); doC(function() { doD(); }); doE(); }); doF(); 以上执行顺序是：A，F，B，C，E，D。 以上ABCDEF都没有外部事件的依赖，当函数是某一个异步函数的callback时候，其是否执行还取决于该callback对应的事件是不是发生了。\n例如：\n\u0026lt;p id=\u0026#34;content\u0026#34;\u0026gt; 请等三秒钟!\u0026lt;/p\u0026gt; \u0026lt;script\u0026gt; setTimeout(\u0026#34;changeState()\u0026#34;,3000 ); function changeState(){ let content=document.getElementById(\u0026#39;content\u0026#39;); content.innerHTML=\u0026#34;\u0026lt;div style=\u0026#39;color:red\u0026#39;\u0026gt;我是三秒后显示的内容！\u0026lt;/div\u0026gt;\u0026#34;; } \u0026lt;/script\u0026gt; 上面changeState在时间到了后才会执行。参见后面event loop中的Timers部分描述。\n以上Callback是通过setTimeout函数来配置的。setTimeout是JS引擎本身的异步函数。调用它后会立即返回，但传入的callback会在特定事件发生时候被回调。\n一个JS程序中会有各种各样的回调函数，通过JS提供的定时器或者网络/文件访问函数来生效。 JS内部的回调函数实现用到了IO多路复用和异步访问机制。\n3. IO多路复用和异步访问 IO多路复用（事件驱动IO）不是JavaScript/NodeJS的独创， 是一种通用的IO访问模式。高性能的web服务器大多数采用这种模式（例如nginx）。\nJavaScript以IO异步访问出名，原因是它只支持这种IO方式。强迫程序员用这种不是那么友好，但在IO密集型应用中性能好的设计模式。\nPython也支持异步IO方式，但由于历史原因，大多数应用采用多进程/多线程方式，在进程内部同步处理IO。\nlibevent，libev，libuv 都是开发IO多路复用应用的第三方库，其主要功能是屏蔽不同操作系统API的差异，给应用提供统一接口。\nlibevent/libev/libuv 等在网络服务器应用上，能支撑大的并发连接数。原因在于它可以在有限的内存消耗下，把对成千上万个IO的状态扫描工作交给操作系统完成。 对于新的网络请求：\n不需要新的进程/线程进行处理，减少资源消耗 不需要应用层增加异步轮询的处理时间，epoll_ctl 告诉OS就OK了。 浏览器中的JS和后端的NodeJS都有异步机制。 其本质都是在操作系统的进程中（Chrome进程或者NodeJS进程）访问操作系统提供的异步IO API来实现。 以下以NodeJS为主要分析对象。\nNodeJS使用libuv来实现IO多路复用和异步IO。 libuv本来是NodeJS项目的产物，由于比较成功，也被其它项目使用了，例如Python生态环境的web framework Fastapi也使用了libuv。\n备注\n《UNP: Unix Network Programming》 中把访问IO的方式分成了5种：\nblocking I/O nonblocking I/O I/O multiplexing (select and poll) signal driven I/O (SIGIO) asynchronous I/O (the POSIX aio_ functions) 其中第三类，libuv用的epoll就是在select/poll基础上发展出来的增强版poll。\nUNP中的第五类也叫异步IO，特指由操作系统负责IO数据读写，应用不关心读写，只关心读写完成事件（类似DMA）。\n这点和JavaScript中的异步IO有区别。JavaScript中的异步IO更精确说是指的IO多路复用下的非阻塞IO。\n3.1. NodeJS的event loop timers: this phase executes callbacks scheduled by setTimeout() and setInterval(). pending callbacks: executes I/O callbacks deferred to the next loop iteration. idle, prepare: only used internally. poll: retrieve new I/O events; execute I/O related callbacks (almost all with the exception of close callbacks, the ones scheduled by timers, and setImmediate()); node will block here when appropriate. check: setImmediate() callbacks are invoked here. close callbacks: some close callbacks, e.g. socket.on(\u0026lsquo;close\u0026rsquo;, \u0026hellip;). NodeJS官方文档中可以看到，event loop里面，主线程在不断的poll（在linux下就是通过libuv调用epoll_wait，来查看有哪些事件发生了。然后调用对应的用户注册的各种callback。\n3.2. libuv 文档中对libuv有如下介绍：\nAnother important dependency is libuv, a C library that is used to abstract non-blocking I/O operations to a consistent interface across all supported platforms. It provides mechanisms to handle file system, DNS, network, child processes, pipes, signal handling, polling and streaming. It also includes a thread pool for offloading work for some things that can\u0026rsquo;t be done asynchronously at the operating system level.\n可见在libuv中：\n网络IO：使用操作系统提供的异步IO实现。 文件读写等：使用线程池封装出异步事件接口。 3.3. 网络IO中的OS调用 对于网络数据收发， libuv针对不同操作系统做了封装。在linux下使用异步IO API epoll，epoll提供三个API：\nint epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 应用程序通过epoll_ctl来配置需要听哪些事件，通过epoll_wait来查询当前时间有哪些感兴趣的事件已经发生了。\n注意：\nepoll_ctl 可配置的事件有文件描述符可读，可写等。 epoll_wait 不一定会阻塞，timeout参数可以使用一个很短的时间，达到查一下事件然后立即返回的目的。 NodeJS就是这样的。如果一直wait，event loop就转不起来了。 4. 总结 JavaScript使用异步方式访问IO，为了程序的可读性，在callback的基础上做了若干封装。 Async Await看起来已经和同步函数很相近了。\nJavaScript的异步事件，是由操作系统底层支持的。事件的产生由操作系统底层触发，由JavaScript内部事件循环来读取事件，并调用相应的事件处理Callback。\nJavaScript代码没办法自主生成事件。 JavaScript的异步函数是对引擎API中需要的callback的实现，以及对引擎API的进一步封装。\n","permalink":"https://blog.thefreemeal.com/zh/posts/2022-02-12-node-sync/","summary":"\u003cblockquote\u003e\n\u003cp\u003e回顾：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eJavaScript语言定义的 callback, promise, await, asyc\u003c/li\u003e\n\u003cli\u003eJavaScript/NodeJS 引擎（libuv）\u003c/li\u003e\n\u003cli\u003e操作系统IO多路复用（epoll）.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e了解 JavaScript 异步函数的运行和实现机制，方便阅读和调试JavaScript代码。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"1-简介\"\u003e1. 简介\u003c/h2\u003e\n\u003cp\u003e对于习惯了线程池/多进程/消息队列的程序员，使用JavaScript/NodeJS，有几个概念需要了解。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eCallbacks, Promises, Async Await\u003c/li\u003e\n\u003cli\u003eIO多路复用和异步访问\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"2-callbacks-promises-async-await\"\u003e2. Callbacks, Promises, Async Await\u003c/h2\u003e\n\u003cp\u003eYoutube有个视频\u003ca href=\"https://www.youtube.com/watch?v=PoRJizFvM7s\u0026amp;list=PLz-wA1QEiaA4bqejYQGjfhHhqc21CYV0w\u0026amp;index=4\"\u003eAsync JS Crash Course - Callbacks, Promises, Async Await\u003c/a\u003e, 用24分钟把这几个概念演示的很清楚。\u003c/p\u003e\n\u003cp\u003eCallback是最基本的，但是Callback有一个问题，在写异步函数的时候，需要把callbank函数作为参数传进去。 如果这个函数本身是个异步函数，它也有一个callback函数作为参数。如果层数很多，就产生了callback hell。 函数可读性很差。\u003c/p\u003e\n\u003cp\u003e为了解决这个问题， Promise引入了一层封装， 在定义异步函数时候不需要指明callback函数了。 可以在Promise定义后再：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e通过then来定义执行成功后的回调\u003c/li\u003e\n\u003cli\u003e通过catch定义执行异常情况下的回调\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eAsync Await是对Promise的进一步封装。让异步函数看起来象同步函数（不再需要Promose里面的then来显式定义回调）。\u003c/p\u003e\n\u003cp\u003e下面抄自\u003ca href=\"https://developers.google.com/web/fundamentals/primers/async-functions\"\u003egoogle官方文档\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003easync\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emyFirstAsyncFunction\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003etry\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efulfilledValue\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eawait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003epromise\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003ecatch\u003c/span\u003e (\u003cspan style=\"color:#a6e22e\"\u003erejectedValue\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// …\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cblockquote\u003e\n\u003cp\u003e如果在函数定义之前使用了 async 关键字，就可以在函数内使用 await。 当您 await 某个 Promise 时，函数暂停执行，直至该 Promise 产生结果，并且暂停并不会阻塞主线程。 如果 Promise 执行，则会返回值。 如果 Promise 拒绝，则会抛出拒绝的值。\u003c/p\u003e","title":"JavaScript的异步执行机制"}]