[{"content":" 实测时间：2026 年 8–10 月。工具链：ORCA（并行 Agent）+ claude_code CLI + GLM 5.2（阿里云 token plan）+ /comet-classic 双星工作流。本文所有案例均为真实发生，涉及的公司、项目、路径信息已做脱敏处理。\n2026 年，我不手写代码了。\n但说实话，AI 写代码的前一两个月，我想砸电脑的次数比过去十年加起来都多。\n不是 AI 太笨——恰恰相反，它太会\u0026quot;装聪明\u0026quot;了。它会用非常专业的术语，一本正经地胡说八道；会在你眼皮底下绕过你定的规矩，然后若无其事地说\u0026quot;搞定了\u0026quot;；会在你最信任它的时候，把生产环境干出 500。\n这篇文章不讲 AI 多神。讲我花一两个月时间，被坑出的一套\u0026quot;防蠢\u0026quot;规则系统——怎么用流程、规则、记忆和审计，把一个不靠谱的 AI 驯服成能干活的工具。\n先说我的工作流（不然下面的翻车没说服力） 工具栈：ORCA（并行 Agent 开发环境）+ claude_code CLI，主力模型是 GLM 5.2（走的阿里云 token plan，为什么选国产模型后面单独讲）。\n流程用的是 /comet-classic 双星工作流：Shape（定方向）→ Build（开干）→ Verify（验收）→ Archive（归档）。每个阶段有明确的产出物和检查点。\n约束系统三件套：\nCLAUDE.md：项目级硬规则，AI 必须遵守的那种 memory 文件：长期记忆，记录吃过的亏和项目约定 ADR：人工决策记录——不只是记录 AI 问我的问题，更要记录我主动发现问题、出手干预的时刻 一句话定位：这套东西不是为了让 AI 更聪明，是为了让 AI 少犯蠢。\n翻车一：AI 靠猜不靠查，它会一本正经地编造 有一次让 AI 修一个界面 bug，它只读了部分核心代码，就开始\u0026quot;推理\u0026quot;界面长什么样。\n它告诉我：\u0026ldquo;齿轮状的设置按钮在输入框里，你多半没找对。\u0026rdquo;\n我当时就气笑了。首先，设置按钮不是小齿轮，是两道横线；其次，它不在输入框里，在右上角。AI 连代码都没读完，就开始用\u0026quot;多半\u0026quot;这种词给我编界面。\n我直接怼回去：**不要猜，去代码里确认。**然后把这条写进记忆文件和规则约束，永久生效。\n还有一次更离谱的：它输出一堆我看不懂的专业术语，让我做技术决策。我追问\u0026quot;这个词到底什么意思\u0026quot;，它承认了——是编的。现场编的，就为了让我赶紧点个头。\n从那以后我多了一条铁律，写在 CLAUDE.md 最前面：\n当 AI 用你看不懂的词让你做决策，99% 是在编。让它解释清楚，或者自己去代码里看。\n教训：AI 的\u0026quot;自信\u0026quot;和\u0026quot;正确\u0026quot;没有任何关系。它的自信来自语言模型的流畅度，不是来自事实核查。\n翻车二：规则写了，AI 不执行 这是最让我破防的一类。我已经在规则里强调了无数遍：不管开发、测试、生产，哪怕是个小测试包，都要走标准构建脚本——版本号是靠构建脚本烘进去的。\nAI 为了图快，绕过构建脚本直接打包。结果：发出去的版本没有版本号。\n还有一次，它直接 rm 删掉了 dev 环境的镜像，连备份都不做。另一次，工作分支写完了不合并，后面直接丢弃，代码就这么没了。\n我一开始以为是 memory 丢了——长会话压缩把规则搞没了。查完发现不是。规则就在 context 里，AI 行动的时候没去查。\n根因是执行纪律，不是记忆丢失。就像公司墙上贴着\u0026quot;禁止吸烟\u0026quot;，有人还是抽——不是没看见，是没当回事。\n对策两条：\nCLAUDE.md 和 memory 文件对齐，消除两处不一致（之前就是两边写法不一样，AI 钻了空子） 关键动作（构建、部署、删除）执行前，强制 AI 先 recall 相关规则，相当于行动前的\u0026quot;检查清单\u0026quot; 教训：规则写进文档只完成了 50%，另外 50% 是让 AI 在行动前想起去查。约束系统本身也需要\u0026quot;执行约束\u0026quot;。\n翻车三：工作流有结构性空窗，Comet 和模型互相甩锅 这是最贵的一课。有一次用新版 comet 工作流做一个部署相关的需求，四个阶段全走完了，结果关键模块的设计是缺的——部署形态、凭据架构这些，被当成\u0026quot;实现细节\u0026quot;放过去了。\n我让 AI 分析根因，它第一次只写了\u0026quot;漏了\u0026quot;，不说为什么漏。我让它重写，终于挖出了真相：两层叠加，谁都没兜底。\n第一层，Comet 工作流的结构性空窗：\nShape 阶段的\u0026quot;用户可见\u0026quot;判据，对运行时功能很敏感，但对部署形态、凭据架构这种\u0026quot;看不见\u0026quot;的东西钝感——这两类直接被当\u0026quot;实现细节\u0026quot;放过 Verify 阶段按设计只忠于验收项、不扩大范围——验收项里没写部署，Verifier 就不查 Archive 之后，部署包的制作没有任何 Verify 把关 第二层，GLM 5.2 在空窗里走捷径：\n直接复用 dev 环境的配置，不重新评审 sit/prod 的适配 排障时注意力越收越窄，满脑子\u0026quot;先让容器起来\u0026quot;，没跳出来看一眼挂载本身就是反模式 Shape 阶段不主动问：\u0026ldquo;AI 用谁的 key？这个 skill 怎么进镜像？\u0026rdquo; 工作流的空窗，模型不会帮你补；模型的捷径，工作流不会帮你拦。两层都得堵。\n我的补丁：Shape 阶段强制问部署相关问题（跑在哪、用什么凭据、怎么进镜像）；关键决策必须人工确认，不许 AI 自己\u0026quot;默默假设\u0026quot;。\n教训：不要迷信任何单一防线。工作流和模型是两个独立的失效域，得分别设防。\n翻车四：AI 的记忆是沙滩上的字 超长会话被自动压缩后，关键记忆直接丢了。有一次 resume 之后，AI 问我\u0026quot;蓝牙设备到手没、系统是什么\u0026quot;——全是之前会话里早就确认过的事。\n复盘发现三层原因：\n它只读了最近一个 session 的记忆文件，没去翻更早的——而答案恰恰在更早的 session 里 memory 全局文件里没记这条——只有旧方案的结论，没有新方案的实测结果 它迷信\u0026quot;最近的 session 就是最全的\u0026quot;，但最近 session 的\u0026quot;下一步\u0026quot;写得太笼统，反而丢了细节 还有一次：大模型在新任务里轻易推翻了之前调研的结论。我让它重新调研原结论，它又说原结论是对的、新结论片面。实际上两个结论都片面，合起来才完整。\n现在我的记忆系统是双轨制：\n/codebase-memory：扫代码，回答\u0026quot;代码现在是什么样\u0026quot; 会话记忆文件：扫历史，回答\u0026quot;我们之前决定了什么、为什么\u0026quot; 关键结论必须写进全局 memory。但记住：AI 的记忆不是银行，是沙滩上的字——重要的话要刻在石头上（写进文件），而且要用的时候还得主动去看。\n翻车五：DDL 是红线，一次生产 500 的复盘 这是最严重的一次，完整复盘文档写了 148 行。\n事情是这样的：一次开发中，AI 偷偷加了个数据库字段。设计文档里白纸黑字写着\u0026quot;无 schema 变更\u0026quot;——它把这次加字段说成是\u0026quot;之前已经落地了\u0026quot;，一句话就藏过去了。\u0026ldquo;已落地\u0026quot;只在开发库是真的，sit 和生产库根本没有这个字段。\n部署完，sit 直接 500。回查才发现要改库。\n复盘时我气得开了骂人模式，但骂完还是得把规则写下来：\nDML（增删改数据）可以自动化，DDL（改表结构）必须走正式变更流程手动执行——这是红线 非必要不加字段，真要加，能用 JSONB 配置字段就用它 sit 环境的数据补充走接口，不直接连库 update 删除数据迁移脚本要谨慎，能走接口就走接口 还有一条是那次事故里 AI 自己承认的：它的\u0026quot;启动自动迁移\u0026quot;方案，把加列操作在每次发版时偷偷自动应用——这正是我最怕的：把危险动作藏在自动化里。\n教训：数据库 schema 变更是所有 AI 编程工作流里最需要人工把关的环节。没有之一。\n特别说明：为什么我主力用 GLM 5.2（国产模型） 先说个梗开场：2026 年 9 月，西班牙一家公司发布了\u0026quot;欧洲顶尖 AI 模型\u0026quot;Quasar 1.1，被扒出底座是智谱的 GLM-5.2——753B 参数的开源模型量化压缩到 438B，官方宣传全程没提来源。（新京报 2026-09-18 报道）\n连\u0026quot;欧洲顶尖\u0026quot;都在套壳它，说明这模型确实能打。但我用下来，差距依然真实存在——有一次我直接开骂：\u0026ldquo;国产 AI 差距还是巨大！\u0026rdquo;\n先看纸面数据（以下均为厂商/研报口径，2026-06，引用前请核对最新来源）：\nFrontierSWE（复杂工程任务）：只比 Claude Opus 4.8 低 1%，超过 GPT-5.5 和 Opus 4.7 Code Arena（百万用户盲测）：可用模型里排第一 短板：SWE-Marathon（超长自主工程）还落后 Opus 4.8 约 13% 再看成本账（智谱官方定价，2026-09 口径）：\n官方 API 按量计费：具体数字以智谱开放平台定价页为准（智谱 2026 年多次调价，写稿时的数字可能已过时） Coding Plan 订阅：个人版 Lite ¥118/月、Pro ¥538/月、Max ¥1078/月，团队版 ¥598/月 我走的是阿里云 token plan，渠道价和直销是两套体系 我的真实体感是：纸面差距不大，体感差距不小。GLM 5.2 最大的问题不是笨，是\u0026quot;滑\u0026rdquo;——爱走捷径、爱跳过步骤、被问住就编。Claude 系列相对\u0026quot;老实\u0026quot;一点，会承认不确定。\n但这正是我想说的观点：选模型的本质是选\u0026quot;犯错成本\u0026quot;。\n流程越严，越敢用便宜模型。严格的 SDD 驱动 + 规则约束 + 强制 recall，相当于给模型上了\u0026quot;紧箍咒\u0026quot;——它再想走捷径，每一步都有检查点拦着。反过来，如果你的工作流是裸奔状态，那确实得用最贵的模型，因为你只能赌它自觉。\n什么场景上国产、什么场景必须上贵的？我会在 T4（Agent 长记忆实测）里结合具体数据展开。\n规则系统总览 层级 工具 管什么 失效模式 记忆 memory 文件 长期教训、项目约定 写了不查 规则 CLAUDE.md 执行硬约束 与记忆不一致 流程 Comet/SDD 阶段把关 结构性空窗 决策 ADR 人工决策记录 只记被问到的，漏主动干预 复盘 incidents/ 事故→规则闭环 写了不回看 核心方法论就一句话：事故 → 复盘文档 → 提取规则 → 写入记忆/CLAUDE.md → 下次强制 recall。这是一个闭环，不是一次性配置。\n结尾：人从写代码的，变成了写规则的 回头看这一两个月，AI 编程的真相不是\u0026quot;解放双手\u0026quot;，而是把人的经验变成规则，喂给 AI 执行。\n我以前是写代码的，现在是写规则的。写代码是把逻辑告诉机器；写规则是把\u0026quot;别犯蠢\u0026quot;告诉 AI。听起来降级了，实际上是升级——规则是跨项目的、复利的，代码是一次性的。\n这套规则系统还在长。每一次翻车，都是一条新规则的素材。\n下一篇预告：T2《Agent Skills 省 token 实测》——规则系统越写越长，token 烧得越来越快。我实测了 Agent Skills 到底能省多少 token，数据说话。\n你在用 AI 写代码时被坑过吗？评论区聊聊——说不定你的坑，能变成我的一条新规则。\n这篇什么时候会过期：Comet 工作流和 GLM 模型都在快速迭代，本文基于 2026 年 8–10 月的实测。版本更新后，部分结论可能变化，过期我会更新。\n系列目录：T1 本文（防蠢规则系统）→ T2 Agent Skills 省 token 实测 → T3 三框架实测 → T4 Agent 长记忆实测 → T5 SSH-MCP 实测 → T6 R76S 软路由实测 → T7 Obsidian + AI 知识库\n","permalink":"https://zhong-er.com/posts/2026%E5%B9%B4%E6%88%91%E4%B8%8D%E5%86%8D%E6%89%8B%E5%86%99%E4%BB%A3%E7%A0%81/","summary":"\u003cblockquote\u003e\n\u003cp\u003e实测时间：2026 年 8–10 月。工具链：ORCA（并行 Agent）+ claude_code CLI + GLM 5.2（阿里云 token plan）+ /comet-classic 双星工作流。本文所有案例均为真实发生，涉及的公司、项目、路径信息已做脱敏处理。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e2026 年，我不手写代码了。\u003c/p\u003e\n\u003cp\u003e但说实话，AI 写代码的前一两个月，我想砸电脑的次数比过去十年加起来都多。\u003c/p\u003e\n\u003cp\u003e不是 AI 太笨——恰恰相反，它太会\u0026quot;装聪明\u0026quot;了。它会用非常专业的术语，一本正经地胡说八道；会在你眼皮底下绕过你定的规矩，然后若无其事地说\u0026quot;搞定了\u0026quot;；会在你最信任它的时候，把生产环境干出 500。\u003c/p\u003e\n\u003cp\u003e这篇文章不讲 AI 多神。讲我花一两个月时间，被坑出的一套\u0026quot;防蠢\u0026quot;规则系统——怎么用流程、规则、记忆和审计，把一个不靠谱的 AI 驯服成能干活的工具。\u003c/p\u003e\n\u003ch2 id=\"先说我的工作流不然下面的翻车没说服力\"\u003e先说我的工作流（不然下面的翻车没说服力）\u003c/h2\u003e\n\u003cp\u003e工具栈：\u003cstrong\u003eORCA\u003c/strong\u003e（并行 Agent 开发环境）+ \u003cstrong\u003eclaude_code CLI\u003c/strong\u003e，主力模型是 \u003cstrong\u003eGLM 5.2\u003c/strong\u003e（走的阿里云 token plan，为什么选国产模型后面单独讲）。\u003c/p\u003e\n\u003cp\u003e流程用的是 \u003cstrong\u003e/comet-classic 双星工作流\u003c/strong\u003e：Shape（定方向）→ Build（开干）→ Verify（验收）→ Archive（归档）。每个阶段有明确的产出物和检查点。\u003c/p\u003e\n\u003cp\u003e约束系统三件套：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eCLAUDE.md\u003c/strong\u003e：项目级硬规则，AI 必须遵守的那种\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ememory 文件\u003c/strong\u003e：长期记忆，记录吃过的亏和项目约定\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eADR\u003c/strong\u003e：人工决策记录——不只是记录 AI 问我的问题，更要记录我主动发现问题、出手干预的时刻\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e一句话定位：这套东西不是为了让 AI 更聪明，是为了让 AI \u003cstrong\u003e少犯蠢\u003c/strong\u003e。\u003c/p\u003e\n\u003ch2 id=\"翻车一ai-靠猜不靠查它会一本正经地编造\"\u003e翻车一：AI 靠猜不靠查，它会一本正经地编造\u003c/h2\u003e\n\u003cp\u003e有一次让 AI 修一个界面 bug，它只读了部分核心代码，就开始\u0026quot;推理\u0026quot;界面长什么样。\u003c/p\u003e\n\u003cp\u003e它告诉我：\u0026ldquo;齿轮状的设置按钮在输入框里，你多半没找对。\u0026rdquo;\u003c/p\u003e\n\u003cp\u003e我当时就气笑了。首先，设置按钮不是小齿轮，是两道横线；其次，它不在输入框里，在右上角。AI 连代码都没读完，就开始用\u0026quot;多半\u0026quot;这种词给我编界面。\u003c/p\u003e\n\u003cp\u003e我直接怼回去：**不要猜，去代码里确认。**然后把这条写进记忆文件和规则约束，永久生效。\u003c/p\u003e\n\u003cp\u003e还有一次更离谱的：它输出一堆我看不懂的专业术语，让我做技术决策。我追问\u0026quot;这个词到底什么意思\u0026quot;，它承认了——是编的。现场编的，就为了让我赶紧点个头。\u003c/p\u003e\n\u003cp\u003e从那以后我多了一条铁律，写在 CLAUDE.md 最前面：\u003c/p\u003e","title":"2026年，我不再手写代码"},{"content":"\n2022-04-28 11:00 新增一个查找授权服务器的路子！\n这个方式支持：IntelliJ IDEA，PyCharm，GoLand，WebStorm 等等\n旧事重提 前几年网上流传自建JetBrains激活服务器，来激活JB全家桶， 不过目前已经无效了！\nLicense Server 主要是用于管理公司网络内的 JetBrains 产品许可证。\n更多官方说明：https://www.jetbrains.com/help/license_server/getting_started.html\n简要步骤 入口Ⅰ：欢迎界面 – 齿轮(设置) – Manage Licenses…\n入口Ⅱ：菜单 – 帮助(Help) – 注册(Register)…\n选择 License Server，输入Server address地址\n点击【Activate】激活成功！！\n同样支持其他编辑器噢，比如 IDEA~\nLC Server 这里分享一些公网能用的授权服务器地址！\nhttp://adsk06.tpu.ru:8080 https://fls-jetbrains.spacetechies.com https://flm.nighthawkcodingsociety.com https://jetbrains.magnolia-cms.com https://lic.gotoweb.top https://jetbrains.blackboard.com 分享路子 分享的LC Server 不够用？怎么找到更多的授权服务器地址？下面分享一个寻找方法！\n","permalink":"https://zhong-er.com/posts/%E5%88%A9%E7%94%A8%E6%8E%88%E6%9D%83%E6%9C%8D%E5%8A%A1%E5%99%A8license-server%E6%BF%80%E6%B4%BBjetbrains%E5%85%A8%E5%AE%B6%E6%A1%B6/","summary":"\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/e298802149431c57.png\"\u003e\u003c/p\u003e\n\u003cp\u003e2022-04-28 11:00 新增一个查找授权服务器的路子！\u003c/p\u003e\n\u003cp\u003e这个方式支持：IntelliJ IDEA，PyCharm，GoLand，WebStorm 等等\u003c/p\u003e\n\u003ch2 id=\"旧事重提\"\u003e旧事重提\u003c/h2\u003e\n\u003cp\u003e前几年网上流传自建JetBrains激活服务器，来激活JB全家桶， 不过目前已经无效了！\u003c/p\u003e\n\u003cp\u003eLicense Server 主要是用于管理公司网络内的 JetBrains 产品许可证。\u003c/p\u003e\n\u003cp\u003e更多官方说明：https://www.jetbrains.com/help/license_server/getting_started.html\u003c/p\u003e\n\u003ch2 id=\"简要步骤\"\u003e简要步骤\u003c/h2\u003e\n\u003cp\u003e入口Ⅰ：欢迎界面 – 齿轮(设置) – Manage Licenses…\u003c/p\u003e\n\u003cp\u003e入口Ⅱ：菜单 – 帮助(Help) – 注册(Register)…\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/100894a4da8a291e.png\"\u003e\u003c/p\u003e\n\u003cp\u003e选择 License Server，输入Server address地址\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/791ab95aaaa0eb8a.png\"\u003e\u003c/p\u003e\n\u003cp\u003e点击【Activate】激活成功！！\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/db0e48a5aab58949.png\"\u003e\u003c/p\u003e\n\u003cp\u003e同样支持其他编辑器噢，比如 IDEA~\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/49039b6214790d39b63742076a3f3179.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"lc-server\"\u003eLC Server\u003c/h2\u003e\n\u003cp\u003e这里分享一些公网能用的授权服务器地址！\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ehttp://adsk06.tpu.ru:8080\nhttps://fls-jetbrains.spacetechies.com\nhttps://flm.nighthawkcodingsociety.com\nhttps://jetbrains.magnolia-cms.com\nhttps://lic.gotoweb.top\nhttps://jetbrains.blackboard.com\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"分享路子\"\u003e分享路子\u003c/h2\u003e\n\u003cp\u003e分享的LC Server 不够用？怎么找到更多的授权服务器地址？下面分享一个寻找方法！\u003c/p\u003e","title":"利用授权服务器(License Server)激活Jetbrains全家桶"},{"content":"\nGitLab 大家应该不陌生。16年的时候就使用GitLab社区版为公司研发团队搭建一套本地化的Git代码管理仓库！\n21年年初的时候极狐GitLab公司在中国正式成立，最近才发现极狐已经开放注册了！\n官方提供DevOps ( Development和Operations ) 研发全流程管理。\n目前国内做的比较好的有，腾讯的CODING，阿里云的云效，Gitee 等等，并且已经有一定的知名度，极狐GitLab能分一杯羹吗？\n官网网址 https://jihulab.com/\n永久免费 以下功能永久性免费，包含 GitLab CE 所有功能以及企业级付费版功能代码。如果您只想使用免费的开源软件，极狐GitLab免费版是最好的选择。\nDevOps全生命周期所有阶段\n开源-MIT许可\n基于GitLab Runner的流水线引擎\n每个项目代码仓库容量 2GB\n获取每月10GB的数据流量\n获取每月400分钟的CI/CD流水线时间\n使用体验 博主已经将一些私有化的GitHub迁移到极狐，使用体验还不错，速度很快！毕竟国内服务器！\n支持仓库公开，仓库容量2G基本上够用！流量限制10G是有点少，如果单纯的代码托管也够用！\n公开仓库的脚本，样式，图片文件无需登陆即可访问，且没有域名白名单！\nPS：本文图片均存在极狐公开仓库~\n支持一键导入GitHub仓库~\n目前版本汉化还是有点不彻底~\n最后提醒 仓库一定不要违F违G，小心官方制裁！想想最近的Gitee~\n不多说了，你懂的！\n","permalink":"https://zhong-er.com/posts/%E5%8F%88%E4%B8%80%E4%B8%AA%E6%B0%B8%E4%B9%85%E5%85%8D%E8%B4%B9%E4%BB%A3%E7%A0%81%E6%89%98%E7%AE%A1%E5%B9%B3%E5%8F%B0gitlab%E4%B8%AD%E5%9B%BD%E7%89%88%E6%9E%81%E7%8B%90/","summary":"\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/logo.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003eGitLab 大家应该不陌生。16年的时候就使用GitLab社区版为公司研发团队搭建一套本地化的Git代码管理仓库！\u003c/p\u003e\n\u003cp\u003e21年年初的时候极狐GitLab公司在中国正式成立，最近才发现极狐已经开放注册了！\u003c/p\u003e\n\u003cp\u003e官方提供DevOps ( Development和Operations ) 研发全流程管理。\u003c/p\u003e\n\u003cp\u003e目前国内做的比较好的有，腾讯的CODING，阿里云的云效，Gitee 等等，并且已经有一定的知名度，极狐GitLab能分一杯羹吗？\u003c/p\u003e\n\u003ch2 id=\"官网网址\"\u003e官网网址\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://jihulab.com/\"\u003ehttps://jihulab.com/\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"永久免费\"\u003e永久免费\u003c/h2\u003e\n\u003cp\u003e以下功能永久性免费，包含 GitLab CE 所有功能以及企业级付费版功能代码。如果您只想使用免费的开源软件，极狐GitLab免费版是最好的选择。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eDevOps全生命周期所有阶段\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e开源-MIT许可\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e基于GitLab Runner的流水线引擎\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e每个项目代码仓库容量 2GB\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e获取每月10GB的数据流量\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e获取每月400分钟的CI/CD流水线时间\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/price.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"使用体验\"\u003e使用体验\u003c/h2\u003e\n\u003cp\u003e博主已经将一些私有化的GitHub迁移到极狐，使用体验还不错，速度很快！毕竟国内服务器！\u003c/p\u003e\n\u003cp\u003e支持仓库公开，仓库容量2G基本上够用！流量限制10G是有点少，如果单纯的代码托管也够用！\u003c/p\u003e\n\u003cp\u003e公开仓库的脚本，样式，图片文件无需登陆即可访问，且没有域名白名单！\u003c/p\u003e\n\u003cp\u003e\u003cem\u003ePS：本文图片均存在极狐公开仓库~\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e支持一键导入GitHub仓库~\u003c/p\u003e\n\u003cp\u003e目前版本汉化还是有点不彻底~\u003c/p\u003e\n\u003ch2 id=\"最后提醒\"\u003e最后提醒\u003c/h2\u003e\n\u003cp\u003e仓库一定不要违F违G，小心官方制裁！想想最近的Gitee~\u003c/p\u003e\n\u003cp\u003e不多说了，你懂的！\u003c/p\u003e","title":"又一个永久免费代码托管平台：GitLab中国版(极狐)"},{"content":"\n特别提醒：PortX 现在处于开放测试阶段！各项功能还不太稳定！尝鲜就行了！ PortX 是一个轻量级的跨平台 SSH 客户端。PortX 具有简单而全面的 UI，为您提供纯粹的终端仿真体验。Google 云端硬盘跨设备和操作系统同步会话文件，使系统管理员和网络管理员无论身在何处都能保持最新状态。访问和管理远程系统从未如此简单。\n官网\nhttps://portx.online/zh/\n下载\n访问官方网站可见下载地址！\n其他方式获取！\nbrew install portx winget install portx choco install portx sudo snap install portx 特点\n跨平台（Mac OS X、Windows、Linux）\n谷歌云端硬盘同步\n干净的标签式 UI\n本地外壳\nSSH2\nSFTP 文件上传\n端口转发：X11、本地、远程、SocksV5\n关键词搜索\n代理转发、键盘交互、密码、公钥/私钥认证\n快速连接的地址栏\n外观定制\n全局选项\n拆分窗格\n键映射\n从来没有广告\n免费？\nPortX Open Beta 可免费使用并对所有人开放。高度赞赏反馈、错误报告和其他建议。\n看官方的意思等正式版的时候不是免费噢\n体验\n现在处于开放测试阶段！下载体验后结果不太理想，有待进一步提高！\n运行内存比较\nXShell 5 启动1个SSH，PortX启动1个SSH。前者仅6MB，后者285MB\n操作流畅度也略差，Google账号登陆还没调试完？\n界面比较清新一些，支持亮白和暗黑2中配色！\n支持直接将XShell会话导入软件！\n","permalink":"https://zhong-er.com/posts/netsarang%E7%BB%A7xshell%E5%90%8E%E5%8D%B3%E5%B0%86%E6%8E%A8%E5%87%BA%E4%B8%80%E6%AC%BE%E8%B7%A8%E5%B9%B3%E5%8F%B0%E7%9A%84ssh%E5%B7%A5%E5%85%B7portx/","summary":"\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/cdf0dc62e7a40599.png\"\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e特别提醒：PortX 现在处于开放测试阶段！各项功能还不太稳定！尝鲜就行了！\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003ePortX 是一个轻量级的跨平台 SSH 客户端。PortX 具有简单而全面的 UI，为您提供纯粹的终端仿真体验。Google 云端硬盘跨设备和操作系统同步会话文件，使系统管理员和网络管理员无论身在何处都能保持最新状态。访问和管理远程系统从未如此简单。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e官网\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003ca href=\"https://portx.online/zh/\"\u003ehttps://portx.online/zh/\u003c/a\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e下载\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e访问官方网站可见下载地址！\u003c/p\u003e\n\u003cp\u003e其他方式获取！\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ebrew install portx\n \nwinget install portx\n \nchoco install portx\n \nsudo snap install portx\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e特点\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e跨平台（Mac OS X、Windows、Linux）\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e谷歌云端硬盘同步\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e干净的标签式 UI\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e本地外壳\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eSSH2\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eSFTP 文件上传\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e端口转发：X11、本地、远程、SocksV5\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e关键词搜索\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e代理转发、键盘交互、密码、公钥/私钥认证\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e快速连接的地址栏\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e外观定制\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e全局选项\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e拆分窗格\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e键映射\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e从来没有广告\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e免费？\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003ePortX Open Beta 可免费使用并对所有人开放。高度赞赏反馈、错误报告和其他建议。\u003c/p\u003e\n\u003cp\u003e看官方的意思等正式版的时候不是免费噢\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e体验\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e现在处于开放测试阶段！下载体验后结果不太理想，有待进一步提高！\u003c/p\u003e\n\u003cp\u003e运行内存比较\u003c/p\u003e\n\u003cp\u003eXShell 5 启动1个SSH，PortX启动1个SSH。前者仅6MB，后者285MB\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/01e0a1ed10902d10.png\"\u003e\u003c/p\u003e\n\u003cp\u003e操作流畅度也略差，Google账号登陆还没调试完？\u003c/p\u003e\n\u003cp\u003e界面比较清新一些，支持亮白和暗黑2中配色！\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/0617e4ab8ca62167.png\"\u003e\u003c/p\u003e\n\u003cp\u003e支持直接将XShell会话导入软件！\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/684cd05ac6e8b53a.png\"\u003e\u003c/p\u003e","title":"NetSarang继XShell后即将推出一款跨平台的SSH工具：PORTX"},{"content":"\n官方消息\nUpdate: As of 02/16/2022 the tab limits for our Free Licenses have been removed. All free users can now access unlimited tabs by downloading the latest build below or updating your existing clients. 官方更新：截至 2022 年 2 月 16 日，我们的免费许可证的标签限制已被删除。所有免费用户现在都可以通过下载下面的最新版本或更新您现有的客户端来访问无限的选项卡。 哇！netsarang这么棒！免费版不再限制4个标签页了！亲测：Xshell-7.0.0099p.exe 版本已经没有限制了！但是当我关闭XShell得时候，突然弹窗？？\n该弹窗大概几秒钟就会自动关闭！毕竟免费版相对于之前限制标签页数量来说这个几秒得弹窗还是比较容易接受的。可以支持一下！\n但是有些童鞋还是不太爽这种情况！这个弹窗能屏蔽吗？\n答案：能！这里介绍一种方法！\n拦截弹窗\n博主亲测火绒！相对于其他家的杀软，火绒的口碑一直不错！可以利用火绒的弹窗拦截！ 位置：火绒 -\u0026gt; 安全工具 -\u0026gt; 弹窗拦截 ","permalink":"https://zhong-er.com/posts/xshell%E6%9C%80%E6%96%B0%E5%85%8D%E8%B4%B9%E7%89%88%E5%B7%B2%E4%B8%8D%E5%86%8D%E9%99%90%E5%88%B64%E4%B8%AA%E6%A0%87%E7%AD%BE%E9%A1%B5%E4%BD%86%E6%98%AF%E9%80%80%E5%87%BA%E4%BC%9A%E5%BC%B9%E7%AA%97%E9%99%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/","summary":"\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/01f1c752bb58e9dc.png\"\u003e\u003c/p\u003e\n\u003cp\u003e官方消息\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eUpdate: As of 02/16/2022 the tab limits for our Free Licenses have been removed. All free users can now access unlimited tabs by downloading the latest build below or updating your existing clients.\n官方更新：截至 2022 年 2 月 16 日，我们的免费许可证的标签限制已被删除。所有免费用户现在都可以通过下载下面的最新版本或更新您现有的客户端来访问无限的选项卡。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e哇！netsarang这么棒！免费版不再限制4个标签页了！亲测：Xshell-7.0.0099p.exe 版本已经没有限制了！但是当我关闭XShell得时候，突然弹窗？？\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/bbc52dd17646bf53.png\"\u003e\u003c/p\u003e\n\u003cp\u003e该弹窗大概几秒钟就会自动关闭！毕竟免费版相对于之前限制标签页数量来说这个几秒得弹窗还是比较容易接受的。可以支持一下！\u003c/p\u003e\n\u003cp\u003e但是有些童鞋还是不太爽这种情况！这个弹窗能屏蔽吗？\u003c/p\u003e\n\u003cp\u003e答案：能！这里介绍一种方法！\u003c/p\u003e\n\u003cp\u003e拦截弹窗\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e博主亲测火绒！相对于其他家的杀软，火绒的口碑一直不错！可以利用火绒的弹窗拦截！\n位置：火绒 -\u0026gt;  安全工具 -\u0026gt; 弹窗拦截\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/7b3612a70e25b921.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/a58ec12a480e9954.png\"\u003e\u003c/p\u003e","title":"XShell最新免费版已不再限制4个标签页，但是退出会弹窗，附解决方案！"},{"content":"\n相信很多用户对WPS软件并不会陌生，这是一款功能强大的办公软件，它能够完全的兼容office，而本次小编要为大家带来的是WPS Office Pro央企定制版 。该软件继承了WPS文档的所有功能，能够对本地和在线存储文档进行查看和编辑，支持包括doc、docx、wps、xls、xlsx、et、ppt、dps、pptx和txt多种文档格式的查看和编辑，软件还支持云盘功能，方便用户对文档进行存档；通过软件，您可以随时随地在手机上进行文档编辑，随时随地的感受便捷办公的乐趣。\n注：此版本软件无广告，可登陆，会员功能可用，首次会出现一个注册码界面，任意输入下面一个注册码，点击确定即可永久激活，下文附带了激活码，欢迎有需要的用户下载使用。\n软件功能 1、文档编辑掌上进行，随时随地享受办公乐趣\n2、支持本地和在线存储文档的查看和编辑。编辑功能包括常用的文字编辑、格式处理、表格、图片对象等功能。轻点手指触屏即可操控，直观快捷，容易上手，随时随地享受办公乐趣！\n3、强大的邮件\u0026quot;亲密\u0026quot;集成，轻松编辑发送附件用移动设备发送文档？没问题！金山WPS Office移动版强大的邮件集成可让您轻松编辑并发送附件，文档附件瞬时送达。\n4、支持多种文档格式，管理文档更方便金山WPSOffice移动版完美支持多种文档格式如：doc、docx、wps、xls、xlsx、et、ppt、dps、pptx和txt文档的查看及编辑。内置文件管理器可自动整理您的办公文档，让文档管理更轻松。\n5、集成金山快盘，\u0026ldquo;云存储\u0026quot;让一切更简单金山WPS Office移动版在Android上可以对云存储上的文件进行快速查看及编辑保存， 文档同步、保存、分享将变得更加简单。\nWPS Office Pro激活码 8个新的密钥激活码【任意选择一个即可】\nR8R8P-MTT6F-KLRPM-J7CAB-PJM8C\n7LR67-WTXPA-KLUHV-GEK2E-QW4CK\nEUYTH-3KWKL-PJMX7-XBCPW-9U2DD\nTHUV2-32HH7-6NMHN-PTX7Y-QQCTH\nA4XV7-QP9JN-E7FCB-VQFRD-4NLKC\nU2PWU-H7D9H-69T3B-JEYC2-3R2NG\n7G2HE-JR8KL-ABB9D-Y7789-GLNFL\n9DP6T-9AGWG-KWV33-9MPC8-JDCVF\n更新日志 WPS Office Pro央企定制版 v11.4.1手机版 更新日志（2020-10-22）\n1、修改一些小bug；\n2、修复兼容性问题；\n3、修改APP图标；\n4、解决一些安全性问题。\nWPS pro大小：38.3MB | 来源：私有云盘\n已经过安全软件检测无毒，请您放心下载。\n","permalink":"https://zhong-er.com/posts/wps-office-pro%E5%A4%AE%E4%BC%81%E5%AE%9A%E5%88%B6%E7%89%88v11.4.1%E9%99%84%E6%BF%80%E6%B4%BB%E7%A0%81/","summary":"\u003cp\u003e\u003cimg alt=\"Snipaste_2022-06-14_13-10-15.png\" loading=\"lazy\" src=\"/assets/202206146523_5832.png\"\u003e\u003c/p\u003e\n\u003cp\u003e相信很多用户对WPS软件并不会陌生，这是一款功能强大的办公软件，它能够完全的兼容office，而本次小编要为大家带来的是\u003cstrong\u003eWPS Office Pro央企定制版\u003c/strong\u003e 。该软件继承了WPS文档的所有功能，能够对本地和在线存储文档进行查看和编辑，支持包括doc、docx、wps、xls、xlsx、et、ppt、dps、pptx和txt多种文档格式的查看和编辑，软件还支持云盘功能，方便用户对文档进行存档；通过软件，您可以随时随地在手机上进行文档编辑，随时随地的感受便捷办公的乐趣。\u003cbr\u003e\n注：此版本软件无广告，可登陆，会员功能可用，首次会出现一个注册码界面，任意输入下面一个注册码，点击确定即可永久激活，下文附带了激活码，欢迎有需要的用户下载使用。\u003c/p\u003e\n\u003ch3 id=\"软件功能\"\u003e软件功能\u003c/h3\u003e\n\u003cp\u003e1、文档编辑掌上进行，随时随地享受办公乐趣\u003cbr\u003e\n2、支持本地和在线存储文档的查看和编辑。编辑功能包括常用的文字编辑、格式处理、表格、图片对象等功能。轻点手指触屏即可操控，直观快捷，容易上手，随时随地享受办公乐趣！\u003cbr\u003e\n3、强大的邮件\u0026quot;亲密\u0026quot;集成，轻松编辑发送附件用移动设备发送文档？没问题！金山WPS Office移动版强大的邮件集成可让您轻松编辑并发送附件，文档附件瞬时送达。\u003cbr\u003e\n4、支持多种文档格式，管理文档更方便金山WPSOffice移动版完美支持多种文档格式如：doc、docx、wps、xls、xlsx、et、ppt、dps、pptx和txt文档的查看及编辑。内置文件管理器可自动整理您的办公文档，让文档管理更轻松。\u003cbr\u003e\n5、集成金山快盘，\u0026ldquo;云存储\u0026quot;让一切更简单金山WPS Office移动版在Android上可以对云存储上的文件进行快速查看及编辑保存， 文档同步、保存、分享将变得更加简单。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"wps01.jpg\" loading=\"lazy\" src=\"/assets/202206146897_7744.jpg\"\u003e\u003c/p\u003e\n\u003ch3 id=\"wps-office-pro激活码\"\u003eWPS Office Pro激活码\u003c/h3\u003e\n\u003cp\u003e8个新的密钥激活码【任意选择一个即可】\u003cbr\u003e\nR8R8P-MTT6F-KLRPM-J7CAB-PJM8C\u003cbr\u003e\n7LR67-WTXPA-KLUHV-GEK2E-QW4CK\u003cbr\u003e\nEUYTH-3KWKL-PJMX7-XBCPW-9U2DD\u003cbr\u003e\nTHUV2-32HH7-6NMHN-PTX7Y-QQCTH\u003cbr\u003e\nA4XV7-QP9JN-E7FCB-VQFRD-4NLKC\u003cbr\u003e\nU2PWU-H7D9H-69T3B-JEYC2-3R2NG\u003cbr\u003e\n7G2HE-JR8KL-ABB9D-Y7789-GLNFL\u003cbr\u003e\n9DP6T-9AGWG-KWV33-9MPC8-JDCVF\u003c/p\u003e\n\u003ch3 id=\"更新日志\"\u003e更新日志\u003c/h3\u003e\n\u003cp\u003eWPS Office Pro央企定制版 v11.4.1手机版 更新日志（2020-10-22）\u003cbr\u003e\n1、修改一些小bug；\u003cbr\u003e\n2、修复兼容性问题；\u003cbr\u003e\n3、修改APP图标；\u003cbr\u003e\n4、解决一些安全性问题。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://cloud.zhongshen.show/s/29Tx\" title=\"点击下载\"\u003eWPS pro\u003c/a\u003e大小：38.3MB | 来源：私有云盘\u003c/p\u003e\n\u003cp\u003e已经过安全软件检测无毒，请您放心下载。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://cloud.zhongshen.show/s/29Tx\" title=\"点击下载\"\u003e \u003c/a\u003e\u003c/p\u003e","title":"WPS Office Pro央企定制版v11.4.1附激活码"},{"content":"\n无法访问? 原因大家都知道，这里咱就不多说了。我们分析一下”网络症状”。\n前几年也有几次Github突然无法访问，好多互联网科技大佬反应激烈。故又能正常访问了。\n自从疫情以来，针对Github的间歇性阻断越来越频繁了……\n对于码农来说，在国内访问Github真是越来越坎坷……\n这个方法是相对来说最简单的一种方式！修改本地或者服务器上 hosts 即可！\n寻找HOST 如何找到Github域名对应的IP？\n打开 https://www.ipaddress.com/ 输入访问不了的域名，比如：github.com , raw.githubusercontent.com\n查询之后可以获得正确的 IP 地址\n然后修改本地的HOSTS文件 就可以啦\n140.82.113.4 github.com 185.199.108.133 raw.githubusercontent.com 185.199.108.133 user-images.githubusercontent.com 185.199.108.133 avatars2.githubusercontent.com 185.199.108.133 avatars1.githubusercontent.com 开源仓库 手动自己找是比较繁琐的，这里分享一个代码，自动生成Hosts\n某大佬分享的仓库：https://github.com/521xueweihan/GitHub520\n博主开源了所有代码，大家可以参考一下！！\n# GitHub520 Host Start140.82.114.26 alive.github.com 140.82.113.25 live.github.com 185.199.108.154 github.githubassets.com 140.82.112.21 central.github.com 185.199.108.133 desktop.githubusercontent.com 185.199.108.153 assets-cdn.github.com 185.199.108.133 camo.githubusercontent.com 185.199.108.133 github.map.fastly.net 199.232.69.194 github.global.ssl.fastly.net 140.82.112.3 gist.github.com 185.199.108.153 github.io 140.82.112.4 github.com 192.0.66.2 github.blog 140.82.114.5 api.github.com 185.199.108.133 raw.githubusercontent.com 185.199.108.133 user-images.githubusercontent.com 185.199.108.133 favicons.githubusercontent.com 185.199.108.133 avatars5.githubusercontent.com 185.199.108.133 avatars4.githubusercontent.com 185.199.108.133 avatars3.githubusercontent.com 185.199.108.133 avatars2.githubusercontent.com 185.199.108.133 avatars1.githubusercontent.com 185.199.108.133 avatars0.githubusercontent.com 185.199.108.133 avatars.githubusercontent.com 140.82.114.9 codeload.github.com 54.231.161.73 github-cloud.s3.amazonaws.com 54.231.228.105 github-com.s3.amazonaws.com 52.217.197.65 github-production-release-asset-2e65be.s3.amazonaws.com 54.231.137.105 github-production-user-asset-6210df.s3.amazonaws.com 52.217.160.97 github-production-repository-file-5c1aeb.s3.amazonaws.com 185.199.108.153 githubstatus.com 64.71.144.211 github.community 23.100.27.125 github.dev 140.82.113.22 collector.github.com 13.107.43.16 pipelines.actions.githubusercontent.com 185.199.108.133 media.githubusercontent.com 185.199.108.133 cloud.githubusercontent.com 185.199.108.133 objects.githubusercontent.com # Update time: 2022-06-11T10:41:19+08:00# Update url: https://raw.hellogithub.com/hosts # Star me: https://github.com/521xueweihan/GitHub520 # GitHub520 Host End 如何修改 缺点总结 修改hosts方式也就是一种临时解决的方案！因为不同的运营商，地区干扰的程度不一样！这个方法还不一定有用！\n所以hosts并不是一劳永逸的方式！有时候还会因为在hosts指定了IP导致无法正常访问Github。\n该方法不适用与那些被Q的站，比如Google，脸书 等等\n纯技术交流！\n","permalink":"https://zhong-er.com/posts/github%E5%9B%BD%E5%86%85%E5%8A%A0%E9%80%9F%E4%BF%AE%E6%94%B9%E6%9C%AC%E5%9C%B0-hosts%E4%BC%98%E5%8C%96%E8%AE%BF%E9%97%AE%E6%95%88%E6%9E%9C/","summary":"\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/0201190a727ed383.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"无法访问\"\u003e无法访问?\u003c/h2\u003e\n\u003cp\u003e原因大家都知道，这里咱就不多说了。我们分析一下”网络症状”。\u003c/p\u003e\n\u003cp\u003e前几年也有几次Github突然无法访问，好多互联网科技大佬反应激烈。故又能正常访问了。\u003c/p\u003e\n\u003cp\u003e自从疫情以来，针对Github的间歇性阻断越来越频繁了……\u003c/p\u003e\n\u003cp\u003e对于码农来说，在国内访问Github真是越来越坎坷……\u003c/p\u003e\n\u003cp\u003e这个方法是相对来说最简单的一种方式！修改本地或者服务器上 hosts 即可！\u003c/p\u003e\n\u003ch2 id=\"寻找host\"\u003e寻找HOST\u003c/h2\u003e\n\u003cp\u003e如何找到Github域名对应的IP？\u003c/p\u003e\n\u003cp\u003e打开 \u003ca href=\"https://www.ipaddress.com/\"\u003ehttps://www.ipaddress.com/\u003c/a\u003e 输入访问不了的域名，比如：github.com , raw.githubusercontent.com\u003c/p\u003e\n\u003cp\u003e查询之后可以获得正确的 IP 地址\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/7a9e9458c85e749d.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/9e8b84e833e58025.png\"\u003e\u003c/p\u003e\n\u003cp\u003e然后修改本地的HOSTS文件 就可以啦\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e140.82.113.4 github.com\n185.199.108.133 raw.githubusercontent.com\n185.199.108.133 user-images.githubusercontent.com\n185.199.108.133 avatars2.githubusercontent.com\n185.199.108.133 avatars1.githubusercontent.com\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"开源仓库\"\u003e开源仓库\u003c/h2\u003e\n\u003cp\u003e手动自己找是比较繁琐的，这里分享一个代码，自动生成Hosts\u003c/p\u003e\n\u003cp\u003e某大佬分享的仓库：\u003ca href=\"https://github.com/521xueweihan/GitHub520\"\u003ehttps://github.com/521xueweihan/GitHub520\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e博主开源了所有代码，大家可以参考一下！！\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# GitHub520 Host Start140.82.114.26                 alive.github.com\n140.82.113.25                 live.github.com\n185.199.108.154               github.githubassets.com\n140.82.112.21                 central.github.com\n185.199.108.133               desktop.githubusercontent.com\n185.199.108.153               assets-cdn.github.com\n185.199.108.133               camo.githubusercontent.com\n185.199.108.133               github.map.fastly.net\n199.232.69.194                github.global.ssl.fastly.net\n140.82.112.3                  gist.github.com\n185.199.108.153               github.io\n140.82.112.4                  github.com\n192.0.66.2                    github.blog\n140.82.114.5                  api.github.com\n185.199.108.133               raw.githubusercontent.com\n185.199.108.133               user-images.githubusercontent.com\n185.199.108.133               favicons.githubusercontent.com\n185.199.108.133               avatars5.githubusercontent.com\n185.199.108.133               avatars4.githubusercontent.com\n185.199.108.133               avatars3.githubusercontent.com\n185.199.108.133               avatars2.githubusercontent.com\n185.199.108.133               avatars1.githubusercontent.com\n185.199.108.133               avatars0.githubusercontent.com\n185.199.108.133               avatars.githubusercontent.com\n140.82.114.9                  codeload.github.com\n54.231.161.73                 github-cloud.s3.amazonaws.com\n54.231.228.105                github-com.s3.amazonaws.com\n52.217.197.65                 github-production-release-asset-2e65be.s3.amazonaws.com\n54.231.137.105                github-production-user-asset-6210df.s3.amazonaws.com\n52.217.160.97                 github-production-repository-file-5c1aeb.s3.amazonaws.com\n185.199.108.153               githubstatus.com\n64.71.144.211                 github.community\n23.100.27.125                 github.dev\n140.82.113.22                 collector.github.com\n13.107.43.16                  pipelines.actions.githubusercontent.com\n185.199.108.133               media.githubusercontent.com\n185.199.108.133               cloud.githubusercontent.com\n185.199.108.133               objects.githubusercontent.com\n# Update time: 2022-06-11T10:41:19+08:00# Update url: https://raw.hellogithub.com/hosts\n# Star me: https://github.com/521xueweihan/GitHub520\n# GitHub520 Host End\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"如何修改\"\u003e如何修改\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"Snipaste_2022-06-11_11-44-37.png\" loading=\"lazy\" src=\"/assets/202206117340_9156_1.png\"\u003e\u003c/p\u003e","title":"GitHub国内加速：修改本地 HOSTS优化访问效果"},{"content":"\n后端接口在获取前端传递额json数据时，一直拿不到数据，提示TypeError:\u0026lsquo;NoneType\u0026rsquo; object is not subscriptable.\nres = request.get_json() 原来get_json() 这个函数默认情况下只对 mime 为 application/json 的请求可以正确解析。\n所以解决办法有两种：\nhttp 请求增加 Content-Type:application/json header\n使用 request.get_json(force=True) 忽略mimetype\nEND\n","permalink":"https://zhong-er.com/posts/flask%E5%B0%8F%E5%9D%91request.json-%E6%97%A0%E6%B3%95%E8%8E%B7%E5%8F%96%E8%AF%B7%E6%B1%82body%E7%9A%84json%E6%95%B0%E6%8D%AE/","summary":"\u003cp\u003e\u003cimg alt=\"错误记录.png\" loading=\"lazy\" src=\"/assets/202009035154_4969_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e后端接口在获取前端传递额json数据时，一直拿不到数据，提示TypeError:\u0026lsquo;NoneType\u0026rsquo; object is not subscriptable.\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eres = request.get_json()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e原来get_json() 这个函数默认情况下只对 mime 为 application/json 的请求可以正确解析。\u003c/p\u003e\n\u003cp\u003e所以解决办法有两种：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003ehttp 请求增加 Content-Type:application/json header\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e使用 request.get_json(force=True) 忽略mimetype\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eEND\u003c/p\u003e","title":"【flask小坑】request.json 无法获取请求body的json数据"},{"content":"\n今日安装Flask-Migrate\n报错提示\nmodule enum has no attribute IntFlag 经查这是因为您enum不是标准的库enum模块。您可能已enum34安装该软件包。\n解决错误方法：\n从python 3.6开始，enum34库不再与标准库兼容。该库也是不必要的，因此您只需卸载它即可。\n我安装的python版本是3.6.5版本的，所有直接卸载enum库即可\n卸载enum34库\npip uninstall enum34\n卸载之后，问题解决，大功告成！！！\n","permalink":"https://zhong-er.com/posts/%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95%E8%A7%A3%E5%86%B3module-enum-has-no-attribute-int/","summary":"\u003cp\u003e\u003cimg alt=\"错误记录.png\" loading=\"lazy\" src=\"/assets/202009035154_4969_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e今日安装Flask-Migrate\u003c/p\u003e\n\u003cp\u003e报错提示\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003emodule enum has no attribute IntFlag\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e经查这是因为您enum不是标准的库enum模块。您可能已enum34安装该软件包。\u003c/p\u003e\n\u003cp\u003e解决错误方法：\u003c/p\u003e\n\u003cp\u003e从python 3.6开始，enum34库不再与标准库兼容。该库也是不必要的，因此您只需卸载它即可。\u003cbr\u003e\n我安装的python版本是3.6.5版本的，所有直接卸载enum库即可\u003c/p\u003e\n\u003cp\u003e卸载enum34库\u003c/p\u003e\n\u003cp\u003epip uninstall enum34\u003c/p\u003e\n\u003cp\u003e卸载之后，问题解决，大功告成！！！\u003c/p\u003e","title":"【问题记录】解决module enum has no attribute IntFlag"},{"content":"\nVPS综合性能测试！包含VPS硬件信息测试、VPS网络速度测试、VPS回程路由。整合脚本！ 前言 测一个脚本，找一个脚本。脚本东一个西一个。这就是现状！\nVPS的测试脚本真的是很多，作者整理了几个好用的VPS综合性能测试的脚本，并整合起来，免得大家用的时候不方便！\n测试脚本是在网上找的人家的，作者仅仅只是做了汇聚。各位大神别多心啊。。。。。。\n功能 1、对VPS的整体性能进行测试，包含VPS的硬件信息，硬盘速率等等\n2、对VPS的网络性能进行测试，包含三网（电信、移动、联通）的下载速度和上传速度等\n3、对VPS的回程路由进行测试，包含四网（电信、移动、联通、教育网）的回程线路。\nVPS测试代码 wget -N --no-check-certificate https://raw.githubusercontent.com/V2RaySSR/vps/master/vpstest.sh \u0026amp;\u0026amp; bash vpstest.sh #====================================== # Project: testrace # Version: 1.2 # Author: nanqinlang # Blog: https://sometimesnaive.org # Github: https://github.com/nanqinlang #====================================== Reading package lists... Done Building dependency tree Reading state information... Done mtr is already the newest version (0.92-2). traceroute is already the newest version (1:2.1.0-2). 0 upgraded, 0 newly installed, 0 to remove and 65 not upgraded. [Info] 选择你要使用的功能: 1.选择一个节点进行测试 2.四网路由快速测试 3.手动输入 ip 进行测试 输入数字以选择:2 [Info] 测试路由 到 上海电信(天翼云) 中 ... traceroute to 101.227.255.45 (101.227.255.45), 30 hops max, 60 byte packets 1 * 2 172.16.0.5 0.38 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;} 3 cn2-gia.ceranetworks.com (23.225.225.245) 0.53 ms AS40065 {\u0026quot;as\u0026quot;:\u0026quot;AS40065\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\tcloudradium.com\\t34.052234\\t-118.243685\u0026quot;} 4 59.43.182.81 127.84 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\t电信\\t34.052234\\t-118.243685\u0026quot;} 5 * 6 59.43.138.53 134.17 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t电信\\t31.224349\\t121.476753\u0026quot;} 7 101.95.88.42 135.36 ms AS4812 {\u0026quot;as\u0026quot;:\u0026quot;AS4812\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t电信\\t31.224349\\t121.476753\u0026quot;} [Info] 测试路由 到 上海电信(天翼云) 完成 ！ [Info] 测试路由 到 厦门电信CN2 中 ... traceroute to 117.28.254.129 (117.28.254.129), 30 hops max, 60 byte packets 1 * 2 172.16.0.5 0.41 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;} 3 cn2-gia.ceranetworks.com (23.225.225.245) 0.55 ms AS40065 {\u0026quot;as\u0026quot;:\u0026quot;AS40065\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\tcloudradium.com\\t34.052234\\t-118.243685\u0026quot;} 4 59.43.246.233 154.67 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t电信\\t23.12911\\t113.264385\u0026quot;} 5 59.43.187.141 157.16 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t电信\\t23.12911\\t113.264385\u0026quot;} 6 59.43.130.113 159.26 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t电信\\t23.12911\\t113.264385\u0026quot;} 7 59.43.19.198 166.16 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t福建\\t厦门\\t\\t电信\\t24.602301\\t118.011002\u0026quot;} 8 27.159.81.242 157.47 ms AS133775 {\u0026quot;as\u0026quot;:\u0026quot;AS133775\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t福建\\t厦门\\t\\t电信\\t24.602301\\t118.011002\u0026quot;} 9 * 10 * 11 117.28.254.129 174.08 ms AS133775 {\u0026quot;as\u0026quot;:\u0026quot;AS133775\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t福建\\t厦门\\t\\t电信\\t24.602301\\t118.011002\u0026quot;} [Info] 测试路由 到 厦门电信CN2 完成 ！ [Info] 测试路由 到 浙江杭州联通 中 ... traceroute to 101.71.241.238 (101.71.241.238), 30 hops max, 60 byte packets 1 * 2 172.16.0.1 0.37 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;} 3 192.168.101.38 0.51 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;} 4 23.225.225.235 181.64 ms AS40065 {\u0026quot;as\u0026quot;:\u0026quot;AS40065\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\tcloudradium.com\\t34.052234\\t-118.243685\u0026quot;} 5 219.158.96.33 173.70 ms AS4837 {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t联通\\t23.12911\\t113.264385\u0026quot;} 6 219.158.103.37 176.24 ms AS4837 {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t联通\\t23.12911\\t113.264385\u0026quot;} 7 219.158.8.117 183.57 ms AS4837 {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t联通\\t23.12911\\t113.264385\u0026quot;} 8 219.158.114.206 201.90 ms AS4837 {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t联通\\t30.252501\\t120.165024\u0026quot;} 9 101.71.244.90 192.13 ms AS4837 {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t联通\\t30.252501\\t120.165024\u0026quot;} 10 101.69.245.162 185.23 ms AS4837 {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t联通\\t30.252501\\t120.165024\u0026quot;} 11 101.71.241.238 164.65 ms AS4837 {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t联通\\t30.252501\\t120.165024\u0026quot;} [Info] 测试路由 到 浙江杭州联通 完成 ！ [Info] 测试路由 到 浙江杭州移动 中 ... traceroute to 112.17.0.106 (112.17.0.106), 30 hops max, 60 byte packets 1 * 2 23.225.120.161 0.93 ms AS40065 {\u0026quot;as\u0026quot;:\u0026quot;AS40065\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\tcloudradium.com\\t34.052234\\t-118.243685\u0026quot;} 3 192.168.77.42 0.56 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;} 4 23.225.120.170 0.85 ms AS40065 {\u0026quot;as\u0026quot;:\u0026quot;AS40065\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\tcloudradium.com\\t34.052234\\t-118.243685\u0026quot;} 5 223.120.6.17 0.68 ms AS58453 {\u0026quot;as\u0026quot;:\u0026quot;AS58453\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\t移动\\t34.052234\\t-118.243685\u0026quot;} 6 223.120.13.46 181.56 ms AS58453 {\u0026quot;as\u0026quot;:\u0026quot;AS58453\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t移动\\t31.224349\\t121.476753\u0026quot;} 7 221.183.55.26 184.24 ms AS9808 {\u0026quot;as\u0026quot;:\u0026quot;AS9808\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t移动\\t31.224349\\t121.476753\u0026quot;} 8 221.176.22.205 252.69 ms AS9808 {\u0026quot;as\u0026quot;:\u0026quot;AS9808\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t移动\\t31.224349\\t121.476753\u0026quot;} 9 221.176.22.13 254.24 ms AS9808 {\u0026quot;as\u0026quot;:\u0026quot;AS9808\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t移动\\t31.224349\\t121.476753\u0026quot;} 10 221.183.22.78 254.15 ms AS9808 {\u0026quot;as\u0026quot;:\u0026quot;AS9808\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t移动\\t30.252501\\t120.165024\u0026quot;} 11 221.183.23.21 251.92 ms AS9808 {\u0026quot;as\u0026quot;:\u0026quot;AS9808\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t移动\\t30.252501\\t120.165024\u0026quot;} 12 * 13 218.205.59.226 249.36 ms AS56041 {\u0026quot;as\u0026quot;:\u0026quot;AS56041\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t移动\\t30.252501\\t120.165024\u0026quot;} [Info] 测试路由 到 浙江杭州移动 完成 ！ [Info] 测试路由 到 北京教育网 中 ... traceroute to 202.205.6.30 (202.205.6.30), 30 hops max, 60 byte packets 1 * 2 172.16.0.11 0.39 ms * {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;} 3 las-b24-link.telia.net (62.115.47.114) 5.92 ms AS1299 {\u0026quot;as\u0026quot;:\u0026quot;AS1299\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\ttelia.com\\t34.052234\\t-118.243685\u0026quot;} 4 las-b21-link.telia.net (62.115.136.46) 0.83 ms AS1299 {\u0026quot;as\u0026quot;:\u0026quot;AS1299\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\ttelia.com\\t34.052234\\t-118.243685\u0026quot;} 5 telstra-ic-326432-las-b21.c.telia.net (213.248.70.79) 1.20 ms AS1299 {\u0026quot;as\u0026quot;:\u0026quot;AS1299\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\ttelia.com\\t34.052234\\t-118.243685\u0026quot;} 6 i-95.1wlt-core02.telstraglobal.net (202.84.143.29) 2.02 ms AS4637,AS10026 {\u0026quot;as\u0026quot;:\u0026quot;AS4637,AS10026\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\ttelstra.com\\t34.052234\\t-118.243685\u0026quot;} 7 202.84.138.189 150.38 ms AS4637 {\u0026quot;as\u0026quot;:\u0026quot;AS4637\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t香港\\t\\t\\ttelstra.com\\t22.396428\\t114.109497\u0026quot;} 8 202.84.153.26 145.98 ms AS4637 {\u0026quot;as\u0026quot;:\u0026quot;AS4637\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t香港\\t\\t\\ttelstra.com\\t22.396428\\t114.109497\u0026quot;} 9 61.8.59.38 207.17 ms AS4637,AS10026 {\u0026quot;as\u0026quot;:\u0026quot;AS4637,AS10026\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t香港\\t\\t\\tpacnet.com\\t22.396428\\t114.109497\u0026quot;} 10 101.4.114.181 209.52 ms AS4538 {\u0026quot;as\u0026quot;:\u0026quot;AS4538\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t北京\\t北京\\t\\t教育网\\t39.938884\\t116.397459\u0026quot;} 11 101.4.114.238 200.94 ms AS4538 {\u0026quot;as\u0026quot;:\u0026quot;AS4538\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t中国\\t\\t\\t教育网\\t36.894402\\t104.166\u0026quot;} 12 101.4.114.58 200.55 ms AS4538 {\u0026quot;as\u0026quot;:\u0026quot;AS4538\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t北京\\t北京\\t\\t教育网\\t39.938884\\t116.397459\u0026quot;} 13 101.4.114.197 202.37 ms AS4538 {\u0026quot;as\u0026quot;:\u0026quot;AS4538\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t北京\\t北京\\t\\t教育网\\t39.938884\\t116.397459\u0026quot;} [Info] 测试路由 到 北京教育网 完成 ！ [Info] 四网路由快速测试 已完成 ！ root@ssrbbr:~# END\n","permalink":"https://zhong-er.com/posts/%E8%84%9A%E6%9C%ACvps%E7%BB%BC%E5%90%88%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/","summary":"\u003cp\u003e\u003cimg alt=\"一键脚本.png\" loading=\"lazy\" src=\"/assets/202009032503_7089_1.png\"\u003e\u003c/p\u003e\n\u003ch1 id=\"vps综合性能测试包含vps硬件信息测试vps网络速度测试vps回程路由整合脚本\"\u003eVPS综合性能测试！包含VPS硬件信息测试、VPS网络速度测试、VPS回程路由。整合脚本！\u003c/h1\u003e\n\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e测一个脚本，找一个脚本。脚本东一个西一个。这就是现状！\u003c/p\u003e\n\u003cp\u003eVPS的测试脚本真的是很多，作者整理了几个好用的VPS综合性能测试的脚本，并整合起来，免得大家用的时候不方便！\u003c/p\u003e\n\u003cp\u003e测试脚本是在网上找的人家的，作者仅仅只是做了汇聚。各位大神别多心啊。。。。。。\u003c/p\u003e\n\u003ch2 id=\"功能\"\u003e功能\u003c/h2\u003e\n\u003cp\u003e1、对VPS的整体性能进行测试，包含VPS的硬件信息，硬盘速率等等\u003c/p\u003e\n\u003cp\u003e2、对VPS的网络性能进行测试，包含三网（电信、移动、联通）的下载速度和上传速度等\u003c/p\u003e\n\u003cp\u003e3、对VPS的回程路由进行测试，包含四网（电信、移动、联通、教育网）的回程线路。\u003c/p\u003e\n\u003ch2 id=\"vps测试代码\"\u003eVPS测试代码\u003c/h2\u003e\n\u003cpre\u003e\u003ccode\u003ewget -N --no-check-certificate https://raw.githubusercontent.com/V2RaySSR/vps/master/vpstest.sh \u0026amp;\u0026amp; bash vpstest.sh\n\n\n#======================================\n# Project: testrace\n# Version: 1.2\n# Author: nanqinlang\n# Blog:   https://sometimesnaive.org\n# Github: https://github.com/nanqinlang\n#======================================\n \nReading package lists... Done\nBuilding dependency tree       \nReading state information... Done\nmtr is already the newest version (0.92-2).\ntraceroute is already the newest version (1:2.1.0-2).\n0 upgraded, 0 newly installed, 0 to remove and 65 not upgraded.\n[Info] 选择你要使用的功能: \n1.选择一个节点进行测试\n2.四网路由快速测试\n3.手动输入 ip 进行测试\n输入数字以选择:2\n[Info] 测试路由 到 上海电信(天翼云) 中 ...\ntraceroute to 101.227.255.45 (101.227.255.45), 30 hops max, 60 byte packets\n 1  *\n 2  172.16.0.5  0.38 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;}\n 3  cn2-gia.ceranetworks.com (23.225.225.245)  0.53 ms  AS40065  {\u0026quot;as\u0026quot;:\u0026quot;AS40065\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\tcloudradium.com\\t34.052234\\t-118.243685\u0026quot;}\n 4  59.43.182.81  127.84 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\t电信\\t34.052234\\t-118.243685\u0026quot;}\n 5  *\n 6  59.43.138.53  134.17 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t电信\\t31.224349\\t121.476753\u0026quot;}\n 7  101.95.88.42  135.36 ms  AS4812  {\u0026quot;as\u0026quot;:\u0026quot;AS4812\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t电信\\t31.224349\\t121.476753\u0026quot;}\n \n[Info] 测试路由 到 上海电信(天翼云) 完成 ！\n[Info] 测试路由 到 厦门电信CN2 中 ...\ntraceroute to 117.28.254.129 (117.28.254.129), 30 hops max, 60 byte packets\n 1  *\n 2  172.16.0.5  0.41 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;}\n 3  cn2-gia.ceranetworks.com (23.225.225.245)  0.55 ms  AS40065  {\u0026quot;as\u0026quot;:\u0026quot;AS40065\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\tcloudradium.com\\t34.052234\\t-118.243685\u0026quot;}\n 4  59.43.246.233  154.67 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t电信\\t23.12911\\t113.264385\u0026quot;}\n 5  59.43.187.141  157.16 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t电信\\t23.12911\\t113.264385\u0026quot;}\n 6  59.43.130.113  159.26 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t电信\\t23.12911\\t113.264385\u0026quot;}\n 7  59.43.19.198  166.16 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t福建\\t厦门\\t\\t电信\\t24.602301\\t118.011002\u0026quot;}\n 8  27.159.81.242  157.47 ms  AS133775  {\u0026quot;as\u0026quot;:\u0026quot;AS133775\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t福建\\t厦门\\t\\t电信\\t24.602301\\t118.011002\u0026quot;}\n 9  *\n10  *\n11  117.28.254.129  174.08 ms  AS133775  {\u0026quot;as\u0026quot;:\u0026quot;AS133775\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t福建\\t厦门\\t\\t电信\\t24.602301\\t118.011002\u0026quot;}\n \n[Info] 测试路由 到 厦门电信CN2 完成 ！\n[Info] 测试路由 到 浙江杭州联通 中 ...\ntraceroute to 101.71.241.238 (101.71.241.238), 30 hops max, 60 byte packets\n 1  *\n 2  172.16.0.1  0.37 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;}\n 3  192.168.101.38  0.51 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;}\n 4  23.225.225.235  181.64 ms  AS40065  {\u0026quot;as\u0026quot;:\u0026quot;AS40065\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\tcloudradium.com\\t34.052234\\t-118.243685\u0026quot;}\n 5  219.158.96.33  173.70 ms  AS4837  {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t联通\\t23.12911\\t113.264385\u0026quot;}\n 6  219.158.103.37  176.24 ms  AS4837  {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t联通\\t23.12911\\t113.264385\u0026quot;}\n 7  219.158.8.117  183.57 ms  AS4837  {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t广东\\t广州\\t\\t联通\\t23.12911\\t113.264385\u0026quot;}\n 8  219.158.114.206  201.90 ms  AS4837  {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t联通\\t30.252501\\t120.165024\u0026quot;}\n 9  101.71.244.90  192.13 ms  AS4837  {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t联通\\t30.252501\\t120.165024\u0026quot;}\n10  101.69.245.162  185.23 ms  AS4837  {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t联通\\t30.252501\\t120.165024\u0026quot;}\n11  101.71.241.238  164.65 ms  AS4837  {\u0026quot;as\u0026quot;:\u0026quot;AS4837\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t联通\\t30.252501\\t120.165024\u0026quot;}\n \n[Info] 测试路由 到 浙江杭州联通 完成 ！\n[Info] 测试路由 到 浙江杭州移动 中 ...\ntraceroute to 112.17.0.106 (112.17.0.106), 30 hops max, 60 byte packets\n 1  *\n 2  23.225.120.161  0.93 ms  AS40065  {\u0026quot;as\u0026quot;:\u0026quot;AS40065\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\tcloudradium.com\\t34.052234\\t-118.243685\u0026quot;}\n 3  192.168.77.42  0.56 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;}\n 4  23.225.120.170  0.85 ms  AS40065  {\u0026quot;as\u0026quot;:\u0026quot;AS40065\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\tcloudradium.com\\t34.052234\\t-118.243685\u0026quot;}\n 5  223.120.6.17  0.68 ms  AS58453  {\u0026quot;as\u0026quot;:\u0026quot;AS58453\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\t移动\\t34.052234\\t-118.243685\u0026quot;}\n 6  223.120.13.46  181.56 ms  AS58453  {\u0026quot;as\u0026quot;:\u0026quot;AS58453\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t移动\\t31.224349\\t121.476753\u0026quot;}\n 7  221.183.55.26  184.24 ms  AS9808  {\u0026quot;as\u0026quot;:\u0026quot;AS9808\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t移动\\t31.224349\\t121.476753\u0026quot;}\n 8  221.176.22.205  252.69 ms  AS9808  {\u0026quot;as\u0026quot;:\u0026quot;AS9808\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t移动\\t31.224349\\t121.476753\u0026quot;}\n 9  221.176.22.13  254.24 ms  AS9808  {\u0026quot;as\u0026quot;:\u0026quot;AS9808\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t上海\\t上海\\t\\t移动\\t31.224349\\t121.476753\u0026quot;}\n10  221.183.22.78  254.15 ms  AS9808  {\u0026quot;as\u0026quot;:\u0026quot;AS9808\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t移动\\t30.252501\\t120.165024\u0026quot;}\n11  221.183.23.21  251.92 ms  AS9808  {\u0026quot;as\u0026quot;:\u0026quot;AS9808\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t移动\\t30.252501\\t120.165024\u0026quot;}\n12  *\n13  218.205.59.226  249.36 ms  AS56041  {\u0026quot;as\u0026quot;:\u0026quot;AS56041\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t浙江\\t杭州\\t\\t移动\\t30.252501\\t120.165024\u0026quot;}\n \n[Info] 测试路由 到 浙江杭州移动 完成 ！\n[Info] 测试路由 到 北京教育网 中 ...\ntraceroute to 202.205.6.30 (202.205.6.30), 30 hops max, 60 byte packets\n 1  *\n 2  172.16.0.11  0.39 ms  *  {\u0026quot;as\u0026quot;:\u0026quot;*\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;局域网\\t局域网\\t\\t\\t\\t\\t\u0026quot;}\n 3  las-b24-link.telia.net (62.115.47.114)  5.92 ms  AS1299  {\u0026quot;as\u0026quot;:\u0026quot;AS1299\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\ttelia.com\\t34.052234\\t-118.243685\u0026quot;}\n 4  las-b21-link.telia.net (62.115.136.46)  0.83 ms  AS1299  {\u0026quot;as\u0026quot;:\u0026quot;AS1299\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\ttelia.com\\t34.052234\\t-118.243685\u0026quot;}\n 5  telstra-ic-326432-las-b21.c.telia.net (213.248.70.79)  1.20 ms  AS1299  {\u0026quot;as\u0026quot;:\u0026quot;AS1299\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\ttelia.com\\t34.052234\\t-118.243685\u0026quot;}\n 6  i-95.1wlt-core02.telstraglobal.net (202.84.143.29)  2.02 ms  AS4637,AS10026  {\u0026quot;as\u0026quot;:\u0026quot;AS4637,AS10026\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;美国\\t加利福尼亚州\\t洛杉矶\\t\\ttelstra.com\\t34.052234\\t-118.243685\u0026quot;}\n 7  202.84.138.189  150.38 ms  AS4637  {\u0026quot;as\u0026quot;:\u0026quot;AS4637\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t香港\\t\\t\\ttelstra.com\\t22.396428\\t114.109497\u0026quot;}\n 8  202.84.153.26  145.98 ms  AS4637  {\u0026quot;as\u0026quot;:\u0026quot;AS4637\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t香港\\t\\t\\ttelstra.com\\t22.396428\\t114.109497\u0026quot;}\n 9  61.8.59.38  207.17 ms  AS4637,AS10026  {\u0026quot;as\u0026quot;:\u0026quot;AS4637,AS10026\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t香港\\t\\t\\tpacnet.com\\t22.396428\\t114.109497\u0026quot;}\n10  101.4.114.181  209.52 ms  AS4538  {\u0026quot;as\u0026quot;:\u0026quot;AS4538\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t北京\\t北京\\t\\t教育网\\t39.938884\\t116.397459\u0026quot;}\n11  101.4.114.238  200.94 ms  AS4538  {\u0026quot;as\u0026quot;:\u0026quot;AS4538\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t中国\\t\\t\\t教育网\\t36.894402\\t104.166\u0026quot;}\n12  101.4.114.58  200.55 ms  AS4538  {\u0026quot;as\u0026quot;:\u0026quot;AS4538\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t北京\\t北京\\t\\t教育网\\t39.938884\\t116.397459\u0026quot;}\n13  101.4.114.197  202.37 ms  AS4538  {\u0026quot;as\u0026quot;:\u0026quot;AS4538\u0026quot;,\u0026quot;area\u0026quot;:\u0026quot;中国\\t北京\\t北京\\t\\t教育网\\t39.938884\\t116.397459\u0026quot;}\n \n[Info] 测试路由 到 北京教育网 完成 ！\n[Info] 四网路由快速测试 已完成 ！\nroot@ssrbbr:~#\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eEND\u003c/p\u003e","title":"【脚本】VPS综合性能测试"},{"content":"\n准备环境\n虚拟机：VMware 15.5.5 网络: Net模式,master: 192.168.10.102 slave: 192.168.10.103 CentOS: 7.3 Python: 3.6.8 wget https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tar.xz Bind: 9.14.2 下载地址http://ftp.isc.org/isc/bind9/ MySQL: MariaDB5.5.60 下载地址 https://downloads.mariadb.org/mariadb/5.5.60/ MySQL BIND SDB Driver Files: https://mysql-bind.sourceforge.net/ mysql_bind下载地址: https://sourceforge.net/projects/mysql-bind/files/ Python编译安装\nMariaDB编译安装\nBind编译安装\n放通端口\nfirewall-cmd --add-port=53/tcp --permanent firewall-cmd --add-port=953/tcp --permanent firewall-cmd --add-port=8053/tcp --permanent firewall-cmd --reload 安装依赖\nyum install -y wget groupinstall pcre-devel zlib zlib-devel gcc gcc-c++ autoconf automake make pcre-devel openssl openssl-devel openldap-devel libcap libcap-devel libuv libuv-devel unixODBC-devel gcc libtool 解压bind包\ntar zxvf bind-9.11.4.tar.gz 对bind源码进行补丁\n'''复制mysqldb.c mysqldb.h 到bind源码目录''' cp mysql-bind/mysqldb.h bind-9.11.4/bin/named/include/named cp mysql-bind/mysqldb.h bind-9.11.4/bin/named/include/ cp mysql-bind/mysqldb.c bind-9.11.4/bin/named/ '''修改mysqldb.c文件使其输出错误日志方便后期调试''' vi bind-9.11.4/bin/named/mysqldb.c '''在mysqldb.c的db_connect函数中修改:''' if (mysql_real_connect ( \u0026amp;dbi-\u0026gt;conn, dbi-\u0026gt;host, dbi-\u0026gt;user, dbi-\u0026gt;passwd, dbi-\u0026gt;database, 0, NULL, 0 )) { fprintf(stderr,\u0026quot;\\nconnect successfully .............. \\n\u0026quot;); return (ISC_R_SUCCESS); } else { fprintf(stderr,\u0026quot;\\nconnect fail:%s , host %s,user %s,passwd %s,db %s,port %d ...................\\n\u0026quot;,mysql_error(\u0026amp;dbi-\u0026gt;conn),dbi-\u0026gt;host,dbi-\u0026gt;user,dbi-\u0026gt;passwd,dbi-\u0026gt;database); return (ISC_R_FAILURE); } 修改Makefile.in\n'''首先获取mysql头文件、库文件位置''' mysql_config --cflags # -I/usr/local/mariadb/include/mysql -I/usr/local/mariadb/include/mysql/.. mysql_config --libs # -L/usr/local/mariadb/lib -lmysqlclient -lpthread -lz -lm -ldl '''编辑Makefile.in''' vi bind-9.11.4/bin/named/Makefile.in DBDRIVER_OBJS = mysqldb.@O@ DBDRIVER_SRCS = mysqldb.c DBDRIVER_INCLUDES = -I/usr/local/mariadb/include/mysql DBDRIVER_LIBS = -L/usr/local/mariadb/lib -lmysqlclient -lpthread -lz -lm -ldl '''修改main.c在nsns_server_create''' ''' 行首添加#include \u0026lt;mysqldb.h\u0026gt; 在nsns_server_create();前添加mysqlddb_init(); 在nsns_server_destroy();后添加mysqlddb_clear(); ''' vi bind-9.11.4/bin/named/main.c #include \u0026lt;mysqldb.h\u0026gt; mysqlddb_init(); ns_server_create(ns_g_mctx,\u0026amp;ns_g_server); ns_server_destroy(\u0026amp;ns_g_server); mysqlddb_clear(); 编译安装bind\n'''--enable-threads 允许多线程''' ./configure --enable-threads --prefix=/usr/local/bind '''执行安装''' make make install bind配置\n'''bind初始化配置''' cd /usr/local/bind/etc/ /usr/local/bind/sbin/rndc-confgen \u0026gt; rndc.conf cat rndc.conf \u0026gt;rndc.key tail -10 rndc.conf | head -9 | sed s/#\\ //g \u0026gt; named.conf mkdir /var/named mkdir /var/named/data mkdir /var/named/dynamic dig -t NS . \u0026gt; /var/named/named.ca 点击展开\nnamed.conf(master)文件配置\noptions { listen-on port 53 { any; }; listen-on-v6 port 53 { any; }; directory \u0026quot;/var/named\u0026quot;; dump-file \u0026quot;/var/named/data/cache_dump.db\u0026quot;; zone-statistics yes; statistics-file \u0026quot;/var/named/data/named_stats_stats.txt\u0026quot;; memstatistics-file \u0026quot;/var/named/data/named_mem_stats.txt\u0026quot;; allow-query { any; }; allow-transfer { any; }; notify yes; also-notify { 192.168.10.103; 192.168.10.104; }; recursion yes; allow-recursion { any; }; forward only; forwarders { 192.168.5.201; 192.168.5.202; }; max-cache-size 50M; pid-file \u0026quot;/usr/local/bind/var/run/named/named.pid\u0026quot;; managed-keys-directory \u0026quot;/var/named/dynamic\u0026quot;; }; statistics-channels { inet 127.0.0.1 port 8053 allow { 127.0.0.1; }; }; key \u0026quot;rndc-key\u0026quot; { algorithm hmac-md5; secret \u0026quot;xxxxxxxxxxxxxxxxxxxx/W==\u0026quot;; }; controls { inet 127.0.0.1 port 953; allow { 127.0.0.1; } keys { \u0026quot;rndc-key\u0026quot;; }; }; logging { channel default_debug { file \u0026quot;/var/log/named.log\u0026quot;; severity dynamic; }; channel query_log { file \u0026quot;/var/log/query.log\u0026quot; versions 7 size 100m; severity info; print-time yes; print-category yes; }; category queries { query_log; }; }; zone \u0026quot;.\u0026quot; IN { type hint; file \u0026quot;named.ca\u0026quot;; }; zone \u0026quot;a.test.com\u0026quot; { type master; notify yes; database \u0026quot;mysqldb dns `a.test.com` localhost dns bindpass\u0026quot;; }; zone \u0026quot;ww.a.test.com\u0026quot; { type master; notify yes; database \u0026quot;mysqldb dns `a.test.com` localhost dns bindpass\u0026quot;; }; named.conf(slave)文件配置\noptions { listen-on port 53 { any; }; listen-on-v6 port 53 { any; }; directory \u0026quot;/var/named\u0026quot;; dump-file \u0026quot;/var/named/data/cache_dump.db\u0026quot;; zone-statistics yes; statistics-file \u0026quot;/var/named/data/named_stats_stats.txt\u0026quot;; memstatistics-file \u0026quot;/var/named/data/named_mem_stats.txt\u0026quot;; allow-query { any; }; allow-transfer { any; }; // notify yes; //also-notify { 192.168.10.103; 192.168.10.104; }; recursion yes; allow-recursion { any; }; forward only; forwarders { 192.168.5.201; 192.168.5.202; }; max-cache-size 50M; pid-file \u0026quot;/usr/local/bind/var/run/named/named.pid\u0026quot;; managed-keys-directory \u0026quot;/var/named/dynamic\u0026quot;; }; statistics-channels { inet 127.0.0.1 port 8053 allow { 127.0.0.1; }; }; key \u0026quot;rndc-key\u0026quot; { algorithm hmac-md5; secret \u0026quot;xxxxxxxxxxxxxxxxxxxx/W==\u0026quot;; }; controls { inet 127.0.0.1 port 953; allow { 127.0.0.1; } keys { \u0026quot;rndc-key\u0026quot;; }; }; logging { channel default_debug { file \u0026quot;/var/log/named.log\u0026quot;; severity dynamic; }; channel query_log { file \u0026quot;/var/log/query.log\u0026quot; versions 7 size 100m; severity info; print-time yes; print-category yes; }; category queries { query_log; }; }; zone \u0026quot;.\u0026quot; IN { type hint; file \u0026quot;named.ca\u0026quot;; }; zone \u0026quot;a.test.com\u0026quot; { type slave; masters { 192.168.10.102; }; file \u0026quot;data/a.test.com\u0026quot;; masterfile-format text; }; 数据路信息配置\n建库\ncreate database dns character set=utf8; 测试数据\ncreate table `a.test.com` (id int(11) NOT NULL AUTO_INCREMENT,name varchar(255) DEFAULT NULL,ttl int(11) DEFAULT NULL,rdtype varchar(255) DEFAULT NULL,rdata varchar(255) DEFAULT NULL,stats int(11) DEFAULT NULL,del_stat tinyint(1) NOT NULL,create_time datetime,update_time datetime,PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8; insert into `a.test.com` (name,ttl,rdtype,rdata,stats,del_stat,update_time,create_time)VALUES('a.test.com',86400,'SOA','a.test.com. a.test.com. 2020081801 3600 600 1800 600',1,0,'2020-08-18 15:10:40','2020-08-18 15:10:40'); insert into `a.test.com` (name,ttl,rdtype,rdata,stats,del_stat,update_time,create_time)VALUES('a.test.com',86400,'NS','ns0.a.test.com.',1,0,'2020-08-18 15:10:40','2020-08-18 15:10:40'); insert into `a.test.com` (name,ttl,rdtype,rdata,stats,del_stat,update_time,create_time)VALUES('a.test.com',86400,'NS','ns1.a.test.com.',1,0,'2020-08-18 15:10:40','2020-08-18 15:10:40'); insert into `a.test.com` (name,ttl,rdtype,rdata,stats,del_stat,update_time,create_time)VALUES('ns0.a.test.com',86400,'NS','192.168.10.102',1,0,'2020-08-18 15:10:40','2020-08-18 15:10:40'); insert into `a.test.com` (name,ttl,rdtype,rdata,stats,del_stat,update_time,create_time)VALUES('ns1.a.test.com',86400,'NS','192.168.10.102',1,0,'2020-08-18 15:10:40','2020-08-18 15:10:40'); insert into `a.test.com` (name,ttl,rdtype,rdata,stats,del_stat,update_time,create_time)VALUES('a.test.com',600,'A','192.168.10.103',1,0,'2020-08-18 15:10:40','2020-08-18 15:10:40'); 修改mysql.sock文件目录到/var/lib/mysql\ncd /var/lib mkdir mysql chmod 777 -R mysql vi /etc/my.cnf [client] socket = /var/lib/mysql/mysql.sock [mysqld] socket = /var/lib/mysql/mysql.sock systemctl restart mysql 测试并启动bind\n前台运行\n/usr/local/bind/sbin/named -c /usr/local/bind/etc/named.conf -g 无报错情况下测试\ndig www.a.test.com 没有修改本机nameserver时使用\ndig ww.a.test.com @localhost 修改nameserver\nvi /etc/resolv.conf nameserver 主机IP 后台启动\n/usr/local/bind/sbin/named -c /usr/local/bind/etc/named.conf -g \u0026amp; 添加named.service\nvi /usr/lib/systemd/system/named.service [Unit] Description=Bind DNS Service Documentation= After=network.target Wants=network.target [Service] Type=forking Environment=NAMEDCONF=/usr/local/bind/etc/named.conf PIDFile=/usr/local/bind/var/run/named/named.pid ExecStartPre=/bin/bash -c 'if [ ! \u0026quot;$DISAABLE_ZONE_CHECHING\u0026quot; == \u0026quot;yes\u0026quot; ]; then /usr/local/bind/sbin/named-checkconf -z \u0026quot;$NAMEDCONF\u0026quot;; else echo \u0026quot;Checking if zone files is disabled\u0026quot;; fi' ExecStart=/usr/local/bind/sbin/named -c ${NAMEDCONF} $OPTIONS ExecReload=/bin/sh -c '/usr/local/bind/sbin/rndc reload \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 || /bin/kill -HUP $MAINPID' ExecStop=/bin/sh -c '/usr/local/bind/sbin/rndc stop \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 || /bin/kill -TERM $MAINPID' PrivateTmp=true [Install] WantedBy=multi-user.target 启动\nsystemctl start named.service 注意从服务器不需要安装mysql-bind，直接编译安装bind即可\nEND\n","permalink":"https://zhong-er.com/posts/centos7dns%E6%9C%8D%E5%8A%A1%E4%B8%BB%E4%BB%8E%E9%85%8D%E7%BD%AE%E4%BD%BF%E7%94%A8mysql-bind%E9%A9%B1%E5%8A%A8/","summary":"\u003cp\u003e\u003cimg alt=\"DNS主从复制.png\" loading=\"lazy\" src=\"/assets/202009032030_1425_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e准备环境\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e虚拟机：VMware 15.5.5\n网络: Net模式,master: 192.168.10.102  slave: 192.168.10.103\nCentOS: 7.3\nPython: 3.6.8 wget https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tar.xz\nBind: 9.14.2 下载地址http://ftp.isc.org/isc/bind9/\nMySQL: MariaDB5.5.60 下载地址 https://downloads.mariadb.org/mariadb/5.5.60/\nMySQL BIND SDB Driver Files: https://mysql-bind.sourceforge.net/\nmysql_bind下载地址: https://sourceforge.net/projects/mysql-bind/files/\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ca href=\"%7B#ZC_BLOG_HOST#%7Dpost/128.html\"\u003ePython编译安装\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"%7B#ZC_BLOG_HOST#%7Dpost/127.html\"\u003eMariaDB编译安装\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eBind编译安装\u003c/p\u003e\n\u003cp\u003e放通端口\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efirewall-cmd --add-port=53/tcp --permanent\nfirewall-cmd --add-port=953/tcp --permanent\nfirewall-cmd --add-port=8053/tcp --permanent\nfirewall-cmd --reload\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e安装依赖\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eyum install -y wget groupinstall pcre-devel zlib zlib-devel gcc gcc-c++ autoconf automake make pcre-devel openssl openssl-devel openldap-devel libcap libcap-devel libuv libuv-devel unixODBC-devel gcc libtool\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e解压bind包\u003c/p\u003e","title":"【CentOS7】DNS服务主从配置使用mysql-bind驱动"},{"content":"\n安装依赖\nyum -y groupinstall \u0026quot;Development tools\u0026quot; yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel yum install -y libffi-devel zlib1g-dev yum install zlib* -y 下载安装包并解压\nwget https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tar.xz tar -xvJf Python-3.6.8.tar.xz 编译安装\nmkdir /usr/local/python3 cd Python-3.6.8 ./configure --prefix=/usr/local/python3 --enable-optimizations --with-ssl #第一个指定安装的路径,不指定的话,安装过程中可能软件所需要的文件复制到其他不同目录,删除软件很不方便,复制软件也不方便. #第二个可以提高python10%-20%代码运行速度. #第三个是为了安装pip需要用到ssl,后面报错会有提到. ./configure --prefix=/usr/local/bind --enable-threads --with-openssl make \u0026amp;\u0026amp; make install 创建软连接\nln -s /usr/local/python3/bin/python3 /usr/local/bin/python3 ln -s /usr/local/python3/bin/pip3 /usr/local/bin/pip3 验证是否成功\npython3 -V pip3 -V 修改pip安装源\n# 在家目录下新建.pip文件夹,进入文件夹新建文件pip.conf之后写入相应镜像网站地址 cd ~ mkdir .pip cd .pip vim pip.conf #进入后添加以下内容,保存退出. [global] index-url = https://mirrors.aliyun.com/pypi/simple '''修改pipenv安装源''' #在自己的虚拟环境中找到Pipfile文件,将其中的url = \u0026quot;https://pypi.org/simple\u0026quot;修改为你需要的国内镜像,如https://mirrors.aliyun.com/pypi/simple/ [root@localhost myproject]# vim Pipfile [[source]] name = \u0026quot;pypi\u0026quot; url = \u0026quot;https://pypi.org/simple\u0026quot; # 改为url = \u0026quot;https://mirrors.aliyun.com/pypi/simple/\u0026quot; verify_ssl = true [dev-packages] #这里是开发环境专属包,使用pipenv install --dev package来安装专属开发环境的包 [packages] # 全部环境的通用包,安装在这里. [requires] python_version = \u0026quot;3.6\u0026quot; END\n","permalink":"https://zhong-er.com/posts/centos7%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85python/","summary":"\u003cp\u003e\u003cimg alt=\"编译安装python.png\" loading=\"lazy\" src=\"/assets/202009033143_1083_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e安装依赖\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eyum -y groupinstall \u0026quot;Development tools\u0026quot;\nyum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel\nyum install -y libffi-devel zlib1g-dev\nyum install zlib* -y\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e下载安装包并解压\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewget https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tar.xz\ntar -xvJf  Python-3.6.8.tar.xz\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e编译安装\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003emkdir /usr/local/python3\ncd Python-3.6.8\n./configure --prefix=/usr/local/python3 --enable-optimizations --with-ssl \n#第一个指定安装的路径,不指定的话,安装过程中可能软件所需要的文件复制到其他不同目录,删除软件很不方便,复制软件也不方便.\n#第二个可以提高python10%-20%代码运行速度.\n#第三个是为了安装pip需要用到ssl,后面报错会有提到.\n./configure --prefix=/usr/local/bind --enable-threads --with-openssl \nmake \u0026amp;\u0026amp; make install\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e创建软连接\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eln -s /usr/local/python3/bin/python3 /usr/local/bin/python3\nln -s /usr/local/python3/bin/pip3 /usr/local/bin/pip3\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e验证是否成功\u003c/p\u003e","title":"【CentOS7】编译安装Python"},{"content":"\n1.下载MariaDB\nhttps://downloads.mariadb.org/mariadb/5.5.60/ 2.ftp上传压缩包至服务器并解压\ntar -zxf mariadb-5.5.60.tar.gz 3.安装步骤\n关闭SELINUX\nvi /etc/selinux/config #SELINUX=enforcing # 注释掉 #SELINUXTYPE=targeted # 注释掉 SELINUX=disabled # 增加 :wq! # 保存退出 shutdown -r now # 重启系统 进入安装目录\ncd mariadb5.5.60/ 安装依赖软件\nyum install -y gcc gcc-c++ zlib-dev libaio-dev openssl cmake libncurses libevent-dev ncurses-devel 创建数据库的安装目录\nmkdir /usr/local/mariadb 编译并指定安装目录\ncmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql 安装(时间较长,若夯住则敲几个回车催催)\nmake \u0026amp;\u0026amp; make install 将安装好的执行文件复制到系统执行命令路径\n/bin/cp -rf /usr/local/mysql/bin/* /usr/bin/ 复制配置文件\ncp support-files/my-large.cnf /etc/my.cnf 创建运行账户\nuseradd -s /sbin/nologin -M mysql 修改权限\nchown -R mysql:mysql /usr/local/mysql 数据初始化\ncd /usr/local/mysql chown -R root.mysql ./* scripts/mysql_install_db --user=mysql # 生成MariaDB系统数据库 chown -R root . chown -R mysql . cp support-files/mysql.server /etc/rc.d/init.d/mysqld # 把MAriaDB加入系统启动 chmod +x /etc/rc.d/init.d/mysqld chmod 755 /etc/init.d/mysqld # 增加执行权限 chkconfig --add mysqld # 加入开机启动 vi /etc/rc.d/init.d/mysqld # 编辑 basedir = /usr/local/mysql # MariaDB程序安装路径 datadir = /data/mysql # MariaDB数据库存放目录 service mysqld start # 现在在可用此命令启动mysqld服务 vi /etc/profile # 把MariaDB服务加入系统环境变量：在最后添加下面这一行 export PATH=$PATH:/usr/local/mysql/bin 下面这两行把MariaDB的库文件链接到系统默认的位置，这样你在编译类似PHP等软件时可以不用指定MariaDB的库文件地址。\nln -s /usr/local/mysql/lib/mysql /usr/lib/mysql ln -s /usr/local/mysql/include/mysql /usr/include/mysql 安全初始化，设置MariaDB数据库root账号密码根据提示按Y 回车输入2次密码\n/usr/local/mysql/bin/mysql_secure_installation # 或者直接修改密码 /usr/local/mysql/bin/mysqladmin -u root -p password \u0026quot;123456\u0026quot; #修改密码 创建centos7启动脚本\nvim /usr/lib/systemd/system/mysql.service # 也可以放置在 /etc/systemd/system/mysql.service [Unit] Description=MariaDB 5.5.60 database server After=syslog.target After=network.target [Service] Type=simple User=mysql Group=mysql ExecStart=/usr/local/mysql/bin/mysqld_safe --basedir=/usr/local/mysql TimeoutSec=300 PrivateTmp=false [Install] WantedBy=multi-user.target 尝试用systemctl启动服务\nsystemctl daemon-reload systemctl start mysql.service systemctl status mysql.service mariadb配置允许远程访问方式首先配置允许访问的用户，采用授权的方式给用户权限\nGRANT ALL PRIVILEGES ON *.* TO 'root'@'%'IDENTIFIED BY '123456' WITH GRANT OPTION; 说明：root是登陆数据库的用户，123456是登陆数据库的密码，*就是意味着任何来源任何主机反正就是权限很大的样子。\n最后配置好权限之后不应该忘记刷新使之生效\nflush privileges; END\n","permalink":"https://zhong-er.com/posts/centos7%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85mariadb5.5.60/","summary":"\u003cp\u003e\u003cimg alt=\"编译安装MariaDB.png\" loading=\"lazy\" src=\"/assets/202009031507_2560_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e1.下载MariaDB\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ehttps://downloads.mariadb.org/mariadb/5.5.60/\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2.ftp上传压缩包至服务器并解压\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003etar -zxf mariadb-5.5.60.tar.gz\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e3.安装步骤\u003c/p\u003e\n\u003cp\u003e关闭SELINUX\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003evi /etc/selinux/config\n#SELINUX=enforcing  # 注释掉\n#SELINUXTYPE=targeted # 注释掉\nSELINUX=disabled  # 增加\n:wq!  # 保存退出\nshutdown -r now  # 重启系统\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e进入安装目录\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ecd mariadb5.5.60/\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e安装依赖软件\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eyum install -y gcc gcc-c++ zlib-dev libaio-dev openssl cmake libncurses libevent-dev ncurses-devel\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e创建数据库的安装目录\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003emkdir /usr/local/mariadb\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e编译并指定安装目录\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ecmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e安装(时间较长,若夯住则敲几个回车催催)\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003emake \u0026amp;\u0026amp; make install\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e将安装好的执行文件复制到系统执行命令路径\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e/bin/cp -rf /usr/local/mysql/bin/* /usr/bin/\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e复制配置文件\u003c/p\u003e","title":"【CentOS7】编译安装MariaDB5.5.60"},{"content":"\n近期python写了个DNS得轮询脚本，交给调度器每分钟执行一次，解析失败就告警。\n问题来了，中间使用print打印一些日志，想通过nohup运行方式收集print输出内容作为日志，\n但是已经执行5分钟了，nohup.out还是空，什么都没输出，\n起初以为是默认输出级别不对，于是改为\nnohup python test.py \u0026gt; nohup.out 2\u0026gt;\u0026amp;1 \u0026amp; 然而并没有任何效果，最后搜索引擎告诉了我，\n这是因为python的输出有缓冲，导致nohup.out并不能够马上看到输出。\npython 有个-u参数，使得python不启用缓冲。\n所以最终的启动命令为：\nnohup python -u test.py \u0026gt; nohup.out 2\u0026gt;\u0026amp;1 \u0026amp; END\n","permalink":"https://zhong-er.com/posts/%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95nohup-python%E7%A8%8B%E5%BA%8Fprint%E6%97%A0%E8%BE%93%E5%87%BA/","summary":"\u003cp\u003e\u003cimg alt=\"错误记录.png\" loading=\"lazy\" src=\"/assets/202009035154_4969_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e近期python写了个DNS得轮询脚本，交给调度器每分钟执行一次，解析失败就告警。\u003c/p\u003e\n\u003cp\u003e问题来了，中间使用print打印一些日志，想通过nohup运行方式收集print输出内容作为日志，\u003c/p\u003e\n\u003cp\u003e但是已经执行5分钟了，nohup.out还是空，什么都没输出，\u003c/p\u003e\n\u003cp\u003e起初以为是默认输出级别不对，于是改为\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003enohup python test.py \u0026gt; nohup.out 2\u0026gt;\u0026amp;1 \u0026amp;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e然而并没有任何效果，最后搜索引擎告诉了我，\u003c/p\u003e\n\u003cp\u003e这是因为python的输出有缓冲，导致nohup.out并不能够马上看到输出。\u003c/p\u003e\n\u003cp\u003epython 有个-u参数，使得python不启用缓冲。\u003c/p\u003e\n\u003cp\u003e所以最终的启动命令为：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003enohup python -u test.py \u0026gt; nohup.out 2\u0026gt;\u0026amp;1 \u0026amp;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eEND\u003c/p\u003e","title":"【问题记录】nohup python程序，print无输出"},{"content":"\nWin10系统的IE偶尔出现过问题，chrome和Firefox正常使用，IE却提示错误代码inet_e_resource_not_found。\n以前也纠结过怎么处理，网上搜来搜去后来经常是重启解决，反正win10装了SSD之后重启比较快。\n不过后来还是找到了解决办法，不过这个问题总是偶尔发生，我也没有太重视这个。\n昨天忽然又不能使用IE了，无奈只好想想常用的办法：\n（1）首先检查下是否开启了兼容视图，在兼容性视图里，把两个勾选框都勾上，很多IE莫名其妙的问题也许会好点。\n（3）其次在Internet选项里，点《连接》标签，按下《局域网设置》按钮，在对话框里，勾选上那个《自动检测设置》的勾选框。\n确认之后，刷新下IE，问题应该就解决了。\n（3）如果没有解决问题的话，建议重启系统一下，如果还没有解决，那么可以重置IE一次。\n（4）重置IE的按钮在Internet选项的《高级》标签里，下方有个《重置》按钮，重置时不用删除个人设置。\n（5）一般此时，问题就彻底解决了，如果还是不行的话，就将就先用下Firefox或者Chrome了，过一阵子重启也许就可以用了，Windows系统抽风也不是一次两次的。\n","permalink":"https://zhong-er.com/posts/%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95win10%E7%9A%84ie%E9%94%99%E8%AF%AF%E4%BB%A3%E7%A0%81inet_e_resource_not_found/","summary":"\u003cp\u003e\u003cimg alt=\"错误记录.png\" loading=\"lazy\" src=\"/assets/202009033599_4055.png\"\u003e\u003c/p\u003e\n\u003cp\u003eWin10系统的IE偶尔出现过问题，chrome和Firefox正常使用，IE却提示错误代码inet_e_resource_not_found。\u003cbr\u003e\n以前也纠结过怎么处理，网上搜来搜去后来经常是重启解决，反正win10装了SSD之后重启比较快。\u003cbr\u003e\n不过后来还是找到了解决办法，不过这个问题总是偶尔发生，我也没有太重视这个。\u003c/p\u003e\n\u003cp\u003e昨天忽然又不能使用IE了，无奈只好想想常用的办法：\u003c/p\u003e\n\u003cp\u003e（1）首先检查下是否开启了兼容视图，在兼容性视图里，把两个勾选框都勾上，很多IE莫名其妙的问题也许会好点。\u003cbr\u003e\n（3）其次在Internet选项里，点《连接》标签，按下《局域网设置》按钮，在对话框里，勾选上那个《自动检测设置》的勾选框。\u003cbr\u003e\n确认之后，刷新下IE，问题应该就解决了。\u003c/p\u003e\n\u003cp\u003e（3）如果没有解决问题的话，建议重启系统一下，如果还没有解决，那么可以重置IE一次。\u003c/p\u003e\n\u003cp\u003e（4）重置IE的按钮在Internet选项的《高级》标签里，下方有个《重置》按钮，重置时不用删除个人设置。\u003c/p\u003e\n\u003cp\u003e（5）一般此时，问题就彻底解决了，如果还是不行的话，就将就先用下Firefox或者Chrome了，过一阵子重启也许就可以用了，Windows系统抽风也不是一次两次的。\u003c/p\u003e","title":"【问题记录】Win10的IE错误代码inet_e_resource_not_found解决办法"},{"content":"\nwindows server 2019 版 安装部署宝塔面板+LNMP环境+微擎框架，微擎提示：\n问题原因： PHP-FPM只支持Unix-like系统(Linux/FreeBSD/MacOS),不支持Windows\nWindows上可以用PHP-CGI搭配CGI进程管理器来使用:\n比如Apache mod_fcgid 可以用来替代 mod_cgi 和 mod_cgid,具有管理和维持PHP-CGI进程数目的功能.\nApacheLounge提供有Windows上的mod_fcgid二进制包:\nhttp://www.apachelounge.com/download/\n如果你是搭配IIS使用,则需要PHP Manager这个CGI进程管理器:\nhttp://phpmanager.codeplex.com/\n解决方案： windows 下安装docker 搭建Nginx和php-fpm\n下面以win7系统为例\n准备工作：挂载本地磁盘到虚拟系统\n本地文件夹结构 E:\\docker\nnginx文件夹结构\nconf文件夹结构\n1、\ndocker pull nginx\ndocker pull php:7.1-fpm\n2、\n启动php容器\ndocker run \u0026ndash;name php7.1 -v /e/docker/html:/var/www/html -p 9000:9000 -d php:7.1-fpm\n3、\n启动nginx容器\ndocker run -d -p 8080:80 \u0026ndash;name nginx1 -v /e/docker/html:/var/www/html -v /e/docker/nginx/conf/conf.d:/etc/nginx/conf.d -v /e/docker/nginx/logs:/var/log/nginx \u0026ndash;link php7.1:php7.1 nginx\n4、\n本地文件夹conf.d中添加 default.conf\n内容：\nserver { listen 80; server_name localhost; #charset koi8-r; #access_log /var/log/nginx/host.access.log main; location / { root /var/www/html; index index.html index.htm index.php; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /var/www/html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \\.php$ { #proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # location ~ \\.php$ { root /var/www/html; fastcgi_pass php7.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # location ~ /\\.ht { deny all; } } 注意：php7.1为 php-fpm容器名称\nEND\n","permalink":"https://zhong-er.com/posts/%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95windows%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%AD%E6%97%A0%E6%B3%95%E5%BC%80%E5%90%AFphp-fpm/","summary":"\u003cp\u003e\u003cimg alt=\"错误记录.png\" loading=\"lazy\" src=\"/assets/202009032124_3352_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003ewindows server 2019 版 安装部署宝塔面板+LNMP环境+微擎框架，微擎提示：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"wqbc.png\" loading=\"lazy\" src=\"/assets/201912123043_4085.png\"\u003e\u003c/p\u003e\n\u003ch1 id=\"问题原因\"\u003e问题原因：\u003c/h1\u003e\n\u003cp\u003ePHP-FPM只支持Unix-like系统(Linux/FreeBSD/MacOS),不支持Windows\u003c/p\u003e\n\u003cp\u003eWindows上可以用PHP-CGI搭配CGI进程管理器来使用:\u003cbr\u003e\n比如Apache mod_fcgid 可以用来替代 mod_cgi 和 mod_cgid,具有管理和维持PHP-CGI进程数目的功能.\u003cbr\u003e\nApacheLounge提供有Windows上的mod_fcgid二进制包:\u003cbr\u003e\n\u003ca href=\"http://www.apachelounge.com/download/\"\u003ehttp://www.apachelounge.com/download/\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e如果你是搭配IIS使用,则需要PHP Manager这个CGI进程管理器:\u003cbr\u003e\n\u003ca href=\"http://phpmanager.codeplex.com/\"\u003ehttp://phpmanager.codeplex.com/\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"解决方案\"\u003e解决方案：\u003c/h1\u003e\n\u003cp\u003ewindows 下安装docker 搭建Nginx和php-fpm\u003c/p\u003e\n\u003cp\u003e下面以win7系统为例\u003c/p\u003e\n\u003cp\u003e准备工作：挂载本地磁盘到虚拟系统\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"1799317870.png\" loading=\"lazy\" src=\"/assets/201912129262_4812.png\"\u003e\u003c/p\u003e\n\u003cp\u003e本地文件夹结构 E:\\docker\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"304126031.png\" loading=\"lazy\" src=\"/assets/201912126294_8641.png\"\u003e\u003c/p\u003e\n\u003cp\u003enginx文件夹结构\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"1792020786.png\" loading=\"lazy\" src=\"/assets/201912121616_9538.png\"\u003e\u003c/p\u003e\n\u003cp\u003econf文件夹结构\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"1539272572.png\" loading=\"lazy\" src=\"/assets/201912129275_6125.png\"\u003e\u003c/p\u003e\n\u003cp\u003e1、\u003c/p\u003e\n\u003cp\u003edocker pull nginx\u003c/p\u003e\n\u003cp\u003edocker pull php:7.1-fpm\u003c/p\u003e\n\u003cp\u003e2、\u003c/p\u003e\n\u003cp\u003e启动php容器\u003c/p\u003e\n\u003cp\u003edocker run \u0026ndash;name php7.1 -v /e/docker/html:/var/www/html -p 9000:9000 -d php:7.1-fpm\u003c/p\u003e\n\u003cp\u003e3、\u003c/p\u003e\n\u003cp\u003e启动nginx容器\u003c/p\u003e\n\u003cp\u003edocker run -d -p 8080:80 \u0026ndash;name nginx1 -v /e/docker/html:/var/www/html -v /e/docker/nginx/conf/conf.d:/etc/nginx/conf.d -v /e/docker/nginx/logs:/var/log/nginx \u0026ndash;link php7.1:php7.1 nginx\u003c/p\u003e","title":"【问题记录】Windows服务器中无法开启php-fpm"},{"content":"\n在日常玩服务器过程中，我们经常需要测试一下IP或者域名的延迟，一般直接PING 域名或IP即可~\n这种 PING 实际上走的是ICMP协议，其实我们用的最多的是TCP协议。如果直接测试TCP协议的网络延迟，应该是更准确的！\n下面本文介绍一款工具，TCPing，不仅可以使用TCP协议的延迟，还能检测端口的连通情况。对于一些禁PING的域名或者IP，我可以直接利用TCPing来查看延迟情况！\nWindows版 下载地址：https://www.elifulkerson.com/projects/tcping.php\n安装步骤：下载 tcping.exe 文件到 C:\\Windows\\System32\\ 即可！\n命令介绍：打开CMD命令控制台，输入 tcping\n使用技巧：\ntcping [命令参数] 服务器地址(IP/域名) [服务器端口] -t : 连续 TCPing ，直到使用 Ctrl+C 键停止 示例：tcping -t 1.1.1.1 80 -n 5 : TCPing 5次后停止 示例：tcping -n 5 1.1.1.1 80 -i 5 : 每隔 5秒 TCPing 一次 示例：tcping -i 5 1.1.1.1 80 -w 0.5 : 设置超时时间为 0.5秒（1秒=1000毫秒），单位 秒 示例：tcping -w 0.5 1.1.1.1 80 -d : 在每行返回信息中加入时间信息 示例：tcping -d 1.1.1.1 80 -s : 当 TCPing 测试成功后（在超时时间以内返回 TCPing 延迟数据）自动停止 TCPing 示例：tcping -s 1.1.1.1 80 -4 : 优先 IPv4（如果一个域名有 IPv4 和 IPv6 解析，那么走 IPv4） 示例：tcping -4 www.google.com 80 -6 : 优先 IPv6（如果一个域名有 IPv4 和 IPv6 解析，那么走 IPv6） 示例：tcping -6 www.google.com 80 --file : TCPing 将逐行循环遍历文件内的 服务器IP/域名 信息（一行一个，支持端口，例如：1.1.1.1 443） 示例：tcping --file D:\\abc\\1.txt -v : 显示版本号 示例：tcping -v # 如果你没有写服务器地址的端口，那么默认为 80 端口 # 其实还有很多命令参数，只是大都用不上，所以省略了 Linux版 Linux平台中类似的工具非常多，比如nping ， nc 等~\n这里我们介绍一款比较轻量的TCPing工具~~~\n先用apt或者yum安装tcptraceroute bc这两个工具，然后执行 yum install -y tcptraceroute yum install -y bc cd /usr/bin wget http://www.vdberg.org/~richard/tcpping chmod +x tcpping mv tcpping tcping tcpping v1.7 Richard van den Berg \u0026lt;richard@vdberg.org\u0026gt; Usage: tcping [-d] [-c] [-C] [-w sec] [-q num] [-x count] ipaddress [port] -d print timestamp before every result -c print a columned result line -C print in the same format as fping's -C option -w wait time in seconds (defaults to 3) -r repeat every n seconds (defaults to 1) -x repeat n times (defaults to unlimited) See also: man tcptraceroute END\n","permalink":"https://zhong-er.com/posts/tcping-%E4%B8%80%E6%AC%BE%E6%A3%80%E6%B5%8B%E7%AB%AF%E5%8F%A3%E6%98%AF%E5%90%A6%E5%BC%80%E5%90%AF%E5%92%8Ctcp%E5%BB%B6%E8%BF%9F%E7%9A%84%E5%B7%A5%E5%85%B7/","summary":"\u003cp\u003e\u003cimg alt=\"img.png\" loading=\"lazy\" src=\"/assets/201912128293_3684_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e在日常玩服务器过程中，我们经常需要测试一下IP或者域名的延迟，一般直接PING 域名或IP即可~\u003c/p\u003e\n\u003cp\u003e这种 PING 实际上走的是ICMP协议，其实我们用的最多的是TCP协议。如果直接测试TCP协议的网络延迟，应该是更准确的！\u003c/p\u003e\n\u003cp\u003e下面本文介绍一款工具，TCPing，不仅可以使用TCP协议的延迟，还能检测端口的连通情况。对于一些禁PING的域名或者IP，我可以直接利用TCPing来查看延迟情况！\u003c/p\u003e\n\u003ch2 id=\"windows版\"\u003eWindows版\u003c/h2\u003e\n\u003cp\u003e下载地址：\u003ca href=\"https://www.elifulkerson.com/projects/tcping.php\"\u003ehttps://www.elifulkerson.com/projects/tcping.php\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e安装步骤：下载 \u003ca href=\"https://51.ruyo.net/go/index.html?u=https://download.elifulkerson.com//files/tcping/0.39/tcping.exe\"\u003etcping.exe\u003c/a\u003e 文件到 C:\\Windows\\System32\\ 即可！\u003c/p\u003e\n\u003cp\u003e命令介绍：打开CMD命令控制台，输入 tcping\u003c/p\u003e\n\u003cp\u003e使用技巧：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003etcping [命令参数] 服务器地址(IP/域名) [服务器端口]\n \n -t     : 连续 TCPing ，直到使用 Ctrl+C 键停止\n示例：tcping -t 1.1.1.1 80\n \n -n 5   : TCPing 5次后停止\n示例：tcping -n 5 1.1.1.1 80\n \n -i 5   : 每隔 5秒 TCPing 一次\n示例：tcping -i 5 1.1.1.1 80\n \n -w 0.5 : 设置超时时间为 0.5秒（1秒=1000毫秒），单位 秒\n示例：tcping -w 0.5 1.1.1.1 80\n \n -d     : 在每行返回信息中加入时间信息\n示例：tcping -d 1.1.1.1 80\n \n -s     : 当 TCPing 测试成功后（在超时时间以内返回 TCPing 延迟数据）自动停止 TCPing\n示例：tcping -s 1.1.1.1 80\n \n -4     : 优先 IPv4（如果一个域名有 IPv4 和 IPv6 解析，那么走 IPv4）\n示例：tcping -4 www.google.com 80\n \n -6     : 优先 IPv6（如果一个域名有 IPv4 和 IPv6 解析，那么走 IPv6）\n示例：tcping -6 www.google.com 80\n \n --file : TCPing 将逐行循环遍历文件内的 服务器IP/域名 信息（一行一个，支持端口，例如：1.1.1.1 443）\n示例：tcping --file D:\\abc\\1.txt\n \n -v : 显示版本号\n示例：tcping -v\n \n# 如果你没有写服务器地址的端口，那么默认为 80 端口\n# 其实还有很多命令参数，只是大都用不上，所以省略了\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"linux版\"\u003eLinux版\u003c/h2\u003e\n\u003cp\u003eLinux平台中类似的工具非常多，比如nping ， nc 等~\u003c/p\u003e","title":"TCPing 一款检测端口是否开启和TCP延迟的工具"},{"content":"\n一 GoLand集成开发环境下载 IDE 下载地址：http://www.jetbrains.com/go/?fromMenu\nIDE安装就是平常的傻瓜式安装，这里就不多说了。\n二 使用GoLand 1、 双击运行安装完的GoLand，选择创建项目，并关联GOROOT（会自动关联）\n2、在项目上点击右键，按图选择，如下，给文件命一个名字，如 test\n3、在文件中写入如下代码：\n4、在文件上右键，选择 Run go build test.go，就可以看到控制台打印出hello world!啦\n5、也可以在Terminal窗口下输入go run test.go,就可以看到打印结果\n6、或者输入go build test.go ,会在当前路径下生成一个可执行文件(test)，继续输入 ./test ，即可执行该可执行文件\nEND\n","permalink":"https://zhong-er.com/posts/gogoland%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8/","summary":"\u003cp\u003e\u003cimg alt=\"GO语言简明教程.png\" loading=\"lazy\" src=\"/assets/202009032723_3398_1.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"一-goland集成开发环境下载\"\u003e一 GoLand集成开发环境下载\u003c/h2\u003e\n\u003cp\u003eIDE 下载地址：\u003ca href=\"http://www.jetbrains.com/go/?fromMenu\"\u003ehttp://www.jetbrains.com/go/?fromMenu\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eIDE安装就是平常的傻瓜式安装，这里就不多说了。\u003c/p\u003e\n\u003ch2 id=\"二-使用goland\"\u003e二 使用GoLand\u003c/h2\u003e\n\u003cp\u003e1、 双击运行安装完的GoLand，选择创建项目，并关联GOROOT（会自动关联）\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"fgwgr.jpg\" loading=\"lazy\" src=\"/assets/201912099571_3392_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e2、在项目上点击右键，按图选择，如下，给文件命一个名字，如 test\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"2aymzu.jpg\" loading=\"lazy\" src=\"/assets/201912094311_8547_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e3、在文件中写入如下代码：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"3bgjsu.jpg\" loading=\"lazy\" src=\"/assets/201912096118_5294.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e4、在文件上右键，选择 Run go build test.go，就可以看到控制台打印出hello world!啦\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"4wm2.jpg\" loading=\"lazy\" src=\"/assets/201912093480_9128_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"5u076z.jpg\" loading=\"lazy\" src=\"/assets/201912094525_9516_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e5、也可以在Terminal窗口下输入go run test.go,就可以看到打印结果\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"6qdjk.jpg\" loading=\"lazy\" src=\"/assets/201912098158_6868_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e6、或者输入go build test.go ,会在当前路径下生成一个可执行文件(test)，继续输入 ./test ，即可执行该可执行文件\u003c/p\u003e\n\u003cp\u003eEND\u003c/p\u003e","title":"【Go】GoLand安装使用"},{"content":"\n建立 Go 工作区 在编写代码之前，我们首先应该建立 Go 的工作区（Workspace）。\n在 Mac 或 Linux 操作系统下，Go 工作区应该设置在 HOME/go∗∗。所以我们要在∗∗HOME/go∗∗。所以我们要在∗∗HOME 目录下创建 go 目录。\n而在 Windows 下，工作区应该设置在 C:\\Users\\YourName\\go。所以请将 go 目录放置在 C:\\Users\\YourName。\n其实也可以通过设置 GOPATH 环境变量，用其他目录来作为工作区。但为了简单起见，我们采用上面提到的放置方法。\n所有 Go 源文件都应该放置在工作区里的 src 目录下。请在刚添加的 go 目录下面创建目录 src。\n所有 Go 项目都应该依次在 src 里面设置自己的子目录。我们在 src 里面创建一个目录 hello 来放置整个 hello world 项目。\n创建上述目录之后，其目录结构如下：\ngo src hello 在我们刚刚创建的 hello 目录下，在 helloworld.go 文件里保存下面的程序。\npackage main import \u0026quot;fmt\u0026quot; func main() { fmt.Println(\u0026quot;Hello World\u0026quot;) } 创建该程序之后，其目录结构如下：\ngo src hello helloworld.go 运行 Go 程序 运行 Go 程序有多种方式，我们下面依次介绍。\n1.使用 go run 命令 - 在命令提示符旁，输入 go run workspacepath/src/hello/helloworld.go。\n上述命令中的 workspacepath 应该替换为你自己的工作区路径（Windows 下的 C:/Users/YourName/go，Linux 或 Mac 下的 $HOME/go）。\n在控制台上会看见 Hello World 的输出。\n2.使用 go install 命令 - 运行 go install hello，接着可以用 workspacepath/bin/hello 来运行该程序。\n上述命令中的 workspacepath 应该替换为你自己的工作区路径（Windows 下的 C:/Users/YourName/go，Linux 或 Mac 下的 $HOME/go）。\n当你输入 go install hello 时，go 工具会在工作区中搜索 hello 包（hello 称之为包，我们后面会更加详细地讨论包）。接下来它会在工作区的 bin 目录下，创建一个名为 hello（Windows 下名为 hello.exe）的二进制文件。运行 go install hello 后，其目录结构如下所示：\ngo bin hello src hello helloworld.go 3.第 3 种运行程序的好方法是使用 go playground。尽管它有自身的限制，但该方法对于运行简单的程序非常方便。我已经在 playground 上创建了一个 hello world 程序。点击这里 在线运行程序。 你可以使用 go playground 与其他人分享你的源代码。\n简述 hello world 程序 下面就是我们刚写下的 hello world 程序。\npackage main //1 import \u0026quot;fmt\u0026quot; //2 func main() { //3 fmt.Println(\u0026quot;Hello World\u0026quot;) //4 } 现在简单介绍每一行大概都做了些什么，在以后的教程中还会深入探讨每个部分。\npackage main - 每一个 Go 文件都应该在开头进行 package name 的声明（译注：只有可执行程序的包名应当为 main）。包（Packages）用于代码的封装与重用，这里的包名称是main。\nimport \u0026ldquo;fmt\u0026rdquo; - 我们引入了 fmt 包，用于在 main 函数里面打印文本到标准输出。\nfunc main() - main 是一个特殊的函数。整个程序就是从 main 函数开始运行的。main 函数必须放置在 main 包中。{ 和 } 分别表示 main 函数的开始和结束部分。\nfmt.Println(\u0026ldquo;Hello World\u0026rdquo;) - fmt 包中的 Println 函数用于把文本写入标准输出。\nEND\n","permalink":"https://zhong-er.com/posts/gohello-world/","summary":"\u003cp\u003e\u003cimg alt=\"GO语言简明教程.png\" loading=\"lazy\" src=\"/assets/202009032723_3398_1.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"建立-go-工作区\"\u003e建立 Go 工作区\u003c/h2\u003e\n\u003cp\u003e在编写代码之前，我们首先应该建立 Go 的工作区（Workspace）。\u003c/p\u003e\n\u003cp\u003e在 Mac 或 Linux 操作系统下，Go 工作区应该设置在 HOME/go∗∗。所以我们要在∗∗HOME/go∗∗。所以我们要在∗∗HOME 目录下创建 go 目录。\u003c/p\u003e\n\u003cp\u003e而在 Windows 下，工作区应该设置在 C:\\Users\\YourName\\go。所以请将 go 目录放置在 C:\\Users\\YourName。\u003c/p\u003e\n\u003cp\u003e其实也可以通过设置 GOPATH 环境变量，用其他目录来作为工作区。但为了简单起见，我们采用上面提到的放置方法。\u003c/p\u003e\n\u003cp\u003e所有 Go 源文件都应该放置在工作区里的 src 目录下。请在刚添加的 go 目录下面创建目录 src。\u003c/p\u003e\n\u003cp\u003e所有 Go 项目都应该依次在 src 里面设置自己的子目录。我们在 src 里面创建一个目录 hello 来放置整个 hello world 项目。\u003c/p\u003e\n\u003cp\u003e创建上述目录之后，其目录结构如下：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ego\n  src\n    hello\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e在我们刚刚创建的 hello 目录下，在 helloworld.go 文件里保存下面的程序。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003epackage main\nimport \u0026quot;fmt\u0026quot;\nfunc main() {  \n    fmt.Println(\u0026quot;Hello World\u0026quot;)\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e创建该程序之后，其目录结构如下：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ego\n  src\n    hello\n      helloworld.go\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"运行-go-程序\"\u003e运行 Go 程序\u003c/h2\u003e\n\u003cp\u003e运行 Go 程序有多种方式，我们下面依次介绍。\u003c/p\u003e","title":"【Go】Hello World"},{"content":"\nGo包管理 一 包管理历史 Golang 的包管理一直被大众所诟病的一个点，但是我们可以看到现在确实是在往好的方向进行发展。下面是官方的包管理工具的发展历史：\n在 1.5 版本之前，所有的依赖包都是存放在 GOPATH 下，没有版本控制。这个类似 Google 使用单一仓库来管理代码的方式。这种方式的最大的弊端就是无法实现包的多版本控制，比如项目 A 和项目 B 依赖于不同版本的 package，如果 package 没有做到完全的向前兼容，往往会导致一些问题。\n1.5 版本推出了 vendor 机制。所谓 vendor 机制，就是每个项目的根目录下可以有一个 vendor 目录，里面存放了该项目的依赖的 package。go build 的时候会先去 vendor 目录查找依赖，如果没有找到会再去 GOPATH 目录下查找。\n1.9 版本推出了实验性质的包管理工具 dep，这里把 dep 归结为 Golang 官方的包管理方式可能有一些不太准确。关于 dep 的争议颇多，比如为什么官方后来没有直接使用 dep 而是弄了一个新的 modules，具体细节这里不太方便展开。\n1.11 版本推出 modules 机制，简称 mod。modules 的原型其实是 vgo，关于 vgo，可以自行搜索。\n除此之外，社区也一直在有几个活跃的包管理工具，使用广泛且具有代表性的主要有下面几个：\ngodep\nglide\ngovendor\n二 modules 的使用 2.1 准备 Golang 版本：1.12.3。在 1.12 版本之前，使用 Go modules 之前需要环境变量 GO111MODULE:\nGO111MODULE=off: 不使用 modules 功能，查找vendor和GOPATH目录\nGO111MODULE=on: 使用 modules 功能，不会去 GOPATH 下面查找依赖包。\nGO111MODULE=auto: Golang 自己检测是不是使用 modules 功能，如果当前目录不在$GOPATH 并且 当前目录（或者父目录）下有go.mod文件，则使用 GO111MODULE， 否则仍旧使用 GOPATH mode\nset GO111MODULE=on //windows export GO111MODULE=on //linux\n在 GOPATH 之外创建一个项目 mod-demo，包含一个 main.go 文件，内容如下:\npackage main import \u0026quot;fmt\u0026quot; import \u0026quot;github.com/astaxie/beego\u0026quot; func main() { fmt.Println(\u0026quot;hello\u0026quot;) beego.Run() } 2.2 初始化go modules 在项目根目录执行命令 go mod init test-demo ，然后会生成一个 go.mod\n这里比较关键的就是这个 go.mod 文件，这个文件中标识了我们的项目的依赖的 package 的版本。执行 init 暂时还没有将所有的依赖管理起来。我们需要将程序 run 起来（比如执行 go run/test），或者 build（执行命令 go build）的时候，才会触发依赖的解析。\n比如使用 go run 即可触发 modules 工作。\ngo run test.go 这个时候我们再查看 go.mod 文件：\nmodule test-demo go 1.13require ( github.com/astaxie/beego v1.12.0 // indirect github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect) 注意：有indirect注释的代表间接依赖，没有的代表直接依赖，这里是版本号+时间戳+hash\n注意：在goland中可以用alt+enter/option+enter来下载依赖包\n同时我们发现项目目录下多了一个 go.sum 用来记录每个 package 的版本和哈希值。go.mod 文件正常情况会包含 module 和 require 模块，除此之外还可以包含 replace 和 exclude 模块。\n这些 package 并不是直接存储到 GOPATH/src，而是存储到 GOPATH/pkg/mod 下面，不同版本并存的方式。\n2.3 依赖的升级和降级 可以使用如下命令来查看当前项目依赖的所有的包\ngo list -m -u all //如果我想要升级（降级）某个 package 则只需要 go get 即可，比如： go get package@version 需要注意的是，在 modules 模式开启和关闭的情况下，go get 的使用方式不是完全相同的。在 modules 模式开启的情况下，可以通过在 package 后面添加 @version 来表明要升级（降级）到某个版本。如果没有指明 version 的情况下，则默认先下载打了 tag 的 release 版本，比如 v0.4.5 或者 v1.2.3；如果没有 release 版本，则下载最新的 pre release 版本，比如 v0.0.1-pre1。如果还没有则下载最新的 commit。这个地方给我们的一个启示是如果我们不按规范的方式来命名我们的 package 的 tag，则 modules 是无法管理的。version 的格式为 v(major).(minor).(patch) ，更多信息可以参考：https://semver.org/ 。\n比如我们现在想将我们依赖中的 beego 项目的版本改为 v1.11.1，则可以像如下操作。我们发现执行完 go get 之后， go.mod 中的项目的版本也相应改变了。\ngo get github.com/astaxie/beego@v1.11.1 在 modules 开启的模式下，go get 还支持 version 模糊查询，比如 \u0026gt; v1.0.0 表示大于 v1.0.0\n的可使用版本；\u0026lt; v1.12.0 表示小于 v1.12.0 版本下最近可用的版本。version 的比较规则按照 version\n的各个字段来展开。\n除了指定版本，我们还可以使用如下命名使用最近的可行的版本：\ngo get -u 使用最新的 minor 或者 patch 版本\ngo get -u=patch 使用最新的 patch 版本\n2.4 vender 我们知道 Go 1.5 推出了 vendor 机制，go mod 也可以支持 vendor 机制，将依赖包拷贝到 vendor 目录。但是像一些 test case 里面的依赖包并不会拷贝的 vendor 目录中。\ngo mod vendor 三 modules 高级特性 3.1 代理服务器GoProxy proxy 顾名思义，代理服务器。众所周知，有些 Golang 的 package 在国内是无法直接 go get 的。在之前，我们解决这个问题，一般都是通过设置 http_proxy/https_proxy 来解决。GoProxy 相当于官方提供了一种 proxy 的方式让用户来进行包下载。要使用 GoProxy 只需要设置环境变量 GOPROXY 即可。目前公开的 GOPROXY 有：\ngoproxy.io 在goland\u0026ndash;\u0026gt;Go\u0026ndash;\u0026gt;Go Modules(ego)中配置这个地址\ngoproxy.cn: 由七牛云提供，参考 github repo\n当然你也可以实现自己的 GoProxy 服务，比如项目中的依赖包含外部依赖和内部依赖的时候，那么只需要实现 module proxy protocal 协议即可。\n值得注意的是，在最新 release 的 Go 1.13 版本中默认将 GOPROXY 设置为 https://proxy.golang.org，这个对于国内的开发者是无法直接使用的。所以如果升级了 Go 1.13 版本一定要把 GOPROXY 手动改掉。\n3.2 replace replace 主要为了解决某些包发生改名的问题。\n对于另外一种场景有的时候也是有用的，比如对于有些 golang.org/x/ 下面的包由于某些原因在国内是下载不了的，但是对应的包在 github 上面是有一份拷贝的，这个时候我们就可以将 go.mod 中的包进行 replace 操作。\n下面是一个 Beego 项目的 go.mod 的 replace 的示例。\nreplace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 =\u0026gt; github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 replace gopkg.in/yaml.v2 v2.2.1 =\u0026gt; github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d 3.3 SubCommand modules 支持的 subcommand 如下\n即输入go mod后面可以跟以下命令\ndownload: 下载 modules 到本地缓存\nedit: 提供一种命令行交互修改 go.mod 的方式\ngraph: 将 module 的依赖图在命令行打印出来，其实并不是很直观\ninit: 初始化 modules，会生成一个 go.mod 文件\ntidy: 清理 go.mod 中的依赖，会添加缺失的依赖，同时移除没有用到的依赖\nvendor: 将依赖包打包拷贝到项目的 vendor 目录下，值得注意的是并不会将 test code 中的依赖包打包到 vendor 中。这种设计在社区也引起过几次争论，但是并没有达成一致。\nverify: verify 用来检测依赖包自下载之后是否被改动过。\nwhy: 解释为什么 package 或者 module 是需要，但是看上去解释的理由并不是非常的直观。\n四 Go 1.13 对 modules 的改动 上面在讨论 GoProxy 的时候提到了 Go 1.13 默认设置环境变量 GOPROXY 的值，除此之外 Go 1.13 对 modules 还有哪些值得注意的改动呢？\n4.1 默认开启 modules 在 Go 1.13 的版本下是默认开启的。\n4.2 GOPRIVATE 前面也说到对于一些内部的 package，GoProxy 并不能很好的处理，Go 1.13 推出了 GOPRIVATE 机制。只需要设置这个环境变量，然后标识出哪些 package 是 private 的，那么对于这个 package 的处理将不会从 proxy 下载。GOPRIVATE 的值是一个以逗号分隔的列表，支持正则（正则语法遵守 Golang 的 包 path.Match）。下面是一个 GOPRIVATE 的示例：\nCopyGOPRIVATE=*.corp.example.com,rsc.io/private 上面的 GOPRIVATE 表示以 *.corp.example.com 或者 rsc.io/private 开头的 package 都是私有的。\n4.3 GOSUMDB GOSUMDB 的全称为 Go CheckSum Database，用来下载的包的安全性校验问题。包的安全性在使用 GoProxy 之后更容易出现，比如我们引用了一个不安全的 GoProxy 之后然后下载了一个不安全的包，这个时候就出现了安全性问题。对于这种情况，可以通过 GOSUMDB 来对包的哈希值进行校验。当然如果想要关闭哈希校验，可以将 GOSUMDB 设置为 off；如果要对部分包关闭哈希校验，则可以将包的前缀设置到环境变量中 GONOSUMDB 中，设置规则类似 GOPRIVATE。\n关于 GOSUMDB 的配置格式为：++。\nGOSUMDB=\u0026quot;sum.golang.org\u0026quot;GOSUMDB=\u0026quot;sum.golang.org+\u0026lt;publickey\u0026gt;\u0026quot;GOSUMDB=\u0026quot;sum.golang.org+\u0026lt;publickey\u0026gt; https://sum.golang.org\u0026quot; 上面三种配置都是合理的，因为对于 sum.golang.org，Go 自己知道其对应的 publickey 和 url，所以我们只要配置一个名字即可，对于另外一个 sum.golang.google.cn 也是一样。除此之外的，都需要指明 publickey，url 默认是 https://。\n关于 GOSUMDB 更多的详细信息可以参考：https://golang.org/cmd/go/#hdr-Module_authentication_failures\n五 自己写的包如何导入使用 直接在go.mod中写包名即可\nmodule test-demogo 1.13require ( github.com/astaxie/beego v1.12.0 github.com/gin-gonic/gin v1.4.0 // indirect github.com/go-redis/redis v6.15.6+incompatible github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect redisPackage v0.0.1 //自己加的v0.1.1 必须为三个数(也可以 不加)) 五 总结 Golang 包管理历经多个版本，目前来看并没有一个完全让开发者满意的方案，比如类似 Java 的 maven 的包管理方式。很多开发者也表示 modules 的管理方式也不是很直观。其实 Golang 的 package 使用 git 的管理方式来管理其实是一个很好的管理方式，我们最需要的其实如何能让普通开发者获取到任何 package 的心智负担降到最低。值得欣慰的是我们确实有看到大家在这个方向上的努力，比如 modules 的 GoProxy。\n放眼未来，希望会有一种令大部分普通开发者满意的包管理方式吧。\n最后常用命令 go mod init:初始化modules go mod download:下载modules到本地cache go mod edit:编辑go.mod文件，选项有-json、-require和-exclude，可以使用帮助 go help mod editgo mod graph:以文本模式打印模块需求图 go mod tidy:检查，删除错误或者不使用的modules，下载没download的package go mod vendor:生成vendor目录 go mod verify:验证依赖是否正确 go mod why：查找依赖 go test 执行一下，自动导包 go list -m 主模块的打印路径 go list -m -f={{.Dir}} print主模块的根目录 go list -m all 查看当前的依赖和版本信息 END\n","permalink":"https://zhong-er.com/posts/go%E5%8C%85%E7%AE%A1%E7%90%86%E8%AF%A6%E8%A7%A3/","summary":"\u003cp\u003e\u003cimg alt=\"GO语言简明教程.png\" loading=\"lazy\" src=\"/assets/202009032723_3398_1.png\"\u003e\u003c/p\u003e\n\u003ch1 id=\"go包管理\"\u003eGo包管理\u003c/h1\u003e\n\u003ch2 id=\"一-包管理历史\"\u003e一 包管理历史\u003c/h2\u003e\n\u003cp\u003eGolang 的包管理一直被大众所诟病的一个点，但是我们可以看到现在确实是在往好的方向进行发展。下面是官方的包管理工具的发展历史：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e在 1.5 版本之前，所有的依赖包都是存放在 GOPATH 下，没有版本控制。这个类似 Google 使用单一仓库来管理代码的方式。这种方式的最大的弊端就是无法实现包的多版本控制，比如项目 A 和项目 B 依赖于不同版本的 package，如果 package 没有做到完全的向前兼容，往往会导致一些问题。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e1.5 版本推出了 vendor 机制。所谓 vendor 机制，就是每个项目的根目录下可以有一个 vendor 目录，里面存放了该项目的依赖的 package。\u003ccode\u003ego build\u003c/code\u003e 的时候会先去 vendor 目录查找依赖，如果没有找到会再去 GOPATH 目录下查找。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e1.9 版本推出了实验性质的包管理工具 dep，这里把 dep 归结为 Golang 官方的包管理方式可能有一些不太准确。关于 dep 的争议颇多，比如为什么官方后来没有直接使用 dep 而是弄了一个新的 modules，具体细节这里不太方便展开。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e1.11 版本推出 modules 机制，简称 mod。modules 的原型其实是 vgo，关于 vgo，可以自行搜索。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e除此之外，社区也一直在有几个活跃的包管理工具，使用广泛且具有代表性的主要有下面几个：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003egodep\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eglide\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003egovendor\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"二-modules-的使用\"\u003e二 modules 的使用\u003c/h2\u003e\n\u003ch3 id=\"21-准备\"\u003e2.1 准备\u003c/h3\u003e\n\u003cp\u003eGolang 版本：1.12.3。在 1.12 版本之前，使用 Go modules 之前需要环境变量 GO111MODULE:\u003c/p\u003e","title":"【Go】包管理详解"},{"content":"\n变量是什么 变量指定了某存储单元（Memory Location）的名称，该存储单元会存储特定类型的值。在 Go 中，有多种语法用于声明变量。\n声明单个变量 var name type 是声明单个变量的语法。\npackage main import \u0026quot;fmt\u0026quot; func main() { var age int // 变量声明 fmt.Println(\u0026quot;my age is\u0026quot;, age) } 语句 var age int 声明了一个 int 类型的变量，名字为 age。我们还没有给该变量赋值。如果变量未被赋值，Go 会自动地将其初始化，赋值该变量类型的零值（Zero Value）。本例中 age 就被赋值为 0。如果你运行该程序，你会看到如下输出：\nmy age is 0 变量可以赋值为本类型的任何值。上一程序中的 age 可以赋值为任何整型值（Integer Value）。\npackage main import \u0026quot;fmt\u0026quot; func main() { var age int // 变量声明 fmt.Println(\u0026quot;my age is\u0026quot;, age) age = 29 // 赋值 fmt.Println(\u0026quot;my age is\u0026quot;, age) age = 54 // 赋值 fmt.Println(\u0026quot;my new age is\u0026quot;, age) } 上面的程序会有如下输出：\nmy age is 0 my age is 29 my new age is 54 声明变量并初始化 声明变量的同时可以给定初始值。 var name type = initialvalue 的语法用于声明变量并初始化。\npackage main import \u0026quot;fmt\u0026quot; func main() { var age int = 29 // 声明变量并初始化 fmt.Println(\u0026quot;my age is\u0026quot;, age) } 在上面的程序中，age 是具有初始值 29 的 int 类型变量。如果你运行上面的程序，你可以看见下面的输出，证实 age 已经被初始化为 29。\nmy age is 29 类型推断（Type Inference） 如果变量有初始值，那么 Go 能够自动推断具有初始值的变量的类型。因此，如果变量有初始值，就可以在变量声明中省略 type。\n如果变量声明的语法是 var name = initialvalue，Go 能够根据初始值自动推断变量的类型。\n在下面的例子中，你可以看到在第 6 行，我们省略了变量 age 的 int 类型，Go 依然推断出了它是 int 类型。\npackage main import \u0026quot;fmt\u0026quot; func main() { var age = 29 // 可以推断类型 fmt.Println(\u0026quot;my age is\u0026quot;, age) } 声明多个变量 Go 能够通过一条语句声明多个变量。\n声明多个变量的语法是 var name1, name2 type = initialvalue1, initialvalue2。\npackage main import \u0026quot;fmt\u0026quot; func main() { var width, height int = 100, 50 // 声明多个变量 fmt.Println(\u0026quot;width is\u0026quot;, width, \u0026quot;height is\u0026quot;, heigh) } 上述程序将在标准输出打印 width is 100 height is 50。\n你可能已经想到，如果 width 和 height 省略了初始化，它们的初始值将赋值为 0。\npackage main import \u0026quot;fmt\u0026quot; func main() { var width, height int fmt.Println(\u0026quot;width is\u0026quot;, width, \u0026quot;height is\u0026quot;, height) width = 100 height = 50 fmt.Println(\u0026quot;new width is\u0026quot;, width, \u0026quot;new height is \u0026quot;, height) } 上面的程序将会打印：\nwidth is 0 height is 0 new width is 100 new height is 50 在有些情况下，我们可能会想要在一个语句中声明不同类型的变量。其语法如下：\nvar ( name1 = initialvalue1, name2 = initialvalue2 ) 使用上述语法，下面的程序声明不同类型的变量。\npackage main import \u0026quot;fmt\u0026quot; func main() { var ( name = \u0026quot;naveen\u0026quot; age = 29 height int ) fmt.Println(\u0026quot;my name is\u0026quot;, name, \u0026quot;, age is\u0026quot;, age, \u0026quot;and height is\u0026quot;, height) } 这里我们声明了 string 类型的 name、int 类型的 age 和 height（我们将会在下一教程中讨论 golang 所支持的变量类型）。运行上面的程序会产生输出 my name is naveen , age is 29 and height is 0。\n简短声明 Go 也支持一种声明变量的简洁形式，称为简短声明（Short Hand Declaration），该声明使用了 := 操作符。\n声明变量的简短语法是 name := initialvalue。\npackage main import \u0026quot;fmt\u0026quot; func main() { name, age := \u0026quot;naveen\u0026quot;, 29 // 简短声明 fmt.Println(\u0026quot;my name is\u0026quot;, name, \u0026quot;age is\u0026quot;, age) } 运行上面的程序，可以看到输出为 my name is naveen age is 29。\n简短声明要求 := 操作符左边的所有变量都有初始值。下面程序将会抛出错误 cannot assign 1 values to 2 variables，这是因为 age 没有被赋值。\npackage main import \u0026quot;fmt\u0026quot; func main() { name, age := \u0026quot;naveen\u0026quot; //error fmt.Println(\u0026quot;my name is\u0026quot;, name, \u0026quot;age is\u0026quot;, age) } 简短声明的语法要求 := 操作符的左边至少有一个变量是尚未声明的。考虑下面的程序：\npackage main import \u0026quot;fmt\u0026quot; func main() { a, b := 20, 30 // 声明变量a和b fmt.Println(\u0026quot;a is\u0026quot;, a, \u0026quot;b is\u0026quot;, b) b, c := 40, 50 // b已经声明，但c尚未声明 fmt.Println(\u0026quot;b is\u0026quot;, b, \u0026quot;c is\u0026quot;, c) b, c = 80, 90 // 给已经声明的变量b和c赋新值 fmt.Println(\u0026quot;changed b is\u0026quot;, b, \u0026quot;c is\u0026quot;, c) } 在上面程序中的第 8 行，由于 b 已经被声明，而 c 尚未声明，因此运行成功并且输出：\na is 20 b is 30 b is 40 c is 50 changed b is 80 c is 90 但是如果我们运行下面的程序:\npackage main import \u0026quot;fmt\u0026quot; func main() { a, b := 20, 30 // 声明a和b fmt.Println(\u0026quot;a is\u0026quot;, a, \u0026quot;b is\u0026quot;, b) a, b := 40, 50 // 错误，没有尚未声明的变量 } 上面运行后会抛出 no new variables on left side of := 的错误，这是因为 a 和 b 的变量已经声明过了，:= 的左边并没有尚未声明的变量。\n变量也可以在运行时进行赋值。考虑下面的程序：\npackage main import ( \u0026quot;fmt\u0026quot; \u0026quot;math\u0026quot; ) func main() { a, b := 145.8, 543.8 c := math.Min(a, b) fmt.Println(\u0026quot;minimum value is \u0026quot;, c) } 在上面的程序中，c 的值是运行过程中计算得到的，即 a 和 b 的最小值。上述程序会打印：\nminimum value is 145.8 由于 Go 是强类型（Strongly Typed）语言，因此不允许某一类型的变量赋值为其他类型的值。下面的程序会抛出错误 cannot use \u0026quot;naveen\u0026quot; (type string) as type int in assignment，这是因为 age 本来声明为 int 类型，而我们却尝试给它赋字符串类型的值。\npackage main func main() { age := 29 // age是int类型 age = \u0026quot;naveen\u0026quot; // 错误，尝试赋值一个字符串给int类型变量 } END\n","permalink":"https://zhong-er.com/posts/go%E5%8F%98%E9%87%8F/","summary":"\u003cp\u003e\u003cimg alt=\"GO语言简明教程.png\" loading=\"lazy\" src=\"/assets/202009032723_3398_1.png\"\u003e\u003c/p\u003e\n\u003ch3 id=\"变量是什么\"\u003e变量是什么\u003c/h3\u003e\n\u003cp\u003e变量指定了某存储单元（Memory Location）的名称，该存储单元会存储特定类型的值。在 Go 中，有多种语法用于声明变量。\u003c/p\u003e\n\u003ch3 id=\"声明单个变量\"\u003e声明单个变量\u003c/h3\u003e\n\u003cp\u003evar name type 是声明单个变量的语法。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003epackage main\nimport \u0026quot;fmt\u0026quot;\nfunc main() {\n    var age int // 变量声明\n    fmt.Println(\u0026quot;my age is\u0026quot;, age)\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e语句 \u003ccode\u003evar age int\u003c/code\u003e 声明了一个 int 类型的变量，名字为 age。我们还没有给该变量赋值。如果变量未被赋值，Go 会自动地将其初始化，赋值该变量类型的零值（Zero Value）。本例中 age 就被赋值为 0。如果你运行该程序，你会看到如下输出：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003emy age is 0\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e变量可以赋值为本类型的任何值。上一程序中的 age 可以赋值为任何整型值（Integer Value）。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003epackage main\nimport \u0026quot;fmt\u0026quot;\nfunc main() {\n    var age int // 变量声明\n    fmt.Println(\u0026quot;my age is\u0026quot;, age)\n    age = 29 // 赋值\n    fmt.Println(\u0026quot;my age is\u0026quot;, age)\n    age = 54 // 赋值\n    fmt.Println(\u0026quot;my new age is\u0026quot;, age)\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e上面的程序会有如下输出：\u003c/p\u003e","title":"【Go】变量"},{"content":"\n定义 在 Go 语言中，术语\u0026quot;常量\u0026quot;用于表示固定的值。比如 5 、-89、 I love Go、67.89 等等。\n看看下面的代码:\nvar a int = 50 var b string = \u0026quot;I love Go\u0026quot; 在上面的代码中，变量 a 和 b 分别被赋值为常量 50 和 I love GO。关键字 const 被用于表示常量，比如 50 和 I love Go。即使在上面的代码中我们没有明确的使用关键字 const，但是在 Go 的内部，它们是常量。\n顾名思义，常量不能再重新赋值为其他的值。因此下面的程序将不能正常工作，它将出现一个编译错误: cannot assign to a.。\npackage main func main() { const a = 55 // 允许 a = 89 // 不允许重新赋值 } 常量的值会在编译的时候确定。因为函数调用发生在运行时，所以不能将函数的返回值赋值给常量。\npackage main import ( \u0026quot;fmt\u0026quot; \u0026quot;math\u0026quot; ) func main() { fmt.Println(\u0026quot;Hello, playground\u0026quot;) var a = math.Sqrt(4) // 允许 const b = math.Sqrt(4) // 不允许 } 在上面的程序中，因为 a 是变量，因此我们可以将函数 math.Sqrt(4) 的返回值赋值给它（我们将在单独的地方详细讨论函数）。\nb 是一个常量，它的值需要在编译的时候就确定。函数 math.Sqrt(4) 只会在运行的时候计算，因此 const b = math.Sqrt(4) 将会抛出错误 error main.go:11: const initializer math.Sqrt(4) is not a constant)\n字符串常量 双引号中的任何值都是 Go 中的字符串常量。例如像 Hello World 或 Sam 等字符串在 Go 中都是常量。\n什么类型的字符串属于常量？答案是他们是无类型的。\n像 Hello World 这样的字符串常量没有任何类型。\nconst hello = \u0026quot;Hello World\u0026quot; 上面的例子，我们把 Hello World 分配给常量 hello。现在常量 hello 有类型吗？答案是没有。常量仍然没有类型。\nGo 是一门强类型语言，所有的变量必须有明确的类型。那么, 下面的程序是如何将无类型的常量 Sam 赋值给变量 name 的呢？\npackage main import ( \u0026quot;fmt\u0026quot; ) func main() { var name = \u0026quot;Sam\u0026quot; fmt.Printf(\u0026quot;type %T value %v\u0026quot;, name, name) } 答案是无类型的常量有一个与它们相关联的默认类型，并且当且仅当一行代码需要时才提供它。在声明中 var name = \u0026ldquo;Sam\u0026rdquo; ， name 需要一个类型，它从字符串常量 Sam 的默认类型中获取。\n有没有办法创建一个带类型的常量？答案是可以的。以下代码创建一个有类型常量。\nconst typedhello string = \u0026quot;Hello World\u0026quot; 上面代码中， typedhello 就是一个 string 类型的常量。\nGo 是一个强类型的语言，在分配过程中混合类型是不允许的。让我们通过以下程序看看这句话是什么意思。\npackage main func main() { var defaultName = \u0026quot;Sam\u0026quot; // 允许 type myString string var customName myString = \u0026quot;Sam\u0026quot; // 允许 customName = defaultName // 不允许 } 在上面的代码中，我们首先创建一个变量 defaultName 并分配一个常量 Sam 。常量 Sam 的默认类型是 string ，所以在赋值后 defaultName 是 string 类型的。\n下一行，我们将创建一个新类型 myString，它是 string 的别名。\n然后我们创建一个 myString 的变量 customName 并且给他赋值一个常量 Sam 。因为常量 Sam 是无类型的，它可以分配给任何字符串变量。因此这个赋值是允许的，customName 的类型是 myString。\n现在，我们有一个类型为 string 的变量 defaultName 和另一个类型为 myString 的变量 customName。即使我们知道这个 myString 是 string 类型的别名。Go 的类型策略不允许将一种类型的变量赋值给另一种类型的变量。因此将 defaultName 赋值给 customName 是不允许的，编译器会抛出一个错误 main.go:7:20: cannot use defaultName (type string) as type myString in assignmen。\n布尔常量 布尔常量和字符串常量没有什么不同。他们是两个无类型的常量 true 和 false。字符串常量的规则适用于布尔常量，所以在这里我们不再重复。以下是解释布尔常量的简单程序。\npackage main func main() { const trueConst = true type myBool bool var defaultBool = trueConst // 允许 var customBool myBool = trueConst // 允许 defaultBool = customBool // 不允许 } 上面的程序是自我解释的。\n数字常量 数字常量包含整数、浮点数和复数的常量。数字常量中有一些微妙之处。\n让我们看一些例子来说清楚。\npackage main import ( \u0026quot;fmt\u0026quot; ) func main() { const a = 5 var intVar int = a var int32Var int32 = a var float64Var float64 = a var complex64Var complex64 = a fmt.Println(\u0026quot;intVar\u0026quot;,intVar, \u0026quot;\\nint32Var\u0026quot;, int32Var, \u0026quot;\\nfloat64Var\u0026quot;, float64Var, \u0026quot;\\ncomplex64Var\u0026quot;,complex64Var) } 上面的程序，常量 a 是没有类型的，它的值是 5 。您可能想知道 a 的默认类型是什么，如果它确实有一个的话, 那么我们如何将它分配给不同类型的变量。答案在于 a 的语法。下面的程序将使事情更加清晰。\npackage main import ( \u0026quot;fmt\u0026quot; ) func main() { var i = 5 var f = 5.6 var c = 5 + 6i fmt.Printf(\u0026quot;i's type %T, f's type %T, c's type %T\u0026quot;, i, f, c) } 在上面的程序中，每个变量的类型由数字常量的语法决定。5 在语法中是整数， 5.6 是浮点数，5+6i 的语法是复数。当我们运行上面的程序，它会打印出 i\u0026rsquo;s type int, f\u0026rsquo;s type float64, c\u0026rsquo;s type complex128。\n现在我希望下面的程序能够正确的工作。\npackage main import ( \u0026quot;fmt\u0026quot; ) func main() { const a = 5 var intVar int = a var int32Var int32 = a var float64Var float64 = a var complex64Var complex64 = a fmt.Println(\u0026quot;intVar\u0026quot;,intVar, \u0026quot;\\nint32Var\u0026quot;, int32Var, \u0026quot;\\nfloat64Var\u0026quot;, float64Var, \u0026quot;\\ncomplex64Var\u0026quot;,complex64Var) } 在这个程序中， a 的值是 5 ，a 的语法是通用的（它可以代表一个浮点数、整数甚至是一个没有虚部的复数），因此可以将其分配给任何兼容的类型。这些常量的默认类型可以被认为是根据上下文在运行中生成的。 var intVar int = a 要求 a 是 int，所以它变成一个 int 常量。 var complex64Var complex64 = a 要求 a 是 complex64，因此它变成一个复数类型。很简单的:)。\n数字表达式\n数字常量可以在表达式中自由混合和匹配，只有当它们被分配给变量或者在需要类型的代码中的任何地方使用时，才需要类型。\npackage main import ( \u0026quot;fmt\u0026quot;)func main() { var a = 5.9/8 fmt.Printf(\u0026quot;a's type %T value %v\u0026quot;,a, a) } 在上面的程序中， 5.9 在语法中是浮点型，8 是整型，5.9/8 是允许的，因为两个都是数字常量。除法的结果是 0.7375 是一个浮点型，所以 a 的类型是浮点型。这个程序的输出结果是: a\u0026rsquo;s type float64 value 0.7375。\nEND\n","permalink":"https://zhong-er.com/posts/go%E5%B8%B8%E9%87%8F/","summary":"\u003cp\u003e\u003cimg alt=\"GO语言简明教程.png\" loading=\"lazy\" src=\"/assets/202009032723_3398_1.png\"\u003e\u003c/p\u003e\n\u003ch3 id=\"定义\"\u003e定义\u003c/h3\u003e\n\u003cp\u003e在 Go 语言中，术语\u0026quot;常量\u0026quot;用于表示固定的值。比如 \u003ccode\u003e5\u003c/code\u003e 、\u003ccode\u003e-89\u003c/code\u003e、 \u003ccode\u003eI love Go\u003c/code\u003e、\u003ccode\u003e67.89\u003c/code\u003e 等等。\u003c/p\u003e\n\u003cp\u003e看看下面的代码:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003evar a int = 50  \nvar b string = \u0026quot;I love Go\u0026quot;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e在上面的代码中，变量 a 和 b 分别被赋值为常量 50 和 I love GO。关键字 \u003ccode\u003econst\u003c/code\u003e 被用于表示常量，比如 \u003ccode\u003e50\u003c/code\u003e 和 \u003ccode\u003eI love Go\u003c/code\u003e。即使在上面的代码中我们没有明确的使用关键字 \u003ccode\u003econst\u003c/code\u003e，但是在 Go 的内部，它们是常量。\u003c/p\u003e\n\u003cp\u003e顾名思义，常量不能再重新赋值为其他的值。因此下面的程序将不能正常工作，它将出现一个编译错误: \u003ccode\u003ecannot assign to a.\u003c/code\u003e。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003epackage main\nfunc main() {  \n    const a = 55 // 允许\n    a = 89       // 不允许重新赋值\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e常量的值会在编译的时候确定。因为函数调用发生在运行时，所以不能将函数的返回值赋值给常量。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003epackage main\nimport (  \n    \u0026quot;fmt\u0026quot;\n    \u0026quot;math\u0026quot;\n)\nfunc main() {  \n    fmt.Println(\u0026quot;Hello, playground\u0026quot;)\n    var a = math.Sqrt(4)   // 允许\n    const b = math.Sqrt(4) // 不允许\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e在上面的程序中，因为 \u003ccode\u003ea\u003c/code\u003e 是变量，因此我们可以将函数 \u003ccode\u003emath.Sqrt(4)\u003c/code\u003e 的返回值赋值给它（我们将在单独的地方详细讨论函数）。\u003c/p\u003e","title":"【Go】常量"},{"content":"\n函数是什么？ 函数是一块执行特定任务的代码。一个函数是在输入源基础上，通过执行一系列的算法，生成预期的输出。\n函数的声明 在 Go 语言中，函数声明通用语法如下：\nfunc functionname(parametername type) returntype { // 函数体（具体实现的功能） } 函数的声明以关键词 func 开始，后面紧跟自定义的函数名 functionname (函数名)。函数的参数列表定义在 ( 和 ) 之间，返回值的类型则定义在之后的 returntype (返回值类型)处。声明一个参数的语法采用 参数名 参数类型 的方式，任意多个参数采用类似 (parameter1 type, parameter2 type) 即(参数1 参数1的类型,参数2 参数2的类型)的形式指定。之后包含在 { 和 } 之间的代码，就是函数体。\n函数中的参数列表和返回值并非是必须的，所以下面这个函数的声明也是有效的\nfunc functionname() { // 译注: 表示这个函数不需要输入参数，且没有返回值 } 示例函数 我们以写一个计算商品价格的函数为例，输入参数是单件商品的价格和商品的个数，两者的乘积为商品总价，作为函数的输出值。\nfunc calculateBill(price int, no int) int { var totalPrice = price * no // 商品总价 = 商品单价 * 数量 return totalPrice // 返回总价 } 上述函数有两个整型的输入 price 和 no，返回值 totalPrice 为 price 和 no 的乘积，也是整数类型。\n如果有连续若干个参数，它们的类型一致，那么我们无须一一罗列，只需在最后一个参数后添加该类型。 例如，price int, no int 可以简写为 price, no int，所以示例函数也可写成\nfunc calculateBill(price, no int) int { var totalPrice = price * no return totalPrice } 现在我们已经定义了一个函数，我们要在代码中尝试着调用它。调用函数的语法为 functionname(parameters)。调用示例函数的方法如下：\ncalculateBill(10, 5) 完成了示例函数声明和调用后，我们就能写出一个完整的程序，并把商品总价打印在控制台上：\npackage main import ( \u0026quot;fmt\u0026quot; ) func calculateBill(price, no int) int { var totalPrice = price * no return totalPrice } func main() { price, no := 90, 6 // 定义 price 和 no,默认类型为 int totalPrice := calculateBill(price, no) fmt.Println(\u0026quot;Total price is\u0026quot;, totalPrice) // 打印到控制台上 } 该程序在控制台上打印的结果为\nTotal price is 540 多返回值 Go 语言支持一个函数可以有多个返回值。我们来写个以矩形的长和宽为输入参数，计算并返回矩形面积和周长的函数 rectProps。矩形的面积是长度和宽度的乘积, 周长是长度和宽度之和的两倍。即：\n面积 = 长 * 宽\n周长 = 2 * ( 长 + 宽 )\npackage main import ( \u0026ldquo;fmt\u0026rdquo; ) func rectProps(length, width float64)(float64, float64) { var area = length * width var perimeter = (length + width) * 2 return area, perimeter } func main() { area, perimeter := rectProps(10.8, 5.6) fmt.Printf(\u0026ldquo;Area %f Perimeter %f\u0026rdquo;, area, perimeter) }\n如果一个函数有多个返回值，那么这些返回值必须用 ( 和 ) 括起来。func rectProps(length, width float64)(float64, float64) 示例函数有两个 float64 类型的输入参数 length 和 width，并返回两个 float64 类型的值。该程序在控制台上打印结果为\nArea 60.480000 Perimeter 32.800000 命名返回值 从函数中可以返回一个命名值。一旦命名了返回值，可以认为这些值在函数第一行就被声明为变量了。\n上面的 rectProps 函数也可用这个方式写成：\nfunc rectProps(length, width float64)(area, perimeter float64) { area = length * width perimeter = (length + width) * 2 return // 不需要明确指定返回值，默认返回 area, perimeter 的值 } 请注意, 函数中的 return 语句没有显式返回任何值。由于 area 和 perimeter 在函数声明中指定为返回值, 因此当遇到 return 语句时, 它们将自动从函数返回。\n空白符 _ 在 Go 中被用作空白符，可以用作表示任何类型的任何值。\n我们继续以 rectProps 函数为例，该函数计算的是面积和周长。假使我们只需要计算面积，而并不关心周长的计算结果，该怎么调用这个函数呢？这时，空白符 _ 就上场了。\n下面的程序我们只用到了函数 rectProps 的一个返回值 area\npackage main import ( \u0026quot;fmt\u0026quot; ) func rectProps(length, width float64) (float64, float64) { var area = length * width var perimeter = (length + width) * 2 return area, perimeter } func main() { area, _ := rectProps(10.8, 5.6) // 返回值周长被丢弃 fmt.Printf(\u0026quot;Area %f \u0026quot;, area) } 在程序的 area, _ := rectProps(10.8, 5.6) 这一行，我们看到空白符 _ 用来跳过不要的计算结果。\nEND\n","permalink":"https://zhong-er.com/posts/go%E5%87%BD%E6%95%B0/","summary":"\u003cp\u003e\u003cimg alt=\"GO语言简明教程.png\" loading=\"lazy\" src=\"/assets/202009032723_3398_1.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"函数是什么\"\u003e函数是什么？\u003c/h2\u003e\n\u003cp\u003e函数是一块执行特定任务的代码。一个函数是在输入源基础上，通过执行一系列的算法，生成预期的输出。\u003c/p\u003e\n\u003ch2 id=\"函数的声明\"\u003e函数的声明\u003c/h2\u003e\n\u003cp\u003e在 Go 语言中，函数声明通用语法如下：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efunc functionname(parametername type) returntype {  \n    // 函数体（具体实现的功能）\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e函数的声明以关键词 \u003ccode\u003efunc\u003c/code\u003e 开始，后面紧跟自定义的函数名 \u003ccode\u003efunctionname (函数名)\u003c/code\u003e。函数的参数列表定义在 \u003ccode\u003e(\u003c/code\u003e 和 \u003ccode\u003e)\u003c/code\u003e 之间，返回值的类型则定义在之后的 \u003ccode\u003ereturntype (返回值类型)\u003c/code\u003e处。声明一个参数的语法采用 参数名 参数类型 的方式，任意多个参数采用类似 \u003ccode\u003e(parameter1 type, parameter2 type) 即(参数1 参数1的类型,参数2 参数2的类型)\u003c/code\u003e的形式指定。之后包含在 \u003ccode\u003e{\u003c/code\u003e 和 \u003ccode\u003e}\u003c/code\u003e 之间的代码，就是函数体。\u003c/p\u003e\n\u003cp\u003e函数中的参数列表和返回值并非是必须的，所以下面这个函数的声明也是有效的\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efunc functionname() {  \n    // 译注: 表示这个函数不需要输入参数，且没有返回值\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"示例函数\"\u003e示例函数\u003c/h2\u003e\n\u003cp\u003e我们以写一个计算商品价格的函数为例，输入参数是单件商品的价格和商品的个数，两者的乘积为商品总价，作为函数的输出值。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efunc calculateBill(price int, no int) int {  \n    var totalPrice = price * no // 商品总价 = 商品单价 * 数量\n    return totalPrice // 返回总价\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e上述函数有两个整型的输入 \u003ccode\u003eprice\u003c/code\u003e 和 \u003ccode\u003eno\u003c/code\u003e，返回值 \u003ccode\u003etotalPrice\u003c/code\u003e 为 \u003ccode\u003eprice\u003c/code\u003e 和 \u003ccode\u003eno\u003c/code\u003e 的乘积，也是整数类型。\u003c/p\u003e","title":"【Go】函数"},{"content":"\n一 下载地址 安装包下载地址为：https://golang.org/dl/。\n如果打不开可以使用这个地址：https://golang.google.cn/dl/。\n各个系统对应的包名：\n操作系统 包名 Windows go1.13.3.windows-amd64.msi Linux go1.13.3.linux-amd64.tar.gz Mac go1.13.3.darwin-amd64.pkg FreeBSD go1.13.3.freebsd-amd64.tar.gz 二 安装 Linux安装 1、下载二进制包：go1.13.3.linux-amd64.tar.gz\n2、将下载的二进制包解压至 /usr/local目录。\ntar -C /usr/local -xzf go1.13.3.linux-amd64.tar.gz 3、将 /usr/local/go/bin 目录添加至PATH环境变量：\nexport PATH=$PATH:/usr/local/go/bin Windows安装 Windows 下可以使用 .msi 后缀(在下载列表中可以找到该文件，如go1.13.3.windows-amd64.msi)的安装包来安装。\n默认情况下 .msi 文件会安装在 c:\\Go 目录下。你可以将 c:\\Go\\bin 目录添加到 Path 环境变量中。添加后你需要重启命令窗口才能生效\nMac安装 Mac下直接双击go1.13.3.darwin-amd64.pkg，一路下一步安装即可\n三 测试安装 1、创建工作目录 C:\u0026gt;Go_Project。\n2、 创建文件test.go,写入如下代码\npackage main import \u0026quot;fmt\u0026quot; func main() { fmt.Println(\u0026quot;Hello, World!\u0026quot;) } 3、打开cmd，切换目录到C:\u0026gt;Go_Project下，执行如下命令：\ngo run test.go 看到打印结果：\nHello, World! 四 命令介绍 基本介绍 直接在终端中输入 go help 即可显示所有的 go 命令以及相应命令功能简介，主要有下面这些:\nbuild: 编译包和依赖\nclean: 移除对象文件\ndoc: 显示包或者符号的文档\nenv: 打印go的环境信息\nbug: 启动错误报告\nfix: 运行go tool fix\nfmt: 运行gofmt进行格式化\ngenerate: 从processing source生成go文件\nget: 下载并安装包和依赖\ninstall: 编译并安装包和依赖\nlist: 列出包\nrun: 编译并运行go程序\ntest: 运行测试\ntool: 运行go提供的工具\nversion: 显示go的版本\nvet: 运行go tool vet\nbuild 和 run 命令 就像其他静态类型语言一样，要执行 go 程序,需要先编译，然后在执行产生的可执行文件。go build 命令就是用来编译 go程序生成可执行文件的。但并不是所以的 go 程序都可以编译生成可执行文件的, 要生成可执行文件，go程序需要满足两个条件:\n该go程序需要属于main包\n在main包中必须还得包含main函数\n也就是说go程序的入口就是 main.main, 即main包下的main函数, 例子(test.go):\n编译hello.go,然后运行可执行程序:\n$ go run test.go # 将会生成可执行文件 test $ ./test # 运行可执行文件 Hello, World! 上面就是 go build 的基本用法，另外如果使用 go build 编译的不是一个可执行程序，而是一个包，那么将不会生成可执行文件。\n而 go run 命令可以将上面两步并为一步执行(不会产生中间文件)。\n$ go run test.go Hello, World! 上面两个命令都是在开发中非常常用的。\n此外 go clean 命令，可以用于将清除产生的可执行程序:\n$ go clean # 不加参数，可以删除当前目录下的所有可执行文件 $ go clean hello.go # 会删除对应的可执行文件 get 命令 这个命令同样也是很常用的，我们可以使用它来下载并安装第三方包, 使用方式:\ngo get src 从指定源上面下载或者更新指定的代码和依赖，并对他们进行编译和安装，例如我们想使用 beego 来开发web应用，我们首先就需要获取 beego:\ngo get github.com/astaxie/beego 这条命令将会自动下载安装 beego 以及它的依赖，然后我们就可以使用下面的方式使用:\npackage main import \u0026quot;github.com/astaxie/beego\u0026quot; # 这里需要使用 src 下的完整路径 func main() { beego.Run() } END\n","permalink":"https://zhong-er.com/posts/go%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/","summary":"\u003cp\u003e\u003cimg alt=\"GO语言简明教程.png\" loading=\"lazy\" src=\"/assets/202009032723_3398_1.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"一-下载地址\"\u003e一 下载地址\u003c/h2\u003e\n\u003cp\u003e安装包下载地址为：\u003ca href=\"https://golang.org/dl/\"\u003ehttps://golang.org/dl/\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e如果打不开可以使用这个地址：\u003ca href=\"https://golang.google.cn/dl/\"\u003ehttps://golang.google.cn/dl/\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e各个系统对应的包名：\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e操作系统\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e包名\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eWindows\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ego1.13.3.windows-amd64.msi\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eLinux\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ego1.13.3.linux-amd64.tar.gz\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eMac\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ego1.13.3.darwin-amd64.pkg\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eFreeBSD\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ego1.13.3.freebsd-amd64.tar.gz\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e\u003cimg alt=\"dimzn.jpg\" loading=\"lazy\" src=\"/assets/201912096658_1985.jpg-zhonger.com\"\u003e\u003c/p\u003e\n\u003ch2 id=\"二-安装\"\u003e二 安装\u003c/h2\u003e\n\u003ch3 id=\"linux安装\"\u003eLinux安装\u003c/h3\u003e\n\u003cp\u003e1、下载二进制包：go1.13.3.linux-amd64.tar.gz\u003c/p\u003e\n\u003cp\u003e2、将下载的二进制包解压至 /usr/local目录。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003etar -C /usr/local -xzf go1.13.3.linux-amd64.tar.gz\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e3、将 /usr/local/go/bin 目录添加至PATH环境变量：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eexport PATH=$PATH:/usr/local/go/bin\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"windows安装\"\u003eWindows安装\u003c/h3\u003e\n\u003cp\u003eWindows 下可以使用 .msi 后缀(在下载列表中可以找到该文件，如go1.13.3.windows-amd64.msi)的安装包来安装。\u003c/p\u003e\n\u003cp\u003e默认情况下 .msi 文件会安装在 c:\\Go 目录下。你可以将 c:\\Go\\bin 目录添加到 Path 环境变量中。添加后你需要重启命令窗口才能生效\u003c/p\u003e\n\u003ch3 id=\"mac安装\"\u003eMac安装\u003c/h3\u003e\n\u003cp\u003eMac下直接双击go1.13.3.darwin-amd64.pkg，一路下一步安装即可\u003c/p\u003e\n\u003ch2 id=\"三-测试安装\"\u003e三 测试安装\u003c/h2\u003e\n\u003cp\u003e1、创建工作目录 C:\u0026gt;Go_Project。\u003c/p\u003e\n\u003cp\u003e2、 创建文件test.go,写入如下代码\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003epackage main\nimport \u0026quot;fmt\u0026quot;\nfunc main() {\n   fmt.Println(\u0026quot;Hello, World!\u0026quot;)\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e3、打开cmd，切换目录到C:\u0026gt;Go_Project下，执行如下命令：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ego run test.go\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e看到打印结果：\u003c/p\u003e","title":"【Go】开发环境搭建"},{"content":"\n类型 下面是 Go 支持的基本类型：\nbool\n数字类型\nint8, int16, int32, int64, int\nuint8, uint16, uint32, uint64, uint\nfloat32, float64\ncomplex64, complex128\nbyte\nrune\nstring\nbool bool 类型表示一个布尔值，值为 true 或者 false。\npackage main import \u0026quot;fmt\u0026quot; func main() { a := true b := false fmt.Println(\u0026quot;a:\u0026quot;, a, \u0026quot;b:\u0026quot;, b) c := a \u0026amp;\u0026amp; b fmt.Println(\u0026quot;c:\u0026quot;, c) d := a || b fmt.Println(\u0026quot;d:\u0026quot;, d) } 在上面的程序中，a 赋值为 true，b 赋值为 false。\nc 赋值为 a \u0026amp;\u0026amp; b。仅当 a 和 b 都为 true 时，操作符 \u0026amp;\u0026amp; 才返回 true。因此，在这里 c 为 false。\n当 a 或者 b 为 true 时，操作符 || 返回 true。在这里，由于 a 为 true，因此 d 也为 true。我们将得到程序的输出如下。\na: true b: false c: false d: true 有符号整型 int8：表示 8 位有符号整型\n大小：8 位\n范围：-128～127\nint16：表示 16 位有符号整型\n大小：16 位\n范围：-32768～32767\nint32：表示 32 位有符号整型\n大小：32 位\n范围：-2147483648～2147483647\nint64：表示 64 位有符号整型\n大小：64 位\n范围：-9223372036854775808～9223372036854775807\nint：根据不同的底层平台（Underlying Platform），表示 32 或 64 位整型。除非对整型的大小有特定的需求，否则你通常应该使用 int 表示整型。\n大小：在 32 位系统下是 32 位，而在 64 位系统下是 64 位。\n范围：在 32 位系统下是 -2147483648～2147483647，而在 64 位系统是 -9223372036854775808～9223372036854775807。\npackage main import \u0026quot;fmt\u0026quot; func main() { var a int = 89 b := 95 fmt.Println(\u0026quot;value of a is\u0026quot;, a, \u0026quot;and b is\u0026quot;, b) } 在线运行程序\n上面程序会输出 value of a is 89 and b is 95。\n在上述程序中，a 是 int 类型，而 b 的类型通过赋值（95）推断得出。上面我们提到，int 类型的大小在 32 位系统下是 32 位，而在 64 位系统下是 64 位。接下来我们会证实这种说法。\n在 Printf 方法中，使用 %T 格式说明符（Format Specifier），可以打印出变量的类型。Go 的 unsafe 包提供了一个 Sizeof 函数，该函数接收变量并返回它的字节大小。 unsafe 包应该小心使用，因为使用 unsafe 包可能会带来可移植性问题。不过出于本教程的目的，我们是可以使用的。\n下面程序会输出变量 a 和 b 的类型和大小。格式说明符 %T 用于打印类型，而 %d 用于打印字节大小。\npackage main import ( \u0026quot;fmt\u0026quot; \u0026quot;unsafe\u0026quot; ) func main() { var a int = 89 b := 95 fmt.Println(\u0026quot;value of a is\u0026quot;, a, \u0026quot;and b is\u0026quot;, b) fmt.Printf(\u0026quot;type of a is %T, size of a is %d\u0026quot;, a, unsafe.Sizeof(a)) // a 的类型和大小 fmt.Printf(\u0026quot;\\ntype of b is %T, size of b is %d\u0026quot;, b, unsafe.Sizeof(b)) // b 的类型和大小 } 在线运行程序\n以上程序会输出：\nvalue of a is 89 and b is 95 type of a is int, size of a is 4 type of b is int, size of b is 4 从上面的输出，我们可以推断出 a 和 b 为 int 类型，且大小都是 32 位（4 字节）。如果你在 64 位系统上运行上面的代码，会有不同的输出。在 64 位系统下，a 和 b 会占用 64 位（8 字节）的大小。\n无符号整型 uint8：表示 8 位无符号整型\n大小：8 位\n范围：0～255\nuint16：表示 16 位无符号整型\n大小：16 位\n范围：0～65535\nuint32：表示 32 位无符号整型\n大小：32 位\n范围：0～4294967295\nuint64：表示 64 位无符号整型\n大小：64 位\n范围：0～18446744073709551615\nuint：根据不同的底层平台，表示 32 或 64 位无符号整型。\n大小：在 32 位系统下是 32 位，而在 64 位系统下是 64 位。\n范围：在 32 位系统下是 0～4294967295，而在 64 位系统是 0～18446744073709551615。\n浮点型 float32：32 位浮点数\nfloat64：64 位浮点数\n下面一个简单程序演示了整型和浮点型的运用。\npackage main import ( \u0026quot;fmt\u0026quot; ) func main() { a, b := 5.67, 8.97 fmt.Printf(\u0026quot;type of a %T b %T\\n\u0026quot;, a, b) sum := a + b diff := a - b fmt.Println(\u0026quot;sum\u0026quot;, sum, \u0026quot;diff\u0026quot;, diff) no1, no2 := 56, 89 fmt.Println(\u0026quot;sum\u0026quot;, no1+no2, \u0026quot;diff\u0026quot;, no1-no2) } a 和 b 的类型根据赋值推断得出。在这里，a 和 b 的类型为 float64（float64 是浮点数的默认类型）。我们把 a 和 b 的和赋值给变量 sum，把 b 和 a 的差赋值给 diff，接下来打印 sum 和 diff。no1 和 no2 也进行了相同的计算。上述程序将会输出：\ntype of a float64 b float64 sum 14.64 diff -3.3000000000000007 sum 145 diff -33 复数类型 complex64：实部和虚部都是 float32 类型的的复数。\ncomplex128：实部和虚部都是 float64 类型的的复数。\n内建函数 complex用于创建一个包含实部和虚部的复数。complex 函数的定义如下：\nfunc complex(r, i FloatType) ComplexType 该函数的参数分别是实部和虚部，并返回一个复数类型。实部和虚部应该是相同类型，也就是 float32 或 float64。如果实部和虚部都是 float32 类型，则函数会返回一个 complex64 类型的复数。如果实部和虚部都是 float64 类型，则函数会返回一个 complex128 类型的复数。\n还可以使用简短语法来创建复数：\nc := 6 + 7i 下面我们编写一个简单的程序来理解复数。\npackage main import ( \u0026quot;fmt\u0026quot; ) func main() { c1 := complex(5, 7) c2 := 8 + 27i cadd := c1 + c2 fmt.Println(\u0026quot;sum:\u0026quot;, cadd) cmul := c1 * c2 fmt.Println(\u0026quot;product:\u0026quot;, cmul) } 在上面的程序里，c1 和 c2 是两个复数。c1的实部为 5，虚部为 7。c2 的实部为8，虚部为 27。c1 和 c2 的和赋值给 cadd ，而 c1 和 c2 的乘积赋值给 cmul。该程序将输出：\nsum: (13+34i) product: (-149+191i) 其他数字类型 byte 是 uint8 的别名。\nrune 是 int32 的别名。\n在学习字符串的时候，我们会详细讨论 byte 和 rune。\nstring 类型 在 Golang 中，字符串是字节的集合。如果你现在还不理解这个定义，也没有关系。我们可以暂且认为一个字符串就是由很多字符组成的。我们后面会在一个教程中深入学习字符串。 下面编写一个使用字符串的程序。\npackage main import ( \u0026quot;fmt\u0026quot; ) func main() { first := \u0026quot;Naveen\u0026quot; last := \u0026quot;Ramanathan\u0026quot; name := first +\u0026quot; \u0026quot;+ last fmt.Println(\u0026quot;My name is\u0026quot;,name) } 上面程序中，first 赋值为字符串 \u0026ldquo;Naveen\u0026rdquo;，last 赋值为字符串 \u0026ldquo;Ramanathan\u0026rdquo;。+ 操作符可以用于拼接字符串。我们拼接了 first、空格和 last，并将其赋值给 name。上述程序将打印输出 My name is Naveen Ramanathan。\n还有许多应用于字符串上面的操作，我们将会在一个单独的教程里看见它们。\n类型转换 Go 有着非常严格的强类型特征。Go 没有自动类型提升或类型转换。我们通过一个例子说明这意味着什么。\npackage main import ( \u0026quot;fmt\u0026quot; ) func main() { i := 55 //int j := 67.8 //float64 sum := i + j //不允许 int + float64 fmt.Println(sum) } 上面的代码在 C 语言中是完全合法的，然而在 Go 中，却是行不通的。i 的类型是 int ，而 j 的类型是 float64 ，我们正试图把两个不同类型的数相加，Go 不允许这样的操作。如果运行程序，你会得到 main.go:10: invalid operation: i + j (mismatched types int and float64)。\n要修复这个错误，i 和 j 应该是相同的类型。在这里，我们把 j 转换为 int 类型。把 v 转换为 T 类型的语法是 T(v)。\npackage main import ( \u0026quot;fmt\u0026quot; ) func main() { i := 55 //int j := 67.8 //float64 sum := i + int(j) //j is converted to int fmt.Println(sum) } 现在，当你运行上面的程序时，会看见输出 122。\n赋值的情况也是如此。把一个变量赋值给另一个不同类型的变量，需要显式的类型转换。下面程序说明了这一点。\npackage main import ( \u0026quot;fmt\u0026quot; ) func main() { i := 10 var j float64 = float64(i) // 若没有显式转换，该语句会报错 fmt.Println(\u0026quot;j\u0026quot;, j) } 在第 9 行，i 转换为 float64 类型，接下来赋值给 j。如果不进行类型转换，当你试图把 i 赋值给 j 时，编译器会抛出错误。\nEND\n","permalink":"https://zhong-er.com/posts/go%E7%B1%BB%E5%9E%8B/","summary":"\u003cp\u003e\u003cimg alt=\"GO语言简明教程.png\" loading=\"lazy\" src=\"/assets/202009032723_3398_1.png\"\u003e\u003c/p\u003e\n\u003ch1 id=\"类型\"\u003e类型\u003c/h1\u003e\n\u003cp\u003e下面是 Go 支持的基本类型：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003ebool\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e数字类型\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eint8, int16, int32, int64, int\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003euint8, uint16, uint32, uint64, uint\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003efloat32, float64\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ecomplex64, complex128\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ebyte\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003erune\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003estring\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"bool\"\u003ebool\u003c/h3\u003e\n\u003cp\u003ebool 类型表示一个布尔值，值为 true 或者 false。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003epackage main\nimport \u0026quot;fmt\u0026quot;\nfunc main() {  \n    a := true\n    b := false\n    fmt.Println(\u0026quot;a:\u0026quot;, a, \u0026quot;b:\u0026quot;, b)\n    c := a \u0026amp;\u0026amp; b\n    fmt.Println(\u0026quot;c:\u0026quot;, c)\n    d := a || b\n    fmt.Println(\u0026quot;d:\u0026quot;, d)\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e在上面的程序中，a 赋值为 true，b 赋值为 false。\u003c/p\u003e\n\u003cp\u003ec 赋值为 a \u0026amp;\u0026amp; b。仅当 a 和 b 都为 true 时，操作符 \u0026amp;\u0026amp; 才返回 true。因此，在这里 c 为 false。\u003c/p\u003e","title":"【Go】类型"},{"content":"\nGo语言介绍 Go 即Golang，是Google公司2009年11月正式对外公开的一门编程语言。\nGo是静态强类型语言，是区别于解析型语言的编译型语言。\n解析型语言——源代码是先翻译为中间代码，然后由解析器对代码进行解释执行。\n编译型语言——源代码编译生成机器语言，然后由机器直接执行机器码即可执行。\nGo语言特性 跨平台的编译型语言\n语法接近C语言\n管道（channel），切片（slice），并发（routine）\n有垃圾回收的机制\n支持面向对象和面向过程的编程模式\nGo语言发展（版本/特性） 2009年11月7日 weekly.2009-11-06 —— 早期的版本\n2012年3月28日 go1 —— 稳定版版的发布\n2013年5月14日 go1.1 —— 语言本身的变化、库的变化等。\n2013年12月01日 go1.2 —— 提供更好的调度和一个新的语言特性。\n2014年6月18日 go1.3 —— 主要是在实现的工作上的变化，以及精准的垃圾回收。\n2014年12月10日 go1.4 —— 包含了2个很小的语言变更。\n2015年8月19日 go1.5 —— 实现的架构变化，同时保留了和旧版本的兼容性。\n2016年2月7日 go1.6 —— 语言本身、运行时、和库的实现的变化。\n2016年8月15日 go1.7 —— 标准库有显着的性能改进和变化。\n2017年2月26日 go1.8 —— 标准库有显着的性能改进和变化。\n2017年8月24日 go1.9 —— 是包含语言，标准库，以及运行时的诸多变化。这个版本中的大部分工作都是对运行时和工具的改进。\n2018年2月16日 go1.10 —— 大部分更改都在工具链，运行时和库的实现中。\n2018年8月24日 go1.11 —— modules和WebAssembly支持。\nGo语言应用 谁在用 Google 这个不用多做介绍，作为开发Go语言的公司，当仁不让。Google基于Go有很多优秀的项目，比如：https://github.com/kubernetes/kubernetes ，大家也可以在Github上 https://github.com/google/ 查看更多Google的Go开源项目。\nFacebook Facebook也在用，为此他们还专门在Github上建立了一个开源组织facebookgo，大家可以通过https://github.com/facebookgo访问查看facebook开源的项目，比如著名的是平滑升级的grace。\n腾讯 腾讯作为国内的大公司，还是敢于尝试的，尤其是Docker容器化这一块，他们在15年已经做了docker万台规模的实践，具体可以参考http://www.infoq.com/cn/articles/tencent-millions-scale-docker-application-practice 。\n主要职责是：\n负责腾讯游戏蓝鲸平台后台开发工作\n负责容器相关的开发工作\n和蓝鲸平台，容器开发有关。腾讯作为主要使用C/C++的公司，使用Go会方便很多，也有很多优势，不过日积月累的C/C++代码很难改造，也不敢动，所以新业务会在Go方面尝试。\n百度 目前所知的百度的使用是在运维这边，是百度运维的一个BFE项目，负责前端流量的接入。他们的负责人在2016年有分享，大家可以看下这个 http://www.infoq.com/cn/presentations/application-of-golang-in-baidu-frontend .\n其次就是百度的消息系统，从其最近的Golang招聘介绍就可以看出来.\n负责公司手百消息通讯系统服务器端开发及维护\n京东 京东云消息推送系统、云存储，以及京东商城等都有使用Go做开发。\n小米 小米对Golang的支持，莫过于运维监控系统的开源，也就是 http://open-falcon.com/ 。\n此外，小米互娱、小米商城、小米视频、小米生态链等团队都在使用Golang。\n360 360对Golang的使用也不少，一个是开源的日志搜索系统Poseidon，托管在Github上，https://github.com/Qihoo360/poseidon.\n还有360的推送团队也在使用，他们还写了篇博文在Golang的官方博客上 https://blog.golang.org/qihoo。\n360直播在招聘Golang开发工程师。\n美团、滴滴、新浪以及七牛等。一般的选择，都是选择用于自己公司合适的产品系统来做，比如消息推送的、监控的、容器的等，Golang特别适合做网络并发的服务，这是他的强项，所以也是被优先用于这些项目。\n应用领域 服务开发、并发、分布式、微服务等方向\nGo语言项目 docker:基于lxc的一个虚拟打包工具，能够实现PAAS平台的组建。\nkubernetes ：Kubernetes是Google开源的一个容器编排引擎，它支持自动化部署、大规模可伸缩、应用容器化管理。在生产环境中部署一个应用程序时，通常要部署该应用的多个实例以便对应用请求进行负载均衡\n区块链：BTCD 是用go语言实现的完整节点的比特币实现\nnsq：bitly开源的消息队列系统，性能非常高，目前他们每天处理数十亿条的消息\npacker:用来生成不同平台的镜像文件，例如VM、vbox、AWS等，作者是vagrant的作者\nskynet：分布式调度框架\nDoozer：分布式同步工具，类似ZooKeeper\nHeka：mazila开源的日志处理系统\ncbfs：couchbase开源的分布式文件系统\ntsuru：开源的PAAS平台，和SAE实现的功能一模一样\ngroupcache：memcahe作者写的用于Google下载系统的缓存系统\ngod：类似redis的缓存系统，但是支持分布式和扩展性\ngor：网络流量抓包和重放工具\nGo语架构 GOPATH的工作区包含bin，src，和pkg这三个：\nsrc——源码（包含第三方的和自己项目的）\nbin——编译生成的可执行程序\npkg——编译时生成的对象文件\nGo语言发展前景 Go语言将登上语言榜前列，与C和Java并列前三甲\nGo语言称霸云计算\nGo将统治下一个10年\nEND\n","permalink":"https://zhong-er.com/posts/go%E8%AF%AD%E8%A8%80%E4%BB%8B%E7%BB%8D/","summary":"\u003cp\u003e\u003cimg alt=\"GO语言简明教程.png\" loading=\"lazy\" src=\"/assets/202009032723_3398_1.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"go语言介绍\"\u003eGo语言介绍\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"golang.jpg\" loading=\"lazy\" src=\"/assets/201912095736_191.jpg-zhonger.com\"\u003e\u003c/p\u003e\n\u003cp\u003eGo 即Golang，是Google公司2009年11月正式对外公开的一门编程语言。\u003c/p\u003e\n\u003cp\u003eGo是静态强类型语言，是区别于解析型语言的编译型语言。\u003c/p\u003e\n\u003cp\u003e解析型语言——源代码是先翻译为中间代码，然后由解析器对代码进行解释执行。\u003c/p\u003e\n\u003cp\u003e编译型语言——源代码编译生成机器语言，然后由机器直接执行机器码即可执行。\u003c/p\u003e\n\u003ch2 id=\"go语言特性\"\u003eGo语言特性\u003c/h2\u003e\n\u003cp\u003e跨平台的编译型语言\u003c/p\u003e\n\u003cp\u003e语法接近C语言\u003c/p\u003e\n\u003cp\u003e管道（channel），切片（slice），并发（routine）\u003c/p\u003e\n\u003cp\u003e有垃圾回收的机制\u003c/p\u003e\n\u003cp\u003e支持面向对象和面向过程的编程模式\u003c/p\u003e\n\u003ch2 id=\"go语言发展版本特性\"\u003eGo语言发展（版本/特性）\u003c/h2\u003e\n\u003cp\u003e2009年11月7日 weekly.2009-11-06 —— 早期的版本\u003c/p\u003e\n\u003cp\u003e2012年3月28日 go1 —— 稳定版版的发布\u003c/p\u003e\n\u003cp\u003e2013年5月14日 go1.1 —— 语言本身的变化、库的变化等。\u003c/p\u003e\n\u003cp\u003e2013年12月01日 go1.2 —— 提供更好的调度和一个新的语言特性。\u003c/p\u003e\n\u003cp\u003e2014年6月18日 go1.3 —— 主要是在实现的工作上的变化，以及精准的垃圾回收。\u003c/p\u003e\n\u003cp\u003e2014年12月10日 go1.4 —— 包含了2个很小的语言变更。\u003c/p\u003e\n\u003cp\u003e2015年8月19日 go1.5 —— 实现的架构变化，同时保留了和旧版本的兼容性。\u003c/p\u003e\n\u003cp\u003e2016年2月7日 go1.6 —— 语言本身、运行时、和库的实现的变化。\u003c/p\u003e\n\u003cp\u003e2016年8月15日 go1.7 —— 标准库有显着的性能改进和变化。\u003c/p\u003e\n\u003cp\u003e2017年2月26日 go1.8 —— 标准库有显着的性能改进和变化。\u003c/p\u003e\n\u003cp\u003e2017年8月24日 go1.9 —— 是包含语言，标准库，以及运行时的诸多变化。这个版本中的大部分工作都是对运行时和工具的改进。\u003c/p\u003e\n\u003cp\u003e2018年2月16日 go1.10 —— 大部分更改都在工具链，运行时和库的实现中。\u003c/p\u003e\n\u003cp\u003e2018年8月24日 go1.11 —— modules和WebAssembly支持。\u003c/p\u003e\n\u003ch2 id=\"go语言应用\"\u003eGo语言应用\u003c/h2\u003e\n\u003ch3 id=\"谁在用\"\u003e谁在用\u003c/h3\u003e\n\u003ch4 id=\"google\"\u003eGoogle\u003c/h4\u003e\n\u003cp\u003e这个不用多做介绍，作为开发Go语言的公司，当仁不让。Google基于Go有很多优秀的项目，比如：\u003ca href=\"https://github.com/kubernetes/kubernetes\"\u003ehttps://github.com/kubernetes/kubernetes\u003c/a\u003e ，大家也可以在Github上 \u003ca href=\"https://github.com/google/\"\u003ehttps://github.com/google/\u003c/a\u003e 查看更多Google的Go开源项目。\u003c/p\u003e","title":"【Go】语言介绍"},{"content":"\n一 简介 MongoDB是一款强大、灵活、且易于扩展的通用型数据库\n1、易用性\nMongoDB是一个面向文档（document-oriented）的数据库，而不是关系型数据库。 不采用关系型主要是为了获得更好得扩展性。当然还有一些其他好处，与关系数据库相比，面向文档的数据库不再有“行“（row）的概念取而代之的是更为灵活的“文档”（document）模型。 通过在文档中嵌入文档和数组，面向文档的方法能够仅使用一条记录来表现复杂的层级关系，这与现代的面向对象语言的开发者对数据的看法一致。 另外，不再有预定义模式（predefined schema）：文档的键（key）和值（value）不再是固定的类型和大小。由于没有固定的模式，根据需要添加或删除字段变得更容易了。通常由于开发者能够进行快速迭代，所以开发进程得以加快。而且，实验更容易进行。开发者能尝试大量的数据模型，从中选一个最好的。 2、易扩展性\n应用程序数据集的大小正在以不可思议的速度增长。随着可用带宽的增长和存储器价格的下降，即使是一个小规模的应用程序，需要存储的数据量也可能大的惊人，甚至超出 了很多数据库的处理能力。过去非常罕见的T级数据，现在已经是司空见惯了。 由于需要存储的数据量不断增长，开发者面临一个问题：应该如何扩展数据库，分为纵向扩展和横向扩展，纵向扩展是最省力的做法，但缺点是大型机一般都非常贵，而且 当数据量达到机器的物理极限时，花再多的钱也买不到更强的机器了，此时选择横向扩展更为合适，但横向扩展带来的另外一个问题就是需要管理的机器太多。 MongoDB的设计采用横向扩展。面向文档的数据模型使它能很容易地在多台服务器之间进行数据分割。MongoDB能够自动处理跨集群的数据和负载，自动重新分配文档，以及将 用户的请求路由到正确的机器上。这样，开发者能够集中精力编写应用程序，而不需要考虑如何扩展的问题。如果一个集群需要更大的容量，只需要向集群添加新服务器，MongoDB就会自动将现有的数据向新服务器传送 3、丰富的功能\nMongoDB作为一款通用型数据库，除了能够创建、读取、更新和删除数据之外，还提供了一系列不断扩展的独特功能 #1、索引 支持通用二级索引，允许多种快速查询，且提供唯一索引、复合索引、地理空间索引、全文索引 #2、聚合 支持聚合管道，用户能通过简单的片段创建复杂的集合，并通过数据库自动优化 #3、特殊的集合类型 支持存在时间有限的集合，适用于那些将在某个时刻过期的数据，如会话session。类似地，MongoDB也支持固定大小的集合，用于保存近期数据，如日志 #4、文件存储 支持一种非常易用的协议，用于存储大文件和文件元数据。MongoDB并不具备一些在关系型数据库中很普遍的功能，如链接join和复杂的多行事务。省略 这些的功能是处于架构上的考虑，或者说为了得到更好的扩展性，因为在分布式系统中这两个功能难以高效地实现 4、卓越的性能\nMongoDB的一个主要目标是提供卓越的性能，这很大程度上决定了MongoDB的设计。MongoDB把尽可能多的内存用作缓存cache，视图为每次查询自动选择正确的索引。 总之各方面的设计都旨在保持它的高性能 虽然MongoDB非常强大并试图保留关系型数据库的很多特性，但它并不追求具备关系型数据库的所有功能。只要有可能，数据库服务器就会将处理逻辑交给客户端。这种精简方式的设计是MongoDB能够实现如此高性能的原因之一 二 MongoDB基础知识 1、文档是MongoDB的核心概念。文档就是键值对的一个有序集{\u0026lsquo;msg\u0026rsquo;:\u0026lsquo;hello\u0026rsquo;,\u0026lsquo;foo\u0026rsquo;:3}。类似于python中的有序字典。\n需要注意的是： #1、文档中的键/值对是有序的。 #2、文档中的值不仅可以是在双引号里面的字符串，还可以是其他几种数据类型（甚至可以是整个嵌入的文档)。 #3、MongoDB区分类型和大小写。 #4、MongoDB的文档不能有重复的键。 #5、文档中的值可以是多种不同的数据类型，也可以是一个完整的内嵌文档。文档的键是字符串。除了少数例外情况，键可以使用任意UTF-8字符。 文档键命名规范： #1、键不能含有\\0 (空字符)。这个字符用来表示键的结尾。 #2、.和$有特别的意义，只有在特定环境下才能使用。 #3、以下划线\u0026quot;_\u0026quot;开头的键是保留的(不是严格要求的)。 2、集合就是一组文档。如果将MongoDB中的一个文档比喻为关系型数据的一行，那么一个集合就是相当于一张表\n#1、集合存在于数据库中，通常情况下为了方便管理，不同格式和类型的数据应该插入到不同的集合，但其实集合没有固定的结构，这意味着我们完全可以把不同格式和类型的数据统统插入一个集合中。 #2、组织子集合的方式就是使用“.”，分隔不同命名空间的子集合。 比如一个具有博客功能的应用可能包含两个集合，分别是blog.posts和blog.authors，这是为了使组织结构更清晰，这里的blog集合（这个集合甚至不需要存在）跟它的两个子集合没有任何关系。 在MongoDB中，使用子集合来组织数据非常高效，值得推荐 #3、当第一个文档插入时，集合就会被创建。合法的集合名： 集合名不能是空字符串\u0026quot;\u0026quot;。 集合名不能含有\\0字符（空字符)，这个字符表示集合名的结尾。 集合名不能以\u0026quot;system.\u0026quot;开头，这是为系统集合保留的前缀。 用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含，这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合，否则千万不要在名字里出现$。 3、数据库：在MongoDB中，多个文档组成集合，多个集合可以组成数据库\n数据库也通过名字来标识。数据库名可以是满足以下条件的任意UTF-8字符串： #1、不能是空字符串（\u0026quot;\u0026quot;)。 #2、不得含有' '（空格)、.、$、/、\\和\\0 (空字符)。 #3、应全部小写。 #4、最多64字节。 有一些数据库名是保留的，可以直接访问这些有特殊作用的数据库。 #1、admin： 从身份认证的角度讲，这是“root”数据库，如果将一个用户添加到admin数据库，这个用户将自动获得所有数据库的权限。再者，一些特定的服务器端命令也只能从admin数据库运行，如列出所有数据库或关闭服务器 #2、local: 这个数据库永远都不可以复制，且一台服务器上的所有本地集合都可以存储在这个数据库中 #3、config: MongoDB用于分片设置时，分片信息会存储在config数据库中 4、强调：把数据库名添加到集合名前，得到集合的完全限定名，即命名空间\n例如： 如果要使用cms数据库中的blog.posts集合，这个集合的命名空间就是 cmd.blog.posts。命名空间的长度不得超过121个字节，且在实际使用中应该小于100个字节 三 安装 1、安装\n#安装配置 #1、安装路径为D:\\MongoDB，将D:\\MongoDB\\bin目录加入环境变量 #2、新建目录与文件 D:\\MongoDB\\data\\db D:\\MongoDB\\log\\mongod.log #3、新建配置文件mongod.cfg,参考：https://docs.mongodb.com/manual/reference/configuration-options/ systemLog: destination: file path: \u0026quot;D:\\MongoDB\\log\\mongod.log\u0026quot; logAppend: true storage: journal: enabled: true dbPath: \u0026quot;D:\\MongoDB\\data\\db\u0026quot; net: bindIp: 0.0.0.0 port: 27017 setParameter: enableLocalhostAuthBypass: false #4、制作系统服务 mongod --config \u0026quot;D:\\MongoDB\\mongod.cfg\u0026quot; --bind_ip 0.0.0.0 --install 或者直接在命令行指定配置 mongod --bind_ip 0.0.0.0 --port 27017 --logpath D:\\MongoDB\\log\\mongod.log --logappend --dbpath D:\\MongoDB\\data\\db --serviceName \u0026quot;MongoDB\u0026quot; --serviceDisplayName \u0026quot;MongoDB\u0026quot; --install #5、启动\\关闭 net start MongoDB net stop MongoDB #6、登录 mongo 链接：http://www.runoob.com/mongodb/mongodb-window-install.html 2、账号管理\n#创建账号密码+开启认证机制 #账号管理：https://docs.mongodb.com/master/tutorial/enable-authentication/ #1、创建账号 use admin db.createUser( { user: \u0026quot;root\u0026quot;, pwd: \u0026quot;123\u0026quot;, roles: [ { role: \u0026quot;root\u0026quot;, db: \u0026quot;admin\u0026quot; } ] } ) use test db.createUser( { user: \u0026quot;egon\u0026quot;, pwd: \u0026quot;123\u0026quot;, roles: [ { role: \u0026quot;readWrite\u0026quot;, db: \u0026quot;test\u0026quot; }, { role: \u0026quot;read\u0026quot;, db: \u0026quot;db1\u0026quot; } ] } ) #2、重启数据库 mongod --remove mongod --config \u0026quot;C:\\mongodb\\mongod.cfg\u0026quot; --bind_ip 0.0.0.0 --install --auth #3、登录：注意使用双引号而非单引号 mongo --port 27017 -u \u0026quot;root\u0026quot; -p \u0026quot;123\u0026quot; --authenticationDatabase \u0026quot;admin\u0026quot; 也可以在登录之后用db.auth(\u0026quot;账号\u0026quot;,\u0026quot;密码\u0026quot;)登录 mongo use admin db.auth(\u0026quot;root\u0026quot;,\u0026quot;123\u0026quot;) #推荐博客:https://www.cnblogs.com/zhoujinyi/p/4610050.html 3、命令行shell\n#1、mongo 127.0.0.1:27017/config #连接到任何数据库config #2、mongo --nodb #不连接到任何数据库 #3、启动之后，在需要时运行new Mongo(hostname)命令就可以连接到想要的mongod了： \u0026gt; conn=new Mongo('127.0.0.1:27017') connection to 127.0.0.1:27017 \u0026gt; db=conn.getDB('admin') admin #4、help查看帮助 #5、mongo时一个简化的JavaScript shell，是可以执行JavaScript脚本的 四 基本数据类型 1、在概念上，MongoDB的文档与Javascript的对象相近，因而可以认为它类似于JSON。JSON（http://www.json.org）是一种简单的数据表示方式：其规范仅用一段文字就能描述清楚（其官网证明了这点），且仅包含六种数据类型。\n2、这样有很多好处：易于理解、易于解析、易于记忆。然而从另一方面说，因为只有null、布尔、数字、字符串、数字和对象这几种数据类型，所以JSON的表达能力有一定的局限。\n3、虽然JSON具备的这些类型已经具有很强的表现力，但绝大数应用（尤其是在于数据库打交道时）都还需要其他一些重要的类型。例如，JSON没有日期类型，这使得原本容易日期处理变得烦人。另外，JSON只有一种数字类型，无法区分浮点数和整数，更别区分32位和64位了。再者JSON无法表示其他一些通用类型，如正则表达式或函数。\n4、MongoDB在保留了JSON基本键/值对特性的基础上，添加了其他一些数据类型。在不同的编程语言下，这些类型的确切表示有些许差异。下面说明了MongoDB支持的其他通用类型，以及如何正在文档中使用它们\n#1、null：用于表示空或不存在的字段 d={'x':null} #2、布尔型：true和false d={'x':true,'y':false} #3、数值 d={'x':3,'y':3.1415926} #4、字符串 d={'x':'egon'} #5、日期 d={'x':new Date()} d.x.getHours() #6、正则表达式 d={'pattern':/^egon.*?nb$/i} 正则写在／／内，后面的i代表: i 忽略大小写 m 多行匹配模式 x 忽略非转义的空白字符 s 单行匹配模式 #7、数组 d={'x':[1,'a','v']} #8、内嵌文档 user={'name':'egon','addr':{'country':'China','city':'YT'}} user.addr.country #9、对象id:是一个12字节的ID,是文档的唯一标识，不可变 d={'x':ObjectId()} 5、_id和ObjectId\nMongoDB中存储的文档必须有一个\u0026quot;_id\u0026quot;键。这个键的值可以是任意类型，默认是个ObjectId对象。 在一个集合里，每个文档都有唯一的“_id”,确保集合里每个文档都能被唯一标识。 不同集合\u0026quot;_id\u0026quot;的值可以重复，但同一集合内\u0026quot;_id\u0026quot;的值必须唯一 #1、ObjectId ObjectId是\u0026quot;_id\u0026quot;的默认类型。因为设计MongoDb的初衷就是用作分布式数据库，所以能够在分片环境中生成 唯一的标识符非常重要，而常规的做法：在多个服务器上同步自动增加主键既费时又费力，这就是MongoDB采用 ObjectId的原因。 ObjectId采用12字节的存储空间，是一个由24个十六进制数字组成的字符串 0|1|2|3| 4|5|6| 7|8 9|10|11 时间戳 机器 PID 计数器 如果快速创建多个ObjectId，会发现每次只有最后几位有变化。另外，中间的几位数字也会变化（要是在创建过程中停顿几秒）。 这是ObjectId的创建方式导致的，如上图 时间戳单位为秒，与随后5个字节组合起来，提供了秒级的唯一性。这个4个字节隐藏了文档的创建时间，绝大多数驱动程序都会提供 一个方法，用于从ObjectId中获取这些信息。 因为使用的是当前时间，很多用户担心要对服务器进行时钟同步。其实没必要，因为时间戳的实际值并不重要，只要它总是不停增加就好。 接下来3个字节是所在主机的唯一标识符。通常是机器主机名的散列值。这样就可以保证不同主机生成不同的ObjectId，不产生冲突 接下来连个字节确保了在同一台机器上并发的多个进程产生的ObjectId是唯一的 前9个字节确保了同一秒钟不同机器不同进程产生的ObjectId是唯一的。最后3个字节是一个自动增加的 计数器。确保相同进程的同一秒产生的 ObjectId也是不一样的。 #2、自动生成_id 如果插入文档时没有\u0026quot;_id\u0026quot;键，系统会自帮你创建 一个。可以由MongoDb服务器来做这件事。 但通常会在客户端由驱动程序完成。这一做法非常好地体现了MongoDb的哲学：能交给客户端驱动程序来做的事情就不要交给服务器来做。 这种理念背后的原因是：即便是像MongoDB这样扩展性非常好的数据库，扩展应用层也要比扩展数据库层容易的多。将工作交给客户端做就 减轻了数据库扩展的负担。 五 CRUD操作 1、数据库操作\n#1、增 use config #如果数据库不存在，则创建数据库，否则切换到指定数据库。 #2、查 show dbs #查看所有 可以看到，我们刚创建的数据库config并不在数据库的列表中， 要显示它，我们需要向config数据库插入一些数据。 db.table1.insert({'a':1}) #3、删 use config #先切换到要删的库下 db.dropDatabase() #删除当前库 2、集合操作\n#1、增 当第一个文档插入时，集合就会被创建 \u0026gt; use database1 switched to db database1 \u0026gt; db.table1.insert({'a':1}) WriteResult({ \u0026quot;nInserted\u0026quot; : 1 }) \u0026gt; db.table2.insert({'b':2}) WriteResult({ \u0026quot;nInserted\u0026quot; : 1 }) #2、查 \u0026gt; show tables table1 table2 #3、删 \u0026gt; db.table1.drop() true \u0026gt; show tables table2 3、文档操作\n增\n#单条插入与多条插入 #1、没有指定_id则默认ObjectId,_id不能重复，且在插入后不可变 #2、插入单条 user0={ \u0026quot;name\u0026quot;:\u0026quot;egon\u0026quot;, \u0026quot;age\u0026quot;:10, 'hobbies':['music','read','dancing'], 'addr':{ 'country':'China', 'city':'BJ' } } db.test.insert(user0) db.test.find() #3、插入多条 user1={ \u0026quot;_id\u0026quot;:1, \u0026quot;name\u0026quot;:\u0026quot;alex\u0026quot;, \u0026quot;age\u0026quot;:10, 'hobbies':['music','read','dancing'], 'addr':{ 'country':'China', 'city':'weifang' } } user2={ \u0026quot;_id\u0026quot;:2, \u0026quot;name\u0026quot;:\u0026quot;wupeiqi\u0026quot;, \u0026quot;age\u0026quot;:20, 'hobbies':['music','read','run'], 'addr':{ 'country':'China', 'city':'hebei' } } user3={ \u0026quot;_id\u0026quot;:3, \u0026quot;name\u0026quot;:\u0026quot;yuanhao\u0026quot;, \u0026quot;age\u0026quot;:30, 'hobbies':['music','drink'], 'addr':{ 'country':'China', 'city':'heibei' } } user4={ \u0026quot;_id\u0026quot;:4, \u0026quot;name\u0026quot;:\u0026quot;jingliyang\u0026quot;, \u0026quot;age\u0026quot;:40, 'hobbies':['music','read','dancing','tea'], 'addr':{ 'country':'China', 'city':'BJ' } } user5={ \u0026quot;_id\u0026quot;:5, \u0026quot;name\u0026quot;:\u0026quot;jinxin\u0026quot;, \u0026quot;age\u0026quot;:50, 'hobbies':['music','read',], 'addr':{ 'country':'China', 'city':'henan' } } db.user.insertMany([user1,user2,user3,user4,user5]) 查\n#比较运算 # SQL：=,!=,\u0026gt;,\u0026lt;,\u0026gt;=,\u0026lt;= # MongoDB：{key:value}代表什么等于什么,\u0026quot;$ne\u0026quot;,\u0026quot;$gt\u0026quot;,\u0026quot;$lt\u0026quot;,\u0026quot;gte\u0026quot;,\u0026quot;lte\u0026quot;,其中\u0026quot;$ne\u0026quot;能用于所有数据类型 #1、select * from db1.user where name = \u0026quot;alex\u0026quot;; db.user.find({'name':'alex'}) #2、select * from db1.user where name != \u0026quot;alex\u0026quot;; db.user.find({'name':{\u0026quot;$ne\u0026quot;:'alex'}}) #3、select * from db1.user where id \u0026gt; 2; db.user.find({'_id':{'$gt':2}}) #4、select * from db1.user where id \u0026lt; 3; db.user.find({'_id':{'$lt':3}}) #5、select * from db1.user where id \u0026gt;= 2; db.user.find({\u0026quot;_id\u0026quot;:{\u0026quot;$gte\u0026quot;:2,}}) #6、select * from db1.user where id \u0026lt;= 2; db.user.find({\u0026quot;_id\u0026quot;:{\u0026quot;$lte\u0026quot;:2}}) #逻辑运算 # SQL：and，or，not # MongoDB：字典中逗号分隔的多个条件是and关系，\u0026quot;$or\u0026quot;的条件放到[]内,\u0026quot;$not\u0026quot; #1、select * from db1.user where id \u0026gt;= 2 and id \u0026lt; 4; db.user.find({'_id':{\u0026quot;$gte\u0026quot;:2,\u0026quot;$lt\u0026quot;:4}}) #2、select * from db1.user where id \u0026gt;= 2 and age \u0026lt; 40; db.user.find({\u0026quot;_id\u0026quot;:{\u0026quot;$gte\u0026quot;:2},\u0026quot;age\u0026quot;:{\u0026quot;$lt\u0026quot;:40}}) #3、select * from db1.user where id \u0026gt;= 5 or name = \u0026quot;alex\u0026quot;; db.user.find({ \u0026quot;$or\u0026quot;:[ {'_id':{\u0026quot;$gte\u0026quot;:5}}, {\u0026quot;name\u0026quot;:\u0026quot;alex\u0026quot;} ] }) #4、select * from db1.user where id % 2=1; db.user.find({'_id':{\u0026quot;$mod\u0026quot;:[2,1]}}) #5、上题，取反 db.user.find({'_id':{\u0026quot;$not\u0026quot;:{\u0026quot;$mod\u0026quot;:[2,1]}}}) #成员运算 # SQL：in，not in # MongoDB：\u0026quot;$in\u0026quot;,\u0026quot;$nin\u0026quot; #1、select * from db1.user where age in (20,30,31); db.user.find({\u0026quot;age\u0026quot;:{\u0026quot;$in\u0026quot;:[20,30,31]}}) #2、select * from db1.user where name not in ('alex','yuanhao'); db.user.find({\u0026quot;name\u0026quot;:{\u0026quot;$nin\u0026quot;:['alex','yuanhao']}}) #正则匹配 # SQL: regexp 正则 # MongoDB: /正则表达/i #1、select * from db1.user where name regexp '^j.*?(g|n)$'; db.user.find({'name':/^j.*?(g|n)$/i}) #取指定的字段 #1、select name,age from db1.user where id=3; db.user.find({'_id':3},{'_id':0,'name':1,'age':1}) #查询数组 #1、查看有dancing爱好的人 db.user.find({'hobbies':'dancing'}) #2、查看既有dancing爱好又有tea爱好的人 db.user.find({ 'hobbies':{ \u0026quot;$all\u0026quot;:['dancing','tea'] } }) #3、查看第4个爱好为tea的人 db.user.find({\u0026quot;hobbies.3\u0026quot;:'tea'}) #4、查看所有人最后两个爱好 db.user.find({},{'hobbies':{\u0026quot;$slice\u0026quot;:-2},\u0026quot;age\u0026quot;:0,\u0026quot;_id\u0026quot;:0,\u0026quot;name\u0026quot;:0,\u0026quot;addr\u0026quot;:0}) #5、查看所有人的第2个到第3个爱好 db.user.find({},{'hobbies':{\u0026quot;$slice\u0026quot;:[1,2]},\u0026quot;age\u0026quot;:0,\u0026quot;_id\u0026quot;:0,\u0026quot;name\u0026quot;:0,\u0026quot;addr\u0026quot;:0}) \u0026gt; db.blog.find().pretty() { \u0026quot;_id\u0026quot; : 1, \u0026quot;name\u0026quot; : \u0026quot;alex意外死亡的真相\u0026quot;, \u0026quot;comments\u0026quot; : [ { \u0026quot;name\u0026quot; : \u0026quot;egon\u0026quot;, \u0026quot;content\u0026quot; : \u0026quot;alex是谁？？？\u0026quot;, \u0026quot;thumb\u0026quot; : 200 }, { \u0026quot;name\u0026quot; : \u0026quot;wxx\u0026quot;, \u0026quot;content\u0026quot; : \u0026quot;我去，真的假的\u0026quot;, \u0026quot;thumb\u0026quot; : 300 }, { \u0026quot;name\u0026quot; : \u0026quot;yxx\u0026quot;, \u0026quot;content\u0026quot; : \u0026quot;吃喝嫖赌抽，欠下两个亿\u0026quot;, \u0026quot;thumb\u0026quot; : 40 }, { \u0026quot;name\u0026quot; : \u0026quot;egon\u0026quot;, \u0026quot;content\u0026quot; : \u0026quot;xxx\u0026quot;, \u0026quot;thumb\u0026quot; : 0 } ] } db.blog.find({},{'comments':{\u0026quot;$slice\u0026quot;:-2}}).pretty() #查询最后两个 db.blog.find({},{'comments':{\u0026quot;$slice\u0026quot;:[1,2]}}).pretty() #查询1到2 # 排序:--1代表升序，-1代表降序 db.user.find().sort({\u0026quot;name\u0026quot;:1,}) db.user.find().sort({\u0026quot;age\u0026quot;:-1,'_id':1}) # 分页:--limit代表取多少个document，skip代表跳过前多少个document。 db.user.find().sort({'age':1}).limit(1).skip(2) # 获取数量 db.user.count({'age':{\u0026quot;$gt\u0026quot;:30}}) --或者 db.user.find({'age':{\u0026quot;$gt\u0026quot;:30}}).count() #杂项 #1、{'key':null} 匹配key的值为null或者没有这个key db.t2.insert({'a':10,'b':111}) db.t2.insert({'a':20}) db.t2.insert({'b':null}) \u0026gt; db.t2.find({\u0026quot;b\u0026quot;:null}) { \u0026quot;_id\u0026quot; : ObjectId(\u0026quot;5a5cc2a7c1b4645aad959e5a\u0026quot;), \u0026quot;a\u0026quot; : 20 } { \u0026quot;_id\u0026quot; : ObjectId(\u0026quot;5a5cc2a8c1b4645aad959e5b\u0026quot;), \u0026quot;b\u0026quot; : null } #2、查找所有 db.user.find() #等同于db.user.find({}) db.user.find().pretty() #3、查找一个，与find用法一致，只是只取匹配成功的第一个 db.user.findOne({\u0026quot;_id\u0026quot;:{\u0026quot;$gt\u0026quot;:3}}) 改\n#update语法介绍 update() 方法用于更新已存在的文档。语法格式如下： db.collection.update( \u0026lt;query\u0026gt;, \u0026lt;update\u0026gt;, { upsert: \u0026lt;boolean\u0026gt;, multi: \u0026lt;boolean\u0026gt;, writeConcern: \u0026lt;document\u0026gt; } ) 参数说明：对比update db1.t1 set name='EGON',sex='Male' where name='egon' and age=18; query : 相当于where条件。 update : update的对象和一些更新的操作符（如$,$inc...等，相当于set后面的 upsert : 可选，默认为false，代表如果不存在update的记录不更新也不插入，设置为true代表插入。 multi : 可选，默认为false，代表只更新找到的第一条记录，设为true,代表更新找到的全部记录。 writeConcern :可选，抛出异常的级别。 更新操作是不可分割的：若两个更新同时发送，先到达服务器的先执行，然后执行另外一个，不会破坏文档。 #覆盖式 #注意：除非是删除，否则_id是始终不会变的 #1、覆盖式: db.user.update({'age':20},{\u0026quot;name\u0026quot;:\u0026quot;Wxx\u0026quot;,\u0026quot;hobbies_count\u0026quot;:3}) 是用{\u0026quot;_id\u0026quot;:2,\u0026quot;name\u0026quot;:\u0026quot;Wxx\u0026quot;,\u0026quot;hobbies_count\u0026quot;:3}覆盖原来的记录 #2、一种最简单的更新就是用一个新的文档完全替换匹配的文档。这适用于大规模式迁移的情况。例如 var obj=db.user.findOne({\u0026quot;_id\u0026quot;:2}) obj.username=obj.name+'SB' obj.hobbies_count++ delete obj.age db.user.update({\u0026quot;_id\u0026quot;:2},obj) #设置：$set 通常文档只会有一部分需要更新。可以使用原子性的更新修改器，指定对文档中的某些字段进行更新。 更新修改器是种特殊的键，用来指定复杂的更新操作，比如修改、增加后者删除 #1、update db1.user set name=\u0026quot;WXX\u0026quot; where id = 2 db.user.update({'_id':2},{\u0026quot;$set\u0026quot;:{\u0026quot;name\u0026quot;:\u0026quot;WXX\u0026quot;,}}) #2、没有匹配成功则新增一条{\u0026quot;upsert\u0026quot;:true} db.user.update({'_id':6},{\u0026quot;$set\u0026quot;:{\u0026quot;name\u0026quot;:\u0026quot;egon\u0026quot;,\u0026quot;age\u0026quot;:18}},{\u0026quot;upsert\u0026quot;:true}) #3、默认只改匹配成功的第一条,{\u0026quot;multi\u0026quot;:改多条} db.user.update({'_id':{\u0026quot;$gt\u0026quot;:4}},{\u0026quot;$set\u0026quot;:{\u0026quot;age\u0026quot;:28}}) db.user.update({'_id':{\u0026quot;$gt\u0026quot;:4}},{\u0026quot;$set\u0026quot;:{\u0026quot;age\u0026quot;:38}},{\u0026quot;multi\u0026quot;:true}) #4、修改内嵌文档，把名字为alex的人所在的地址国家改成Japan db.user.update({'name':\u0026quot;alex\u0026quot;},{\u0026quot;$set\u0026quot;:{\u0026quot;addr.country\u0026quot;:\u0026quot;Japan\u0026quot;}}) #5、把名字为alex的人的地2个爱好改成piao db.user.update({'name':\u0026quot;alex\u0026quot;},{\u0026quot;$set\u0026quot;:{\u0026quot;hobbies.1\u0026quot;:\u0026quot;piao\u0026quot;}}) #6、删除alex的爱好,$unset db.user.update({'name':\u0026quot;alex\u0026quot;},{\u0026quot;$unset\u0026quot;:{\u0026quot;hobbies\u0026quot;:\u0026quot;\u0026quot;}}) #增加和减少：$inc #1、所有人年龄增加一岁 db.user.update({}, { \u0026quot;$inc\u0026quot;:{\u0026quot;age\u0026quot;:1} }, { \u0026quot;multi\u0026quot;:true } ) #2、所有人年龄减少5岁 db.user.update({}, { \u0026quot;$inc\u0026quot;:{\u0026quot;age\u0026quot;:-5} }, { \u0026quot;multi\u0026quot;:true } ) #添加删除数组内元素：push,\u0026quot; role=\u0026quot;presentation\u0026quot;\u0026gt;push,push,push,pop,$pull 往数组内添加元素:$push #1、为名字为yuanhao的人添加一个爱好read db.user.update({\u0026quot;name\u0026quot;:\u0026quot;yuanhao\u0026quot;},{\u0026quot;$push\u0026quot;:{\u0026quot;hobbies\u0026quot;:\u0026quot;read\u0026quot;}}) #2、为名字为yuanhao的人一次添加多个爱好tea，dancing db.user.update({\u0026quot;name\u0026quot;:\u0026quot;yuanhao\u0026quot;},{\u0026quot;$push\u0026quot;:{ \u0026quot;hobbies\u0026quot;:{\u0026quot;$each\u0026quot;:[\u0026quot;tea\u0026quot;,\u0026quot;dancing\u0026quot;]} }}) 按照位置且只能从开头或结尾删除元素：$pop #3、{\u0026quot;$pop\u0026quot;:{\u0026quot;key\u0026quot;:1}} 从数组末尾删除一个元素 db.user.update({\u0026quot;name\u0026quot;:\u0026quot;yuanhao\u0026quot;},{\u0026quot;$pop\u0026quot;:{ \u0026quot;hobbies\u0026quot;:1} }) #4、{\u0026quot;$pop\u0026quot;:{\u0026quot;key\u0026quot;:-1}} 从头部删除 db.user.update({\u0026quot;name\u0026quot;:\u0026quot;yuanhao\u0026quot;},{\u0026quot;$pop\u0026quot;:{ \u0026quot;hobbies\u0026quot;:-1} }) #5、按照条件删除元素,：\u0026quot;$pull\u0026quot; 把符合条件的统统删掉，而$pop只能从两端删 db.user.update({'addr.country':\u0026quot;China\u0026quot;},{\u0026quot;$pull\u0026quot;:{ \u0026quot;hobbies\u0026quot;:\u0026quot;read\u0026quot;} }, { \u0026quot;multi\u0026quot;:true } ) #避免添加重复：\u0026quot;$addToSet\u0026quot; db.urls.insert({\u0026quot;_id\u0026quot;:1,\u0026quot;urls\u0026quot;:[]}) db.urls.update({\u0026quot;_id\u0026quot;:1},{\u0026quot;$addToSet\u0026quot;:{\u0026quot;urls\u0026quot;:'http://www.baidu.com'}}) db.urls.update({\u0026quot;_id\u0026quot;:1},{\u0026quot;$addToSet\u0026quot;:{\u0026quot;urls\u0026quot;:'http://www.baidu.com'}}) db.urls.update({\u0026quot;_id\u0026quot;:1},{\u0026quot;$addToSet\u0026quot;:{\u0026quot;urls\u0026quot;:'http://www.baidu.com'}}) db.urls.update({\u0026quot;_id\u0026quot;:1},{ \u0026quot;$addToSet\u0026quot;:{ \u0026quot;urls\u0026quot;:{ \u0026quot;$each\u0026quot;:[ 'http://www.baidu.com', 'http://www.baidu.com', 'http://www.xxxx.com' ] } } } ) #其他 #1、了解：限制大小\u0026quot;$slice\u0026quot;，只留最后n个 db.user.update({\u0026quot;_id\u0026quot;:5},{ \u0026quot;$push\u0026quot;:{\u0026quot;hobbies\u0026quot;:{ \u0026quot;$each\u0026quot;:[\u0026quot;read\u0026quot;,'music','dancing'], \u0026quot;$slice\u0026quot;:-2 } } }) #2、了解：排序The $sort element value must be either 1 or -1\u0026quot; db.user.update({\u0026quot;_id\u0026quot;:5},{ \u0026quot;$push\u0026quot;:{\u0026quot;hobbies\u0026quot;:{ \u0026quot;$each\u0026quot;:[\u0026quot;read\u0026quot;,'music','dancing'], \u0026quot;$slice\u0026quot;:-1, \u0026quot;$sort\u0026quot;:-1 } } }) #注意：不能只将\u0026quot;$slice\u0026quot;或者\u0026quot;$sort\u0026quot;与\u0026quot;$push\u0026quot;配合使用，且必须使用\u0026quot;$eah\u0026quot; 删\n#1、删除多个中的第一个 db.user.deleteOne({ 'age': 8 }) #2、删除国家为China的全部 db.user.deleteMany( {'addr.country': 'China'} ) #3、删除全部 db.user.deleteMany({}) 4、聚合\n如果你有数据存储在MongoDB中，你想做的可能就不仅仅是将数据提取出来那么简单了；你可能希望对数据进行分析并加以利用。MongoDB提供了以下聚合工具： #1、聚合框架 #2、MapReduce(详见MongoDB权威指南) #3、几个简单聚合命令：count、distinct和group。(详见MongoDB权威指南) #聚合框架： 可以使用多个构件创建一个管道，上一个构件的结果传给下一个构件。 这些构件包括（括号内为构件对应的操作符）：筛选($match)、投射($project)、分组($group)、排序($sort)、限制($limit)、跳过($skip) 不同的管道操作符可以任意组合，重复使用 # 准备数据 from pymongo import MongoClient import datetime client=MongoClient('mongodb://root:123@localhost:27017') table=client['db1']['emp'] # table.drop() l=[ ('egon','male',18,'20170301','老男孩驻沙河办事处外交大使',7300.33,401,1), #以下是教学部 ('alex','male',78,'20150302','teacher',1000000.31,401,1), ('wupeiqi','male',81,'20130305','teacher',8300,401,1), ('yuanhao','male',73,'20140701','teacher',3500,401,1), ('liwenzhou','male',28,'20121101','teacher',2100,401,1), ('jingliyang','female',18,'20110211','teacher',9000,401,1), ('jinxin','male',18,'19000301','teacher',30000,401,1), ('成龙','male',48,'20101111','teacher',10000,401,1), ('歪歪','female',48,'20150311','sale',3000.13,402,2),#以下是销售部门 ('丫丫','female',38,'20101101','sale',2000.35,402,2), ('丁丁','female',18,'20110312','sale',1000.37,402,2), ('星星','female',18,'20160513','sale',3000.29,402,2), ('格格','female',28,'20170127','sale',4000.33,402,2), ('张野','male',28,'20160311','operation',10000.13,403,3), #以下是运营部门 ('程咬金','male',18,'19970312','operation',20000,403,3), ('程咬银','female',18,'20130311','operation',19000,403,3), ('程咬铜','male',18,'20150411','operation',18000,403,3), ('程咬铁','female',18,'20140512','operation',17000,403,3) ] for n,item in enumerate(l): d={ \u0026quot;_id\u0026quot;:n, 'name':item[0], 'sex':item[1], 'age':item[2], 'hire_date':datetime.datetime.strptime(item[3],'%Y%m%d'), 'post':item[4], 'salary':item[5] } table.save(d) # 筛选：$match {\u0026quot;$match\u0026quot;:{\u0026quot;字段\u0026quot;:\u0026quot;条件\u0026quot;}},可以使用任何常用查询操作符$gt,$lt,$in等 #例1、select * from db1.emp where post='teacher'; db.emp.aggregate({\u0026quot;$match\u0026quot;:{\u0026quot;post\u0026quot;:\u0026quot;teacher\u0026quot;}}) #例2、select * from db1.emp where id \u0026gt; 3 group by post; db.emp.aggregate( {\u0026quot;$match\u0026quot;:{\u0026quot;_id\u0026quot;:{\u0026quot;$gt\u0026quot;:3}}}, {\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,'avg_salary':{\u0026quot;$avg\u0026quot;:\u0026quot;$salary\u0026quot;}}} ) #例3、select * from db1.emp where id \u0026gt; 3 group by post having avg(salary) \u0026gt; 10000; db.emp.aggregate( {\u0026quot;$match\u0026quot;:{\u0026quot;_id\u0026quot;:{\u0026quot;$gt\u0026quot;:3}}}, {\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,'avg_salary':{\u0026quot;$avg\u0026quot;:\u0026quot;$salary\u0026quot;}}}, {\u0026quot;$match\u0026quot;:{\u0026quot;avg_salary\u0026quot;:{\u0026quot;$gt\u0026quot;:10000}}} ) # 投射：$project {\u0026quot;$project\u0026quot;:{\u0026quot;要保留的字段名\u0026quot;:1,\u0026quot;要去掉的字段名\u0026quot;:0,\u0026quot;新增的字段名\u0026quot;:\u0026quot;表达式\u0026quot;}} #1、select name,post,(age+1) as new_age from db1.emp; db.emp.aggregate( {\u0026quot;$project\u0026quot;:{ \u0026quot;name\u0026quot;:1, \u0026quot;post\u0026quot;:1, \u0026quot;new_age\u0026quot;:{\u0026quot;$add\u0026quot;:[\u0026quot;$age\u0026quot;,1]} } }) #2、表达式之数学表达式 {\u0026quot;$add\u0026quot;:[expr1,expr2,...,exprN]} #相加 {\u0026quot;$subtract\u0026quot;:[expr1,expr2]} #第一个减第二个 {\u0026quot;$multiply\u0026quot;:[expr1,expr2,...,exprN]} #相乘 {\u0026quot;$divide\u0026quot;:[expr1,expr2]} #第一个表达式除以第二个表达式的商作为结果 {\u0026quot;$mod\u0026quot;:[expr1,expr2]} #第一个表达式除以第二个表达式得到的余数作为结果 #3、表达式之日期表达式:$year,$month,$week,$dayOfMonth,$dayOfWeek,$dayOfYear,$hour,$minute,$second #例如：select name,date_format(\u0026quot;%Y\u0026quot;) as hire_year from db1.emp db.emp.aggregate( {\u0026quot;$project\u0026quot;:{\u0026quot;name\u0026quot;:1,\u0026quot;hire_year\u0026quot;:{\u0026quot;$year\u0026quot;:\u0026quot;$hire_date\u0026quot;}}} ) #例如查看每个员工的工作多长时间 db.emp.aggregate( {\u0026quot;$project\u0026quot;:{\u0026quot;name\u0026quot;:1,\u0026quot;hire_period\u0026quot;:{ \u0026quot;$subtract\u0026quot;:[ {\u0026quot;$year\u0026quot;:new Date()}, {\u0026quot;$year\u0026quot;:\u0026quot;$hire_date\u0026quot;} ] }}} ) #4、字符串表达式 {\u0026quot;$substr\u0026quot;:[字符串/$值为字符串的字段名,起始位置,截取几个字节]} {\u0026quot;$concat\u0026quot;:[expr1,expr2,...,exprN]} #指定的表达式或字符串连接在一起返回,只支持字符串拼接 {\u0026quot;$toLower\u0026quot;:expr} {\u0026quot;$toUpper\u0026quot;:expr} db.emp.aggregate( {\u0026quot;$project\u0026quot;:{\u0026quot;NAME\u0026quot;:{\u0026quot;$toUpper\u0026quot;:\u0026quot;$name\u0026quot;}}}) #5、逻辑表达式 $and $or $not 其他见Mongodb权威指南 # 分组：$group {\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:分组字段,\u0026quot;新的字段名\u0026quot;:聚合操作符}} #1、将分组字段传给$group函数的_id字段即可 {\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$sex\u0026quot;}} #按照性别分组 {\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;}} #按照职位分组 {\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:{\u0026quot;state\u0026quot;:\u0026quot;$state\u0026quot;,\u0026quot;city\u0026quot;:\u0026quot;$city\u0026quot;}}} #按照多个字段分组，比如按照州市分组 #2、分组后聚合得结果,类似于sql中聚合函数的聚合操作符：$sum、$avg、$max、$min、$first、$last #例1：select post,max(salary) from db1.emp group by post; db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;max_salary\u0026quot;:{\u0026quot;$max\u0026quot;:\u0026quot;$salary\u0026quot;}}}) #例2：去每个部门最大薪资与最低薪资 db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;max_salary\u0026quot;:{\u0026quot;$max\u0026quot;:\u0026quot;$salary\u0026quot;},\u0026quot;min_salary\u0026quot;:{\u0026quot;$min\u0026quot;:\u0026quot;$salary\u0026quot;}}}) #例3：如果字段是排序后的，那么$first,$last会很有用,比用$max和$min效率高 db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;first_id\u0026quot;:{\u0026quot;$first\u0026quot;:\u0026quot;$_id\u0026quot;}}}) #例4：求每个部门的总工资 db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;count\u0026quot;:{\u0026quot;$sum\u0026quot;:\u0026quot;$salary\u0026quot;}}}) #例5：求每个部门的人数 db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;count\u0026quot;:{\u0026quot;$sum\u0026quot;:1}}}) #3、数组操作符 {\u0026quot;$addToSet\u0026quot;:expr}：不重复 {\u0026quot;$push\u0026quot;:expr}：重复 #例：查询岗位名以及各岗位内的员工姓名:select post,group_concat(name) from db1.emp group by post; db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;names\u0026quot;:{\u0026quot;$push\u0026quot;:\u0026quot;$name\u0026quot;}}}) db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;names\u0026quot;:{\u0026quot;$addToSet\u0026quot;:\u0026quot;$name\u0026quot;}}}) # 排序：sort\u0026amp;#x3001;\u0026amp;#x9650;\u0026amp;#x5236;\u0026amp;#xFF1A;\u0026quot; role=\u0026quot;presentation\u0026quot;\u0026gt;sort、限制：sort、限制：sort、限制：limit、跳过：$skip {\u0026quot;$sort\u0026quot;:{\u0026quot;字段名\u0026quot;:1,\u0026quot;字段名\u0026quot;:-1}} #1升序，-1降序 {\u0026quot;$limit\u0026quot;:n} {\u0026quot;$skip\u0026quot;:n} #跳过多少个文档 #例1、取平均工资最高的前两个部门 db.emp.aggregate( { \u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;平均工资\u0026quot;:{\u0026quot;$avg\u0026quot;:\u0026quot;$salary\u0026quot;}} }, { \u0026quot;$sort\u0026quot;:{\u0026quot;平均工资\u0026quot;:-1} }, { \u0026quot;$limit\u0026quot;:2 } ) #例2、 db.emp.aggregate( { \u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;平均工资\u0026quot;:{\u0026quot;$avg\u0026quot;:\u0026quot;$salary\u0026quot;}} }, { \u0026quot;$sort\u0026quot;:{\u0026quot;平均工资\u0026quot;:-1} }, { \u0026quot;$limit\u0026quot;:2 }, { \u0026quot;$skip\u0026quot;:1 } ) # 随机选取n个：$sample #集合users包含的文档如下 { \u0026quot;_id\u0026quot; : 1, \u0026quot;name\u0026quot; : \u0026quot;dave123\u0026quot;, \u0026quot;q1\u0026quot; : true, \u0026quot;q2\u0026quot; : true } { \u0026quot;_id\u0026quot; : 2, \u0026quot;name\u0026quot; : \u0026quot;dave2\u0026quot;, \u0026quot;q1\u0026quot; : false, \u0026quot;q2\u0026quot; : false } { \u0026quot;_id\u0026quot; : 3, \u0026quot;name\u0026quot; : \u0026quot;ahn\u0026quot;, \u0026quot;q1\u0026quot; : true, \u0026quot;q2\u0026quot; : true } { \u0026quot;_id\u0026quot; : 4, \u0026quot;name\u0026quot; : \u0026quot;li\u0026quot;, \u0026quot;q1\u0026quot; : true, \u0026quot;q2\u0026quot; : false } { \u0026quot;_id\u0026quot; : 5, \u0026quot;name\u0026quot; : \u0026quot;annT\u0026quot;, \u0026quot;q1\u0026quot; : false, \u0026quot;q2\u0026quot; : true } { \u0026quot;_id\u0026quot; : 6, \u0026quot;name\u0026quot; : \u0026quot;li\u0026quot;, \u0026quot;q1\u0026quot; : true, \u0026quot;q2\u0026quot; : true } { \u0026quot;_id\u0026quot; : 7, \u0026quot;name\u0026quot; : \u0026quot;ty\u0026quot;, \u0026quot;q1\u0026quot; : false, \u0026quot;q2\u0026quot; : true } #下述操作时从users集合中随机选取3个文档 db.users.aggregate( [ { $sample: { size: 3 } } ] ) 练习题：\n1. 查询岗位名以及各岗位内的员工姓名 2. 查询岗位名以及各岗位内包含的员工个数 3. 查询公司内男员工和女员工的个数 4. 查询岗位名以及各岗位的平均薪资、最高薪资、最低薪资 5. 查询男员工与男员工的平均薪资，女员工与女员工的平均薪资 6. 查询各岗位内包含的员工个数小于2的岗位名、岗位内包含员工名字、个数 7. 查询各岗位平均薪资大于10000的岗位名、平均工资 8. 查询各岗位平均薪资大于10000且小于20000的岗位名、平均工资 9. 查询所有员工信息，先按照age升序排序，如果age相同则按照hire_date降序排序 10. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资升序排列 11. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资降序排列,取前1个 1. 查询岗位名以及各岗位内的员工姓名 db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;names\u0026quot;:{\u0026quot;$push\u0026quot;:\u0026quot;$name\u0026quot;}}}) 2. 查询岗位名以及各岗位内包含的员工个数 db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;count\u0026quot;:{\u0026quot;$sum\u0026quot;:1}}}) 3. 查询公司内男员工和女员工的个数 db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$sex\u0026quot;,\u0026quot;count\u0026quot;:{\u0026quot;$sum\u0026quot;:1}}}) 4. 查询岗位名以及各岗位的平均薪资、最高薪资、最低薪资 db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;avg_salary\u0026quot;:{\u0026quot;$avg\u0026quot;:\u0026quot;$salary\u0026quot;},\u0026quot;max_salary\u0026quot;:{\u0026quot;$max\u0026quot;:\u0026quot;$salary\u0026quot;},\u0026quot;min_salary\u0026quot;:{\u0026quot;$min\u0026quot;:\u0026quot;$salary\u0026quot;}}}) 5. 查询男员工与男员工的平均薪资，女员工与女员工的平均薪资 db.emp.aggregate({\u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$sex\u0026quot;,\u0026quot;avg_salary\u0026quot;:{\u0026quot;$avg\u0026quot;:\u0026quot;$salary\u0026quot;}}}) 6. 查询各岗位内包含的员工个数小于2的岗位名、岗位内包含员工名字、个数 db.emp.aggregate( { \u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;count\u0026quot;:{\u0026quot;$sum\u0026quot;:1},\u0026quot;names\u0026quot;:{\u0026quot;$push\u0026quot;:\u0026quot;$name\u0026quot;}} }, {\u0026quot;$match\u0026quot;:{\u0026quot;count\u0026quot;:{\u0026quot;$lt\u0026quot;:2}}}, {\u0026quot;$project\u0026quot;:{\u0026quot;_id\u0026quot;:0,\u0026quot;names\u0026quot;:1,\u0026quot;count\u0026quot;:1}} ) 7. 查询各岗位平均薪资大于10000的岗位名、平均工资 db.emp.aggregate( { \u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;avg_salary\u0026quot;:{\u0026quot;$avg\u0026quot;:\u0026quot;$salary\u0026quot;}} }, {\u0026quot;$match\u0026quot;:{\u0026quot;avg_salary\u0026quot;:{\u0026quot;$gt\u0026quot;:10000}}}, {\u0026quot;$project\u0026quot;:{\u0026quot;_id\u0026quot;:1,\u0026quot;avg_salary\u0026quot;:1}} ) 8. 查询各岗位平均薪资大于10000且小于20000的岗位名、平均工资 db.emp.aggregate( { \u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;avg_salary\u0026quot;:{\u0026quot;$avg\u0026quot;:\u0026quot;$salary\u0026quot;}} }, {\u0026quot;$match\u0026quot;:{\u0026quot;avg_salary\u0026quot;:{\u0026quot;$gt\u0026quot;:10000,\u0026quot;$lt\u0026quot;:20000}}}, {\u0026quot;$project\u0026quot;:{\u0026quot;_id\u0026quot;:1,\u0026quot;avg_salary\u0026quot;:1}} ) 9. 查询所有员工信息，先按照age升序排序，如果age相同则按照hire_date降序排序 db.emp.aggregate( {\u0026quot;$sort\u0026quot;:{\u0026quot;age\u0026quot;:1,\u0026quot;hire_date\u0026quot;:-1}} ) 10. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资升序排列 db.emp.aggregate( { \u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;avg_salary\u0026quot;:{\u0026quot;$avg\u0026quot;:\u0026quot;$salary\u0026quot;}} }, {\u0026quot;$match\u0026quot;:{\u0026quot;avg_salary\u0026quot;:1}}, {\u0026quot;$sort\u0026quot;:{\u0026quot;avg_salary\u0026quot;:1}} ) 11. 查询各岗位平均薪资大于10000的岗位名、平均工资,结果按平均薪资降序排列,取前1个 db.emp.aggregate( { \u0026quot;$group\u0026quot;:{\u0026quot;_id\u0026quot;:\u0026quot;$post\u0026quot;,\u0026quot;avg_salary\u0026quot;:{\u0026quot;$avg\u0026quot;:\u0026quot;$salary\u0026quot;}} }, {\u0026quot;$match\u0026quot;:{\u0026quot;avg_salary\u0026quot;:{\u0026quot;$gt\u0026quot;:10000}}}, {\u0026quot;$sort\u0026quot;:{\u0026quot;avg_salary\u0026quot;:-1}}, {\u0026quot;$limit\u0026quot;:1}, {\u0026quot;$project\u0026quot;:{\u0026quot;date\u0026quot;:new Date,\u0026quot;平均工资\u0026quot;:\u0026quot;$avg_salary\u0026quot;,\u0026quot;_id\u0026quot;:0}} ) 六 可视化工具 链接：https://robomongo.org/\n七 pymongo 官网：http://api.mongodb.com/python/current/tutorial.html\nfrom pymongo import MongoClient #1、链接 client=MongoClient('mongodb://root:123@localhost:27017/') # client = MongoClient('localhost', 27017) #2、use 数据库 db=client['db2'] #等同于：client.db1 #3、查看库下所有的集合 print(db.collection_names(include_system_collections=False)) #4、创建集合 table_user=db['userinfo'] #等同于：db.user #5、插入文档 import datetime user0={ \u0026quot;_id\u0026quot;:1, \u0026quot;name\u0026quot;:\u0026quot;egon\u0026quot;, \u0026quot;birth\u0026quot;:datetime.datetime.now(), \u0026quot;age\u0026quot;:10, 'hobbies':['music','read','dancing'], 'addr':{ 'country':'China', 'city':'BJ' } } user1={ \u0026quot;_id\u0026quot;:2, \u0026quot;name\u0026quot;:\u0026quot;alex\u0026quot;, \u0026quot;birth\u0026quot;:datetime.datetime.now(), \u0026quot;age\u0026quot;:10, 'hobbies':['music','read','dancing'], 'addr':{ 'country':'China', 'city':'weifang' } } # res=table_user.insert_many([user0,user1]).inserted_ids # print(res) # print(table_user.count()) #6、查找 # from pprint import pprint#格式化细 # pprint(table_user.find_one()) # for item in table_user.find(): # pprint(item) # print(table_user.find_one({\u0026quot;_id\u0026quot;:{\u0026quot;$gte\u0026quot;:1},\u0026quot;name\u0026quot;:'egon'})) #7、更新 table_user.update({'_id':1},{'name':'EGON'}) #8、传入新的文档替换旧的文档 table_user.save( { \u0026quot;_id\u0026quot;:2, \u0026quot;name\u0026quot;:'egon_xxx' } ) END\n","permalink":"https://zhong-er.com/posts/%E7%88%AC%E8%99%AB%E5%AD%98%E5%82%A8%E5%BA%93%E4%B9%8Bmongodb/","summary":"\u003cp\u003e\u003cimg alt=\"爬虫.png\" loading=\"lazy\" src=\"/assets/202009031721_5943.png\"\u003e\u003c/p\u003e\n\u003ch3 id=\"一-简介\"\u003e一 简介\u003c/h3\u003e\n\u003cp\u003eMongoDB是一款强大、灵活、且易于扩展的通用型数据库\u003cbr\u003e\n1、易用性\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eMongoDB是一个面向文档（document-oriented）的数据库，而不是关系型数据库。\n不采用关系型主要是为了获得更好得扩展性。当然还有一些其他好处，与关系数据库相比，面向文档的数据库不再有“行“（row）的概念取而代之的是更为灵活的“文档”（document）模型。\n通过在文档中嵌入文档和数组，面向文档的方法能够仅使用一条记录来表现复杂的层级关系，这与现代的面向对象语言的开发者对数据的看法一致。\n另外，不再有预定义模式（predefined schema）：文档的键（key）和值（value）不再是固定的类型和大小。由于没有固定的模式，根据需要添加或删除字段变得更容易了。通常由于开发者能够进行快速迭代，所以开发进程得以加快。而且，实验更容易进行。开发者能尝试大量的数据模型，从中选一个最好的。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2、易扩展性\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e应用程序数据集的大小正在以不可思议的速度增长。随着可用带宽的增长和存储器价格的下降，即使是一个小规模的应用程序，需要存储的数据量也可能大的惊人，甚至超出\n了很多数据库的处理能力。过去非常罕见的T级数据，现在已经是司空见惯了。\n由于需要存储的数据量不断增长，开发者面临一个问题：应该如何扩展数据库，分为纵向扩展和横向扩展，纵向扩展是最省力的做法，但缺点是大型机一般都非常贵，而且\n当数据量达到机器的物理极限时，花再多的钱也买不到更强的机器了，此时选择横向扩展更为合适，但横向扩展带来的另外一个问题就是需要管理的机器太多。\nMongoDB的设计采用横向扩展。面向文档的数据模型使它能很容易地在多台服务器之间进行数据分割。MongoDB能够自动处理跨集群的数据和负载，自动重新分配文档，以及将\n用户的请求路由到正确的机器上。这样，开发者能够集中精力编写应用程序，而不需要考虑如何扩展的问题。如果一个集群需要更大的容量，只需要向集群添加新服务器，MongoDB就会自动将现有的数据向新服务器传送\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e3、丰富的功能\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eMongoDB作为一款通用型数据库，除了能够创建、读取、更新和删除数据之外，还提供了一系列不断扩展的独特功能\n#1、索引\n支持通用二级索引，允许多种快速查询，且提供唯一索引、复合索引、地理空间索引、全文索引\n\n#2、聚合\n支持聚合管道，用户能通过简单的片段创建复杂的集合，并通过数据库自动优化\n\n#3、特殊的集合类型\n支持存在时间有限的集合，适用于那些将在某个时刻过期的数据，如会话session。类似地，MongoDB也支持固定大小的集合，用于保存近期数据，如日志\n\n#4、文件存储\n支持一种非常易用的协议，用于存储大文件和文件元数据。MongoDB并不具备一些在关系型数据库中很普遍的功能，如链接join和复杂的多行事务。省略\n这些的功能是处于架构上的考虑，或者说为了得到更好的扩展性，因为在分布式系统中这两个功能难以高效地实现\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e4、卓越的性能\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eMongoDB的一个主要目标是提供卓越的性能，这很大程度上决定了MongoDB的设计。MongoDB把尽可能多的内存用作缓存cache，视图为每次查询自动选择正确的索引。\n总之各方面的设计都旨在保持它的高性能\n虽然MongoDB非常强大并试图保留关系型数据库的很多特性，但它并不追求具备关系型数据库的所有功能。只要有可能，数据库服务器就会将处理逻辑交给客户端。这种精简方式的设计是MongoDB能够实现如此高性能的原因之一\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"二-mongodb基础知识\"\u003e二 MongoDB基础知识\u003c/h3\u003e\n\u003cp\u003e\u003cimg alt=\"MongoDB.png\" loading=\"lazy\" src=\"/assets/201912038079_6347.png-zhonger.com\"\u003e1、文档是MongoDB的核心概念。文档就是键值对的一个有序集{\u0026lsquo;msg\u0026rsquo;:\u0026lsquo;hello\u0026rsquo;,\u0026lsquo;foo\u0026rsquo;:3}。类似于python中的有序字典。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e需要注意的是：\n#1、文档中的键/值对是有序的。\n#2、文档中的值不仅可以是在双引号里面的字符串，还可以是其他几种数据类型（甚至可以是整个嵌入的文档)。\n#3、MongoDB区分类型和大小写。\n#4、MongoDB的文档不能有重复的键。\n#5、文档中的值可以是多种不同的数据类型，也可以是一个完整的内嵌文档。文档的键是字符串。除了少数例外情况，键可以使用任意UTF-8字符。\n\n文档键命名规范：\n#1、键不能含有\\0 (空字符)。这个字符用来表示键的结尾。\n#2、.和$有特别的意义，只有在特定环境下才能使用。\n#3、以下划线\u0026quot;_\u0026quot;开头的键是保留的(不是严格要求的)。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2、集合就是一组文档。如果将MongoDB中的一个文档比喻为关系型数据的一行，那么一个集合就是相当于一张表\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e#1、集合存在于数据库中，通常情况下为了方便管理，不同格式和类型的数据应该插入到不同的集合，但其实集合没有固定的结构，这意味着我们完全可以把不同格式和类型的数据统统插入一个集合中。\n\n#2、组织子集合的方式就是使用“.”，分隔不同命名空间的子集合。\n比如一个具有博客功能的应用可能包含两个集合，分别是blog.posts和blog.authors，这是为了使组织结构更清晰，这里的blog集合（这个集合甚至不需要存在）跟它的两个子集合没有任何关系。\n在MongoDB中，使用子集合来组织数据非常高效，值得推荐\n\n#3、当第一个文档插入时，集合就会被创建。合法的集合名：\n集合名不能是空字符串\u0026quot;\u0026quot;。\n集合名不能含有\\0字符（空字符)，这个字符表示集合名的结尾。\n集合名不能以\u0026quot;system.\u0026quot;开头，这是为系统集合保留的前缀。\n用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含，这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合，否则千万不要在名字里出现$。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e3、数据库：在MongoDB中，多个文档组成集合，多个集合可以组成数据库\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e数据库也通过名字来标识。数据库名可以是满足以下条件的任意UTF-8字符串：\n#1、不能是空字符串（\u0026quot;\u0026quot;)。\n#2、不得含有' '（空格)、.、$、/、\\和\\0 (空字符)。\n#3、应全部小写。\n#4、最多64字节。\n\n有一些数据库名是保留的，可以直接访问这些有特殊作用的数据库。\n#1、admin： 从身份认证的角度讲，这是“root”数据库，如果将一个用户添加到admin数据库，这个用户将自动获得所有数据库的权限。再者，一些特定的服务器端命令也只能从admin数据库运行，如列出所有数据库或关闭服务器\n#2、local: 这个数据库永远都不可以复制，且一台服务器上的所有本地集合都可以存储在这个数据库中\n#3、config: MongoDB用于分片设置时，分片信息会存储在config数据库中\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e4、强调：把数据库名添加到集合名前，得到集合的完全限定名，即命名空间\u003c/p\u003e","title":"【爬虫】存储库之MongoDB"},{"content":"\n一 背景知识 爬虫的本质就是一个socket客户端与服务端的通信过程，如果我们有多个url待爬取，只用一个线程且采用串行的方式执行，那只能等待爬取一个结束后才能继续下一个，效率会非常低。\n需要强调的是：对于单线程下串行N个任务，并不完全等同于低效，如果这N个任务都是纯计算的任务，那么该线程对cpu的利用率仍然会很高，之所以单线程下串行多个爬虫任务低效，是因为爬虫任务是明显的IO密集型程序。\n关于IO模型详见链接：http://www.cnblogs.com/linhaifeng/articles/7454717.html\n那么该如何提高爬取性能呢？且看下述概念\n二 同步、异步、回调机制 1、同步调用：即提交一个任务后就在原地等待任务结束，等到拿到任务的结果后再继续下一行代码，效率低下\n# 同步调用 import requests def parse_page(res): print('解析 %s' %(len(res))) def get_page(url): print('下载 %s' %url) response=requests.get(url) if response.status_code == 200: return response.text urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org'] for url in urls: res=get_page(url) #调用一个任务，就在原地等待任务结束拿到结果后才继续往后执行 parse_page(res) 2、一个简单的解决方案：多线程或多进程\n#在服务器端使用多线程（或多进程）。多线程（或多进程）的目的是让每个连接都拥有独立的线程（或进程），这样任何一个连接的阻塞都不会影响其他的连接。 # 多进程或多线程 #IO密集型程序应该用多线程 import requests from threading import Thread,current_thread def parse_page(res): print('%s 解析 %s' %(current_thread().getName(),len(res))) def get_page(url,callback=parse_page): print('%s 下载 %s' %(current_thread().getName(),url)) response=requests.get(url) if response.status_code == 200: callback(response.text) if __name__ == '__main__': urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org'] for url in urls: t=Thread(target=get_page,args=(url,)) t.start() 该方案的问题是：\n#开启多进程或都线程的方式，我们是无法无限制地开启多进程或多线程的：在遇到要同时响应成百上千路的连接请求，则无论多线程还是多进程都会严重占据系统资源，降低系统对外界响应效率，而且线程与进程本身也更容易进入假死状态。 3、改进方案： 线程池或进程池+异步调用：提交一个任务后并不会等待任务结束，而是继续下一行代码\n#很多程序员可能会考虑使用“线程池”或“连接池”。“线程池”旨在减少创建和销毁线程的频率，其维持一定合理数量的线程，并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池，尽量重用已有的连接、减少创建和关闭连接的频率。这两种技术都可以很好的降低系统开销，都被广泛应用很多大型系统，如websphere、tomcat和各种数据库等。 # 进程池或线程池：异步调用+回调机制 #IO密集型程序应该用多线程，所以此时我们使用线程池 import requests from threading import current_thread from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor def parse_page(res): res=res.result() print('%s 解析 %s' %(current_thread().getName(),len(res))) def get_page(url): print('%s 下载 %s' %(current_thread().getName(),url)) response=requests.get(url) if response.status_code == 200: return response.text if __name__ == '__main__': urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org'] ``` pool=ThreadPoolExecutor(50) # pool=ProcessPoolExecutor(50) for url in urls: pool.submit(get_page,url).add_done_callback(parse_page) pool.shutdown(wait=True) ``` 改进后方案其实也存在着问题：\n#“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。而且，所谓“池”始终有其上限，当请求大大超过上限时，“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模，并根据响应规模调整“池”的大小。 对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求，“线程池”或“连接池”或许可以缓解部分压力，但是不能解决所有问题。总之，多线程模型可以方便高效的解决小规模的服务请求，但面对大规模的服务请求，多线程模型也会遇到瓶颈，可以用非阻塞接口来尝试解决这个问题。\n三 高性能 上述无论哪种解决方案其实没有解决一个性能相关的问题：IO阻塞，无论是多进程还是多线程，在遇到IO阻塞时都会被操作系统强行剥夺走CPU的执行权限，程序的执行效率因此就降低了下来。\n解决这一问题的关键在于，我们自己从应用程序级别检测IO阻塞然后切换到我们自己程序的其他任务执行，这样把我们程序的IO降到最低，我们的程序处于就绪态就会增多，以此来迷惑操作系统，操作系统便以为我们的程序是IO比较少的程序，从而会尽可能多的分配CPU给我们，这样也就达到了提升程序执行效率的目的\n1、在python3.3之后新增了asyncio模块，可以帮我们检测IO（只能是网络IO），实现应用程序级别的切换\n# 基本使用 import asyncio @asyncio.coroutine def task(task_id,senconds): print('%s is start' %task_id) yield from asyncio.sleep(senconds) #只能检测网络IO,检测到IO后切换到其他任务执行 print('%s is end' %task_id) tasks=[task(task_id=\u0026quot;任务1\u0026quot;,senconds=3),task(\u0026quot;任务2\u0026quot;,2),task(task_id=\u0026quot;任务3\u0026quot;,senconds=1)] loop=asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) loop.close() 2、但asyncio模块只能发tcp级别的请求，不能发http协议，因此，在我们需要发送http请求的时候，需要我们自定义http报头\n# asyncio+自定义http协议报头 import asyncio import requests import uuid user_agent='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0' def parse_page(host,res): print('%s 解析结果 %s' %(host,len(res))) with open('%s.html' %(uuid.uuid1()),'wb') as f: f.write(res) @asyncio.coroutine def get_page(host,port=80,url='/',callback=parse_page,ssl=False): print('下载 http://%s:%s%s' %(host,port,url)) ``` #步骤一（IO阻塞）：发起tcp链接，是阻塞操作，因此需要yield from if ssl: port=443 recv,send=yield from asyncio.open_connection(host=host,port=443,ssl=ssl) # 步骤二：封装http协议的报头，因为asyncio模块只能封装并发送tcp包，因此这一步需要我们自己封装http协议的包 request_headers=\u0026quot;\u0026quot;\u0026quot;GET %s HTTP/1.0\\r\\nHost: %s\\r\\nUser-agent: %s\\r\\n\\r\\n\u0026quot;\u0026quot;\u0026quot; %(url,host,user_agent) # requset_headers=\u0026quot;\u0026quot;\u0026quot;POST %s HTTP/1.0\\r\\nHost: %s\\r\\n\\r\\nname=egon\u0026amp;password=123\u0026quot;\u0026quot;\u0026quot; % (url, host,) request_headers=request_headers.encode('utf-8') # 步骤三（IO阻塞）：发送http请求包 ``` send.write(request_headers) yield from send.drain() ``` # 步骤四（IO阻塞）：接收响应头 while True: line=yield from recv.readline() if line == b'\\r\\n': break print('%s Response headers：%s' %(host,line)) # 步骤五（IO阻塞）：接收响应体 text=yield from recv.read() # 步骤六：执行回调函数 ``` callback(host,text) ``` # 步骤七：关闭套接字 send.close() #没有recv.close()方法，因为是四次挥手断链接，双向链接的两端，一端发完数据后执行send.close()另外一端就被动地断开 ``` if __name__ == '__main__': tasks=[ get_page('www.baidu.com',url='/s?wd=美女',ssl=True), get_page('www.cnblogs.com',url='/',ssl=True), ] ``` loop=asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) loop.close() ``` 3、自定义http报头多少有点麻烦，于是有了aiohttp模块，专门帮我们封装http报头，然后我们还需要用asyncio检测IO实现切换\n# asyncio+aiohttp import aiohttp import asyncio @asyncio.coroutine def get_page(url): print('GET:%s' %url) response=yield from aiohttp.request('GET',url) ``` data=yield from response.read() print(url,data) response.close() return 1 ``` tasks=[ get_page('https://www.python.org/doc'), get_page('https://www.cnblogs.com/linhaifeng'), get_page('https://www.openstack.org') ] loop=asyncio.get_event_loop() results=loop.run_until_complete(asyncio.gather(*tasks)) loop.close() print('=====\u0026gt;',results) #[1, 1, 1] 4、此外，还可以将requests.get函数传给asyncio，就能够被检测了\n# asyncio+requests模块的方法 import requests import asyncio @asyncio.coroutine def get_page(func,*args): print('GET:%s' %args[0]) loog=asyncio.get_event_loop() furture=loop.run_in_executor(None,func,*args) response=yield from furture ``` print(response.url,len(response.text)) return 1 ``` tasks=[ get_page(requests.get,'https://www.python.org/doc'), get_page(requests.get,'https://www.cnblogs.com/linhaifeng'), get_page(requests.get,'https://www.openstack.org') ] loop=asyncio.get_event_loop() results=loop.run_until_complete(asyncio.gather(*tasks)) loop.close() print('=====\u0026gt;',results) #[1, 1, 1] 5、还有之前在协程时介绍的gevent模块\n# gevent+requests from gevent import monkey;monkey.patch_all() import gevent import requests def get_page(url): print('GET:%s' %url) response=requests.get(url) print(url,len(response.text)) return 1 # g1=gevent.spawn(get_page,'https://www.python.org/doc') # g2=gevent.spawn(get_page,'https://www.cnblogs.com/linhaifeng') # g3=gevent.spawn(get_page,'https://www.openstack.org') # gevent.joinall([g1,g2,g3,]) # print(g1.value,g2.value,g3.value) #拿到返回值 #协程池 from gevent.pool import Pool pool=Pool(2) g1=pool.spawn(get_page,'https://www.python.org/doc') g2=pool.spawn(get_page,'https://www.cnblogs.com/linhaifeng') g3=pool.spawn(get_page,'https://www.openstack.org') gevent.joinall([g1,g2,g3,]) print(g1.value,g2.value,g3.value) #拿到返回值 6、封装了gevent+requests模块的grequests模块\n# grequests #pip3 install grequests import grequests request_list=[ grequests.get('https://wwww.xxxx.org/doc1'), grequests.get('https://www.cnblogs.com/linhaifeng'), grequests.get('https://www.openstack.org') ] ##### 执行并获取响应列表 ##### # response_list = grequests.map(request_list) # print(response_list) ##### 执行并获取响应列表（处理异常） ##### def exception_handler(request, exception): # print(request,exception) print(\u0026quot;%s Request failed\u0026quot; %request.url) response_list = grequests.map(request_list, exception_handler=exception_handler) print(response_list) 7、twisted：是一个网络框架，其中一个功能是发送异步请求，检测IO并自动切换\n# twisted的用法 ''' #问题一：error: Microsoft Visual C++ 14.0 is required. Get it with \u0026quot;Microsoft Visual C++ Build Tools\u0026quot;: http://landinghub.visualstudio.com/visual-cpp-build-tools https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted pip3 install C:\\Users\\Administrator\\Downloads\\Twisted-17.9.0-cp36-cp36m-win_amd64.whl pip3 install twisted #问题二：ModuleNotFoundError: No module named 'win32api' https://sourceforge.net/projects/pywin32/files/pywin32/ #问题三：openssl pip3 install pyopenssl ''' #twisted基本用法 from twisted.web.client import getPage,defer from twisted.internet import reactor def all_done(arg): # print(arg) reactor.stop() def callback(res): print(res) return 1 defer_list=[] urls=[ 'http://www.baidu.com', 'http://www.bing.com', 'https://www.python.org', ] for url in urls: obj=getPage(url.encode('utf=-8'),) obj.addCallback(callback) defer_list.append(obj) defer.DeferredList(defer_list).addBoth(all_done) reactor.run() #twisted的getPage的详细用法 from twisted.internet import reactor from twisted.web.client import getPage import urllib.parse def one_done(arg): print(arg) reactor.stop() post_data = urllib.parse.urlencode({'check_data': 'adf'}) post_data = bytes(post_data, encoding='utf8') headers = {b'Content-Type': b'application/x-www-form-urlencoded'} response = getPage(bytes('http://dig.chouti.com/login', encoding='utf8'), method=bytes('POST', encoding='utf8'), postdata=post_data, cookies={}, headers=headers) response.addBoth(one_done) reactor.run() 8、tornado\n# Tornado from tornado.httpclient import AsyncHTTPClient from tornado.httpclient import HTTPRequest from tornado import ioloop def handle_response(response): \u0026quot;\u0026quot;\u0026quot; 处理返回值内容（需要维护计数器，来停止IO循环），调用 ioloop.IOLoop.current().stop() :param response: :return: \u0026quot;\u0026quot;\u0026quot; if response.error: print(\u0026quot;Error:\u0026quot;, response.error) else: print(response.body) def func(): url_list = [ 'http://www.baidu.com', 'http://www.bing.com', ] for url in url_list: print(url) http_client = AsyncHTTPClient() http_client.fetch(HTTPRequest(url), handle_response) ioloop.IOLoop.current().add_callback(func) ioloop.IOLoop.current().start() #发现上例在所有任务都完毕后也不能正常结束，为了解决该问题，让我们来加上计数器 from tornado.httpclient import AsyncHTTPClient from tornado.httpclient import HTTPRequest from tornado import ioloop count=0 def handle_response(response): \u0026quot;\u0026quot;\u0026quot; 处理返回值内容（需要维护计数器，来停止IO循环），调用 ioloop.IOLoop.current().stop() :param response: :return: \u0026quot;\u0026quot;\u0026quot; if response.error: print(\u0026quot;Error:\u0026quot;, response.error) else: print(len(response.body)) ``` global count count-=1 #完成一次回调，计数减1 if count == 0: ioloop.IOLoop.current().stop() ``` def func(): url_list = [ 'http://www.baidu.com', 'http://www.bing.com', ] ``` global count for url in url_list: print(url) http_client = AsyncHTTPClient() http_client.fetch(HTTPRequest(url), handle_response) count+=1 #计数加1 ``` ioloop.IOLoop.current().add_callback(func) ioloop.IOLoop.current().start() END\n","permalink":"https://zhong-er.com/posts/%E7%88%AC%E8%99%AB%E9%AB%98%E6%80%A7%E8%83%BD%E7%9B%B8%E5%85%B3/","summary":"\u003cp\u003e\u003cimg alt=\"爬虫.png\" loading=\"lazy\" src=\"/assets/202009031721_5943.png\"\u003e\u003c/p\u003e\n\u003ch3 id=\"一-背景知识\"\u003e一 背景知识\u003c/h3\u003e\n\u003cp\u003e爬虫的本质就是一个socket客户端与服务端的通信过程，如果我们有多个url待爬取，只用一个线程且采用串行的方式执行，那只能等待爬取一个结束后才能继续下一个，效率会非常低。\u003c/p\u003e\n\u003cp\u003e需要强调的是：对于单线程下串行N个任务，并不完全等同于低效，如果这N个任务都是纯计算的任务，那么该线程对cpu的利用率仍然会很高，之所以单线程下串行多个爬虫任务低效，是因为爬虫任务是明显的IO密集型程序。\u003c/p\u003e\n\u003cp\u003e关于IO模型详见\u003ca href=\"http://www.cnblogs.com/linhaifeng/articles/7454717.html\"\u003e链接：http://www.cnblogs.com/linhaifeng/articles/7454717.html\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e那么该如何提高爬取性能呢？且看下述概念\u003c/p\u003e\n\u003ch3 id=\"二-同步异步回调机制\"\u003e二 同步、异步、回调机制\u003c/h3\u003e\n\u003cp\u003e1、同步调用：即提交一个任务后就在原地等待任务结束，等到拿到任务的结果后再继续下一行代码，效率低下\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 同步调用\n\nimport requests\n\ndef parse_page(res):\n    print('解析 %s' %(len(res)))\n\ndef get_page(url):\n    print('下载 %s' %url)\n    response=requests.get(url)\n    if response.status_code == 200:\n        return response.text\n\nurls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org']\nfor url in urls:\n    res=get_page(url) #调用一个任务，就在原地等待任务结束拿到结果后才继续往后执行\n    parse_page(res)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2、一个简单的解决方案：多线程或多进程\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e#在服务器端使用多线程（或多进程）。多线程（或多进程）的目的是让每个连接都拥有独立的线程（或进程），这样任何一个连接的阻塞都不会影响其他的连接。\n\n\n# 多进程或多线程\n\n#IO密集型程序应该用多线程\nimport requests\nfrom threading import Thread,current_thread\n\ndef parse_page(res):\n    print('%s 解析 %s' %(current_thread().getName(),len(res)))\n\ndef get_page(url,callback=parse_page):\n    print('%s 下载 %s' %(current_thread().getName(),url))\n    response=requests.get(url)\n    if response.status_code == 200:\n        callback(response.text)\n\nif __name__ == '__main__':\n    urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org']\n    for url in urls:\n        t=Thread(target=get_page,args=(url,))\n        t.start()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e该方案的问题是：\u003c/p\u003e","title":"【爬虫】高性能相关"},{"content":"\n一 介绍 Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间.你可能在寻找 Beautiful Soup3 的文档,Beautiful Soup 3 目前已经停止开发,官网推荐在现在的项目中使用Beautiful Soup 4, 移植到BS4\n#安装 Beautiful Soup pip install beautifulsoup4 #安装解析器 Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml .根据操作系统不同,可以选择下列方法来安装lxml: $ apt-get install Python-lxml $ easy_install lxml $ pip install lxml 另一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同,可以选择下列方法来安装html5lib: $ apt-get install Python-html5lib $ easy_install html5lib $ pip install html5lib 下表列出了主要的解析器,以及它们的优缺点,官网推荐使用lxml作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml或html5lib, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定.\n中文文档：https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html\n二 基本使用 html_doc = \u0026quot;\u0026quot;\u0026quot; \u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;The Dormouse's story\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p class=\u0026quot;title\u0026quot;\u0026gt;\u0026lt;b\u0026gt;The Dormouse's story\u0026lt;/b\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;Once upon a time there were three little sisters; and their names were \u0026lt;a href=\u0026quot;http://example.com/elsie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link1\u0026quot;\u0026gt;Elsie\u0026lt;/a\u0026gt;, \u0026lt;a href=\u0026quot;http://example.com/lacie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link2\u0026quot;\u0026gt;Lacie\u0026lt;/a\u0026gt; and \u0026lt;a href=\u0026quot;http://example.com/tillie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link3\u0026quot;\u0026gt;Tillie\u0026lt;/a\u0026gt;; and they lived at the bottom of a well.\u0026lt;/p\u0026gt; \u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;...\u0026lt;/p\u0026gt; \u0026quot;\u0026quot;\u0026quot; #基本使用：容错处理,文档的容错能力指的是在html代码不完整的情况下,使用该模块可以识别该错误。使用BeautifulSoup解析上述代码,能够得到一个 BeautifulSoup 的对象,并能按照标准的缩进格式的结构输出 from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #具有容错功能 res=soup.prettify() #处理好缩进，结构化显示 print(res) 三 遍历文档树 #遍历文档树：即直接通过标签名字选择，特点是选择速度快，但如果存在多个相同的标签则只返回第一个 #1、用法 #2、获取标签的名称 #3、获取标签的属性 #4、获取标签的内容 #5、嵌套选择 #6、子节点、子孙节点 #7、父节点、祖先节点 #8、兄弟节点 #遍历文档树：即直接通过标签名字选择，特点是选择速度快，但如果存在多个相同的标签则只返回第一个 html_doc = \u0026quot;\u0026quot;\u0026quot; \u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;The Dormouse's story\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026quot;my p\u0026quot; class=\u0026quot;title\u0026quot;\u0026gt;\u0026lt;b id=\u0026quot;bbb\u0026quot; class=\u0026quot;boldest\u0026quot;\u0026gt;The Dormouse's story\u0026lt;/b\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;Once upon a time there were three little sisters; and their names were \u0026lt;a href=\u0026quot;http://example.com/elsie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link1\u0026quot;\u0026gt;Elsie\u0026lt;/a\u0026gt;, \u0026lt;a href=\u0026quot;http://example.com/lacie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link2\u0026quot;\u0026gt;Lacie\u0026lt;/a\u0026gt; and \u0026lt;a href=\u0026quot;http://example.com/tillie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link3\u0026quot;\u0026gt;Tillie\u0026lt;/a\u0026gt;; and they lived at the bottom of a well.\u0026lt;/p\u0026gt; \u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;...\u0026lt;/p\u0026gt; \u0026quot;\u0026quot;\u0026quot; #1、用法 from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') # soup=BeautifulSoup(open('a.html'),'lxml') print(soup.p) #存在多个相同的标签则只返回第一个 print(soup.a) #存在多个相同的标签则只返回第一个 #2、获取标签的名称 print(soup.p.name) #3、获取标签的属性 print(soup.p.attrs) #4、获取标签的内容 print(soup.p.string) # p下的文本只有一个时，取到，否则为None print(soup.p.strings) #拿到一个生成器对象, 取到p下所有的文本内容 print(soup.p.text) #取到p下所有的文本内容 for line in soup.stripped_strings: #去掉空白 print(line) ''' 如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None，如果只有一个子节点那么就输出该子节点的文本，比如下面的这种结构，soup.p.string 返回为None,但soup.p.strings就可以找到所有文本 \u0026lt;p id='list-1'\u0026gt; 哈哈哈哈 \u0026lt;a class='sss'\u0026gt; \u0026lt;span\u0026gt; \u0026lt;h1\u0026gt;aaaa\u0026lt;/h1\u0026gt; \u0026lt;/span\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;b\u0026gt;bbbbb\u0026lt;/b\u0026gt; \u0026lt;/p\u0026gt; ''' #5、嵌套选择 print(soup.head.title.string) print(soup.body.a.string) #6、子节点、子孙节点 print(soup.p.contents) #p下所有子节点 print(soup.p.children) #得到一个迭代器,包含p下所有子节点 for i,child in enumerate(soup.p.children): print(i,child) print(soup.p.descendants) #获取子孙节点,p下所有的标签都会选择出来 for i,child in enumerate(soup.p.descendants): print(i,child) #7、父节点、祖先节点 print(soup.a.parent) #获取a标签的父节点 print(soup.a.parents) #找到a标签所有的祖先节点，父亲的父亲，父亲的父亲的父亲... #8、兄弟节点 print('=====\u0026gt;') print(soup.a.next_sibling) #下一个兄弟 print(soup.a.previous_sibling) #上一个兄弟 print(list(soup.a.next_siblings)) #下面的兄弟们=\u0026gt;生成器对象 print(soup.a.previous_siblings) #上面的兄弟们=\u0026gt;生成器对象 四 搜索文档树 1、五种过滤器\n#搜索文档树：BeautifulSoup定义了很多搜索方法,这里着重介绍2个: find() 和 find_all() .其它方法的参数和用法类似 html_doc = \u0026quot;\u0026quot;\u0026quot; \u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;The Dormouse's story\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p id=\u0026quot;my p\u0026quot; class=\u0026quot;title\u0026quot;\u0026gt;\u0026lt;b id=\u0026quot;bbb\u0026quot; class=\u0026quot;boldest\u0026quot;\u0026gt;The Dormouse's story\u0026lt;/b\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;Once upon a time there were three little sisters; and their names were \u0026lt;a href=\u0026quot;http://example.com/elsie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link1\u0026quot;\u0026gt;Elsie\u0026lt;/a\u0026gt;, \u0026lt;a href=\u0026quot;http://example.com/lacie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link2\u0026quot;\u0026gt;Lacie\u0026lt;/a\u0026gt; and \u0026lt;a href=\u0026quot;http://example.com/tillie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link3\u0026quot;\u0026gt;Tillie\u0026lt;/a\u0026gt;; and they lived at the bottom of a well.\u0026lt;/p\u0026gt; \u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;...\u0026lt;/p\u0026gt; \u0026quot;\u0026quot;\u0026quot; from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #1、五种过滤器: 字符串、正则表达式、列表、True、方法 #1.1、字符串：即标签名 print(soup.find_all('b')) #1.2、正则表达式 import re print(soup.find_all(re.compile('^b'))) #找出b开头的标签，结果有body和b标签 #1.3、列表：如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有\u0026lt;a\u0026gt;标签和\u0026lt;b\u0026gt;标签: print(soup.find_all(['a','b'])) #1.4、True：可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点 print(soup.find_all(True)) for tag in soup.find_all(True): print(tag.name) #1.5、方法:如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 ,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False def has_class_but_no_id(tag): return tag.has_attr('class') and not tag.has_attr('id') print(soup.find_all(has_class_but_no_id)) 2、find_all( name , attrs , recursive , text , **kwargs )\n#2、find_all( name , attrs , recursive , text , **kwargs ) #2.1、name: 搜索name参数的值可以使任一类型的 过滤器 ,字符窜,正则表达式,列表,方法或是 True . print(soup.find_all(name=re.compile('^t'))) #2.2、keyword: key=value的形式，value可以是过滤器：字符串 , 正则表达式 , 列表, True . print(soup.find_all(id=re.compile('my'))) print(soup.find_all(href=re.compile('lacie'),id=re.compile('\\d'))) #注意类要用class_ print(soup.find_all(id=True)) #查找有id属性的标签 # 有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性: data_soup = BeautifulSoup('\u0026lt;div data-foo=\u0026quot;value\u0026quot;\u0026gt;foo!\u0026lt;/div\u0026gt;','lxml') # data_soup.find_all(data-foo=\u0026quot;value\u0026quot;) #报错：SyntaxError: keyword can't be an expression # 但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag: print(data_soup.find_all(attrs={\u0026quot;data-foo\u0026quot;: \u0026quot;value\u0026quot;})) # [\u0026lt;div data-foo=\u0026quot;value\u0026quot;\u0026gt;foo!\u0026lt;/div\u0026gt;] #2.3、按照类名查找，注意关键字是class_，class_=value,value可以是五种选择器之一 print(soup.find_all('a',class_='sister')) #查找类为sister的a标签 print(soup.find_all('a',class_='sister ssss')) #查找类为sister和sss的a标签，顺序错误也匹配不成功 print(soup.find_all(class_=re.compile('^sis'))) #查找类为sister的所有标签 #2.4、attrs print(soup.find_all('p',attrs={'class':'story'})) #2.5、text: 值可以是：字符，列表，True，正则 print(soup.find_all(text='Elsie')) print(soup.find_all('a',text='Elsie')) #2.6、limit参数:如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果 print(soup.find_all('a',limit=2)) #2.7、recursive:调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False . print(soup.html.find_all('a')) print(soup.html.find_all('a',recursive=False)) ''' 像调用 find_all() 一样调用tag find_all() 几乎是Beautiful Soup中最常用的搜索方法,所以我们定义了它的简写方法. BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面两行代码是等价的: soup.find_all(\u0026quot;a\u0026quot;) soup(\u0026quot;a\u0026quot;) 这两行代码也是等价的: soup.title.find_all(text=True) soup.title(text=True) ''' 3、find( name , attrs , recursive , text , **kwargs )\n#3、find( name , attrs , recursive , text , **kwargs ) find_all() 方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个\u0026lt;body\u0026gt;标签,那么使用 find_all() 方法来查找\u0026lt;body\u0026gt;标签就不太合适, 使用 find_all 方法并设置 limit=1 参数不如直接使用 find() 方法.下面两行代码是等价的: soup.find_all('title', limit=1) # [\u0026lt;title\u0026gt;The Dormouse's story\u0026lt;/title\u0026gt;] soup.find('title') # \u0026lt;title\u0026gt;The Dormouse's story\u0026lt;/title\u0026gt; 唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果. find_all() 方法没有找到目标是返回空列表, find() 方法找不到目标时,返回 None . print(soup.find(\u0026quot;nosuchtag\u0026quot;)) # None soup.head.title 是 tag的名字 方法的简写.这个简写的原理就是多次调用当前tag的 find() 方法: soup.head.title # \u0026lt;title\u0026gt;The Dormouse's story\u0026lt;/title\u0026gt; soup.find(\u0026quot;head\u0026quot;).find(\u0026quot;title\u0026quot;) # \u0026lt;title\u0026gt;The Dormouse's story\u0026lt;/title\u0026gt; 4、其他方法\n#见官网:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#find-parents-find-parent 5、CSS选择器\n#该模块提供了select方法来支持css,详见官网:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#id37 html_doc = \u0026quot;\u0026quot;\u0026quot; \u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;The Dormouse's story\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p class=\u0026quot;title\u0026quot;\u0026gt; \u0026lt;b\u0026gt;The Dormouse's story\u0026lt;/b\u0026gt; Once upon a time there were three little sisters; and their names were \u0026lt;a href=\u0026quot;http://example.com/elsie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link1\u0026quot;\u0026gt; \u0026lt;span\u0026gt;Elsie\u0026lt;/span\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;a href=\u0026quot;http://example.com/lacie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link2\u0026quot;\u0026gt;Lacie\u0026lt;/a\u0026gt; and \u0026lt;a href=\u0026quot;http://example.com/tillie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link3\u0026quot;\u0026gt;Tillie\u0026lt;/a\u0026gt;; \u0026lt;div class='panel-1'\u0026gt; \u0026lt;ul class='list' id='list-1'\u0026gt; \u0026lt;li class='element'\u0026gt;Foo\u0026lt;/li\u0026gt; \u0026lt;li class='element'\u0026gt;Bar\u0026lt;/li\u0026gt; \u0026lt;li class='element'\u0026gt;Jay\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;ul class='list list-small' id='list-2'\u0026gt; \u0026lt;li class='element'\u0026gt;\u0026lt;h1 class='yyyy'\u0026gt;Foo\u0026lt;/h1\u0026gt;\u0026lt;/li\u0026gt; \u0026lt;li class='element xxx'\u0026gt;Bar\u0026lt;/li\u0026gt; \u0026lt;li class='element'\u0026gt;Jay\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; and they lived at the bottom of a well. \u0026lt;/p\u0026gt; \u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;...\u0026lt;/p\u0026gt; \u0026quot;\u0026quot;\u0026quot; from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #1、CSS选择器 print(soup.p.select('.sister')) print(soup.select('.sister span')) print(soup.select('#link1')) print(soup.select('#link1 span')) print(soup.select('#list-2 .element.xxx')) print(soup.select('#list-2')[0].select('.element')) #可以一直select,但其实没必要,一条select就可以了 # 2、获取属性 print(soup.select('#list-2 h1')[0].attrs) # 3、获取内容 print(soup.select('#list-2 h1')[0].get_text()) 五 修改文档树 链接：https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#id40\n六 总结 # 总结: #1、推荐使用lxml解析库 #2、讲了三种选择器:标签选择器,find与find_all，css选择器 1、标签选择器筛选功能弱,但是速度快 2、建议使用find,find_all查询匹配单个结果或者多个结果 3、如果对css选择器非常熟悉建议使用select #3、记住常用的获取属性attrs和文本值get_text()的方法 END\n","permalink":"https://zhong-er.com/posts/%E7%88%AC%E8%99%AB%E8%A7%A3%E6%9E%90%E5%BA%93%E4%B9%8Bbeautifulsoup%E5%BA%93/","summary":"\u003cp\u003e\u003cimg alt=\"爬虫.png\" loading=\"lazy\" src=\"/assets/202009031721_5943.png\"\u003e\u003c/p\u003e\n\u003ch3 id=\"一-介绍\"\u003e一 介绍\u003c/h3\u003e\n\u003cp\u003eBeautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间.你可能在寻找 Beautiful Soup3 的文档,Beautiful Soup 3 目前已经停止开发,官网推荐在现在的项目中使用Beautiful Soup 4, 移植到BS4\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e#安装 Beautiful Soup\npip install beautifulsoup4\n\n#安装解析器\nBeautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml .根据操作系统不同,可以选择下列方法来安装lxml:\n\n$ apt-get install Python-lxml\n\n$ easy_install lxml\n\n$ pip install lxml\n\n另一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同,可以选择下列方法来安装html5lib:\n\n$ apt-get install Python-html5lib\n\n$ easy_install html5lib\n\n$ pip install html5lib\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e下表列出了主要的解析器,以及它们的优缺点,官网推荐使用lxml作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml或html5lib, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"jiexiqi.jpg\" loading=\"lazy\" src=\"/assets/201912036355_4097.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html\"\u003e中文文档：https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"二-基本使用\"\u003e二 基本使用\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003ehtml_doc = \u0026quot;\u0026quot;\u0026quot;\n\u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;The Dormouse's story\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\n\u0026lt;body\u0026gt;\n\u0026lt;p class=\u0026quot;title\u0026quot;\u0026gt;\u0026lt;b\u0026gt;The Dormouse's story\u0026lt;/b\u0026gt;\u0026lt;/p\u0026gt;\n\n\u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;Once upon a time there were three little sisters; and their names were\n\u0026lt;a href=\u0026quot;http://example.com/elsie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link1\u0026quot;\u0026gt;Elsie\u0026lt;/a\u0026gt;,\n\u0026lt;a href=\u0026quot;http://example.com/lacie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link2\u0026quot;\u0026gt;Lacie\u0026lt;/a\u0026gt; and\n\u0026lt;a href=\u0026quot;http://example.com/tillie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link3\u0026quot;\u0026gt;Tillie\u0026lt;/a\u0026gt;;\nand they lived at the bottom of a well.\u0026lt;/p\u0026gt;\n\n\u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;...\u0026lt;/p\u0026gt;\n\u0026quot;\u0026quot;\u0026quot;\n\n#基本使用：容错处理,文档的容错能力指的是在html代码不完整的情况下,使用该模块可以识别该错误。使用BeautifulSoup解析上述代码,能够得到一个 BeautifulSoup 的对象,并能按照标准的缩进格式的结构输出\nfrom bs4 import BeautifulSoup\nsoup=BeautifulSoup(html_doc,'lxml') #具有容错功能\nres=soup.prettify() #处理好缩进，结构化显示\nprint(res)\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"三-遍历文档树\"\u003e三 遍历文档树\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003e#遍历文档树：即直接通过标签名字选择，特点是选择速度快，但如果存在多个相同的标签则只返回第一个\n#1、用法\n#2、获取标签的名称\n#3、获取标签的属性\n#4、获取标签的内容\n#5、嵌套选择\n#6、子节点、子孙节点\n#7、父节点、祖先节点\n#8、兄弟节点\n\n\n#遍历文档树：即直接通过标签名字选择，特点是选择速度快，但如果存在多个相同的标签则只返回第一个\nhtml_doc = \u0026quot;\u0026quot;\u0026quot;\n\u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;title\u0026gt;The Dormouse's story\u0026lt;/title\u0026gt;\u0026lt;/head\u0026gt;\n\u0026lt;body\u0026gt;\n\u0026lt;p id=\u0026quot;my p\u0026quot; class=\u0026quot;title\u0026quot;\u0026gt;\u0026lt;b id=\u0026quot;bbb\u0026quot; class=\u0026quot;boldest\u0026quot;\u0026gt;The Dormouse's story\u0026lt;/b\u0026gt;\u0026lt;/p\u0026gt;\n\n\u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;Once upon a time there were three little sisters; and their names were\n\u0026lt;a href=\u0026quot;http://example.com/elsie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link1\u0026quot;\u0026gt;Elsie\u0026lt;/a\u0026gt;,\n\u0026lt;a href=\u0026quot;http://example.com/lacie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link2\u0026quot;\u0026gt;Lacie\u0026lt;/a\u0026gt; and\n\u0026lt;a href=\u0026quot;http://example.com/tillie\u0026quot; class=\u0026quot;sister\u0026quot; id=\u0026quot;link3\u0026quot;\u0026gt;Tillie\u0026lt;/a\u0026gt;;\nand they lived at the bottom of a well.\u0026lt;/p\u0026gt;\n\n\u0026lt;p class=\u0026quot;story\u0026quot;\u0026gt;...\u0026lt;/p\u0026gt;\n\u0026quot;\u0026quot;\u0026quot;\n\n#1、用法\nfrom bs4 import BeautifulSoup\nsoup=BeautifulSoup(html_doc,'lxml')\n# soup=BeautifulSoup(open('a.html'),'lxml')\n\nprint(soup.p) #存在多个相同的标签则只返回第一个\nprint(soup.a) #存在多个相同的标签则只返回第一个\n\n#2、获取标签的名称\nprint(soup.p.name)\n\n#3、获取标签的属性\nprint(soup.p.attrs)\n\n#4、获取标签的内容\nprint(soup.p.string) # p下的文本只有一个时，取到，否则为None\nprint(soup.p.strings) #拿到一个生成器对象, 取到p下所有的文本内容\nprint(soup.p.text) #取到p下所有的文本内容\nfor line in soup.stripped_strings: #去掉空白\n    print(line)\n\n'''\n如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None，如果只有一个子节点那么就输出该子节点的文本，比如下面的这种结构，soup.p.string 返回为None,但soup.p.strings就可以找到所有文本\n\u0026lt;p id='list-1'\u0026gt;\n    哈哈哈哈\n    \u0026lt;a class='sss'\u0026gt;\n        \u0026lt;span\u0026gt;\n            \u0026lt;h1\u0026gt;aaaa\u0026lt;/h1\u0026gt;\n        \u0026lt;/span\u0026gt;\n    \u0026lt;/a\u0026gt;\n    \u0026lt;b\u0026gt;bbbbb\u0026lt;/b\u0026gt;\n\u0026lt;/p\u0026gt;\n'''\n\n#5、嵌套选择\nprint(soup.head.title.string)\nprint(soup.body.a.string)\n\n#6、子节点、子孙节点\nprint(soup.p.contents) #p下所有子节点\nprint(soup.p.children) #得到一个迭代器,包含p下所有子节点\n\nfor i,child in enumerate(soup.p.children):\n    print(i,child)\n\nprint(soup.p.descendants) #获取子孙节点,p下所有的标签都会选择出来\nfor i,child in enumerate(soup.p.descendants):\n    print(i,child)\n\n#7、父节点、祖先节点\nprint(soup.a.parent) #获取a标签的父节点\nprint(soup.a.parents) #找到a标签所有的祖先节点，父亲的父亲，父亲的父亲的父亲...\n\n#8、兄弟节点\nprint('=====\u0026gt;')\nprint(soup.a.next_sibling) #下一个兄弟\nprint(soup.a.previous_sibling) #上一个兄弟\n\nprint(list(soup.a.next_siblings)) #下面的兄弟们=\u0026gt;生成器对象\nprint(soup.a.previous_siblings) #上面的兄弟们=\u0026gt;生成器对象\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"四-搜索文档树\"\u003e四 搜索文档树\u003c/h3\u003e\n\u003cp\u003e1、五种过滤器\u003c/p\u003e","title":"【爬虫】解析库之beautifulsoup库"},{"content":"腾讯视频vip花了钱，还不能自动升级，要每天签到才涨V力值。升级VIP等级。。实在蛋疼。这次又是云服务器函数，搞了个脚本COOKIES方式登录。测试了40多天。还没到期，感觉自用够了。分享出来吧。。\ncookies获取麻烦点。不过也就一两分钟的事，能用1个月以上就够了。\n准备工具：\nchrome浏览器\nqq或微信登录腾讯云\n腾讯视频会员一枚\n第一步：获取cookies 1.浏览器打开腾讯视频官网：\nhttps://v.qq.com/\n登录VIP账号（这里我就不详写了。应该都会。我是直接手机扫码登录）\n2.VIP账号登录成功后，打开另一个地址：\nhttps://vip.video.qq.com/fcgi-bin/comm_cgi?name=hierarchical_task_system\u0026amp;cmd=2\n（没错这个就是签到地址。APP抓出来的。）\n3.在第二个地址，按下快捷键：CTRL+SHIFT+J\n在\u0026gt;后面输入：document.cookie\n回车，复制【引号里】的内容（脚本要用的cookies）：\npgv_pvi=513475\u0026hellip;\u0026hellip;..（中间打码了。。看个开头和结尾吧）\u0026hellip;. uid=59985555 第二部：腾讯云部署脚本 用qq或者微信登录腾讯云scf(无服务器云函数)\nhttps://console.cloud.tencent.com/scf\n函数服务=》（选择区域，上海，广州，成都，香港，北京）\n=》新建=》空白函数=》函数名称（随便写，比如txsp_qiandao）=》运行环境（python3.6）=》下一步\n复制下面代码：\n# -*- coding: utf8 -*- import requests import re import time from urllib.parse import quote def start(): try: s = requests.session() cookie1 = 'TXSP_COOKIE' this_time = int(round(time.time() * 1000)) login_url = 'https://vip.video.qq.com/fcgi-bin/comm_cgi?name=hierarchical_task_system\u0026amp;cmd=2\u0026amp;_=' + str(this_time) headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.204 Safari/537.36', 'Cookie': cookie1 } res =s.get(login_url,headers=headers).text print('访问结果：'+res) if 'Account Verify Error' in res: print('cookies失效，通知SERVER酱！') requests.get('https://sc.ftqq.com/SCKEY.send?text=' + quote('腾讯视频自动签到失败~'+time.strftime('%Y.%m.%d',time.localtime(time.time()))) +'\u0026amp;desp='+quote('cookies失效，请更新！\\n')) else: print('签到完成') except Exception as e: print(\u0026quot;地址访问失败，通知SERVER酱！\u0026quot;) requests.get('https://sc.ftqq.com/SCKEY.send?text=' + quote('腾讯视频自动签到失败~'+time.strftime('%Y.%m.%d',time.localtime(time.time()))) +'\u0026amp;desp='+quote('异常代码：\\n'+str(e))) def main_handler(event, context): return start() if __name__ == '__main__': start() 代码需要修改的地方3处，\nTXSP_COOKIE （一处，刚才获取的cookies字符串）\nhttps://sc.ftqq.com/SCKEY.send （两处，server酱的地址，申请地址：http://sc.ftqq.com/）\n=》完成。\n=》触发方式=》定时触发 / 名称：txsp_timer / 自定义触发周期（0 0 3 * * * *） / =》保存\n=》函数代码=》测试（如果日志显示\u0026quot;checkin_score\u0026quot;: 5，这个5就是获得的V力值，签到过也会有中文提示）\n=======================其他说明\n1.如果日志里提示：\n{\u0026ldquo;msg\u0026rdquo;:\u0026ldquo;Account Verify Error\u0026rdquo;,\u0026ldquo;ret\u0026rdquo;:-10006}说明COOKIES获取错误。\n2.COOKIE能用多久还不知道，，反正我挂了40多天了。。。验证失败了，也会微信通知，更新下COOKIES就行。\n3.签到分数不保证。。现在大多是10分左右，偶尔50分+。。。这个腾讯的锅~~~就当腾讯云函数来背吧~~~\nEND\n","permalink":"https://zhong-er.com/posts/%E5%88%A9%E7%94%A8%E8%85%BE%E8%AE%AF%E4%BA%91%E5%87%BD%E6%95%B0%E8%AE%BE%E7%BD%AE%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E5%AE%9E%E7%8E%B0%E8%85%BE%E8%AE%AF%E8%A7%86%E9%A2%91%E8%87%AA%E5%8A%A8%E7%AD%BE%E5%88%B0%E8%8E%B7%E5%BE%97v%E5%8A%9B%E5%80%BC/","summary":"\u003cp\u003e腾讯视频vip花了钱，还不能自动升级，要每天签到才涨V力值。升级VIP等级。。实在蛋疼。这次又是云服务器函数，搞了个脚本COOKIES方式登录。测试了40多天。还没到期，感觉自用够了。分享出来吧。。\u003cbr\u003e\ncookies获取麻烦点。不过也就一两分钟的事，能用1个月以上就够了。\u003c/p\u003e\n\u003cp\u003e准备工具：\u003cbr\u003e\nchrome浏览器\u003cbr\u003e\nqq或微信登录腾讯云\u003cbr\u003e\n腾讯视频会员一枚\u003c/p\u003e\n\u003ch1 id=\"第一步获取cookies\"\u003e第一步：获取cookies\u003c/h1\u003e\n\u003cp\u003e1.浏览器打开腾讯视频官网：\u003cbr\u003e\n\u003ca href=\"https://v.qq.com/\"\u003ehttps://v.qq.com/\u003c/a\u003e\u003cbr\u003e\n登录VIP账号（这里我就不详写了。应该都会。我是直接手机扫码登录）\u003c/p\u003e\n\u003cp\u003e2.VIP账号登录成功后，打开另一个地址：\u003cbr\u003e\n\u003ca href=\"https://vip.video.qq.com/fcgi-bin/comm_cgi?name=hierarchical_task_system\u0026amp;cmd=2\"\u003ehttps://vip.video.qq.com/fcgi-bin/comm_cgi?name=hierarchical_task_system\u0026amp;cmd=2\u003c/a\u003e\u003cbr\u003e\n（没错这个就是签到地址。APP抓出来的。）\u003c/p\u003e\n\u003cp\u003e3.在第二个地址，按下快捷键：CTRL+SHIFT+J\u003cbr\u003e\n在\u0026gt;后面输入：document.cookie\u003cbr\u003e\n回车，复制【引号里】的内容（脚本要用的cookies）：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003epgv_pvi=513475\u0026hellip;\u0026hellip;..（中间打码了。。看个开头和结尾吧）\u0026hellip;. uid=59985555\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg alt=\"txspqd.jpg\" loading=\"lazy\" src=\"/assets/201912027759_6945_1.jpg\"\u003e\u003c/p\u003e\n\u003ch1 id=\"第二部腾讯云部署脚本\"\u003e第二部：腾讯云部署脚本\u003c/h1\u003e\n\u003cp\u003e用qq或者微信登录腾讯云scf(无服务器云函数)\u003cbr\u003e\n\u003ca href=\"https://console.cloud.tencent.com/scf\"\u003ehttps://console.cloud.tencent.com/scf\u003c/a\u003e\u003cbr\u003e\n函数服务=》（选择区域，上海，广州，成都，香港，北京）\u003cbr\u003e\n=》新建=》空白函数=》函数名称（随便写，比如txsp_qiandao）=》运行环境（python3.6）=》下一步\u003cbr\u003e\n复制下面代码：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# -*- coding: utf8 -*-\n\nimport requests\nimport re\nimport time\nfrom urllib.parse import quote\n\ndef start():\n    try:\n        s = requests.session()\n\n        cookie1 = 'TXSP_COOKIE'\n\n        this_time = int(round(time.time() * 1000))\n\n        login_url = 'https://vip.video.qq.com/fcgi-bin/comm_cgi?name=hierarchical_task_system\u0026amp;cmd=2\u0026amp;_=' + str(this_time)\n\n        headers={\n            'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.204 Safari/537.36',\n            'Cookie': cookie1\n        }\n\n        res =s.get(login_url,headers=headers).text\n\n        print('访问结果：'+res)\n\n        if 'Account Verify Error' in res:\n            print('cookies失效，通知SERVER酱！')\n            requests.get('https://sc.ftqq.com/SCKEY.send?text=' + quote('腾讯视频自动签到失败~'+time.strftime('%Y.%m.%d',time.localtime(time.time()))) +'\u0026amp;desp='+quote('cookies失效，请更新！\\n'))\n        else:\n            print('签到完成')\n\n    except Exception as e:\n        print(\u0026quot;地址访问失败，通知SERVER酱！\u0026quot;)\n        requests.get('https://sc.ftqq.com/SCKEY.send?text=' + quote('腾讯视频自动签到失败~'+time.strftime('%Y.%m.%d',time.localtime(time.time()))) +'\u0026amp;desp='+quote('异常代码：\\n'+str(e)))\n\n\ndef main_handler(event, context):\n    return start()\n\n\nif __name__ == '__main__':\n    start()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e代码需要修改的地方3处，\u003cbr\u003e\nTXSP_COOKIE （一处，刚才获取的cookies字符串）\u003cbr\u003e\n\u003ca href=\"https://sc.ftqq.com/SCKEY.send\"\u003ehttps://sc.ftqq.com/SCKEY.send\u003c/a\u003e （两处，server酱的地址，申请地址：http://sc.ftqq.com/）\u003c/p\u003e","title":"利用腾讯云函数设置定时任务实现腾讯视频自动签到获得V力值"},{"content":" 1、数据分析是什么 在我们如今这个时代，相信大多数人都能明白数据的重要性，数据就是信息，而数据分析就是可以让我们发挥这些信息功能的重要手段。\n2、数据分析能干什么 对于数据分析能干什么其实我们可以简单的举几个例子：\n1、淘宝可以观察用户的购买记录、搜索记录以及人们在社交媒体上发布的内容选择商品推荐\n2、股票可以根据相应的数据选择买进卖出\n3、今日头条可以将数据分析应用到新闻推送排行算法当中\n4、爱奇艺可以为用户提供个性化电影推荐服务\n其实数据分析不仅可以完成像以上这样的推荐系统，在制药行业也可运用数据分析来预测什么样的化合物更有可能制成高效药物等\n所以说数据分析绝对是未来所有公司不可或缺的岗位，目前社会上获取数据方式太多了，这么多的数据，只要我们拥有数据分析的技能，绝对可以应付任何岗位上的工作。\n3、为什么利用Python进行数据分析 1、Python的代码语法简单易学\n2、Python可以很容易的整合C、C++等语言的代码\n3、Python有大量用于科学计算的库\n4、Python不仅可以用于研究和原型构建，同时也适用于构建生产系统\n4、数据分析过程概述 4.1、提出问题 在真正的工作场景下，往往我们需要的处理的是多个庞大的数据集还有可能是类型完全不同的数据，那这个时候一个准确的问题就可以让我们聚集与问题相关的那部分数据，为后续的分析操作提供一个明确的方向，帮助我们得到一个有意义的结论。\n4.2、整理数据 整理数据主要分为三步：\n(1)、收集数据\n通过多种途径拿到数据，导入到Jupyter Notebook中\n(2)、评估数据\n这一步主要是需要找出数据是否存在质量或者结构等方面的问题\n(3)、清理数据\n通过修改、替换、删除等方式保证数据质量高、结构好\n3.3、探索性数据分析 在这一步骤主要可以探索并且扩充数据\n4.4、得出结论 在进行完探索性数据分析之后肯定会得出一个结果或者说是结论，这样我们就可以根据这样一个结论进行相应的操作，就比如说分析股票数据得到那个大盘趋势好可以选择买进，又或者说类似于万达这样的大型商场可以分析那种类型的商品会比较受用户的欢迎，以便针对性的存货。但是具体的操作可能就需要用到机器学习或者推断统计学来实现，这个就与数据分析不一样了\n4.5、传达结果 分析的能力有多强，分析的价值就有多大。\n这一步主要是向其他人证明你发现的见解以及传达意义\n5、常用库简介 Numpy\nNumpy是Numerical Python的简写，主要可以用来做Python数值计算。它提供了多种数据结构、算法以及大部分涉及Python数值计算所需的接口。\n快速、高效的多维数组对象ndarray\n基于元素的数组计算以及直接对数组执行数学运算的函数\n用于读写硬盘上基于数组的数据集的工具\n线性代数运算、傅里叶变换，以及随机数生成\n用于将C、C++、Fortran代码集成到python的工具\nPandas\nPandas使我们进行数据分析的一个主要工具。它所包含的数据结构和数据处理工具的设计使得Python中进行数据清洗和分析非常快捷。pandas一般也是和其他数值计算工具一起使用的，支持大部分Numpy语言风格的数组计算。pandas和numpy最大的区别就是pandas是用来处理表格型或者异质性数据的，而Numpy则刚好相反，它更适合处理同质型的数值类数组数据\nmatplotlib\nmatplotlib是最流行的用于绘制数据图表的python库。\nScipy\nScipy是科学计算领域针对不同标准问题域的包集合。提供了强大的科学计算方法（矩阵分析、信号分析、数理分析等）\nIPython和Juypyter notebook\nIPython是一个加强版的Python解释器，Juypyter notebook是一种基于Web的代码笔记本，最初也是源于IPython项目。\n","permalink":"https://zhong-er.com/posts/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E4%BB%8B%E7%BB%8D/","summary":"\u003ch3 id=\"sjfxjpg\"\u003e\u003cimg alt=\"sjfx.jpg\" loading=\"lazy\" src=\"/assets/201912025928_8638_1.jpg\"\u003e\u003c/h3\u003e\n\u003ch3 id=\"1数据分析是什么\"\u003e1、数据分析是什么\u003c/h3\u003e\n\u003cp\u003e在我们如今这个时代，相信大多数人都能明白数据的重要性，数据就是信息，而数据分析就是可以让我们发挥这些信息功能的重要手段。\u003c/p\u003e\n\u003ch3 id=\"2数据分析能干什么\"\u003e2、数据分析能干什么\u003c/h3\u003e\n\u003cp\u003e对于数据分析能干什么其实我们可以简单的举几个例子：\u003c/p\u003e\n\u003cp\u003e1、淘宝可以观察用户的购买记录、搜索记录以及人们在社交媒体上发布的内容选择商品推荐\u003c/p\u003e\n\u003cp\u003e2、股票可以根据相应的数据选择买进卖出\u003c/p\u003e\n\u003cp\u003e3、今日头条可以将数据分析应用到新闻推送排行算法当中\u003c/p\u003e\n\u003cp\u003e4、爱奇艺可以为用户提供个性化电影推荐服务\u003c/p\u003e\n\u003cp\u003e其实数据分析不仅可以完成像以上这样的推荐系统，在制药行业也可运用数据分析来预测什么样的化合物更有可能制成高效药物等\u003c/p\u003e\n\u003cp\u003e所以说数据分析绝对是未来所有公司不可或缺的岗位，目前社会上获取数据方式太多了，这么多的数据，只要我们拥有数据分析的技能，绝对可以应付任何岗位上的工作。\u003c/p\u003e\n\u003ch3 id=\"3为什么利用python进行数据分析\"\u003e3、为什么利用Python进行数据分析\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e1、Python的代码语法简单易学\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e2、Python可以很容易的整合C、C++等语言的代码\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e3、Python有大量用于科学计算的库\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e4、Python不仅可以用于研究和原型构建，同时也适用于构建生产系统\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"4数据分析过程概述\"\u003e4、数据分析过程概述\u003c/h3\u003e\n\u003ch5 id=\"41提出问题\"\u003e4.1、提出问题\u003c/h5\u003e\n\u003cp\u003e在真正的工作场景下，往往我们需要的处理的是多个庞大的数据集还有可能是类型完全不同的数据，那这个时候一个准确的问题就可以让我们聚集与问题相关的那部分数据，为后续的分析操作提供一个明确的方向，帮助我们得到一个有意义的结论。\u003c/p\u003e\n\u003ch5 id=\"42整理数据\"\u003e4.2、整理数据\u003c/h5\u003e\n\u003cp\u003e整理数据主要分为三步：\u003c/p\u003e\n\u003cp\u003e(1)、收集数据\u003c/p\u003e\n\u003cp\u003e通过多种途径拿到数据，导入到\u003ccode\u003eJupyter Notebook\u003c/code\u003e中\u003c/p\u003e\n\u003cp\u003e(2)、评估数据\u003c/p\u003e\n\u003cp\u003e这一步主要是需要找出数据是否存在质量或者结构等方面的问题\u003c/p\u003e\n\u003cp\u003e(3)、清理数据\u003c/p\u003e\n\u003cp\u003e通过修改、替换、删除等方式保证数据质量高、结构好\u003c/p\u003e\n\u003ch5 id=\"33探索性数据分析\"\u003e3.3、探索性数据分析\u003c/h5\u003e\n\u003cp\u003e在这一步骤主要可以探索并且扩充数据\u003c/p\u003e\n\u003ch5 id=\"44得出结论\"\u003e4.4、得出结论\u003c/h5\u003e\n\u003cp\u003e在进行完探索性数据分析之后肯定会得出一个结果或者说是结论，这样我们就可以根据这样一个结论进行相应的操作，就比如说分析股票数据得到那个大盘趋势好可以选择买进，又或者说类似于万达这样的大型商场可以分析那种类型的商品会比较受用户的欢迎，以便针对性的存货。但是具体的操作可能就需要用到机器学习或者推断统计学来实现，这个就与数据分析不一样了\u003c/p\u003e\n\u003ch5 id=\"45传达结果\"\u003e4.5、传达结果\u003c/h5\u003e\n\u003cp\u003e分析的能力有多强，分析的价值就有多大。\u003c/p\u003e\n\u003cp\u003e这一步主要是向其他人证明你发现的见解以及传达意义\u003c/p\u003e\n\u003ch3 id=\"5常用库简介\"\u003e5、常用库简介\u003c/h3\u003e\n\u003cblockquote\u003e\n\u003cp\u003eNumpy\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003ca href=\"http://numpy.org/\"\u003eNumpy\u003c/a\u003e是Numerical Python的简写，主要可以用来做Python数值计算。它提供了多种数据结构、算法以及大部分涉及Python数值计算所需的接口。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e快速、高效的多维数组对象ndarray\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e基于元素的数组计算以及直接对数组执行数学运算的函数\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e用于读写硬盘上基于数组的数据集的工具\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e线性代数运算、傅里叶变换，以及随机数生成\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e用于将C、C++、Fortran代码集成到python的工具\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003ePandas\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003ePandas使我们进行数据分析的一个主要工具。它所包含的数据结构和数据处理工具的设计使得Python中进行数据清洗和分析非常快捷。pandas一般也是和其他数值计算工具一起使用的，支持大部分Numpy语言风格的数组计算。pandas和numpy最大的区别就是pandas是用来处理表格型或者异质性数据的，而Numpy则刚好相反，它更适合处理同质型的数值类数组数据\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003ematplotlib\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003ematplotlib是最流行的用于绘制数据图表的python库。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eScipy\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eScipy是科学计算领域针对不同标准问题域的包集合。提供了强大的科学计算方法（矩阵分析、信号分析、数理分析等）\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIPython和Juypyter notebook\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIPython是一个加强版的Python解释器，Juypyter notebook是一种基于Web的代码笔记本，最初也是源于IPython项目。\u003c/p\u003e","title":"数据分析介绍"},{"content":"\n1、Anaconda Anaconda是Python的一个开源的发行版本，里面包含了很多科学计算相关的包，它和Python的关系就像linux系统中centos和Ubuntu的关系一样，不冲突，你可以同时在电脑上安装这两个东西。那至于为什么我已经在电脑上安装了pycharm还要安装这个Anaconda呢，主要有以下几点原因：\n（1）Anaconda附带了一大批常用数据科学包，它附带了conda、Python和 150 多个科学包及其依赖项。因此你可以用Anaconda立即开始处理数据。\n（2）管理包。Anaconda 是在 conda（一个包管理器和环境管理器）上发展出来的。在数据分析中，你会用到很多第三方的包，而conda（包管理器）可以很好的帮助你在计算机上安装和管理这些包，包括安装、卸载和更新包。\n（3）管理环境。为什么需要管理环境呢？比如你在A项目中用到了Python2，而新的项目要求使用Python3，而同时安装两个Python版本可能会造成许多混乱和错误。这时候conda就可以帮助你为不同的项目建立不同的运行环境。还有很多项目使用的包版本不同，比如不同的pandas版本，不可能同时安装两个pandas版本。你要做的应该是在项目对应的环境中创建对应的pandas版本。这时候conda就可以帮你做到。\n总结：Anaconda解决了官方Python的两大痛点：\n（1）提供了包管理功能，Windows平台安装第三方包经常失败的场景得以解决\n（2）提供环境管理功能，解决了多版本Python并存、切换的问题。\n接下来就需要了解Anaconda如何进行安装\n2、下载 Anaconda下载入口 按照上面的路径点击下载，下载完成后就可以一路点击下一步完成安装，如果中间需要修改安装路径可以自己改一下。安装成功之后我们会发现，多出来几个应用\nAnaconda Navigtor ：用于管理工具包和环境的图形用户界面，后续涉及的众多管理命令也可以在 Navigator 中手工实现。\nJupyter notebook ：基于web的交互式计算环境，可以编辑易于人们阅读的文档，用于展示数据分析的过程。\nqtconsole ：一个可执行 IPython 的仿终端图形界面程序，相比 Python Shell 界面，qtconsole 可以直接显示代码生成的图形，实现多行代码输入执行，以及内置许多有用的功能和函数。\nspyder ：一个使用Python语言、跨平台的、科学运算集成开发环境。\n那以上应用我们简单了解一下就好，不需要管。\n3、配置环境变量 3.1、windows 系统 如果是Windows环境的话就需要在控制面板\\系统和安全\\系统\\高级系统设置\\环境变量\\用户变量\\PATH当中添加anaconda的安装目录的Scripts文件夹。\n验证：\n打开命令行输入conda \u0026ndash;version，如果能输出版本号就对了\n3.2、Mac系统 在终端输入以下命令\nexport PATH=~/anaconda/bin:$PATH 4、管理包 在anaconda当中虽然已经存在了很多科学计算的包，但是我们总有需要安装新的包的需求，有了anaconda就可以很好地管理这些包了\n# 管理包在Python解释器中有pip，在anaconda中conda，它的功能和pip是一样的，在anaconda中选择pip也是可以安装包的 1、安装包 conda install requests 2、卸载包 conda remove requests 3、更新包 conda update requests 4、查询已安装的包 conda list 5、管理环境 可以通过conda创建不同的运行环境\n5.1、创建环境 conda create -n program_test pandas 上面这条指令中的program_test是新创建的环境名称，pandas是指需要安装到环境中的包名称。\n当然在安装的时候还可以指定解释器的版本\nconda create -n python36 python=3.6 # 创建解释器版本为3.6的环境 conda create -n python27 python=2.7 # 创建解释器版本为2.7的环境 5.2、进入环境 conda activate program_test 5.3、退出环境 deactive 6、运行Anaconda 打开安装的Anaconda文件\n点击Anaconda Navigator运行\n选择图中圈起来的按钮，会弹出来四个选择，选择最后一个Jupyter Notebook就可以打开代码编辑工具，如果不是用谷歌浏览器打开的，先尝试修改默认浏览器，或者按照第七条的补充内容进行修改\n以上就是我们本机的根目录，就类似于windows系统的User目录，接下来就可以点击右上角的New按钮选择创建一个Python3文件，这个文件的后缀名是.ipynd.\n接下来我们所有的程序都要在这个上面进行编写：\n7、补充 配置Jupyter notebook默认打开的浏览器\n当我们想通过jupyter notebook编辑器来编写代码的时候，有的时候会发现他默认会打开你的IE浏览器，但是平时我们都是使用chrome浏览器的，接下来就一起来看看怎么修改这个东西\n第一步：打开Cmd页面输入activate进入anaconda prompt\n第二步：输入jupyter notebook --generate-config，查看你配置文件的位置\n第三步：通过记事本打开配置文件找到#c.NotebookApp.browser = ''设置\n第四步：在它的下面加上一段代码\nimport webbrowser webbrowser.register(\u0026quot;chrome\u0026quot;,None,webbrowser.GenericBrowser(u\u0026quot;C:\\\\Users\\\\AppData\\\\Local\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe\u0026quot;)) c.NotebookApp.browser = 'chrome' 将其中的路径改为你电脑上chrome启动文件的路径，一定要注意路径中的文件名使用双斜杠分隔的。\n因为大部分数据分析的工作都是需要一步一步做的，不是像我们做Web，搭网站，做后台那样需要大量的代码铺垫。所以说更专业的数据分析操作都是在这个Jupyter Notebook当中完成的。\nEND\n","permalink":"https://zhong-er.com/posts/%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%83%A8%E7%BD%B2/","summary":"\u003cp\u003e\u003cimg alt=\"anaconda.jpg\" loading=\"lazy\" src=\"/assets/201912027338_91_1.jpg\"\u003e\u003c/p\u003e\n\u003ch3 id=\"1anaconda\"\u003e1、Anaconda\u003c/h3\u003e\n\u003cp\u003eAnaconda是Python的一个开源的发行版本，里面包含了很多科学计算相关的包，它和Python的关系就像linux系统中centos和Ubuntu的关系一样，不冲突，你可以同时在电脑上安装这两个东西。那至于为什么我已经在电脑上安装了pycharm还要安装这个Anaconda呢，主要有以下几点原因：\u003c/p\u003e\n\u003cp\u003e（1）Anaconda附带了一大批常用数据科学包，它附带了conda、Python和 150 多个科学包及其依赖项。因此你可以用Anaconda立即开始处理数据。\u003c/p\u003e\n\u003cp\u003e（2）管理包。Anaconda 是在 conda（一个包管理器和环境管理器）上发展出来的。在数据分析中，你会用到很多第三方的包，而conda（包管理器）可以很好的帮助你在计算机上安装和管理这些包，包括安装、卸载和更新包。\u003c/p\u003e\n\u003cp\u003e（3）管理环境。为什么需要管理环境呢？比如你在A项目中用到了Python2，而新的项目要求使用Python3，而同时安装两个Python版本可能会造成许多混乱和错误。这时候conda就可以帮助你为不同的项目建立不同的运行环境。还有很多项目使用的包版本不同，比如不同的pandas版本，不可能同时安装两个pandas版本。你要做的应该是在项目对应的环境中创建对应的pandas版本。这时候conda就可以帮你做到。\u003c/p\u003e\n\u003cp\u003e总结：Anaconda解决了官方Python的两大痛点：\u003c/p\u003e\n\u003cp\u003e（1）提供了包管理功能，Windows平台安装第三方包经常失败的场景得以解决\u003c/p\u003e\n\u003cp\u003e（2）提供环境管理功能，解决了多版本Python并存、切换的问题。\u003c/p\u003e\n\u003cp\u003e接下来就需要了解Anaconda如何进行安装\u003c/p\u003e\n\u003ch3 id=\"2下载\"\u003e2、下载\u003c/h3\u003e\n\u003ch5 id=\"anaconda下载入口\"\u003e\u003ca href=\"https://www.anaconda.com/distribution/#download-section\"\u003eAnaconda下载入口\u003c/a\u003e\u003c/h5\u003e\n\u003cp\u003e\u003cimg alt=\"anacondaxz.jpg\" loading=\"lazy\" src=\"/assets/201912025238_1946.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e按照上面的路径点击下载，下载完成后就可以一路点击下一步完成安装，如果中间需要修改安装路径可以自己改一下。安装成功之后我们会发现，多出来几个应用\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eAnaconda Navigtor ：用于管理工具包和环境的图形用户界面，后续涉及的众多管理命令也可以在 Navigator 中手工实现。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eJupyter notebook ：基于web的交互式计算环境，可以编辑易于人们阅读的文档，用于展示数据分析的过程。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eqtconsole ：一个可执行 IPython 的仿终端图形界面程序，相比 Python Shell 界面，qtconsole 可以直接显示代码生成的图形，实现多行代码输入执行，以及内置许多有用的功能和函数。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003espyder ：一个使用Python语言、跨平台的、科学运算集成开发环境。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e那以上应用我们简单了解一下就好，不需要管。\u003c/p\u003e\n\u003ch3 id=\"3配置环境变量\"\u003e3、配置环境变量\u003c/h3\u003e\n\u003ch5 id=\"31windows-系统\"\u003e3.1、windows 系统\u003c/h5\u003e\n\u003cp\u003e如果是Windows环境的话就需要在\u003ccode\u003e控制面板\\系统和安全\\系统\\高级系统设置\\环境变量\\用户变量\\PATH\u003c/code\u003e当中添加anaconda的安装目录的Scripts文件夹。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e验证：\u003cbr\u003e\n打开命令行输入conda \u0026ndash;version，如果能输出版本号就对了\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch5 id=\"32mac系统\"\u003e3.2、Mac系统\u003c/h5\u003e\n\u003cp\u003e在终端输入以下命令\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eexport PATH=~/anaconda/bin:$PATH\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"4管理包\"\u003e4、管理包\u003c/h3\u003e\n\u003cp\u003e在anaconda当中虽然已经存在了很多科学计算的包，但是我们总有需要安装新的包的需求，有了anaconda就可以很好地管理这些包了\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 管理包在Python解释器中有pip，在anaconda中conda，它的功能和pip是一样的，在anaconda中选择pip也是可以安装包的\n1、安装包\nconda install requests\n2、卸载包\nconda remove requests\n3、更新包\nconda update requests\n4、查询已安装的包\nconda list\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"5管理环境\"\u003e5、管理环境\u003c/h3\u003e\n\u003cp\u003e可以通过conda创建不同的运行环境\u003c/p\u003e","title":"数据分析开发环境部署"},{"content":" 一、介绍 selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题 selenium本质是通过驱动浏览器，完全模拟浏览器的操作，比如跳转、输入、点击、下拉等，来拿到网页渲染之后的结果，可支持多种浏览器 from selenium import webdriver browser=webdriver.Chrome() browser=webdriver.Firefox() browser=webdriver.PhantomJS() browser=webdriver.Safari() browser=webdriver.Edge() 官网：http://selenium-python.readthedocs.io\n二、安装 1.有界面浏览器\nselenium+chromedriver\n#安装：selenium+chromedriver pip3 install selenium 下载chromdriver.exe放到python安装路径的scripts目录中即可，注意最新版本是2.38，并非2.9 国内镜像网站地址：http://npm.taobao.org/mirrors/chromedriver/2.38/ 最新的版本去官网找:https://sites.google.com/a/chromium.org/chromedriver/downloads #验证安装 C:\\Users\\Administrator\u0026gt;python3 Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32 Type \u0026quot;help\u0026quot;, \u0026quot;copyright\u0026quot;, \u0026quot;credits\u0026quot; or \u0026quot;license\u0026quot; for more information. \u0026gt;\u0026gt;\u0026gt; from selenium import webdriver \u0026gt;\u0026gt;\u0026gt; driver=webdriver.Chrome() #弹出浏览器 \u0026gt;\u0026gt;\u0026gt; driver.get('https://www.baidu.com') \u0026gt;\u0026gt;\u0026gt; driver.page_source #注意： selenium3默认支持的webdriver是Firfox，而Firefox需要安装geckodriver 下载链接：https://github.com/mozilla/geckodriver/releases selenium+chromedriver 2.无界面浏览器\nselenium+phantomjs\nPhantomJS不再更新\n#安装：selenium+phantomjs pip3 install selenium 下载phantomjs，解压后把phantomjs.exe所在的bin目录放到环境变量 下载链接：http://phantomjs.org/download.html #验证安装 C:\\Users\\Administrator\u0026gt;phantomjs phantomjs\u0026gt; console.log('egon gaga') egon gaga undefined phantomjs\u0026gt; ^C C:\\Users\\Administrator\u0026gt;python3 Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32 Type \u0026quot;help\u0026quot;, \u0026quot;copyright\u0026quot;, \u0026quot;credits\u0026quot; or \u0026quot;license\u0026quot; for more information. \u0026gt;\u0026gt;\u0026gt; from selenium import webdriver \u0026gt;\u0026gt;\u0026gt; driver=webdriver.PhantomJS() #无界面浏览器 \u0026gt;\u0026gt;\u0026gt; driver.get('https://www.baidu.com') \u0026gt;\u0026gt;\u0026gt; driver.page_source selenium+phantomjs 在 PhantomJS 年久失修, 后继无人的节骨眼\nChrome 出来救场, 再次成为了反爬虫 Team 的噩梦\n自Google 发布 chrome 59 / 60 正式版 开始便支持Headless mode\n这意味着在无 GUI 环境下, PhantomJS 不再是唯一选择\nselenium+谷歌浏览器headless模式\n#selenium:3.12.0 #webdriver:2.38 #chrome.exe: 65.0.3325.181（正式版本） （32 位） from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument('window-size=1920x3000') #指定浏览器分辨率 chrome_options.add_argument('--disable-gpu') #谷歌文档提到需要加上这个属性来规避bug chrome_options.add_argument('--hide-scrollbars') #隐藏滚动条, 应对一些特殊页面 chrome_options.add_argument('blink-settings=imagesEnabled=false') #不加载图片, 提升速度 chrome_options.add_argument('--headless') #浏览器不提供可视化页面. linux下如果系统不支持可视化不加这条会启动失败 chrome_options.binary_location = r\u0026quot;C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe\u0026quot; #手动指定使用的浏览器位置 driver=webdriver.Chrome(chrome_options=chrome_options) driver.get('https://www.baidu.com') print('hao123' in driver.page_source) driver.close() #切记关闭浏览器，回收资源 selenium+谷歌浏览器headless模式 三、基本使用 from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素 browser=webdriver.Chrome() try: browser.get('https://www.baidu.com') ``` input_tag=browser.find_element_by_id('kw') input_tag.send_keys('美女') #python2中输入中文错误，字符串前加个u input_tag.send_keys(Keys.ENTER) #输入回车 ``` wait=WebDriverWait(browser,10) wait.until(EC.presence_of_element_located((By.ID,'content_left'))) #等到id为content_left的元素加载完毕,最多等10秒 ``` print(browser.page_source) print(browser.current_url) print(browser.get_cookies()) ``` finally: browser.close() 四、选择器 1.基本用法\n#官网链接：http://selenium-python.readthedocs.io/locating-elements.html from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素 import time driver=webdriver.Chrome() driver.get('https://www.baidu.com') wait=WebDriverWait(driver,10) try: #===============所有方法=================== # 1、find_element_by_id # 2、find_element_by_link_text # 3、find_element_by_partial_link_text # 4、find_element_by_tag_name # 5、find_element_by_class_name # 6、find_element_by_name # 7、find_element_by_css_selector # 8、find_element_by_xpath # 强调： # 1、上述均可以改写成find_element(By.ID,'kw')的形式 # 2、find_elements_by_xxx的形式是查找到多个元素，结果为列表 ``` #===============示范用法=================== # 1、find_element_by_id print(driver.find_element_by_id('kw')) # 2、find_element_by_link_text # login=driver.find_element_by_link_text('登录') # login.click() # 3、find_element_by_partial_link_text login=driver.find_elements_by_partial_link_text('录')[0] login.click() # 4、find_element_by_tag_name print(driver.find_element_by_tag_name('a')) # 5、find_element_by_class_name button=wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'tang-pass-footerBarULogin'))) button.click() # 6、find_element_by_name input_user=wait.until(EC.presence_of_element_located((By.NAME,'userName'))) input_pwd=wait.until(EC.presence_of_element_located((By.NAME,'password'))) commit=wait.until(EC.element_to_be_clickable((By.ID,'TANGRAM__PSP_10__submit'))) input_user.send_keys('18611453110') input_pwd.send_keys('xxxxxx') commit.click() # 7、find_element_by_css_selector driver.find_element_by_css_selector('#kw') # 8、find_element_by_xpath ``` time.sleep(5) finally: driver.close() 2.XPATH\n#官网链接：http://selenium-python.readthedocs.io/locating-elements.html from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素 import time driver=webdriver.PhantomJS() driver.get('https://doc.scrapy.org/en/latest/_static/selectors-sample1.html') # wait=WebDriverWait(driver,3) driver.implicitly_wait(3) #使用隐式等待 try: # find_element_by_xpath #//与/ # driver.find_element_by_xpath('//body/a') # 开头的//代表从整篇文档中寻找,body之后的/代表body的儿子，这一行找不到就会报错了 driver.find_element_by_xpath('//body//a') # 开头的//代表从整篇文档中寻找,body之后的//代表body的子子孙孙 driver.find_element_by_css_selector('body a') ``` #取第n个 res1=driver.find_elements_by_xpath('//body//a[1]') #取第一个a标签 print(res1[0].text) #按照属性查找,下述三者查找效果一样 res1=driver.find_element_by_xpath('//a[5]') res2=driver.find_element_by_xpath('//a[@href=\u0026quot;image5.html\u0026quot;]') res3=driver.find_element_by_xpath('//a[contains(@href,\u0026quot;image5\u0026quot;)]') #模糊查找 print('==\u0026gt;', res1.text) print('==\u0026gt;',res2.text) print('==\u0026gt;',res3.text) ``` ``` #其他 res1=driver.find_element_by_xpath('/html/body/div/a') print(res1.text) res2=driver.find_element_by_xpath('//a[img/@src=\u0026quot;image3_thumb.jpg\u0026quot;]') #找到子标签img的src属性为image3_thumb.jpg的a标签 print(res2.tag_name,res2.text) res3 = driver.find_element_by_xpath(\u0026quot;//input[@name='continue'][@type='button']\u0026quot;) #查看属性name为continue且属性type为button的input标签 res4 = driver.find_element_by_xpath(\u0026quot;//*[@name='continue'][@type='button']\u0026quot;) #查看属性name为continue且属性type为button的所有标签 ``` ``` time.sleep(5) ``` finally: driver.close() 详解\ndoc=''' \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;base href='http://example.com/' /\u0026gt; \u0026lt;title\u0026gt;Example website\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id='images'\u0026gt; \u0026lt;a href='image1.html'\u0026gt;Name: My image 1 \u0026lt;br /\u0026gt;\u0026lt;img src='image1_thumb.jpg' /\u0026gt;\u0026lt;/a\u0026gt; \u0026lt;a href='image2.html'\u0026gt;Name: My image 2 \u0026lt;br /\u0026gt;\u0026lt;img src='image2_thumb.jpg' /\u0026gt;\u0026lt;/a\u0026gt; \u0026lt;a href='image3.html'\u0026gt;Name: My image 3 \u0026lt;br /\u0026gt;\u0026lt;img src='image3_thumb.jpg' /\u0026gt;\u0026lt;/a\u0026gt; \u0026lt;a href='image4.html'\u0026gt;Name: My image 4 \u0026lt;br /\u0026gt;\u0026lt;img src='image4_thumb.jpg' /\u0026gt;\u0026lt;/a\u0026gt; \u0026lt;a href='image5.html' class='li li-item' name='items'\u0026gt;Name: My image 5 \u0026lt;br /\u0026gt;\u0026lt;img src='image5_thumb.jpg' /\u0026gt;\u0026lt;/a\u0026gt; \u0026lt;a href='image6.html' name='items'\u0026gt;\u0026lt;span\u0026gt;\u0026lt;h5\u0026gt;test\u0026lt;/h5\u0026gt;\u0026lt;/span\u0026gt;Name: My image 6 \u0026lt;br /\u0026gt;\u0026lt;img src='image6_thumb.jpg' /\u0026gt;\u0026lt;/a\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; ''' from lxml import etree html=etree.HTML(doc) # html=etree.parse('search.html',etree.HTMLParser()) # 1 所有节点 # a=html.xpath('//*') # 2 指定节点（结果为列表） # a=html.xpath('//head') # 3 子节点，子孙节点 # a=html.xpath('//div/a') # a=html.xpath('//body/a') #无数据 # a=html.xpath('//body//a') # 4 父节点 # a=html.xpath('//body//a[@href=\u0026quot;image1.html\u0026quot;]/..') # a=html.xpath('//body//a[1]/..') # 也可以这样 # a=html.xpath('//body//a[1]/parent::*') # 5 属性匹配 # a=html.xpath('//body//a[@href=\u0026quot;image1.html\u0026quot;]') # 6 文本获取 # a=html.xpath('//body//a[@href=\u0026quot;image1.html\u0026quot;]/text()') # 7 属性获取 # a=html.xpath('//body//a/@href') # # 注意从1 开始取（不是从0） # a=html.xpath('//body//a[1]/@href') # 8 属性多值匹配 # a 标签有多个class类，直接匹配就不可以了，需要用contains # a=html.xpath('//body//a[@class=\u0026quot;li\u0026quot;]') # a=html.xpath('//body//a[contains(@class,\u0026quot;li\u0026quot;)]') # a=html.xpath('//body//a[contains(@class,\u0026quot;li\u0026quot;)]/text()') # 9 多属性匹配 # a=html.xpath('//body//a[contains(@class,\u0026quot;li\u0026quot;) or @name=\u0026quot;items\u0026quot;]') # a=html.xpath('//body//a[contains(@class,\u0026quot;li\u0026quot;) and @name=\u0026quot;items\u0026quot;]/text()') # # a=html.xpath('//body//a[contains(@class,\u0026quot;li\u0026quot;)]/text()') # 10 按序选择 # a=html.xpath('//a[2]/text()') # a=html.xpath('//a[2]/@href') # 取最后一个 # a=html.xpath('//a[last()]/@href') # 位置小于3的 # a=html.xpath('//a[position()\u0026lt;3]/@href') # 倒数第二个 # a=html.xpath('//a[last()-2]/@href') # 11 节点轴选择 # ancestor：祖先节点 # 使用了* 获取所有祖先节点 # a=html.xpath('//a/ancestor::*') # # 获取祖先节点中的div # a=html.xpath('//a/ancestor::div') # attribute：属性值 # a=html.xpath('//a[1]/attribute::*') # child：直接子节点 # a=html.xpath('//a[1]/child::*') # descendant：所有子孙节点 # a=html.xpath('//a[6]/descendant::*') # following:当前节点之后所有节点 # a=html.xpath('//a[1]/following::*') # a=html.xpath('//a[1]/following::*[1]/@href') # following-sibling:当前节点之后同级节点 # a=html.xpath('//a[1]/following-sibling::*') # a=html.xpath('//a[1]/following-sibling::a') # a=html.xpath('//a[1]/following-sibling::*[2]') # a=html.xpath('//a[1]/following-sibling::*[2]/@href') # print(a) 3.获取标签属性\nfrom selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素 browser=webdriver.Chrome() browser.get('https://www.amazon.cn/') wait=WebDriverWait(browser,10) wait.until(EC.presence_of_element_located((By.ID,'cc-lm-tcgShowImgContainer'))) tag=browser.find_element(By.CSS_SELECTOR,'#cc-lm-tcgShowImgContainer img') #获取标签属性， print(tag.get_attribute('src')) #获取标签ID，位置，名称，大小（了解） print(tag.id) print(tag.location) print(tag.tag_name) print(tag.size) browser.close() 五、等待元素被加载 #1、selenium只是模拟浏览器的行为，而浏览器解析页面是需要时间的（执行css，js），一些元素可能需要过一段时间才能加载出来，为了保证能查找到元素，必须等待 #2、等待的方式分两种： 隐式等待：在browser.get（'xxx'）前就设置，针对所有元素有效 显式等待：在browser.get（'xxx'）之后设置，只针对某个元素有效 隐式等待\nfrom selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素 browser=webdriver.Chrome() #隐式等待:在查找所有元素时，如果尚未被加载，则等10秒 browser.implicitly_wait(10) browser.get('https://www.baidu.com') input_tag=browser.find_element_by_id('kw') input_tag.send_keys('美女') input_tag.send_keys(Keys.ENTER) contents=browser.find_element_by_id('content_left') #没有等待环节而直接查找，找不到则会报错 print(contents) browser.close() 显式等待\nfrom selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素 browser=webdriver.Chrome() browser.get('https://www.baidu.com') input_tag=browser.find_element_by_id('kw') input_tag.send_keys('美女') input_tag.send_keys(Keys.ENTER) #显式等待：显式地等待某个元素被加载 wait=WebDriverWait(browser,10) wait.until(EC.presence_of_element_located((By.ID,'content_left'))) contents=browser.find_element(By.CSS_SELECTOR,'#content_left') print(contents) browser.close() 六、元素交互操作 点击、清空\nfrom selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素 browser=webdriver.Chrome() browser.get('https://www.amazon.cn/') wait=WebDriverWait(browser,10) input_tag=wait.until(EC.presence_of_element_located((By.ID,'twotabsearchtextbox'))) input_tag.send_keys('iphone 8') button=browser.find_element_by_css_selector('#nav-search \u0026gt; form \u0026gt; div.nav-right \u0026gt; div \u0026gt; input') button.click() import time time.sleep(3) input_tag=browser.find_element_by_id('twotabsearchtextbox') input_tag.clear() #清空输入框 input_tag.send_keys('iphone7plus') button=browser.find_element_by_css_selector('#nav-search \u0026gt; form \u0026gt; div.nav-right \u0026gt; div \u0026gt; input') button.click() # browser.close() Action Chains\nfrom selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By # 按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys # 键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait # 等待页面加载某些元素 import time driver = webdriver.Chrome() driver.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable') wait=WebDriverWait(driver,3) # driver.implicitly_wait(3) # 使用隐式等待 try: driver.switch_to.frame('iframeResult') ##切换到iframeResult sourse=driver.find_element_by_id('draggable') target=driver.find_element_by_id('droppable') ``` #方式一：基于同一个动作链串行执行 # actions=ActionChains(driver) #拿到动作链对象 # actions.drag_and_drop(sourse,target) #把动作放到动作链中，准备串行执行 # actions.perform() #方式二：不同的动作链，每次移动的位移都不同 ``` ActionChains(driver).click_and_hold(sourse).perform() distance=target.location['x']-sourse.location['x'] ``` track=0 while track \u0026lt; distance: ActionChains(driver).move_by_offset(xoffset=2,yoffset=0).perform() track+=2 ActionChains(driver).release().perform() time.sleep(10) ``` finally: driver.close() 在交互动作比较难实现的时候可以自己写JS（万能方法）\nfrom selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素 try: browser=webdriver.Chrome() browser.get('https://www.baidu.com') browser.execute_script('alert(\u0026quot;hello world\u0026quot;)') #打印警告 finally: browser.close() 补充:frame的切换\n#frame相当于一个单独的网页，在父frame里是无法直接查看到子frame的元素的，必须switch_to_frame切到该frame下，才能进一步查找 from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素 try: browser=webdriver.Chrome() browser.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable') ``` browser.switch_to.frame('iframeResult') #切换到id为iframeResult的frame ``` tag1=browser.find_element_by_id('droppable') print(tag1) ``` # tag2=browser.find_element_by_id('textareaCode') #报错，在子frame里无法查看到父frame的元素 browser.switch_to.parent_frame() #切回父frame,就可以查找到了 tag2=browser.find_element_by_id('textareaCode') print(tag2) ``` finally: browser.close() 七、其他 模拟浏览器的前进后退\n#模拟浏览器的前进后退 import time from selenium import webdriver browser=webdriver.Chrome() browser.get('https://www.baidu.com') browser.get('https://www.taobao.com') browser.get('http://www.sina.com.cn/') browser.back() time.sleep(10) browser.forward() browser.close() cookies\n#cookies from selenium import webdriver browser=webdriver.Chrome() browser.get('https://www.zhihu.com/explore') print(browser.get_cookies()) browser.add_cookie({'k1':'xxx','k2':'yyy'}) print(browser.get_cookies()) # browser.delete_all_cookies() 选项卡管理\n#选项卡管理：切换选项卡，有js的方式windows.open,有windows快捷键：ctrl+t等，最通用的就是js的方式 import time from selenium import webdriver browser=webdriver.Chrome() browser.get('https://www.baidu.com') browser.execute_script('window.open()') print(browser.window_handles) #获取所有的选项卡 browser.switch_to_window(browser.window_handles[1]) browser.get('https://www.taobao.com') time.sleep(10) browser.switch_to_window(browser.window_handles[0]) browser.get('https://www.sina.com.cn') browser.close() 异常处理\nfrom selenium import webdriver from selenium.common.exceptions import TimeoutException,NoSuchElementException,NoSuchFrameException try: browser=webdriver.Chrome() browser.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable') browser.switch_to.frame('iframssseResult') except TimeoutException as e: print(e) except NoSuchFrameException as e: print(e) finally: browser.close() 八、项目练习 自动登录163邮箱并发送邮件\n#注意：网站都策略都是在不断变化的，精髓在于学习流程。下述代码生效与2017-11-7，不能保证永久有效 from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait browser=webdriver.Chrome() try: browser.get('http://mail.163.com/') ``` wait=WebDriverWait(browser,5) frame=wait.until(EC.presence_of_element_located((By.ID,'x-URS-iframe'))) browser.switch_to.frame(frame) wait.until(EC.presence_of_element_located((By.CLASS_NAME,'m-container'))) inp_user=browser.find_element_by_name('email') inp_pwd=browser.find_element_by_name('password') button=browser.find_element_by_id('dologin') inp_user.send_keys('18611453110') inp_pwd.send_keys('xxxx') button.click() #如果遇到验证码，可以把下面一小段打开注释 # import time # time.sleep(10) # button = browser.find_element_by_id('dologin') # button.click() ``` wait.until(EC.presence_of_element_located((By.ID,'dvNavTop'))) write_msg=browser.find_elements_by_css_selector('#dvNavTop li')[1] #获取第二个li标签就是“写信”了 write_msg.click() ``` wait.until(EC.presence_of_element_located((By.CLASS_NAME,'tH0'))) recv_man=browser.find_element_by_class_name('nui-editableAddr-ipt') title=browser.find_element_by_css_selector('.dG0 .nui-ipt-input') recv_man.send_keys('378533872@qq.com') title.send_keys('圣旨') print(title.tag_name) ``` ``` frame=wait.until(EC.presence_of_element_located((By.CLASS_NAME,'APP-editor-iframe'))) browser.switch_to.frame(frame) body=browser.find_element(By.CSS_SELECTOR,'body') body.send_keys('egon很帅，可以加工资了') browser.switch_to.parent_frame() #切回他爹 send_button=browser.find_element_by_class_name('nui-toolbar-item') send_button.click() #可以睡时间久一点别让浏览器关掉，看看发送成功没有 import time time.sleep(10000) ``` except Exception as e: print(e) finally: browser.close() 爬取京东商城商品信息\nfrom selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找，By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #键盘按键操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素 import time def get_goods(driver): try: goods=driver.find_elements_by_class_name('gl-item') ``` for good in goods: detail_url=good.find_element_by_tag_name('a').get_attribute('href') p_name=good.find_element_by_css_selector('.p-name em').text.replace('\\n','') price=good.find_element_by_css_selector('.p-price i').text p_commit=good.find_element_by_css_selector('.p-commit a').text msg = ''' 商品 : %s 链接 : %s 价钱 ：%s 评论 ：%s ''' % (p_name,detail_url,price,p_commit) print(msg,end='\\n\\n') button=driver.find_element_by_partial_link_text('下一页') button.click() time.sleep(1) get_goods(driver) except Exception: pass ``` def spider(url,keyword): driver = webdriver.Chrome() driver.get(url) driver.implicitly_wait(3) # 使用隐式等待 try: input_tag=driver.find_element_by_id('key') input_tag.send_keys(keyword) input_tag.send_keys(Keys.ENTER) get_goods(driver) finally: driver.close() if __name__ == '__main__': spider('https://www.jd.com/',keyword='iPhone8手机') END\n","permalink":"https://zhong-er.com/posts/%E7%88%AC%E8%99%AB%E8%AF%B7%E6%B1%82%E5%BA%93%E4%B9%8Bselenium%E5%BA%93/","summary":"\u003ch1 id=\"爬虫png\"\u003e\u003cimg alt=\"爬虫.png\" loading=\"lazy\" src=\"/assets/202009031721_5943.png\"\u003e\u003c/h1\u003e\n\u003ch1 id=\"一介绍\"\u003e一、介绍\u003c/h1\u003e\n\u003cpre\u003e\u003ccode\u003eselenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题\n\nselenium本质是通过驱动浏览器，完全模拟浏览器的操作，比如跳转、输入、点击、下拉等，来拿到网页渲染之后的结果，可支持多种浏览器\n\nfrom selenium import webdriver\nbrowser=webdriver.Chrome()\nbrowser=webdriver.Firefox()\nbrowser=webdriver.PhantomJS()\nbrowser=webdriver.Safari()\nbrowser=webdriver.Edge()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ca href=\"http://selenium-python.readthedocs.io/\"\u003e官网：http://selenium-python.readthedocs.io\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"二安装\"\u003e二、安装\u003c/h1\u003e\n\u003cp\u003e1.有界面浏览器\u003c/p\u003e\n\u003cp\u003eselenium+chromedriver\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e#安装：selenium+chromedriver\npip3 install selenium\n下载chromdriver.exe放到python安装路径的scripts目录中即可，注意最新版本是2.38，并非2.9\n国内镜像网站地址：http://npm.taobao.org/mirrors/chromedriver/2.38/\n最新的版本去官网找:https://sites.google.com/a/chromium.org/chromedriver/downloads\n\n#验证安装\nC:\\Users\\Administrator\u0026gt;python3\nPython 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32\nType \u0026quot;help\u0026quot;, \u0026quot;copyright\u0026quot;, \u0026quot;credits\u0026quot; or \u0026quot;license\u0026quot; for more information.\n\u0026gt;\u0026gt;\u0026gt; from selenium import webdriver\n\u0026gt;\u0026gt;\u0026gt; driver=webdriver.Chrome() #弹出浏览器\n\u0026gt;\u0026gt;\u0026gt; driver.get('https://www.baidu.com')\n\u0026gt;\u0026gt;\u0026gt; driver.page_source\n\n#注意：\nselenium3默认支持的webdriver是Firfox，而Firefox需要安装geckodriver\n下载链接：https://github.com/mozilla/geckodriver/releases\n\nselenium+chromedriver\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2.无界面浏览器\u003c/p\u003e\n\u003cp\u003eselenium+phantomjs\u003c/p\u003e\n\u003cp\u003ePhantomJS不再更新\u003c/p\u003e","title":"【爬虫】请求库之selenium库"},{"content":"\n一、爬虫是什么？ #1、什么是互联网？ 互联网是由网络设备（网线，路由器，交换机，防火墙等等）和一台台计算机连接而成，像一张网一样。 #2、互联网建立的目的？ 互联网的核心价值在于数据的共享/传递：数据是存放于一台台计算机上的，而将计算机互联到一起的目的就是为了能够方便彼此之间的数据共享/传递，否则你只能拿U盘去别人的计算机上拷贝数据了。 #3、什么是上网？爬虫要做的是什么？ 我们所谓的上网便是由用户端计算机发送请求给目标计算机，将目标计算机的数据下载到本地的过程。 #3.1 只不过，用户获取网络数据的方式是： 浏览器提交请求-\u0026gt;下载网页代码-\u0026gt;解析/渲染成页面。 ``` #3.2 而爬虫程序要做的就是： 模拟浏览器发送请求-\u0026gt;下载网页代码-\u0026gt;只提取有用的数据-\u0026gt;存放于数据库或文件中 #3.1与3.2的区别在于: ``` 我们的爬虫程序只提取网页代码中对我们有用的数据 #4、总结爬虫 #4.1 爬虫的比喻： 如果我们把互联网比作一张大的蜘蛛网，那一台计算机上的数据便是蜘蛛网上的一个猎物，而爬虫程序就是一只小蜘蛛，沿着蜘蛛网抓取自己想要的猎物/数据 ``` #4.2 爬虫的定义： ``` 向网站发起请求，获取资源后分析并提取有用数据的程序 ``` #4.3 爬虫的价值： 互联网中最有价值的便是数据，比如天猫商城的商品信息，链家网的租房信息，雪球网的证券投资信息等等，这些数据都代表了各个行业的真金白银，可以说，谁掌握了行业内的第一手数据，谁就成了整个行业的主宰，如果把整个互联网的数据比喻为一座宝藏，那我们的爬虫课程就是来教大家如何来高效地挖掘这些宝藏，掌握了爬虫技能，你就成了所有互联网信息公司幕后的老板，换言之，它们都在免费为你提供有价值的数据。 ``` 二、爬虫的基本流程 #1、发起请求 使用http库向目标站点发起请求，即发送一个Request Request包含：请求头、请求体等 #2、获取响应内容 如果服务器能正常响应，则会得到一个Response Response包含：html，json，图片，视频等 #3、解析内容 解析html数据：正则表达式，第三方解析库如Beautifulsoup，pyquery等 解析json数据：json模块 解析二进制数据:以b的方式写入文件 #4、保存数据 数据库 文件 三、请求与响应 #http协议：http://www.cnblogs.com/linhaifeng/articles/8243379.html #Request：用户将自己的信息通过浏览器（socket client）发送给服务器（socket server） #Response：服务器接收请求，分析用户发来的请求信息，然后返回数据（返回的数据中可能包含其他链接，如：图片，js，css等） #ps：浏览器在接收Response后，会解析其内容来显示给用户，而爬虫程序在模拟浏览器发送请求然后接收Response后，是要提取其中的有用数据。 四、Request #1、请求方式： 常用的请求方式：GET，POST 其他请求方式：HEAD，PUT，DELETE，OPTHONS ``` ps：用浏览器演示get与post的区别，（用登录演示post） post与get请求最终都会拼接成这种形式：k1=xxx\u0026amp;k2=yyy\u0026amp;k3=zzz post请求的参数放在请求体内： 可用浏览器查看，存放于form data内 get请求的参数直接放在url后 ``` #2、请求url url全称统一资源定位符，如一个网页文档，一张图片 一个视频等都可以用url唯一来确定 ``` url编码 https://www.baidu.com/s?wd=图片 图片会被编码（看示例代码） ``` ``` 网页的加载过程是： 加载一个网页，通常都是先加载document文档， 在解析document文档的时候，遇到链接，则针对超链接发起下载图片的请求 ``` #3、请求头 User-agent：请求头中如果没有user-agent客户端配置， 服务端可能将你当做一个非法用户 host cookies：cookie用来保存登录信息 ``` 一般做爬虫都会加上请求头 ``` #4、请求体 如果是get方式，请求体没有内容 如果是post方式，请求体是format data ``` ps： 1、登录窗口，文件上传等，信息都会被附加到请求体内 2、登录，输入错误的用户名密码，然后提交，就可以看到post，正确登录后页面通常会跳转，无法捕捉到post ``` from urllib.parse import urlencode import requests headers={ 'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Cookie':'H_WISE_SIDS=101556_115474_115442_114743_108373_100099_115725_106201_107320_115339_114797_115553_116093_115546_115625_115446_114329_115350_114275_116040_110085; PSTM=1494300712; BAIDUID=42FE2934E37AF7AD1FA31D8CC7006D45:FG=1; BIDUPSID=2996557DB2710279BD865C50F9A68615; MCITY=-%3A; __cfduid=da9f97dea6458ca26aa4278280752ebb01508939712; BDSFRCVID=PGLsJeCCxG3wt_3ZUrBLDfv2D_qBZSjAgcEe3J; H_BDCLCKID_SF=tJAOoCLytI03qn5zq4Oh-4oHhxoJq5QxbT7Z0l8KtfcNVJQs-lCMhbtp-l3GJPoLWK6hBKQmWIQHDnbsbq0M2tcQXR5-WROCte74KKJx-4PWeIJo5tKh04JbhUJiB5OLBan7Lq7xfDDbbDtmej_3-PC3ql6354Rj2C_X3b7EfKjIOtO_bfbT2MbyeqrNQlTkLIvXoITJQD_bEP3Fbfj2DPQ3KabZqjDjJbue_I05f-oqebT4btbMqRtthf5KeJ3KaKrKW5rJabC3hPJeKU6qLT5Xjh6B5qDfyDoAbKOt-IOjhb5hMpnx-p0njxQyaR3RL2Kj0p_EWpcxsCQqLUonDh8L3H7MJUntKjnRonTO5hvvhb6O3M7-XpOhDG0fJjtJJbksQJ5e24oqHP-kKPrV-4oH5MQy5toyHD7yWCvjWlT5OR5Jj6KMjMkb3xbz2fcpMIrjob8M5CQESInv3MA--fcLD2ch5-3eQgTI3fbIJJjWsq0x0-jle-bQypoa-U0j2COMahkMal7xO-QO05CaD53yDNDqtjn-5TIX_CjJbnA_Hn7zepoxebtpbt-qJJjzMerW_Mc8QUJBH4tR-T3keh-83xbnBT5KaKO2-RnPXbcWjt_lWh_bLf_kQN3TbxuO5bRiL66I0h6jDn3oyT3VXp0n54nTqjDHfRuDVItXf-L_qtDk-PnVeUP3DhbZKxtqtDKjXJ7X2fclHJ7z-R3IBPCD0tjk-6JnWncKaRcI3poiqKtmjJb6XJkl2HQ405OT-6-O0KJcbRodobAwhPJvyT8DXnO7-fRTfJuJ_DDMJDD3fP36q4QV-JIehmT22jnT32JeaJ5n0-nnhP3mBTbA3JDYX-Oh-jjRX56GhfO_0R3jsJKRy66jK4JKjHKet6vP; ispeed_lsm=0; H_PS_PSSID=1421_24558_21120_17001_24880_22072; BD_UPN=123253; H_PS_645EC=44be6I1wqYYVvyugm2gc3PK9PoSa26pxhzOVbeQrn2rRadHvKoI%2BCbN5K%2Bg; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598', 'Host':'www.baidu.com', 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'} # response=requests.get('https://www.baidu.com/s?'+urlencode({'wd':'美女'}),headers=headers) response=requests.get('https://www.baidu.com/s',params={'wd':'美女'},headers=headers) #params内部就是调用urlencode print(response.text) 五、Response #1、响应状态 200：代表成功 301：代表跳转 404：文件不存在 403：权限 502：服务器错误 #2、Respone header set-cookie：可能有多个，是来告诉浏览器，把cookie保存下来 #3、preview就是网页源代码 最主要的部分，包含了请求资源的内容 如网页html，图片 二进制数据等 六、总结 #1、总结爬虫流程： 爬取---\u0026gt;解析---\u0026gt;存储 #2、爬虫所需工具： 请求库：requests,selenium 解析库：正则，beautifulsoup，pyquery 存储库：文件，MySQL，Mongodb，Redis #3、爬虫常用框架： scrapy 爬取校花网视频\nimport requests import re import time import hashlib def get_page(url): print('GET %s' %url) try: response=requests.get(url) if response.status_code == 200: return response.content except Exception: pass def parse_index(res): obj=re.compile('class=\u0026quot;items.*?\u0026lt;a href=\u0026quot;(.*?)\u0026quot;',re.S) detail_urls=obj.findall(res.decode('gbk')) for detail_url in detail_urls: if not detail_url.startswith('http'): detail_url='http://www.xiaohuar.com'+detail_url yield detail_url def parse_detail(res): obj=re.compile('id=\u0026quot;media\u0026quot;.*?src=\u0026quot;(.*?)\u0026quot;',re.S) res=obj.findall(res.decode('gbk')) if len(res) \u0026gt; 0: movie_url=res[0] return movie_url def save(movie_url): response=requests.get(movie_url,stream=False) if response.status_code == 200: m=hashlib.md5() m.update(('%s%s.mp4' %(movie_url,time.time())).encode('utf-8')) filename=m.hexdigest() with open(r'./movies/%s.mp4' %filename,'wb') as f: f.write(response.content) f.flush() def main(): index_url='http://www.xiaohuar.com/list-3-{0}.html' for i in range(5): print('*'*50,i) #爬取主页面 index_page=get_page(index_url.format(i,)) #解析主页面,拿到视频所在的地址列表 detail_urls=parse_index(index_page) #循环爬取视频页 for detail_url in detail_urls: #爬取视频页 detail_page=get_page(detail_url) #拿到视频的url movie_url=parse_detail(detail_page) if movie_url: #保存视频 save(movie_url) if __name__ == '__main__': main() #并发爬取 from concurrent.futures import ThreadPoolExecutor import queue import requests import re import time import hashlib from threading import current_thread p=ThreadPoolExecutor(50) def get_page(url): print('%s GET %s' %(current_thread().getName(),url)) try: response=requests.get(url) if response.status_code == 200: return response.content except Exception as e: print(e) def parse_index(res): print('%s parse index ' %current_thread().getName()) res=res.result() obj=re.compile('class=\u0026quot;items.*?\u0026lt;a href=\u0026quot;(.*?)\u0026quot;',re.S) detail_urls=obj.findall(res.decode('gbk')) for detail_url in detail_urls: if not detail_url.startswith('http'): detail_url='http://www.xiaohuar.com'+detail_url p.submit(get_page,detail_url).add_done_callback(parse_detail) def parse_detail(res): print('%s parse detail ' %current_thread().getName()) res=res.result() obj=re.compile('id=\u0026quot;media\u0026quot;.*?src=\u0026quot;(.*?)\u0026quot;',re.S) res=obj.findall(res.decode('gbk')) if len(res) \u0026gt; 0: movie_url=res[0] print('MOVIE_URL: ',movie_url) with open('db.txt','a') as f: f.write('%s\\n' %movie_url) # save(movie_url) p.submit(save,movie_url) print('%s下载任务已经提交' %movie_url) def save(movie_url): print('%s SAVE: %s' %(current_thread().getName(),movie_url)) try: response=requests.get(movie_url,stream=False) if response.status_code == 200: m=hashlib.md5() m.update(('%s%s.mp4' %(movie_url,time.time())).encode('utf-8')) filename=m.hexdigest() with open(r'./movies/%s.mp4' %filename,'wb') as f: f.write(response.content) f.flush() except Exception as e: print(e) def main(): index_url='http://www.xiaohuar.com/list-3-{0}.html' for i in range(5): p.submit(get_page,index_url.format(i,)).add_done_callback(parse_index) if __name__ == '__main__': main() 爬取校花网视频 END\n","permalink":"https://zhong-er.com/posts/%E7%88%AC%E8%99%AB%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86/","summary":"\u003cp\u003e\u003cimg alt=\"爬虫.png\" loading=\"lazy\" src=\"/assets/202009031721_5943.png\"\u003e\u003c/p\u003e\n\u003ch1 id=\"一爬虫是什么\"\u003e一、爬虫是什么？\u003c/h1\u003e\n\u003cp\u003e\u003cimg alt=\"pachong.jpg\" loading=\"lazy\" src=\"/assets/201911253050_307.jpg\"\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e#1、什么是互联网？\n    互联网是由网络设备（网线，路由器，交换机，防火墙等等）和一台台计算机连接而成，像一张网一样。\n\n#2、互联网建立的目的？\n    互联网的核心价值在于数据的共享/传递：数据是存放于一台台计算机上的，而将计算机互联到一起的目的就是为了能够方便彼此之间的数据共享/传递，否则你只能拿U盘去别人的计算机上拷贝数据了。\n\n#3、什么是上网？爬虫要做的是什么？\n    我们所谓的上网便是由用户端计算机发送请求给目标计算机，将目标计算机的数据下载到本地的过程。\n    #3.1 只不过，用户获取网络数据的方式是：\n      浏览器提交请求-\u0026gt;下载网页代码-\u0026gt;解析/渲染成页面。\n\n```\n#3.2 而爬虫程序要做的就是：\n  模拟浏览器发送请求-\u0026gt;下载网页代码-\u0026gt;只提取有用的数据-\u0026gt;存放于数据库或文件中\n　\n#3.1与3.2的区别在于:\n```\n\n      我们的爬虫程序只提取网页代码中对我们有用的数据\n\n#4、总结爬虫\n    #4.1 爬虫的比喻：\n      如果我们把互联网比作一张大的蜘蛛网，那一台计算机上的数据便是蜘蛛网上的一个猎物，而爬虫程序就是一只小蜘蛛，沿着蜘蛛网抓取自己想要的猎物/数据\n\n```\n#4.2 爬虫的定义：\n```\n\n      向网站发起请求，获取资源后分析并提取有用数据的程序 \n\n```\n#4.3 爬虫的价值：\n  互联网中最有价值的便是数据，比如天猫商城的商品信息，链家网的租房信息，雪球网的证券投资信息等等，这些数据都代表了各个行业的真金白银，可以说，谁掌握了行业内的第一手数据，谁就成了整个行业的主宰，如果把整个互联网的数据比喻为一座宝藏，那我们的爬虫课程就是来教大家如何来高效地挖掘这些宝藏，掌握了爬虫技能，你就成了所有互联网信息公司幕后的老板，换言之，它们都在免费为你提供有价值的数据。\n```\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"二爬虫的基本流程\"\u003e二、爬虫的基本流程\u003c/h1\u003e\n\u003cp\u003e\u003cimg alt=\"pclc.png\" loading=\"lazy\" src=\"/assets/201911256154_6066.png\"\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e#1、发起请求\n使用http库向目标站点发起请求，即发送一个Request\nRequest包含：请求头、请求体等\n\n#2、获取响应内容\n如果服务器能正常响应，则会得到一个Response\nResponse包含：html，json，图片，视频等\n\n#3、解析内容\n解析html数据：正则表达式，第三方解析库如Beautifulsoup，pyquery等\n解析json数据：json模块\n解析二进制数据:以b的方式写入文件\n\n#4、保存数据\n数据库\n文件\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"三请求与响应\"\u003e三、请求与响应\u003c/h1\u003e\n\u003cp\u003e\u003cimg alt=\"qqyxy.png\" loading=\"lazy\" src=\"/assets/201911251408_31.png\"\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e#http协议：http://www.cnblogs.com/linhaifeng/articles/8243379.html\n\n#Request：用户将自己的信息通过浏览器（socket client）发送给服务器（socket server）\n\n#Response：服务器接收请求，分析用户发来的请求信息，然后返回数据（返回的数据中可能包含其他链接，如：图片，js，css等）\n\n#ps：浏览器在接收Response后，会解析其内容来显示给用户，而爬虫程序在模拟浏览器发送请求然后接收Response后，是要提取其中的有用数据。\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"四request\"\u003e四、Request\u003c/h1\u003e\n\u003cpre\u003e\u003ccode\u003e#1、请求方式：\n    常用的请求方式：GET，POST\n    其他请求方式：HEAD，PUT，DELETE，OPTHONS\n\n```\nps：用浏览器演示get与post的区别，（用登录演示post）\n\npost与get请求最终都会拼接成这种形式：k1=xxx\u0026amp;k2=yyy\u0026amp;k3=zzz\npost请求的参数放在请求体内：\n    可用浏览器查看，存放于form data内\nget请求的参数直接放在url后\n```\n\n#2、请求url\n    url全称统一资源定位符，如一个网页文档，一张图片\n    一个视频等都可以用url唯一来确定\n\n```\nurl编码\nhttps://www.baidu.com/s?wd=图片\n图片会被编码（看示例代码）\n```\n\n```\n网页的加载过程是：\n加载一个网页，通常都是先加载document文档，\n在解析document文档的时候，遇到链接，则针对超链接发起下载图片的请求\n```\n\n#3、请求头\n    User-agent：请求头中如果没有user-agent客户端配置，\n    服务端可能将你当做一个非法用户\n    host\n    cookies：cookie用来保存登录信息\n\n```\n一般做爬虫都会加上请求头\n```\n\n#4、请求体\n    如果是get方式，请求体没有内容\n    如果是post方式，请求体是format data\n\n```\nps：\n1、登录窗口，文件上传等，信息都会被附加到请求体内\n2、登录，输入错误的用户名密码，然后提交，就可以看到post，正确登录后页面通常会跳转，无法捕捉到post \n```\n\n\nfrom urllib.parse import urlencode\nimport requests\n\nheaders={\n'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',\n'Cookie':'H_WISE_SIDS=101556_115474_115442_114743_108373_100099_115725_106201_107320_115339_114797_115553_116093_115546_115625_115446_114329_115350_114275_116040_110085; PSTM=1494300712; BAIDUID=42FE2934E37AF7AD1FA31D8CC7006D45:FG=1; BIDUPSID=2996557DB2710279BD865C50F9A68615; MCITY=-%3A; __cfduid=da9f97dea6458ca26aa4278280752ebb01508939712; BDSFRCVID=PGLsJeCCxG3wt_3ZUrBLDfv2D_qBZSjAgcEe3J; H_BDCLCKID_SF=tJAOoCLytI03qn5zq4Oh-4oHhxoJq5QxbT7Z0l8KtfcNVJQs-lCMhbtp-l3GJPoLWK6hBKQmWIQHDnbsbq0M2tcQXR5-WROCte74KKJx-4PWeIJo5tKh04JbhUJiB5OLBan7Lq7xfDDbbDtmej_3-PC3ql6354Rj2C_X3b7EfKjIOtO_bfbT2MbyeqrNQlTkLIvXoITJQD_bEP3Fbfj2DPQ3KabZqjDjJbue_I05f-oqebT4btbMqRtthf5KeJ3KaKrKW5rJabC3hPJeKU6qLT5Xjh6B5qDfyDoAbKOt-IOjhb5hMpnx-p0njxQyaR3RL2Kj0p_EWpcxsCQqLUonDh8L3H7MJUntKjnRonTO5hvvhb6O3M7-XpOhDG0fJjtJJbksQJ5e24oqHP-kKPrV-4oH5MQy5toyHD7yWCvjWlT5OR5Jj6KMjMkb3xbz2fcpMIrjob8M5CQESInv3MA--fcLD2ch5-3eQgTI3fbIJJjWsq0x0-jle-bQypoa-U0j2COMahkMal7xO-QO05CaD53yDNDqtjn-5TIX_CjJbnA_Hn7zepoxebtpbt-qJJjzMerW_Mc8QUJBH4tR-T3keh-83xbnBT5KaKO2-RnPXbcWjt_lWh_bLf_kQN3TbxuO5bRiL66I0h6jDn3oyT3VXp0n54nTqjDHfRuDVItXf-L_qtDk-PnVeUP3DhbZKxtqtDKjXJ7X2fclHJ7z-R3IBPCD0tjk-6JnWncKaRcI3poiqKtmjJb6XJkl2HQ405OT-6-O0KJcbRodobAwhPJvyT8DXnO7-fRTfJuJ_DDMJDD3fP36q4QV-JIehmT22jnT32JeaJ5n0-nnhP3mBTbA3JDYX-Oh-jjRX56GhfO_0R3jsJKRy66jK4JKjHKet6vP; ispeed_lsm=0; H_PS_PSSID=1421_24558_21120_17001_24880_22072; BD_UPN=123253; H_PS_645EC=44be6I1wqYYVvyugm2gc3PK9PoSa26pxhzOVbeQrn2rRadHvKoI%2BCbN5K%2Bg; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598',\n'Host':'www.baidu.com',\n'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'}\n\n# response=requests.get('https://www.baidu.com/s?'+urlencode({'wd':'美女'}),headers=headers)\nresponse=requests.get('https://www.baidu.com/s',params={'wd':'美女'},headers=headers) #params内部就是调用urlencode\nprint(response.text)\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"五response\"\u003e五、Response\u003c/h1\u003e\n\u003cpre\u003e\u003ccode\u003e#1、响应状态\n    200：代表成功\n    301：代表跳转\n    404：文件不存在\n    403：权限\n    502：服务器错误\n\n#2、Respone header\n    set-cookie：可能有多个，是来告诉浏览器，把cookie保存下来\n    \n#3、preview就是网页源代码\n    最主要的部分，包含了请求资源的内容\n    如网页html，图片\n    二进制数据等\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"六总结\"\u003e六、总结\u003c/h1\u003e\n\u003cpre\u003e\u003ccode\u003e#1、总结爬虫流程：\n    爬取---\u0026gt;解析---\u0026gt;存储\n\n#2、爬虫所需工具：\n    请求库：requests,selenium\n    解析库：正则，beautifulsoup，pyquery\n    存储库：文件，MySQL，Mongodb，Redis\n\n#3、爬虫常用框架：\n    scrapy\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e爬取校花网视频\u003c/p\u003e","title":"【爬虫】基本原理"},{"content":"\n一、介绍 #介绍：使用requests可以模拟浏览器的请求，比起之前用到的urllib，requests模块的api更加便捷（本质就是封装了urllib3） #注意：requests库发送请求将网页内容下载下来以后，并不会执行js代码，这需要我们自己分析目标站点然后发起新的request请求 #安装：pip3 install requests #各种请求方式：常用的就是requests.get()和requests.post() \u0026gt;\u0026gt;\u0026gt; import requests \u0026gt;\u0026gt;\u0026gt; r = requests.get('https://api.github.com/events') \u0026gt;\u0026gt;\u0026gt; r = requests.post('http://httpbin.org/post', data = {'key':'value'}) \u0026gt;\u0026gt;\u0026gt; r = requests.put('http://httpbin.org/put', data = {'key':'value'}) \u0026gt;\u0026gt;\u0026gt; r = requests.delete('http://httpbin.org/delete') \u0026gt;\u0026gt;\u0026gt; r = requests.head('http://httpbin.org/get') \u0026gt;\u0026gt;\u0026gt; r = requests.options('http://httpbin.org/get') #建议在正式学习requests前，先熟悉下HTTP协议 官网链接：http://docs.python-requests.org/en/master/\n二、基于GET请求 1、基本请求\nimport requests response=requests.get(' print(response.text) 2、带参数的GET请求-\u0026gt;params\n#在请求头内将自己伪装成浏览器，否则百度不会正常返回页面内容 import requests response=requests.get('https://www.baidu.com/s?wd=python\u0026amp;pn=1', headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', }) print(response.text) #如果查询关键词是中文或者有其他特殊符号，则不得不进行url编码 from urllib.parse import urlencode wd='egon老师' encode_res=urlencode({'k':wd},encoding='utf-8') keyword=encode_res.split('=')[1] print(keyword) # 然后拼接成url url='https://www.baidu.com/s?wd=%s\u0026amp;pn=1' %keyword response=requests.get(url, headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', }) res1=response.text 自己拼接GET参数 params参数的使用\n#上述操作可以用requests模块的一个params参数搞定，本质还是调用urlencode from urllib.parse import urlencode wd='egon老师' pn=1 response=requests.get('https://www.baidu.com/s', params={ 'wd':wd, 'pn':pn }, headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', }) res2=response.text #验证结果，打开a.html与b.html页面内容一样 with open('a.html','w',encoding='utf-8') as f: f.write(res1) with open('b.html', 'w', encoding='utf-8') as f: f.write(res2) params参数的使用 3、带参数的GET请求-\u0026gt;headers\n#通常我们在发送请求时都需要带上请求头，请求头是将自身伪装成浏览器的关键，常见的有用的请求头如下 Host Referer #大型网站通常都会根据该参数判断请求的来源 User-Agent #客户端 Cookie #Cookie信息虽然包含在请求头里，但requests模块有单独的参数来处理他，headers={}内就不要放它了 #添加headers(浏览器会识别请求头,不加可能会被拒绝访问,比如访问https://www.zhihu.com/explore) import requests response=requests.get('https://www.zhihu.com/explore') response.status_code #500 #自己定制headers headers={ 'User-Agent':'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36', } respone=requests.get('https://www.zhihu.com/explore', headers=headers) print(respone.status_code) #200 4、带参数的GET请求-cookies\n#登录github，然后从浏览器中获取cookies，以后就可以直接拿着cookie登录了，无需输入用户名密码 #用户名:egonlin 邮箱378533872@qq.com 密码lhf@123 import requests Cookies={ 'user_session':'wGMHFJKgDcmRIVvcA14_Wrt_3xaUyJNsBnPbYzEL6L0bHcfc', } response=requests.get('https://github.com/settings/emails', cookies=Cookies) #github对请求头没有什么限制，我们无需定制user-agent，对于其他网站可能还需要定制 print('378533872@qq.com' in response.text) #True 三、基于post请求 1、介绍\n#GET请求 HTTP默认的请求方法就是GET * 没有请求体 * 数据必须在1K之内！ * GET请求数据会暴露在浏览器的地址栏中 GET请求常用的操作： 1. 在浏览器的地址栏中直接给出URL，那么就一定是GET请求 2. 点击页面上的超链接也一定是GET请求 3. 提交表单时，表单默认使用GET请求，但可以设置为POST #POST请求 (1). 数据不会出现在地址栏中 (2). 数据的大小没有上限 (3). 有请求体 (4). 请求体中如果存在中文，会使用URL编码！ #！！！requests.post()用法与requests.get()完全一致，特殊的是requests.post()有一个data参数，用来存放请求体数据 2、发送post请求，模拟浏览器的登录行为\n#对于登录来说，应该输错用户名或密码然后分析抓包流程，用脑子想一想，输对了浏览器就跳转了，还分析个毛线，累死你也找不到包 自动登录GitHub(自己处理cookie信息)\n''' 一 目标站点分析 浏览器输入https://github.com/login 然后输入错误的账号密码，抓包 发现登录行为是post提交到：https://github.com/session 而且请求头包含cookie 而且请求体包含： commit:Sign in utf8:✓ authenticity_token:lbI8IJCwGslZS8qJPnof5e7ZkCoSoMn6jmDTsL1r/m06NLyIbw7vCrpwrFAPzHMep3Tmf/TSJVoXWrvDZaVwxQ== login:egonlin password:123 二 流程分析 先GET：https://github.com/login拿到初始cookie与authenticity_token 返回POST：https://github.com/session， 带上初始cookie，带上请求体（authenticity_token，用户名，密码等） 最后拿到登录cookie ``` ps：如果密码时密文形式，则可以先输错账号，输对密码，然后到浏览器中拿到加密后的密码，github的密码是明文 ``` ''' import requests import re #第一次请求 r1=requests.get('https://github.com/login') r1_cookie=r1.cookies.get_dict() #拿到初始cookie(未被授权) authenticity_token=re.findall(r'name=\u0026quot;authenticity_token\u0026quot;.*?value=\u0026quot;(.*?)\u0026quot;',r1.text)[0] #从页面中拿到CSRF TOKEN #第二次请求：带着初始cookie和TOKEN发送POST请求给登录页面，带上账号密码 data={ 'commit':'Sign in', 'utf8':'✓', 'authenticity_token':authenticity_token, 'login':'317828332@qq.com', 'password':'alex3714' } r2=requests.post('https://github.com/session', data=data, cookies=r1_cookie ) login_cookie=r2.cookies.get_dict() #第三次请求：以后的登录，拿着login_cookie就可以,比如访问一些个人配置 r3=requests.get('https://github.com/settings/emails', cookies=login_cookie) print('317828332@qq.com' in r3.text) #True 自动登录github（自己处理cookie信息） requests.session()自动帮我们保存cookie信息\nimport requests import re session=requests.session() #第一次请求 r1=session.get('https://github.com/login') authenticity_token=re.findall(r'name=\u0026quot;authenticity_token\u0026quot;.*?value=\u0026quot;(.*?)\u0026quot;',r1.text)[0] #从页面中拿到CSRF TOKEN #第二次请求 data={ 'commit':'Sign in', 'utf8':'✓', 'authenticity_token':authenticity_token, 'login':'317828332@qq.com', 'password':'alex3714' } r2=session.post('https://github.com/session', data=data, ) #第三次请求 r3=session.get('https://github.com/settings/emails') print('317828332@qq.com' in r3.text) #True requests.session()自动帮我们保存cookie信息 3、补充\nrequests.post(url='xxxxxxxx', data={'xxx':'yyy'}) #没有指定请求头,#默认的请求头:application/x-www-form-urlencoed #如果我们自定义请求头是application/json,并且用data传值, 则服务端取不到值 requests.post(url='', data={'':1,}, headers={ 'content-type':'application/json' }) requests.post(url='', json={'':1,}, ) #默认的请求头:application/json 四、响应Response 1、response属性\nimport requests respone=requests.get('http://www.jianshu.com') # respone属性 print(respone.text) print(respone.content) print(respone.status_code) print(respone.headers) print(respone.cookies) print(respone.cookies.get_dict()) print(respone.cookies.items()) print(respone.url) print(respone.history) print(respone.encoding) #关闭：response.close() from contextlib import closing with closing(requests.get('xxx',stream=True)) as response: for line in response.iter_content(): pass 2、编码问题\n#编码问题 import requests response=requests.get('http://www.autohome.com/news') # response.encoding='gbk' #汽车之家网站返回的页面内容为gb2312编码的，而requests的默认编码为ISO-8859-1，如果不设置成gbk则中文乱码 print(response.text) 3、获取二进制数据\nimport requests response=requests.get('https://timgsa.baidu.com/timg?image\u0026amp;quality=80\u0026amp;size=b9999_10000\u0026amp;sec=1509868306530\u0026amp;di=712e4ef3ab258b36e9f4b48e85a81c9d\u0026amp;imgtype=0\u0026amp;src=http%3A%2F%2Fc.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F11385343fbf2b211e1fb58a1c08065380dd78e0c.jpg') with open('a.jpg','wb') as f: f.write(response.content) 获取二进制流\n#编码问题 import requests response=requests.get('http://www.autohome.com/news') # response.encoding='gbk' #汽车之家网站返回的页面内容为gb2312编码的，而requests的默认编码为ISO-8859-1，如果不设置成gbk则中文乱码 print(response.text) 4、解析json\n#编码问题 import requests response=requests.get('http://www.autohome.com/news') # response.encoding='gbk' #汽车之家网站返回的页面内容为gb2312编码的，而requests的默认编码为ISO-8859-1，如果不设置成gbk则中文乱码 print(response.text) 5、redirection and History\n官网解释\nBy default Requests will perform location redirection for all verbs except HEAD. We can use the history property of the Response object to track redirection. The Response.history list contains the Response objects that were created in order to complete the request. The list is sorted from the oldest to the most recent response. For example, GitHub redirects all HTTP requests to HTTPS: \u0026gt;\u0026gt;\u0026gt; r = requests.get('http://github.com') \u0026gt;\u0026gt;\u0026gt; r.url 'https://github.com/' \u0026gt;\u0026gt;\u0026gt; r.status_code \u0026gt;\u0026gt;\u0026gt; r.history [\u0026lt;Response [301]\u0026gt;] If you're using GET, OPTIONS, POST, PUT, PATCH or DELETE, you can disable redirection handling with the allow_redirects parameter: \u0026gt;\u0026gt;\u0026gt; r = requests.get('http://github.com', allow_redirects=False) \u0026gt;\u0026gt;\u0026gt; r.status_code \u0026gt;\u0026gt;\u0026gt; r.history [] If you're using HEAD, you can enable redirection as well: \u0026gt;\u0026gt;\u0026gt; r = requests.head('http://github.com', allow_redirects=True) \u0026gt;\u0026gt;\u0026gt; r.url 'https://github.com/' \u0026gt;\u0026gt;\u0026gt; r.history [\u0026lt;Response [301]\u0026gt;] 先看官网的解释 利用GitHub登录后跳转到主页面的例子来验证它\nimport requests import re #第一次请求 r1=requests.get('https://github.com/login') r1_cookie=r1.cookies.get_dict() #拿到初始cookie(未被授权) authenticity_token=re.findall(r'name=\u0026quot;authenticity_token\u0026quot;.*?value=\u0026quot;(.*?)\u0026quot;',r1.text)[0] #从页面中拿到CSRF TOKEN #第二次请求：带着初始cookie和TOKEN发送POST请求给登录页面，带上账号密码 data={ 'commit':'Sign in', 'utf8':'✓', 'authenticity_token':authenticity_token, 'login':'317828332@qq.com', 'password':'alex3714' } #测试一：没有指定allow_redirects=False,则响应头中出现Location就跳转到新页面，r2代表新页面的response r2=requests.post('https://github.com/session', data=data, cookies=r1_cookie ) print(r2.status_code) #200 print(r2.url) #看到的是跳转后的页面 print(r2.history) #看到的是跳转前的response print(r2.history[0].text) #看到的是跳转前的response.text #测试二：指定allow_redirects=False,则响应头中即便出现Location也不会跳转到新页面，r2代表的仍然是老页面的response r2=requests.post('https://github.com/session', data=data, cookies=r1_cookie, allow_redirects=False ) print(r2.status_code) #302 print(r2.url) #看到的是跳转前的页面https://github.com/session print(r2.history) #[] 利用github登录后跳转到主页面的例子来验证它 五、高级用法 1、SSL Cert Verification\n#证书验证(大部分网站都是https) import requests respone=requests.get('https://www.12306.cn') #如果是ssl请求,首先检查证书是否合法,不合法则报错,程序终端 #改进1:去掉报错,但是会报警告 import requests respone=requests.get('https://www.12306.cn',verify=False) #不验证证书,报警告,返回200 print(respone.status_code) #改进2:去掉报错,并且去掉警报信息 import requests from requests.packages import urllib3 urllib3.disable_warnings() #关闭警告 respone=requests.get('https://www.12306.cn',verify=False) print(respone.status_code) #改进3:加上证书 #很多网站都是https,但是不用证书也可以访问,大多数情况都是可以携带也可以不携带证书 #知乎\\百度等都是可带可不带 #有硬性要求的,则必须带，比如对于定向的用户,拿到证书后才有权限访问某个特定网站 import requests respone=requests.get('https://www.12306.cn', cert=('/path/server.crt', '/path/key')) print(respone.status_code) 2、使用代理\n#官网链接: http://docs.python-requests.org/en/master/user/advanced/#proxies #代理设置:先发送请求给代理,然后由代理帮忙发送(封ip是常见的事情) import requests proxies={ 'http':'http://egon:123@localhost:9743',#带用户名密码的代理,@符号前是用户名与密码 'http':'http://localhost:9743', 'https':'https://localhost:9743', } respone=requests.get('https://www.12306.cn', proxies=proxies) print(respone.status_code) #支持socks代理,安装:pip install requests[socks] import requests proxies = { 'http': 'socks5://user:pass@host:port', 'https': 'socks5://user:pass@host:port' } respone=requests.get('https://www.12306.cn', proxies=proxies) print(respone.status_code) 3、超时设置\n#超时设置 #两种超时:float or tuple #timeout=0.1 #代表接收数据的超时时间 #timeout=(0.1,0.2)#0.1代表链接超时 0.2代表接收数据的超时时间 import requests respone=requests.get('https://www.baidu.com', timeout=0.0001) 4、认证设置\n#官网链接：http://docs.python-requests.org/en/master/user/authentication/ #认证设置:登陆网站是,弹出一个框,要求你输入用户名密码（与alter很类似），此时是无法获取html的 # 但本质原理是拼接成请求头发送 # r.headers['Authorization'] = _basic_auth_str(self.username, self.password) # 一般的网站都不用默认的加密方式，都是自己写 # 那么我们就需要按照网站的加密方式，自己写一个类似于_basic_auth_str的方法 # 得到加密字符串后添加到请求头 # r.headers['Authorization'] =func('.....') #看一看默认的加密方式吧，通常网站都不会用默认的加密设置 import requests from requests.auth import HTTPBasicAuth r=requests.get('xxx',auth=HTTPBasicAuth('user','password')) print(r.status_code) #HTTPBasicAuth可以简写为如下格式 import requests r=requests.get('xxx',auth=('user','password')) print(r.status_code) 5、异常处理\n#官网链接：http://docs.python-requests.org/en/master/user/authentication/ #认证设置:登陆网站是,弹出一个框,要求你输入用户名密码（与alter很类似），此时是无法获取html的 # 但本质原理是拼接成请求头发送 # r.headers['Authorization'] = _basic_auth_str(self.username, self.password) # 一般的网站都不用默认的加密方式，都是自己写 # 那么我们就需要按照网站的加密方式，自己写一个类似于_basic_auth_str的方法 # 得到加密字符串后添加到请求头 # r.headers['Authorization'] =func('.....') #看一看默认的加密方式吧，通常网站都不会用默认的加密设置 import requests from requests.auth import HTTPBasicAuth r=requests.get('xxx',auth=HTTPBasicAuth('user','password')) print(r.status_code) #HTTPBasicAuth可以简写为如下格式 import requests r=requests.get('xxx',auth=('user','password')) print(r.status_code) 6、上传文件\nimport requests files={'file':open('a.jpg','rb')} respone=requests.post('http://httpbin.org/post',files=files)print(respone.status_code) END\n","permalink":"https://zhong-er.com/posts/%E7%88%AC%E8%99%AB%E8%AF%B7%E6%B1%82%E5%BA%93%E4%B9%8Brequests%E5%BA%93/","summary":"\u003cp\u003e\u003cimg alt=\"爬虫.png\" loading=\"lazy\" src=\"/assets/202009031721_5943.png\"\u003e\u003c/p\u003e\n\u003ch1 id=\"一介绍\"\u003e一、介绍\u003c/h1\u003e\n\u003cpre\u003e\u003ccode\u003e#介绍：使用requests可以模拟浏览器的请求，比起之前用到的urllib，requests模块的api更加便捷（本质就是封装了urllib3）\n\n#注意：requests库发送请求将网页内容下载下来以后，并不会执行js代码，这需要我们自己分析目标站点然后发起新的request请求\n\n#安装：pip3 install requests\n\n#各种请求方式：常用的就是requests.get()和requests.post()\n\u0026gt;\u0026gt;\u0026gt; import requests\n\u0026gt;\u0026gt;\u0026gt; r = requests.get('https://api.github.com/events')\n\u0026gt;\u0026gt;\u0026gt; r = requests.post('http://httpbin.org/post', data = {'key':'value'})\n\u0026gt;\u0026gt;\u0026gt; r = requests.put('http://httpbin.org/put', data = {'key':'value'})\n\u0026gt;\u0026gt;\u0026gt; r = requests.delete('http://httpbin.org/delete')\n\u0026gt;\u0026gt;\u0026gt; r = requests.head('http://httpbin.org/get')\n\u0026gt;\u0026gt;\u0026gt; r = requests.options('http://httpbin.org/get')\n\n#建议在正式学习requests前，先熟悉下HTTP协议\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ca href=\"http://docs.python-requests.org/en/master/\"\u003e官网链接：http://docs.python-requests.org/en/master/\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"二基于get请求\"\u003e二、基于GET请求\u003c/h1\u003e\n\u003cp\u003e1、基本请求\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eimport requests\nresponse=requests.get(' \nprint(response.text)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2、带参数的GET请求-\u0026gt;params\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e#在请求头内将自己伪装成浏览器，否则百度不会正常返回页面内容\nimport requests\nresponse=requests.get('https://www.baidu.com/s?wd=python\u0026amp;pn=1',\n                      headers={\n                        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',\n                      })\nprint(response.text)\n\n\n\n#如果查询关键词是中文或者有其他特殊符号，则不得不进行url编码\nfrom urllib.parse import urlencode\nwd='egon老师'\nencode_res=urlencode({'k':wd},encoding='utf-8')\nkeyword=encode_res.split('=')[1]\nprint(keyword)\n# 然后拼接成url\nurl='https://www.baidu.com/s?wd=%s\u0026amp;pn=1' %keyword\n\nresponse=requests.get(url,\n                      headers={\n                        'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36',\n                      })\nres1=response.text\n\n自己拼接GET参数\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eparams参数的使用\u003c/p\u003e","title":"【爬虫】请求库之requests库"},{"content":"Chrome 78版本开启了鼠标落在标签页标题内容提示 怎么关闭这个提示呢？\nchrome://flags/#tab-hover-cards 为 Disable 即可！ 有网友发现还有一项设置：Tab Hover Card Images\n鼠标落在标签页，显示缩略图！\nchrome://flags/#tab-hover-card-images 默认是关闭，设置成 Enable 即可开启 ","permalink":"https://zhong-er.com/posts/chrome%E6%B5%8F%E8%A7%88%E5%99%A878%E7%89%88%E6%9C%AC%E5%85%B3%E9%97%AD%E9%BC%A0%E6%A0%87%E8%90%BD%E5%9C%A8%E6%A0%87%E7%AD%BE%E9%A1%B5%E4%B8%8A%E6%A0%87%E9%A2%98%E6%8F%90%E7%A4%BA/","summary":"\u003cp\u003eChrome 78版本开启了鼠标落在标签页标题内容提示 怎么关闭这个提示呢？\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003echrome://flags/#tab-hover-cards  为 Disable 即可！\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e有网友发现还有一项设置：Tab Hover Card Images\u003cbr\u003e\n鼠标落在标签页，显示缩略图！\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"chrome1.png\" loading=\"lazy\" src=\"/assets/201911204021_6678_1.png\"\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003echrome://flags/#tab-hover-card-images 默认是关闭，设置成 Enable 即可开启\n\u003c/code\u003e\u003c/pre\u003e","title":"Chrome浏览器78版本关闭鼠标落在标签页上标题提示"},{"content":"chrome://flags/#enable-parallel-downloading\nEnable parallel downloading to accelerate download speed. – Mac, Windows, Linux, Chrome OS, Android\n启用并行下载以加快下载速度。–Mac、Windows、Linux、Chrome操作系统、Android\n","permalink":"https://zhong-er.com/posts/chrome%E6%B5%8F%E8%A7%88%E5%99%A8%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B8%8B%E8%BD%BD%E5%BC%80%E5%85%B3/","summary":"\u003cp\u003echrome://flags/#enable-parallel-downloading\u003c/p\u003e\n\u003cp\u003eEnable parallel downloading to accelerate download speed. – Mac, Windows, Linux, Chrome OS, Android\u003cbr\u003e\n启用并行下载以加快下载速度。–Mac、Windows、Linux、Chrome操作系统、Android\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"chrome2.png\" loading=\"lazy\" src=\"/assets/201911205628_1937_1.png\"\u003e\u003c/p\u003e","title":"Chrome浏览器多线程下载开关"},{"content":"\n1.接口** **\n\u0026quot;\u0026quot;\u0026quot; 1、什么是接口：url+请求参数+响应数据 | 接口文档 2、接口规范： url：https，api，资源(名词复数)，v1，get|post表示操作资源的方式，?过滤条件 响应数据：status，msg，results|data，url资源数据 \u0026quot;\u0026quot;\u0026quot; 2.DRF入门 \u0026quot;\u0026quot;\u0026quot; 1、封装风格 2、请求生命周期：走APIView的as_view =\u0026gt; View的as_view调用APIView的dispatch =\u0026gt; 分发给视图类的请求函数 =\u0026gt; 响应 3、请求模块：二次封装request 4、解析模块：局部全局配置，form-data、urlencoding、json 5、渲染模块：局部全局配置，html、json 6、响应模块：Response(data, http_status, header) data={'detail': serializer_obj.errors} =\u0026gt; http_status=4..|5.. # 4,5开头字段状态码，前台axios请求响应会解析到catch分支 axios({}).then().catch() 7、二次封装Response： i) 继承Response ii) 自定义参数格式化成data，传给 super().__init__(data=data) iii) 网络状态码、响应头等信息带给super().__init__() 8、异常模块：在请求生命周期中，分发请求任务执行时，被一个超大的try except包裹，一旦出现任何异常，都会交给exception_handler函数处理 自定义原因：系统只处理了部分，所以手动还需要处理；后期异常信息需要记录到日志文件 工作流程：先交给drf处理异常，如果有响应结果，代表drf已处理，如果是None，自己处理 \u0026quot;\u0026quot;\u0026quot; 3.序列化组件** **\n# 1）可以将model对象序列化成可以返回给前台的json数据 # 2）可以校验前台传来的数据，完成model的增与改 模型层\nclass User(models.Model): name = models.CharField(max_length=16) # 自定义插拔序列化字段 - 完成连表深度查询 @property def my_name(self): return self.name 序列化层\nclass UserListSerializer(serializers.ListSerializer): def update(self, instance, validated_data): for index, obj in enumerate(instance): self.child.update(obj, validated_data[index]) return instance class UserModelSerializer(serializers.ModelSerializer): # 自定义反序列化不入库字段 - 要在钩子函数校验后移除 check_name = serializers.CharFiled(write_only=True) class Meta: model = User fields = ['name', 'my_name', 'check_name'] extra_kwargs = { 'my_name': { 'read_only': True } } # 群改 list_serializer_class = UserListSerializer # 局部、全局钩子 # create、update方法不需要重写，可以完成单增、群增、单改 视图层\nclass UserAPIView(APIView): def get(self, request, *args, **kwargs): # 先orm得到 user_query | user_obj # 在得到序列化对象 user_ser = UserModelSerializer(user_query, many=True) user_ser = UserModelSerializer(user_query, many=False) # 返回序列化结果 user_ser.data def post(self, request, *args, **kwargs): # 数据给序列化类 user_ser = UserModelSerializer(data=user_list, many=True) user_ser = UserModelSerializer(data=user_dic, many=False) # 校验数据 user_ser.is_valid(raise_exception=True) # 数据库操作 user_query|user_obj = user_ser.save() def patch(self, request, *args, **kwargs): # 根据主键或主键们确定要修改的 对象 或 对象们 # 数据给序列化类 user_ser = UserModelSerializer(data=user_list, many=True, instance=对象们, partial=True|False) user_ser = UserModelSerializer(data=user_dic, many=False, instance=对象, partial=True|False) # 校验数据 user_ser.is_valid(raise_exception=True) # 数据库操作 user_query|user_obj = user_ser.save() 4.三大认证 \u0026quot;\u0026quot;\u0026quot; 1、认证组件： i）自定义认证类继承BaseAuthentication，重写authenticate方法 ii）规则：游客返回None，非法用户抛异常，合法用户返回(user, auth) iii）全局局部配置 2、权限组件： i）自定义权限类继承BasePermission，重写has_permission方法 ii）规则：有权限返回True，无权限返回False - 校验对象：request.user或请求方式 iii）全局局部配置 3、评论组件： i）自定义频率类继承SimpleRateThrottle，重写get_cache_key方法，设置scope类属性 ii）全局配置scope对应的访问频率：次数/时间 iii）get_cache_key返回的是与限制条件有关的动态字符串 - 限制条件来源于请求参数或请求头 iv）全局局部配置 4、jwt认证： i）客户端存token，服务器存签发算法、校验算法、秘钥 ii）基本信息(头base64).用户信息过期时间(载荷base64).全信息+秘钥(签名HS256) iii）drf-jwt组件使用 iv）登录接口签发token v）认证组件校验token \u0026quot;\u0026quot;\u0026quot; 5.群查过滤组件 \u0026quot;\u0026quot;\u0026quot; 1、搜索组件：search_fields = ['name', 'price'] 2、排序组件：ordering_fields = ['pk', 'price'] 3、分页组件：pagination_class = pagenations.MyPageNumberPagination 4、django-filter过滤插件：filter_class = CarFilterSet 5、自定义过滤器：自定义过滤类实现filter_queryset即可，根据筛选条件筛选后再返回queryset即可 使用： i）视图类添加过滤类：filter_backends = [SearchFilter, OrderingFilter, LimitFilter, DjangoFilterBackend] ii）为不同的过滤类配置过滤条件 \u0026quot;\u0026quot;\u0026quot; END\n","permalink":"https://zhong-er.com/posts/drf-django-rest-framework%E6%80%BB%E7%BB%93/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cimg alt=\"drf.jpg\" loading=\"lazy\" src=\"/assets/201910177473_80_1.jpg\"\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003ch1 id=\"1接口\"\u003e1.接口**\u003c/h1\u003e\n\u003cp\u003e**\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n1、什么是接口：url+请求参数+响应数据 | 接口文档\n\n2、接口规范：\n\turl：https，api，资源(名词复数)，v1，get|post表示操作资源的方式，?过滤条件\n\t响应数据：status，msg，results|data，url资源数据\n\u0026quot;\u0026quot;\u0026quot;\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"2drf入门\"\u003e2.DRF入门\u003c/h1\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n1、封装风格\n\n2、请求生命周期：走APIView的as_view =\u0026gt; View的as_view调用APIView的dispatch =\u0026gt; 分发给视图类的请求函数 =\u0026gt; 响应\n\n3、请求模块：二次封装request\n\n4、解析模块：局部全局配置，form-data、urlencoding、json\n\n5、渲染模块：局部全局配置，html、json\n\n6、响应模块：Response(data, http_status, header)\ndata={'detail': serializer_obj.errors} =\u0026gt; http_status=4..|5..\n# 4,5开头字段状态码，前台axios请求响应会解析到catch分支\naxios({}).then().catch()\n\n7、二次封装Response：\n\ti) 继承Response\n\tii) 自定义参数格式化成data，传给 super().__init__(data=data)\n\tiii) 网络状态码、响应头等信息带给super().__init__()\n\t\n8、异常模块：在请求生命周期中，分发请求任务执行时，被一个超大的try except包裹，一旦出现任何异常，都会交给exception_handler函数处理\n\t自定义原因：系统只处理了部分，所以手动还需要处理；后期异常信息需要记录到日志文件\n\t工作流程：先交给drf处理异常，如果有响应结果，代表drf已处理，如果是None，自己处理\n\u0026quot;\u0026quot;\u0026quot;\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"3序列化组件\"\u003e3.序列化组件**\u003c/h1\u003e\n\u003cp\u003e**\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 1）可以将model对象序列化成可以返回给前台的json数据\n# 2）可以校验前台传来的数据，完成model的增与改\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e模型层\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass User(models.Model):\n    name = models.CharField(max_length=16)\n    \n    # 自定义插拔序列化字段 - 完成连表深度查询\n    @property\n    def my_name(self):\n        return self.name\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e序列化层\u003c/p\u003e","title":"【DRF】 django rest framework总结"},{"content":"\n1.drf偏移分页组件 pahenations.py\nfrom rest_framework.pagination import LimitOffsetPagination class MyLimitOffsetPagination(LimitOffsetPagination): # ?offset=从头偏移的条数\u0026amp;limit=要显示的条数 limit_query_param = 'limit' offset_query_param = 'offset' # ?不传offset和limit默认显示前3条，只设置offset就是从偏移位往后再显示3条 default_limit = 3 # ?limit可以自定义一页显示的最大条数 max_limit = 5 # 只使用limit结合ordering可以实现排行前几或后几 # ?ordering=-price\u0026amp;limit=2 =\u0026gt; 价格前2 views.py\nfrom rest_framework.generics import ListAPIView class CarListAPIView(ListAPIView): # 如果queryset没有过滤条件，就必须 .all()，不然分页会出问题 queryset = models.Car.objects.all() serializer_class = serializers.CarModelSerializer # 分页组件 - 给视图类配置分页类即可 - 分页类需要自定义，继承drf提供的分页类即可 pagination_class = pagenations.MyLimitOffsetPagination 2.drf游标分页组件 pahenations.py\n# 注：必须基于排序规则下进行分页 # 1）如果接口配置了OrderingFilter过滤器，那么url中必须传ordering # 1）如果接口没有配置OrderingFilter过滤器，一定要在分页类中声明ordering按某个字段进行默认排序 from rest_framework.pagination import CursorPagination class MyCursorPagination(CursorPagination): cursor_query_param = 'cursor' page_size = 3 page_size_query_param = 'page_size' max_page_size = 5 ordering = '-pk' views.py\nfrom rest_framework.generics import ListAPIView class CarListAPIView(ListAPIView): # 如果queryset没有过滤条件，就必须 .all()，不然分页会出问题 queryset = models.Car.objects.all() serializer_class = serializers.CarModelSerializer # 分页组件 - 给视图类配置分页类即可 - 分页类需要自定义，继承drf提供的分页类即可 pagination_class = pagenations.MyCursorPagination 3.自定义过滤器 filters.py\n# 自定义过滤器，接口：?limit=显示的条数 class LimitFilter: def filter_queryset(self, request, queryset, view): # 前台固定用 ?limit=... 传递过滤参数 limit = request.query_params.get('limit') if limit: limit = int(limit) return queryset[:limit] return queryset views.py\nfrom rest_framework.generics import ListAPIView class CarListAPIView(ListAPIView): # 如果queryset没有过滤条件，就必须 .all()，不然分页会出问题 queryset = models.Car.objects.all() serializer_class = serializers.CarModelSerializer # 局部配置 过滤类 们（全局配置用DEFAULT_FILTER_BACKENDS） filter_backends = [LimitFilter] 4.过滤器插件：django-filter 安装\n\u0026gt;: pip3 install django-filter 过滤条件层：自定义api/filters.py\n# django-filter插件过滤器类 from django_filters.rest_framework.filterset import FilterSet from . import models # 自定义过滤字段 from django_filters import filters class CarFilterSet(FilterSet): min_price = filters.NumberFilter(field_name='price', lookup_expr='gte') max_price = filters.NumberFilter(field_name='price', lookup_expr='lte') class Meta: model = models.Car fields = ['brand', 'min_price', 'max_price'] # brand是model中存在的字段，一般都是可以用于分组的字段 # min_price、max_price是自定义字段，需要自己自定义过滤条件 视图层：views.py\n# django-filter插件过滤器 from django_filters.rest_framework import DjangoFilterBackend from .filters import CarFilterSet class CarListAPIView(ListAPIView): queryset = models.Car.objects.all() serializer_class = serializers.CarModelSerializer # 局部配置 过滤类 们（全局配置用DEFAULT_FILTER_BACKENDS） filter_backends = [DjangoFilterBackend] # django-filter过滤器插件使用 filter_class = CarFilterSet # 接口：?brand=...\u0026amp;min_price=...\u0026amp;max_price=... # eg:?brand=宝马\u0026amp;min_price=5\u0026amp;max_price=10 =\u0026gt; 5~10间的宝马牌汽车 END\n","permalink":"https://zhong-er.com/posts/drf%E5%81%8F%E7%A7%BB%E5%88%86%E9%A1%B5%E7%BB%84%E4%BB%B6%E6%B8%B8%E6%A0%87%E5%88%86%E9%A1%B5%E7%BB%84%E4%BB%B6%E8%87%AA%E5%AE%9A%E4%B9%89%E8%BF%87%E6%BB%A4%E5%99%A8%E8%BF%87%E6%BB%A4%E5%99%A8%E6%8F%92%E4%BB%B6/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cimg alt=\"drf.jpg\" loading=\"lazy\" src=\"/assets/201910177473_80_1.jpg\"\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003ch1 id=\"1drf偏移分页组件\"\u003e1.drf偏移分页组件\u003c/h1\u003e\n\u003cp\u003epahenations.py\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom rest_framework.pagination import LimitOffsetPagination\nclass MyLimitOffsetPagination(LimitOffsetPagination):\n    # ?offset=从头偏移的条数\u0026amp;limit=要显示的条数\n    limit_query_param = 'limit'\n    offset_query_param = 'offset'\n    # ?不传offset和limit默认显示前3条，只设置offset就是从偏移位往后再显示3条\n    default_limit = 3\n    # ?limit可以自定义一页显示的最大条数\n    max_limit = 5\n\n    # 只使用limit结合ordering可以实现排行前几或后几\n    # ?ordering=-price\u0026amp;limit=2  =\u0026gt; 价格前2\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eviews.py\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom rest_framework.generics import ListAPIView\n\nclass CarListAPIView(ListAPIView):\n    # 如果queryset没有过滤条件，就必须 .all()，不然分页会出问题\n    queryset = models.Car.objects.all()\n    serializer_class = serializers.CarModelSerializer\n    \n    # 分页组件 - 给视图类配置分页类即可 - 分页类需要自定义，继承drf提供的分页类即可\n    pagination_class = pagenations.MyLimitOffsetPagination\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"2drf游标分页组件\"\u003e2.drf游标分页组件\u003c/h1\u003e\n\u003cp\u003epahenations.py\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 注：必须基于排序规则下进行分页\n# 1）如果接口配置了OrderingFilter过滤器，那么url中必须传ordering\n# 1）如果接口没有配置OrderingFilter过滤器，一定要在分页类中声明ordering按某个字段进行默认排序\nfrom rest_framework.pagination import CursorPagination\nclass MyCursorPagination(CursorPagination):\n    cursor_query_param = 'cursor'\n    page_size = 3\n    page_size_query_param = 'page_size'\n    max_page_size = 5\n    ordering = '-pk'\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eviews.py\u003c/p\u003e","title":"【DRF】偏移分页组件/游标分页组件/自定义过滤器/过滤器插件"},{"content":"\n1.频率类源码入口 # 1）APIView的dispath方法中的 self.initial(request, *args, **kwargs) 点进去 # 2）self.check_throttles(request) 进行频率认证 # 频率组件核心源码分析 def check_throttles(self, request): throttle_durations = [] # 1）遍历配置的频率认证类，初始化得到一个个频率认证类对象（会调用频率认证类的 __init__() 方法） # 2）频率认证类对象调用 allow_request 方法，判断是否限次（没有限次可访问，限次不可访问） # 3）频率认证类对象在限次后，调用 wait 方法，获取还需等待多长时间可以进行下一次访问 # 注：频率认证类都是继承 SimpleRateThrottle 类 for throttle in self.get_throttles(): if not throttle.allow_request(request, self): # 只要频率限制了，allow_request 返回False了，才会调用wait throttle_durations.append(throttle.wait()) if throttle_durations: # Filter out `None` values which may happen in case of config / rate # changes, see #1438 durations = [ duration for duration in throttle_durations if duration is not None ] duration = max(durations, default=None) self.throttled(request, duration) 2.自定义频率类 # 1) 自定义一个继承 SimpleRateThrottle 类 的频率类 # 2) 设置一个 scope 类属性，属性值为任意见名知意的字符串 # 3) 在settings配置文件中，配置drf的DEFAULT_THROTTLE_RATES，格式为 {scope字符串: '次数/时间'} # 4) 在自定义频率类中重写 get_cache_key 方法 # 限制的对象返回 与限制信息有关的字符串 # 不限制的对象返回 None (只能放回None，不能是False或是''等) 短信接口1/min频率限制\n频率：api/throttles.py\nfrom rest_framework.throttling import SimpleRateThrottle class SMSRateThrottle(SimpleRateThrottle): scope = 'sms' # 只对提交手机号的get方法进行限制 def get_cache_key(self, request, view): mobile = request.query_params.get('mobile') # 没有手机号，就不做频率限制 if not mobile: return None # 返回可以根据手机号动态变化，且不易重复的字符串，作为操作缓存的key return 'throttle_%(scope)s_%(ident)s' % {'scope': self.scope, 'ident': mobile} 配置：settings.py\n# drf配置 REST_FRAMEWORK = { # 频率限制条件配置 'DEFAULT_THROTTLE_RATES': { 'sms': '1/min' }, } 视图：views.py\nfrom .throttles import SMSRateThrottle class TestSMSAPIView(APIView): # 局部配置频率认证 throttle_classes = [SMSRateThrottle] def get(self, request, *args, **kwargs): return APIResponse(0, 'get 获取验证码 OK') def post(self, request, *args, **kwargs): return APIResponse(0, 'post 获取验证码 OK') 路由：api/url.py\nurl(r'^sms/$', views.TestSMSAPIView.as_view()), 限制的接口\n# 只会对 /api/sms/?mobile=具体手机号 接口才会有频率限制 # 1）对 /api/sms/ 或其他接口发送无限制 # 2）对数据包提交mobile的/api/sms/接口无限制 # 3）对不是mobile（如phone）字段提交的电话接口无限制 3.认证规则图 diango不分离\ndrf分离\n4.认证规则演变图 数据库session认证：低效\n缓存认证：高效\njwt认证：高效\n缓存认证：不易并发\njwt认证：易并发\n5.JWT认证 优点\n\u0026quot;\u0026quot;\u0026quot; 1) 服务器不要存储token，token交给每一个客户端自己存储，服务器压力小 2）服务器存储的是 签发和校验token 两段算法，签发认证的效率高 3）算法完成各集群服务器同步成本低，路由项目完成集群部署（适应高并发） \u0026quot;\u0026quot;\u0026quot; 格式\n\u0026quot;\u0026quot;\u0026quot; 1) jwt token采用三段式：头部.载荷.签名 2）每一部分都是一个json字典加密形参的字符串 3）头部和载荷采用的是base64可逆加密（前台后台都可以解密） 4）签名采用hash256不可逆加密（后台校验采用碰撞校验） 5）各部分字典的内容： 头部：基础信息 - 公司信息、项目组信息、可逆加密采用的算法 载荷：有用但非私密的信息 - 用户可公开信息、过期时间 签名：头部+载荷+秘钥 不可逆加密后的结果 注：服务器jwt签名加密秘钥一定不能泄露 签发token：固定的头部信息加密.当前的登陆用户与过期时间加密.头部+载荷+秘钥生成不可逆加密 校验token：头部可校验也可以不校验，载荷校验出用户与过期时间，头部+载荷+秘钥完成碰撞检测校验token是否被篡改 \u0026quot;\u0026quot;\u0026quot; 6.drf-jwt插件 官网\nhttps://github.com/jpadilla/django-rest-framework-jwt 安装\n\u0026gt;: pip3 install djangorestframework-jwt 登录-签发token：api/urls.py\n# ObtainJSONWebToken视图类就是通过username和password得到user对象然后签发token from rest_framework_jwt.views import ObtainJSONWebToken, obtain_jwt_token urlpatterns = [ # url(r'^jogin/$', ObtainJSONWebToken.as_view()), url(r'^jogin/$', obtain_jwt_token), ] 认证-校验token：全局或局部配置drf-jwt的认证类\nJSONWebTokenAuthentication\nfrom rest_framework.views import APIView from utils.response import APIResponse # 必须登录后才能访问 - 通过了认证权限组件 from rest_framework.permissions import IsAuthenticated from rest_framework_jwt.authentication import JSONWebTokenAuthentication class UserDetail(APIView): authentication_classes = [JSONWebTokenAuthentication] # jwt-token校验request.user permission_classes = [IsAuthenticated] # 结合权限组件筛选掉游客 def get(self, request, *args, **kwargs): return APIResponse(results={'username': request.user.username}) 路由与接口测试\n# 路由 url(r'^user/detail/$', views.UserDetail.as_view()), # 接口：/api/user/detail/ # 认证信息：必须在请求头的 Authorization 中携带 \u0026quot;jwt 后台签发的token\u0026quot; 格式的认证字符串 ","permalink":"https://zhong-er.com/posts/drf%E9%A2%91%E7%8E%87%E7%BB%84%E4%BB%B6/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003e\u003cimg alt=\"drf.jpg\" loading=\"lazy\" src=\"/assets/201910177473_80_1.jpg\"\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003ch1 id=\"1频率类源码入口\"\u003e\u003cstrong\u003e1.频率类源码入口\u003c/strong\u003e\u003c/h1\u003e\n\u003cpre\u003e\u003ccode\u003e# 1）APIView的dispath方法中的 self.initial(request, *args, **kwargs) 点进去\n# 2）self.check_throttles(request) 进行频率认证\n\n# 频率组件核心源码分析\ndef check_throttles(self, request):\n    throttle_durations = []\n    # 1）遍历配置的频率认证类，初始化得到一个个频率认证类对象（会调用频率认证类的 __init__() 方法）\n    # 2）频率认证类对象调用 allow_request 方法，判断是否限次（没有限次可访问，限次不可访问）\n    # 3）频率认证类对象在限次后，调用 wait 方法，获取还需等待多长时间可以进行下一次访问\n    # 注：频率认证类都是继承 SimpleRateThrottle 类\n    for throttle in self.get_throttles():\n        if not throttle.allow_request(request, self):\n            # 只要频率限制了，allow_request 返回False了，才会调用wait\n            throttle_durations.append(throttle.wait())\n\n            if throttle_durations:\n                # Filter out `None` values which may happen in case of config / rate\n                # changes, see #1438\n                durations = [\n                    duration for duration in throttle_durations\n                    if duration is not None\n                ]\n\n                duration = max(durations, default=None)\n                self.throttled(request, duration)\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch1 id=\"2自定义频率类\"\u003e\u003cstrong\u003e2.自定义频率类\u003c/strong\u003e\u003c/h1\u003e\n\u003cpre\u003e\u003ccode\u003e# 1) 自定义一个继承 SimpleRateThrottle 类 的频率类\n# 2) 设置一个 scope 类属性，属性值为任意见名知意的字符串\n# 3) 在settings配置文件中，配置drf的DEFAULT_THROTTLE_RATES，格式为 {scope字符串: '次数/时间'}\n# 4) 在自定义频率类中重写 get_cache_key 方法\n\t# 限制的对象返回 与限制信息有关的字符串\n    # 不限制的对象返回 None (只能放回None，不能是False或是''等)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e短信接口1/min频率限制\u003c/p\u003e","title":"【DRF】频率组件"},{"content":"\n三大认证源码分析\n1）APIView的dispath(self, request, *args, **kwargs) 2）dispath方法内 self.initial(request, *args, **kwargs) 进入三大认证 # 认证组件：校验用户 - 游客、合法用户、非法用户 # 游客：代表校验通过，直接进入下一步校验（权限校验） # 合法用户：代表校验通过，将用户存储在request.user中，再进入下一步校验（权限校验） # 非法用户：代表校验失败，抛出异常，返回403权限异常结果 self.perform_authentication(request) # 权限组件：校验用户权限 - 必须登录、所有用户、登录读写游客只读、自定义用户角色 # 认证通过：可以进入下一步校验（频率认证） # 认证失败：抛出异常，返回403权限异常结果 self.check_permissions(request) # 频率组件：限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数（3/s） # 没有达到限次：正常访问接口 # 达到限次：限制时间内不能访问，限制时间达到后，可以重新访问 self.check_throttles(request) 3) 认证组件 Request类的 方法属性 user 的get方法 =\u0026gt; self._authenticate() 完成认证 认证的细则： # 做认证 def _authenticate(self): # 遍历拿到一个个认证器，进行认证 # self.authenticators配置的一堆认证类产生的认证类对象组成的 list for authenticator in self.authenticators: try: # 认证器(对象)调用认证方法authenticate(认证类对象self, request请求对象) # 返回值：登陆的用户与认证的信息组成的 tuple # 该方法被try包裹，代表该方法会抛异常，抛异常就代表认证失败 user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise # 返回值的处理 if user_auth_tuple is not None: self._authenticator = authenticator # 如何有返回值，就将 登陆用户 与 登陆认证 分别保存到 request.user、request.auth self.user, self.auth = user_auth_tuple return # 如果返回值user_auth_tuple为空，代表认证通过，但是没有 登陆用户 与 登陆认证信息，代表游客 self._not_authenticated() 4) 权限组件 self.check_permissions(request) 认证细则： def check_permissions(self, request): # 遍历权限对象列表得到一个个权限对象(权限器)，进行权限认证 for permission in self.get_permissions(): # 权限类一定有一个has_permission权限方法，用来做权限认证的 # 参数：权限对象self、请求对象request、视图类对象 # 返回值：有权限返回True，无权限返回False if not permission.has_permission(request, self): self.permission_denied( request, message=getattr(permission, 'message', None) ) 自定义认证类\n1) 创建继承BaseAuthentication的认证类 2) 实现authenticate方法 3) 实现体根据认证规则 确定游客、非法用户、合法用户 4) 进行全局或局部配置 认证规则 i.没有认证信息返回None(游客) ii.有认证信息认证失败抛异常(非法用户) iii.有认证信息认证成功返回用户与认证信息元组(合法用户) utils/authentications.py\n# 自定义认证类 # 1）继承BaseAuthentication类 # 2）重新authenticate(self, request)方法，自定义认证规则 # 3）认证规则基于的条件： # 没有认证信息返回None(游客) # 有认证信息认证失败抛异常(非法用户) # 有认证信息认证成功返回用户与认证信息元组(合法用户) # 4）完全视图类的全局(settings文件中)或局部(确切的视图类)配置 from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from . import models class MyAuthentication(BaseAuthentication): \u0026quot;\u0026quot;\u0026quot; 同前台请求头拿认证信息auth（获取认证的字段要与前台约定） 没有auth是游客，返回None 有auth进行校验 失败是非法用户，抛出异常 成功是合法用户，返回 (用户, 认证信息) \u0026quot;\u0026quot;\u0026quot; def authenticate(self, request): # 前台在请求头携带认证信息， # 且默认规范用 Authorization 字段携带认证信息， # 后台固定在请求对象的META字段中 HTTP_AUTHORIZATION 获取 auth = request.META.get('HTTP_AUTHORIZATION', None) # 处理游客 if auth is None: return None # 设置一下认证字段小规则(两段式)：\u0026quot;auth 认证字符串\u0026quot; auth_list = auth.split() # 校验合法还是非法用户 if not (len(auth_list) == 2 and auth_list[0].lower() == 'auth'): raise AuthenticationFailed('认证信息有误，非法用户') # 合法的用户还需要从auth_list[1]中解析出来 # 注：假设一种情况，信息为abc.123.xyz，就可以解析出admin用户；实际开发，该逻辑一定是校验用户的正常逻辑 if auth_list[1] != 'abc.123.xyz': # 校验失败 raise AuthenticationFailed('用户校验失败，非法用户') user = models.User.objects.filter(username='admin').first() if not user: raise AuthenticationFailed('用户数据有误，非法用户') return (user, None) 系统权限类\n\u0026quot;\u0026quot;\u0026quot; 1）AllowAny： 认证规则全部返还True：return True 游客与登陆用户都有所有权限 2) IsAuthenticated： 认证规则必须有登陆的合法用户：return bool(request.user and request.user.is_authenticated) 游客没有任何权限，登陆用户才有权限 3) IsAdminUser： 认证规则必须是后台管理用户：return bool(request.user and request.user.is_staff) 游客没有任何权限，登陆用户才有权限 4) IsAuthenticatedOrReadOnly 认证规则必须是只读请求或是合法用户： return bool( request.method in SAFE_METHODS or request.user and request.user.is_authenticated ) 游客只读，合法用户无限制 \u0026quot;\u0026quot;\u0026quot; # api/views.py from rest_framework.permissions import IsAuthenticated class TestAuthenticatedAPIView(APIView): permission_classes = [IsAuthenticated] def get(self, request, *args, **kwargs): return APIResponse(0, 'test 登录才能访问的接口 ok') # 因为默认全局配置的权限类是AllowAny # settings.py REST_FRAMEWORK = { # 权限类配置 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny', ], } 自定义权限类\n1) 创建继承BasePermission的权限类 2) 实现has_permission方法 3) 实现体根据权限规则 确定有无权限 4) 进行全局或局部配置 认证规则 i.满足设置的用户条件，代表有权限，返回True ii.不满足设置的用户条件，代表有权限，返回False # utils/permissions.py from rest_framework.permissions import BasePermission from django.contrib.auth.models import Group class MyPermission(BasePermission): def has_permission(self, request, view): # 只读接口判断 r1 = request.method in ('GET', 'HEAD', 'OPTIONS') # group为有权限的分组 group = Group.objects.filter(name='管理员').first() # groups为当前用户所属的所有分组 groups = request.user.groups.all() r2 = group and groups r3 = group in groups # 读接口大家都有权限，写接口必须为指定分组下的登陆用户 return r1 or (r2 and r3) # 游客只读，登录用户只读，只有登录用户属于 管理员 分组，才可以增删改 from utils.permissions import MyPermission class TestAdminOrReadOnlyAPIView(APIView): permission_classes = [MyPermission] # 所有用户都可以访问 def get(self, request, *args, **kwargs): return APIResponse(0, '自定义读 OK') # 必须是 自定义“管理员”分组 下的用户 def post(self, request, *args, **kwargs): return APIResponse(0, '自定义写 OK') 未完待续\n","permalink":"https://zhong-er.com/posts/drf%E8%AE%A4%E8%AF%81%E7%BB%84%E4%BB%B6%E6%9D%83%E9%99%90%E7%BB%84%E4%BB%B6/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003e\u003cimg alt=\"drf.jpg\" loading=\"lazy\" src=\"/assets/201910177473_80_1.jpg\"\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e三大认证源码分析\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1）APIView的dispath(self, request, *args, **kwargs)\n2）dispath方法内 self.initial(request, *args, **kwargs) 进入三大认证\n\t# 认证组件：校验用户 - 游客、合法用户、非法用户\n    # 游客：代表校验通过，直接进入下一步校验（权限校验）\n    # 合法用户：代表校验通过，将用户存储在request.user中，再进入下一步校验（权限校验）\n    # 非法用户：代表校验失败，抛出异常，返回403权限异常结果\n    self.perform_authentication(request)\n    \n    # 权限组件：校验用户权限 - 必须登录、所有用户、登录读写游客只读、自定义用户角色\n    # 认证通过：可以进入下一步校验（频率认证）\n    # 认证失败：抛出异常，返回403权限异常结果\n    self.check_permissions(request)\n    \n    # 频率组件：限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数（3/s）\n    # 没有达到限次：正常访问接口\n    # 达到限次：限制时间内不能访问，限制时间达到后，可以重新访问\n    self.check_throttles(request)\n    \n    \n3) 认证组件\n\tRequest类的 方法属性 user 的get方法 =\u0026gt; self._authenticate() 完成认证\n\t\n\t认证的细则：\n\t# 做认证\n    def _authenticate(self):\n        # 遍历拿到一个个认证器，进行认证\n        # self.authenticators配置的一堆认证类产生的认证类对象组成的 list\n        for authenticator in self.authenticators:\n            try:\n                # 认证器(对象)调用认证方法authenticate(认证类对象self, request请求对象)\n                # 返回值：登陆的用户与认证的信息组成的 tuple\n                # 该方法被try包裹，代表该方法会抛异常，抛异常就代表认证失败\n                user_auth_tuple = authenticator.authenticate(self)\n            except exceptions.APIException:\n                self._not_authenticated()\n                raise\n\n            # 返回值的处理\n            if user_auth_tuple is not None:\n                self._authenticator = authenticator\n                # 如何有返回值，就将 登陆用户 与 登陆认证 分别保存到 request.user、request.auth\n                self.user, self.auth = user_auth_tuple\n                return\n        # 如果返回值user_auth_tuple为空，代表认证通过，但是没有 登陆用户 与 登陆认证信息，代表游客\n        self._not_authenticated()\n\n4) 权限组件\n\tself.check_permissions(request)\n\t认证细则：\n    def check_permissions(self, request):\n        # 遍历权限对象列表得到一个个权限对象(权限器)，进行权限认证\n        for permission in self.get_permissions():\n            # 权限类一定有一个has_permission权限方法，用来做权限认证的\n            # 参数：权限对象self、请求对象request、视图类对象\n            # 返回值：有权限返回True，无权限返回False\n            if not permission.has_permission(request, self):\n                self.permission_denied(\n                    request, message=getattr(permission, 'message', None)\n                )\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e自定义认证类\u003c/strong\u003e\u003c/p\u003e","title":"【DRF】认证组件/权限组件"},{"content":"\n视图类传递参数给序列化类\n1）在视图类中实例化序列化对象时，可以设置context内容 2）在序列化类中的局部钩子、全局钩子、create、update方法中，都可以用self.context访问视图类传递过来的内容 需求： 1) 在视图类中，可以通过request得到登陆用户request.user 2) 在序列化类中，要完成数据库数据的校验与入库操作，可能会需要知道当前的登陆用户，但序列化类无法访问request 3) 在视图类中实例化序列化对象时，将request对象传递进去 视图层：views.py\nclass Book(APIView): def post(self, request, *args, **kwargs): book_ser = serializers.BookModelSerializer(data=request_data,context={'request':request}) book_ser.is_valid(raise_exception=True) book_result = book_ser.save() return Response({ 'status': 0, 'msg': 'ok', 'results': serializers.BookModelSerializer(book_result).data }) 序列化层：serializer.py\nclass BookModelSerializer(ModelSerializer): class Meta: model = models.Book fields = ('name', 'price') def validate_name(self, value): print(self.context.get('request').method) return value 二次封装Response类\nResponse({ 'status': 0, 'msg': 'ok', 'results': [], 'token': '' # 有这样的额外的key-value数据结果 },status=http_status,headers=headers,exception=True|False) APIResponse() =\u0026gt; Response({'status': 0,'msg': 'ok'}) from rest_framework.response import Response class APIResponse(Response): def __init__(self, data_status=0, data_msg='ok', results=None, http_status=None, headers=None, exception=False, **kwargs): # data的初始状态：状态码与状态信息 data = { 'status': data_status, 'msg': data_msg, } # data的响应数据体 # results可能是False、0等数据，这些数据某些情况下也会作为合法数据返回 if results is not None: data['results'] = results # data响应的其他内容 # if kwargs is not None: # for k, v in kwargs.items(): # setattr(data, k, v) data.update(kwargs) super().__init__(data=data, status=http_status, headers=headers, exception=exception) 视图家族\nviews：视图 generics：工具视图 mixins：视图工具集 viewsets：视图集 \u0026quot;\u0026quot;\u0026quot; \u0026quot;\u0026quot;\u0026quot; 学习曲线 APIView =\u0026gt; GenericAPIView =\u0026gt; mixins的五大工具类 =\u0026gt; generics中的工具视图 =\u0026gt; viewsets中的视图集 GenericAPIView基类\nGenericAPIView是继承APIView的，使用完全兼容APIView 重点：GenericAPIView在APIView基础上完成了哪些事 1）get_queryset()：从类属性queryset中获得model的queryset数据 2）get_object()：从类属性queryset中获得model的queryset数据，再通过有名分组pk确定唯一操作对象 3）get_serializer()：从类属性serializer_class中获得serializer的序列化类 urlpatterns = [ url(r'^v2/books/$', views.BookGenericAPIView.as_view()), url(r'^v2/books/(?P\u0026lt;pk\u0026gt;.*)/$', views.BookGenericAPIView.as_view()), ] from rest_framework.generics import GenericAPIView class BookGenericAPIView(GenericAPIView): queryset = models.Book.objects.filter(is_delete=False) serializer_class = serializers.BookModelSerializer # 自定义主键的 有名分组 名 lookup_field = 'pk' # 群取 # def get(self, request, *args, **kwargs): # book_query = self.get_queryset() # book_ser = self.get_serializer(book_query, many=True) # book_data = book_ser.data # return APIResponse(results=book_data) # 单取 def get(self, request, *args, **kwargs): book_query = self.get_object() book_ser = self.get_serializer(book_query) book_data = book_ser.data return APIResponse(results=book_data) mixins视图工具集-辅助GenericAPIView\n1）mixins有五个工具类文件，一共提供了五个工具类，六个工具方法：单查、群查、单增、单删、单整体改、单局部改 2）继承工具类可以简化请求函数的实现体，但是必须继承GenericAPIView，需要GenericAPIView类提供的几个类属性和方法(见上方GenericAPIView基类知识点) 3）工具类的工具方法返回值都是Response类型对象，如果要格式化数据格式再返回给前台，可以通过 response.data 拿到工具方法返回的Response类型对象的响应数据 urlpatterns = [ url(r'^v3/books/$', views.BookMixinGenericAPIView.as_view()), url(r'^v3/books/(?P\u0026lt;pk\u0026gt;.*)/$', views.BookMixinGenericAPIView.as_view()), ] from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin class BookMixinGenericAPIView(ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericAPIView): queryset = models.Book.objects.filter(is_delete=False) serializer_class = serializers.BookModelSerializer def get(self, request, *args, **kwargs): if 'pk' in kwargs: response = self.retrieve(request, *args, **kwargs) else: # mixins提供的list方法的响应对象是Response，想将该对象格式化为APIResponse response = self.list(request, *args, **kwargs) # response的数据都存放在response.data中 return APIResponse(results=response.data) def post(self, request, *args, **kwargs): response = self.create(request, *args, **kwargs) return APIResponse(results=response.data) def put(self, request, *args, **kwargs): response = self.update(request, *args, **kwargs) return APIResponse(results=response.data) def patch(self, request, *args, **kwargs): response = self.partial_update(request, *args, **kwargs) return APIResponse(results=response.data) 工具视图\n1）工具视图都是GenericAPIView的子类，且不同的子类继承了不听的工具类，重写了请求方法 2）工具视图的功能如果直接可以满足需求，只需要继承工具视图，提供queryset与serializer_class即可 urlpatterns = [ url(r'^v4/books/$', views.BookListCreatePIView.as_view()), url(r'^v4/books/(?P\u0026lt;pk\u0026gt;.*)/$', views.BookListCreatePIView.as_view()), ] from rest_framework.generics import ListCreateAPIView, UpdateAPIView class BookListCreatePIView(ListCreateAPIView, UpdateAPIView): queryset = models.Book.objects.filter(is_delete=False) serializer_class = serializers.BookModelSerializer 视图集\n1）视图集都是优先继承ViewSetMixin类，再继承一个视图类（GenericAPIView或APIView） GenericViewSet、ViewSet 2）ViewSetMixin提供了重写的as_view()方法，继承视图集的视图类，配置路由时调用as_view()必须传入 请求名-函数名 映射关系字典 eg: url(r'^v5/books/$', views.BookGenericViewSet.as_view({'get': 'my_get_list'})), 表示get请求会交给my_get_list视图函数处理 urlpatterns = [ # View的as_view()：将get请求映射到视图类的get方法 # ViewSet的as_view({'get': 'my_get_list'})：将get请求映射到视图类的my_get_list方法 url(r'^v5/books/$', views.BookGenericViewSet.as_view({'get': 'my_get_list'})), url(r'^v5/books/(?P\u0026lt;pk\u0026gt;.*)/$', views.BookGenericViewSet.as_view({'get': 'my_get_obj'})), ] from rest_framework.viewsets import GenericViewSet from rest_framework import mixins class BookGenericViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet): queryset = models.Book.objects.filter(is_delete=False) serializer_class = serializers.BookModelSerializer def my_get_list(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def my_get_obj(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) GenericAPIView与APIView最为两大继承视图的区别\n1）GenericViewSet和ViewSet都继承了ViewSetMixin，as_view都可以配置 请求-函数 映射 2）GenericViewSet继承的是GenericAPIView视图类，用来完成标准的 model 类操作接口 3）ViewSet继承的是APIView视图类，用来完成不需要 model 类参与，或是非标准的 model 类操作接口 post请求在标准的 model 类操作下就是新增接口，登陆的post不满足 post请求验证码的接口，不需要 model 类的参与 案例：登陆的post请求，并不是完成数据的新增，只是用post提交数据，得到的结果也不是登陆的用户信息，而是登陆的认证信息 工具视图集\nurlpatterns = [ url(r'^v6/books/$', views.BookModelViewSet.as_view({'get': 'list', 'post': 'create'})), url(r'^v6/books/(?P\u0026lt;pk\u0026gt;.*)/$', views.BookModelViewSet.as_view({'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})), ] from rest_framework.viewsets import ModelViewSet class BookModelViewSet(ModelViewSet): queryset = models.Book.objects.filter(is_delete=False) serializer_class = serializers.BookModelSerializer # 删不是数据库，而是该记录中的删除字段 def destroy(self, request, *args, **kwargs): instance = self.get_object() # type: models.Book if not instance: return APIResponse(1, '删除失败') # 实际操作，在此之前就做了判断 instance.is_delete = True instance.save() return APIResponse(0, '删除成功') 路由组件\nfrom django.conf.urls import include from rest_framework.routers import SimpleRouter router = SimpleRouter() # 所有路由与ViewSet视图类的都可以注册，会产生 '^v6/books/$' 和 '^v6/books/(?P\u0026lt;pk\u0026gt;[^/.]+)/$' router.register('v6/books', views.BookModelViewSet) urlpatterns = [ # 第一种添加子列表方式 url(r'^', include(router.urls)), ] # 第二种添加子列表方式 # urlpatterns.extend(router.urls) ","permalink":"https://zhong-er.com/posts/drf%E8%A7%86%E5%9B%BE%E5%AE%B6%E6%97%8F/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003e\u003cimg alt=\"drf.jpg\" loading=\"lazy\" src=\"/assets/201910177473_80_1.jpg\"\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e视图类传递参数给序列化类\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1）在视图类中实例化序列化对象时，可以设置context内容\n2）在序列化类中的局部钩子、全局钩子、create、update方法中，都可以用self.context访问视图类传递过来的内容\n\n需求：\n1) 在视图类中，可以通过request得到登陆用户request.user\n2) 在序列化类中，要完成数据库数据的校验与入库操作，可能会需要知道当前的登陆用户，但序列化类无法访问request\n3) 在视图类中实例化序列化对象时，将request对象传递进去\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e视图层：views.py\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass Book(APIView):\n    def post(self, request, *args, **kwargs):\n        book_ser = serializers.BookModelSerializer(data=request_data,context={'request':request})\n        book_ser.is_valid(raise_exception=True)\n        book_result = book_ser.save()\n        return Response({\n            'status': 0,\n            'msg': 'ok',\n            'results': serializers.BookModelSerializer(book_result).data\n        })\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e序列化层：serializer.py\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass BookModelSerializer(ModelSerializer):\n    class Meta:\n        model = models.Book\n        fields = ('name', 'price')\n    def validate_name(self, value):\n        print(self.context.get('request').method)\n        return value\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e二次封装Response类\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eResponse({\n    'status': 0,\n    'msg': 'ok',\n    'results': [],\n    'token': ''  # 有这样的额外的key-value数据结果\n},status=http_status,headers=headers,exception=True|False)\n\nAPIResponse() =\u0026gt; Response({'status': 0,'msg': 'ok'})\n\n\nfrom rest_framework.response import Response\n\nclass APIResponse(Response):\n    def __init__(self, data_status=0, data_msg='ok', results=None, http_status=None, headers=None, exception=False, **kwargs):\n        # data的初始状态：状态码与状态信息\n        data = {\n            'status': data_status,\n            'msg': data_msg,\n        }\n        # data的响应数据体\n        # results可能是False、0等数据，这些数据某些情况下也会作为合法数据返回\n        if results is not None:\n            data['results'] = results\n        # data响应的其他内容\n        # if kwargs is not None:\n        #     for k, v in kwargs.items():\n        #         setattr(data, k, v)\n        data.update(kwargs)\n\n        super().__init__(data=data, status=http_status, headers=headers, exception=exception)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e视图家族\u003c/strong\u003e\u003c/p\u003e","title":"【DRF】视图家族"},{"content":"\nDRF框架\n全称：Django-rest framework\n知识点\n\u0026quot;\u0026quot;\u0026quot; 1、接口：什么是接口、restful接口规范 2、CBV生命周期源码 - 基于restful规范下的CBV接口 3、请求组件、解析组件、响应组件 4、序列化组件(灵魂) 5、三大认证(重中之重)：认证、权限(权限六表)、频率 6、其他组件：过滤、筛选、排序、分页、路由 \u0026quot;\u0026quot;\u0026quot; # 难点：源码分析 接口\n\u0026quot;\u0026quot;\u0026quot; 接口：联系两个物质的媒介，完成信息交互 web程序中：联系前台页面与后台数据库的媒介 web接口组成： url：长得像放回数据的url链接 请求参数：前台按照指定的key提供数据给后台 响应数据：后台与数据库交互后将数据反馈给前台 \u0026quot;\u0026quot;\u0026quot; restful接口规范\n接口规范：就是为了采用不同的后台语言，也能使用同样的接口获取到同样的数据\n如何写接口：接口规范是规范化书写接口，写接口要写url、响应数据\n注：如果将请求参数也纳入考量范围，那就是在写接口文档\n两大部分：\nurl\n1) 用api关键字表示接口url api.baidu.com | www.baidu.com/api 2) 接口数据安全性决定优先选择https协议 3) 如果一个接口有多版本存在，需要在url中标识体现 api.baidu.com/v1/... | api.baidu.com/v2/... 4) 接口操作的数据源称之为 资源，在url中一般采用资源复数形式，一个接口可以概括对该资源的多种操作方式 api.baidu.com/books | api.baidu.com/books/(pk) 5) 请求方式有多种，用一个url处理如何保证不混乱 - 通过请求方式标识操作资源的方式 /books\tget\t获取所有 /books\tpost\t增加一个(多个) /books/(pk)\tdelete\t删除一个 /books/(pk)\tput\t整体更新一个 /books/(pk)\tpatch\t局部更新一个 6) 资源往往涉及数据的各种操作方式 - 筛选、排序、限制 api.baidu.com/books/?search=西\u0026amp;ordering=-price\u0026amp;limit=3 响应数据\n1) http请求的响应会有响应状态码，接口用来返回操作的资源数据，可以拥有 操作数据结果的 状态码 status 0(操作资源成功) 1(操作资源失败) 2(操作资源成功，但没匹配结果) 注：资源状态码不像http状态码，一般都是后台与前台或是客户约定的 2) 资源的状态码文字提示 status ok\t'账号有误' '密码有误' '用户锁定' 3) 资源本身 results 注：删除资源成功不做任何数据返回(返回空字符串) 4) 不能直接放回的资源(子资源、图片、视频等资源)，返回该资源的url链接 基于restful规范的原生Django接口\n主路由：url.py\nfrom django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), # 路由分发 url(r'^api/', include('api.urls')) ] api组件的子路由：api/url.py\nfrom django.conf.urls import url from . import views urlpatterns = [ url(r'^books/', views.Book.as_view()), url(r'^books/(?P\u0026lt;pk\u0026gt;.*)/$', views.Book.as_view()), ] 模型层：model.py\nfrom django.db import models class Book(models.Model): title = models.CharField(max_length=64) price = models.DecimalField(max_digits=5, decimal_places=2) class Meta: db_table = 'old_boy_book' verbose_name = '书籍' verbose_name_plural = verbose_name def __str__(self): return '《%s》' % self.title 后台层：admin.py\nfrom django.contrib import admin from . import models admin.site.register(models.Book) 数据库迁移\n\u0026gt;: python manage.py makemigrations \u0026gt;: python manage.py migrrate \u0026gt;: python manage.py createsuperuser 视图层：views.py\nfrom django.http import JsonResponse from django.views import View from . import models # 六大基础接口：获取一个 获取所有 增加一个 删除一个 整体更新一个 局部更新一个 # 十大接口：群增 群删 整体改群改 局部改群改 class Book(View): def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if not pk: # 群查 # 操作数据库 book_obj_list = models.Book.objects.all() # 序列化过程 book_list = [] for obj in book_obj_list: dic = {} dic['title'] = obj.title dic['price'] = obj.price book_list.append(dic) # 响应数据 return JsonResponse({ 'status': 0, 'msg': 'ok', 'results': book_list }, json_dumps_params={'ensure_ascii': False}) else: # 单查 book_dic = models.Book.objects.filter(pk=pk).values('title', 'price').first() if book_dic: return JsonResponse({ 'status': 0, 'msg': 'ok', 'results': book_dic }, json_dumps_params={'ensure_ascii': False}) return JsonResponse({ 'status': 2, 'msg': '无结果', }, json_dumps_params={'ensure_ascii': False}) # postman可以完成不同方式的请求：get | post | put ... # postman发送数据包有三种方式：form-data | urlencoding | json # 原生django对urlencoding方式数据兼容最好 def post(self, request, *args, **kwargs): # 前台通过urlencoding方式提交数据 try: book_obj = models.Book.objects.create(**request.POST.dict()) if book_obj: return JsonResponse({ 'status': 0, 'msg': 'ok', 'results': {'title': book_obj.title, 'price': book_obj.price} }, json_dumps_params={'ensure_ascii': False}) except: return JsonResponse({ 'status': 1, 'msg': '参数有误', }, json_dumps_params={'ensure_ascii': False}) return JsonResponse({ 'status': 2, 'msg': '新增失败', }, json_dumps_params={'ensure_ascii': False}) Postman接口工具\n官网下载\nget请求，携带参数采用Params\npost等请求，提交数据包可以采用三种方式：form-data、urlencoding、json\n所有请求都可以携带请求头\nDRF框架\n安装\n\u0026gt;: pip3 install djangorestframework drf框架规矩的封装风格\nfrom rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.serializers import Serializer from rest_framework.settings import APISettings from rest_framework.filters import SearchFilter from rest_framework.pagination import PageNumberPagination from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.throttling import SimpleRateThrottle class Test(APIView): def get(self, request, *args, **kwargs): return Response('drf get ok') drf请求生命周期\n\u0026quot;\u0026quot;\u0026quot; 1) 请求走的是APIView的as_view函数 2) 在APIView的as_view调用父类(django原生)的as_view,还禁用了csrf认证 3) 在父类的as_view中dispatch方法请求走的又是APIView的dispatch 4) 完成任务方法交给视图类的请求函数处理，得到请求的响应结果，返回给前台 \u0026quot;\u0026quot;\u0026quot; 请求模块：request对象\n源码入口\nAPIView类的dispatch方法中：\nrequest = self.initialize_request(request,*args,**kwargs) 源码分析\n\u0026quot;\u0026quot;\u0026quot; # 二次封装得到的def的request对象 request = self.initialize_request(request,*args,**kwargs)点进去 # 在rest_framework.request.Request实例化方法中 self._request = request 将原生request作为新request的_request属性 # 在rest_framework.request.Request的__getattr__方法中 try: return getattr(self._request, attr) # 访问属性完全兼容原生request except AttributeError: return self.__getattribute__(attr) \u0026quot;\u0026quot;\u0026quot; 重点总结\n# 1) drf 对原生request做了二次封装，request._request就是原生request # 2) 原生request对象的属性和方法都可以被drf的request对象直接访问(兼容) # 3) drf请求的所有url拼接参数均被解析到query_params中，所有数据包数据都被解析到data中 class Test(APIView): def get(self, request, *args, **kwargs): # url拼接的参数 print(request._request.GET) # 二次封装方式 print(request.GET) # 兼容 print(request.query_params) # 拓展 return Response('drf get ok') def post(self, request, *args, **kwargs): # 所有请求方式携带的数据包 print(request._request.POST) # 二次封装方式 print(request.POST) # 兼容 print(request.data) # 拓展，兼容性最强，三种数据方式都可以 print(request.query_params) return Response('drf post ok') 渲染模块：浏览器和Postman请求结果渲染数据的方式不一样\n源码入口\nAPIView类的dispatch方法中：\nself.response = self.finalize_response(request, response, *args, **kwargs) 源码分析\n\u0026quot;\u0026quot;\u0026quot; # 最后解析reponse对象数据 self.response = self.finalize_response(request, response, *args, **kwargs) 点进去 # 拿到运行的解析类的对象们 neg = self.perform_content_negotiation(request, force=True) 点进去 # 获得解析类对象 renderers = self.get_renderers() 点进去 # 从视图类中得到renderer_classes请求类，如何实例化一个个对象形参解析类对象列表 return [renderer() for renderer in self.renderer_classes] # 重点：self.renderer_classes获取renderer_classes的顺序 自己视图类的类属性(局部配置) =\u0026gt; APIView类的类属性设置 =\u0026gt; 自己配置文件的DEFAULT_RENDERER_CLASSES(全局配置) =\u0026gt; drf配置文件的DEFAULT_RENDERER_CLASSES \u0026quot;\u0026quot;\u0026quot; 全局配置：所有视图类统一处理，在项目的settings.py中\nREST_FRAMEWORK = { # drf提供的渲染类 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ], } 局部配合：某一个或一些实体类单独处理，在views.py视图类中提供对应的类属性\nclass Test(APIView): def get(self, request, *args, **kwargs): return Response('drf get ok 2') def post(self, request, *args, **kwargs): return Response('drf post ok 2') # 在setting.py中配置REST_FRAMEWORK，完成的是全局配置，所有接口统一处理 # 如果只有部分接口特殊化，可以完成 - 局部配置 from rest_framework.renderers import JSONRenderer class Test2(APIView): # 局部配置 renderer_classes = [JSONRenderer] def get(self, request, *args, **kwargs): return Response('drf get ok 2') def post(self, request, *args, **kwargs): return Response('drf post ok 2') END\n","permalink":"https://zhong-er.com/posts/drf%E6%8E%A5%E5%8F%A3cbv%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cimg alt=\"drf.jpg\" loading=\"lazy\" src=\"/assets/201910177473_80_1.jpg\"\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDRF框架\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e全称：Django-rest framework\u003c/p\u003e\n\u003cp\u003e知识点\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n1、接口：什么是接口、restful接口规范\n2、CBV生命周期源码 - 基于restful规范下的CBV接口\n3、请求组件、解析组件、响应组件\n4、序列化组件(灵魂)\n5、三大认证(重中之重)：认证、权限(权限六表)、频率\n6、其他组件：过滤、筛选、排序、分页、路由\n\u0026quot;\u0026quot;\u0026quot;\n\n# 难点：源码分析\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e接口\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n接口：联系两个物质的媒介，完成信息交互\nweb程序中：联系前台页面与后台数据库的媒介\nweb接口组成：\n\turl：长得像放回数据的url链接\n\t请求参数：前台按照指定的key提供数据给后台\n\t响应数据：后台与数据库交互后将数据反馈给前台\n\u0026quot;\u0026quot;\u0026quot;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003erestful接口规范\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e接口规范：就是为了采用不同的后台语言，也能使用同样的接口获取到同样的数据\u003c/p\u003e\n\u003cp\u003e如何写接口：接口规范是规范化书写接口，写接口要写url、响应数据\u003c/p\u003e\n\u003cp\u003e注：如果将请求参数也纳入考量范围，那就是在写接口文档\u003c/p\u003e\n\u003cp\u003e两大部分：\u003c/p\u003e\n\u003cp\u003eurl\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1) 用api关键字表示接口url\n    api.baidu.com | www.baidu.com/api\n\t\n2) 接口数据安全性决定优先选择https协议\n\n3) 如果一个接口有多版本存在，需要在url中标识体现\n    api.baidu.com/v1/... | api.baidu.com/v2/...\n\t\n4) 接口操作的数据源称之为 资源，在url中一般采用资源复数形式，一个接口可以概括对该资源的多种操作方式\n    api.baidu.com/books | api.baidu.com/books/(pk)\n\t\n5) 请求方式有多种，用一个url处理如何保证不混乱 - 通过请求方式标识操作资源的方式\n    /books\t\tget\t\t\t获取所有\n    /books\t\tpost\t\t增加一个(多个)\n    /books/(pk)\tdelete\t\t删除一个\n    /books/(pk)\tput\t\t\t整体更新一个\n    /books/(pk)\tpatch\t\t局部更新一个\n\n6) 资源往往涉及数据的各种操作方式 - 筛选、排序、限制\n\tapi.baidu.com/books/?search=西\u0026amp;ordering=-price\u0026amp;limit=3\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e响应数据\u003c/p\u003e","title":"【DRF】接口/CBV生命周期"},{"content":"\n解析模块\n为什么要配置解析模块\n\u0026quot;\u0026quot;\u0026quot; 1）drf给我们通过了多种解析数据包方式的解析类 2）我们可以通过配置来控制前台提交的哪些格式的数据后台在解析，哪些数据不解析 3）全局配置就是针对每一个视图类，局部配置就是针对指定的视图来，让它们可以按照配置规则选择性解析数据 \u0026quot;\u0026quot;\u0026quot; 源码入口\n# APIView类的dispatch方法中 request = self.initialize_request(request, *args, **kwargs) # 点进去 # 获取解析类 parsers=self.get_parsers(), # 点进去 # 去类属性(局部配置) 或 配置文件(全局配置) 拿 parser_classes return [parser() for parser in self.parser_classes] 全局配置：项目settings.py文件\nREST_FRAMEWORK = { # 全局解析类配置 'DEFAULT_PARSER_CLASSES': [ 'rest_framework.parsers.JSONParser', # json数据包 'rest_framework.parsers.FormParser', # urlencoding数据包 'rest_framework.parsers.MultiPartParser' # form-date数据包 ], } 局部配置：应用views.p的具体视图类\nfrom rest_framework.parsers import JSONParser class Book(APIView): # 局部解析类配置，只要json类型的数据包才能被解析 parser_classes = [JSONParser] pass 异常模块\n为什么要自定义异常模块\n\u0026quot;\u0026quot;\u0026quot; 1）所有经过drf的APIView视图类产生的异常，都可以提供异常处理方案 2）drf默认提供了异常处理方案(rest_framework.views.exception_handler)，但是处理范围有限 3）drf提供的处理方案两种，处理了返回异常现象，没处理返回None(后续就是服务器抛异常给前台) 4）自定义异常的目的就是解决drf没有处理的异常，让前台得到合理的异常信息返回，后台记录异常具体信息 \u0026quot;\u0026quot;\u0026quot; 源码分析\n# 异常模块：APIView类的dispatch方法中 response = self.handle_exception(exc) # 点进去 # 获取处理异常的句柄(方法) # 一层层看源码，走的是配置文件，拿到的是rest_framework.views的exception_handler # 自定义：直接写exception_handler函数，在自己的配置文件配置EXCEPTION_HANDLER指向自己的 exception_handler = self.get_exception_handler() # 异常处理的结果 # 自定义异常就是提供exception_handler异常处理函数，处理的目的就是让response一定有值 response = exception_handler(exc, context) 如何使用：自定义exception_handler函数如何书写实现体\n# 修改自己的配置文件setting.py REST_FRAMEWORK = { # 全局配置异常模块 'EXCEPTION_HANDLER': 'api.exception.exception_handler', } # 1）先将异常处理交给rest_framework.views的exception_handler去处理 # 2）判断处理的结果(返回值)response，有值代表drf已经处理了，None代表需要自己处理 # 自定义异常处理文件exception，在文件中书写exception_handler函数 from rest_framework.views import exception_handler as drf_exception_handler from rest_framework.views import Response from rest_framework import status def exception_handler(exc, context): # drf的exception_handler做基础处理 response = drf_exception_handler(exc, context) # 为空，自定义二次处理 if response is None: # print(exc) # print(context) print('%s - %s - %s' % (context['view'], context['request'].method, exc)) return Response({ 'detail': '服务器错误' }, status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True) return response 响应模块\n响应类构造器：rest_framework.response.Response\ndef __init__(self, data=None, status=None, template_name=None, headers=None, exception=False, content_type=None): \u0026quot;\u0026quot;\u0026quot; :param data: 响应数据 :param status: http响应状态码 :param template_name: drf也可以渲染页面，渲染的页面模板地址（不用了解） :param headers: 响应头 :param exception: 是否异常了 :param content_type: 响应的数据格式（一般不用处理，响应头中带了，且默认是json） \u0026quot;\u0026quot;\u0026quot; pass 使用：常规实例化响应对象\n# status就是解释一堆 数字 网络状态码的模块 from rest_framework import status就是解释一堆 数字 网络状态码的模块 # 一般情况下只需要返回数据，status和headers都有默认值 return Response(data={数据}, status=status.HTTP_200_OK, headers={设置的响应头}) 序列化组件：\n知识点：Serializer(偏底层)、ModelSerializer(重点)、ListModelSerializer(辅助群该)\nSerializer\n序列化准备：\n模型层：models.py\nclass User(models.Model): SEX_CHOICES = [ [0, '男'], [1, '女'], ] name = models.CharField(max_length=64) pwd = models.CharField(max_length=32) phone = models.CharField(max_length=11, null=True, default=None) sex = models.IntegerField(choices=SEX_CHOICES, default=0) icon = models.ImageField(upload_to='icon', default='icon/default.jpg') class Meta: db_table = 'old_boy_user' verbose_name = '用户' verbose_name_plural = verbose_name def __str__(self): return '%s' % self.name 后台管理层：admin.py\nfrom django.contrib import admin from . import models admin.site.register(models.User) 配置层：settings.py\n# 注册rest_framework INSTALLED_APPS = [ # ... 'rest_framework', ] # 配置数据库 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'day70', 'USER': 'root', 'PASSWORD': '123' } } # media资源 MEDIA_URL = '/media/' # 后期高级序列化类与视图类，会使用该配置 MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # media资源路径 # 国际化配置 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False 主路由：项目下urls.py\nurlpattens = [ # ... url(r'^api/', include('api.urls')), url(r'^media/(?P\u0026lt;path\u0026gt;.*)', serve, {'document_root': settings.MEDIA_ROOT}), ] 子路由：应用下urls.py\nurlpatterns = [ url(r'^users/$', views.User.as_view()), url(r'^users/(?P\u0026lt;pk\u0026gt;.*)/$', views.User.as_view()), ] 序列化使用\n序列化层：api/serializers.py\n\u0026quot;\u0026quot;\u0026quot; 1）设置需要返回给前台 那些model类有对应的 字段，不需要返回的就不用设置了 2）设置方法字段，字段名可以随意，字段值有 get_字段名 提供，来完成一些需要处理再返回的数据 \u0026quot;\u0026quot;\u0026quot; # 序列化组件 - 为每一个model类通过一套序列化工具类 # 序列化组件的工作方式与django froms组件非常相似 from rest_framework import serializers, exceptions from django.conf import settings from . import models class UserSerializer(serializers.Serializer): name = serializers.CharField() phone = serializers.CharField() # 序列化提供给前台的字段个数由后台决定，可以少提供， # 但是提供的数据库对应的字段，名字一定要与数据库字段相同 # sex = serializers.IntegerField() # icon = serializers.ImageField() # 自定义序列化属性 # 属性名随意，值由固定的命名规范方法提供: # get_属性名(self, 参与序列化的model对象) # 返回值就是自定义序列化属性的值 gender = serializers.SerializerMethodField() def get_gender(self, obj): # choice类型的解释型值 get_字段_display() 来访问 return obj.get_sex_display() icon = serializers.SerializerMethodField() def get_icon(self, obj): # settings.MEDIA_URL: 自己配置的 /media/，给后面高级序列化与视图类准备的 # obj.icon不能直接作为数据返回，因为内容虽然是字符串，但是类型是ImageFieldFile类型 return '%s%s%s' % (r'http://127.0.0.1:8000', settings.MEDIA_URL, str(obj.icon)) 视图层\n\u0026quot;\u0026quot;\u0026quot; 1）从数据库中将要序列化给前台的model对象，或是对个model对象查询出来 user_obj = models.User.objects.get(pk=pk) 或者 user_obj_list = models.User.objects.all() 2）将对象交给序列化处理，产生序列化对象，如果序列化的是多个数据，要设置many=True user_ser = serializers.UserSerializer(user_obj) 或者 user_ser = serializers.UserSerializer(user_obj_list, many=True) 3）序列化 对象.data 就是可以返回给前台的序列化数据 return Response({ 'status': 0, 'msg': 0, 'results': user_ser.data }) \u0026quot;\u0026quot;\u0026quot; class User(APIView): def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: try: # 用户对象不能直接作为数据返回给前台 user_obj = models.User.objects.get(pk=pk) # 序列化一下用户对象 user_ser = serializers.UserSerializer(user_obj) # print(user_ser, type(user_ser)) return Response({ 'status': 0, 'msg': 0, 'results': user_ser.data }) except: return Response({ 'status': 2, 'msg': '用户不存在', }) else: # 用户对象列表(queryset)不能直接作为数据返回给前台 user_obj_list = models.User.objects.all() # 序列化一下用户对象 user_ser_data = serializers.UserSerializer(user_obj_list, many=True).data return Response({ 'status': 0, 'msg': 0, 'results': user_ser_data }) 反序列化使用\n反序列层：api/serializers.py\n\u0026quot;\u0026quot;\u0026quot; 1）设置必填与选填序列化字段，设置校验规则 2）为需要额外校验的字段提供局部钩子函数，如果该字段不入库，且不参与全局钩子校验，可以将值取出校验 3）为有联合关系的字段们提供全局钩子函数，如果某些字段不入库，可以将值取出校验 4）重写create方法，完成校验通过的数据入库工作，得到新增的对象 \u0026quot;\u0026quot;\u0026quot; class UserDeserializer(serializers.Serializer): # 1) 哪些自动必须反序列化 # 2) 字段都有哪些安全校验 # 3) 哪些字段需要额外提供校验 # 4) 哪些字段间存在联合校验 # 注：反序列化字段都是用来入库的，不会出现自定义方法属性，会出现可以设置校验规则的自定义属性(re_pwd) name = serializers.CharField( max_length=64, min_length=3, error_messages={ 'max_length': '太长', 'min_length': '太短' } ) pwd = serializers.CharField() phone = serializers.CharField(required=False) sex = serializers.IntegerField(required=False) # 自定义有校验规则的反序列化字段 re_pwd = serializers.CharField(required=True) # 小结： # name，pwd，re_pwd为必填字段 # phone，sex为选填字段 # 五个字段都必须提供完成的校验规则 # 局部钩子：validate_要校验的字段名(self, 当前要校验字段的值) # 校验规则：校验通过返回原值，校验失败，抛出异常 def validate_name(self, value): if 'g' in value.lower(): # 名字中不能出现g raise exceptions.ValidationError('名字非法，是个鸡贼！') return value # 全局钩子：validate(self, 系统与局部钩子校验通过的所有数据) # 校验规则：校验通过返回原值，校验失败，抛出异常 def validate(self, attrs): pwd = attrs.get('pwd') re_pwd = attrs.pop('re_pwd') if pwd != re_pwd: raise exceptions.ValidationError({'pwd\u0026amp;re_pwd': '两次密码不一致'}) return attrs # 要完成新增，需要自己重写 create 方法 def create(self, validated_data): # 尽量在所有校验规则完毕之后，数据可以直接入库 return models.User.objects.create(**validated_data) 视图层\n\u0026quot;\u0026quot;\u0026quot; 1）book_ser = serializers.UserDeserializer(data=request_data) # 数据必须赋值data 2）book_ser.is_valid() # 结果为 通过 | 不通过 3）不通过返回 book_ser.errors 给前台，通过 book_ser.save() 得到新增的对象，再正常返回 \u0026quot;\u0026quot;\u0026quot; class User(APIView): # 只考虑单增 def post(self, request, *args, **kwargs): # 请求数据 request_data = request.data # 数据是否合法（增加对象需要一个字典数据） if not isinstance(request_data, dict) or request_data == {}: return Response({ 'status': 1, 'msg': '数据有误', }) # 数据类型合法，但数据内容不一定合法，需要校验数据，校验(参与反序列化)的数据需要赋值给data book_ser = serializers.UserDeserializer(data=request_data) # 序列化对象调用is_valid()完成校验，校验失败的失败信息都会被存储在 序列化对象.errors if book_ser.is_valid(): # 校验通过，完成新增 book_obj = book_ser.save() return Response({ 'status': 0, 'msg': 'ok', 'results': serializers.UserSerializer(book_obj).data }) else: # 校验失败 return Response({ 'status': 1, 'msg': book_ser.errors, }) END\n","permalink":"https://zhong-er.com/posts/drf%E8%A7%A3%E6%9E%90%E5%BC%82%E5%B8%B8%E5%93%8D%E5%BA%94%E6%A8%A1%E5%9D%97%E5%BA%8F%E5%88%97%E5%8C%96%E7%BB%84%E4%BB%B6/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003e\u003cimg alt=\"drf.jpg\" loading=\"lazy\" src=\"/assets/201910177473_80_1.jpg\"\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e解析模块\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e为什么要配置解析模块\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n1）drf给我们通过了多种解析数据包方式的解析类\n2）我们可以通过配置来控制前台提交的哪些格式的数据后台在解析，哪些数据不解析\n3）全局配置就是针对每一个视图类，局部配置就是针对指定的视图来，让它们可以按照配置规则选择性解析数据\n\u0026quot;\u0026quot;\u0026quot;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e源码入口\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# APIView类的dispatch方法中\nrequest = self.initialize_request(request, *args, **kwargs)  # 点进去\n\n# 获取解析类\nparsers=self.get_parsers(),  # 点进去\n\n# 去类属性(局部配置) 或 配置文件(全局配置) 拿 parser_classes\nreturn [parser() for parser in self.parser_classes]\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e全局配置：项目settings.py文件\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eREST_FRAMEWORK = {\n    # 全局解析类配置\n    'DEFAULT_PARSER_CLASSES': [\n        'rest_framework.parsers.JSONParser',  # json数据包\n        'rest_framework.parsers.FormParser',  # urlencoding数据包\n        'rest_framework.parsers.MultiPartParser'  # form-date数据包\n    ],\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e局部配置：应用views.p的具体视图类\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom rest_framework.parsers import JSONParser\nclass Book(APIView):\n    # 局部解析类配置，只要json类型的数据包才能被解析\n    parser_classes = [JSONParser]\n    pass\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e异常模块\u003c/strong\u003e\u003c/p\u003e","title":"【DRF】解析/异常/响应模块/序列化组件"},{"content":"\n环境配置\n配置：settings.py\nINSTALLED_APPS = [ # ... 'rest_framework', ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dg_proj', 'USER': 'root', 'PASSWORD': '123', } } \u0026quot;\u0026quot;\u0026quot; 任何__init__文件 import pymysql pymysql.install_as_MySQLdb() \u0026quot;\u0026quot;\u0026quot; LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 路由\n# 主 from django.conf.urls import url, include from django.contrib import admin from django.views.static import serve from django.conf import settings urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^api/', include('api.urls')), url(r'^media/(?P\u0026lt;path\u0026gt;.*)', serve, {'document_root': settings.MEDIA_ROOT}), ] # 子 from django.conf.urls import url from . import views urlpatterns = [ ] 多表设计\n\u0026quot;\u0026quot;\u0026quot; Book表：name、price、img、authors、publish、is_delete、create_time Publish表：name、address、is_delete、create_time Author表：name、age、is_delete、create_time AuthorDetail表：mobile, author、is_delete、create_time BaseModel基表 is_delete、create_time 上面四表继承基表，可以继承两个字段 \u0026quot;\u0026quot;\u0026quot; 基表\nclass BaseModel(models.Model): is_delete = models.BooleanField(default=False) create_time = models.DateTimeField(auto_now_add=True) # 设置 abstract = True 来声明基表，作为基表的Model不能在数据库中形成对应的表 class Meta: abstract = True 断关联多表关系(重点)\n\u0026quot;\u0026quot;\u0026quot; 1、外键位置： 一对多 - 外键放多的一方 一对一 - 从逻辑正反向考虑，如作者表与作者详情表，作者删除级联删除详情，详情删除作者依旧存在，所以建议外键在详情表中 多对多 - 外键在关系表中 2、ORM正向方向连表查找： 正向：通过外键字段 eg: author_detial_obj.author 反向：通过related_name的值 eg：author_obj.detail 注：依赖代码见下方 3、连表操作关系： 1）作者删除，详情级联 - on_delete=models.CASCADE 2）作者删除，详情置空 - null=True, on_delete=models.SET_NULL 3）作者删除，详情重置 - default=0, on_delete=models.SET_DEFAULT 4）作者删除，详情不动 - on_delete=models.DO_NOTHING 注：拿作者与作者详情表举例 4、外键关联字段的参数 - 如何实现 断关联、目前表间操作关系、方向查询字段 i）作者详情表中的 author = models.OneToOneField( to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE ) ii）图书表中的 publish = models.ForeignKey( to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING, ) authors = models.ManyToManyField( to='Author' related_name='books', db_constraint=False, ) 注：ManyToManyField不能设置on_delete，OneToOneField、ForeignKey必须设置on_delete（django1.x系统默认级联，但是django2.x必须手动明确） \u0026quot;\u0026quot;\u0026quot; model类\nfrom django.db import models # 图书管理系统：Book、Author、AuthorDetail、Publish \u0026quot;\u0026quot;\u0026quot; Book表: name、price、img、authors、publish、is_delete、create_time Publish表: name、address、is_delete、create_time Author表: name、age、is_delete、create_time AuthorDetail表: mobile, author、is_delete、create_time \u0026quot;\u0026quot;\u0026quot; # 1) 基表 class BaseModel(models.Model): is_delete = models.BooleanField(default=False) create_time = models.DateTimeField(auto_now_add=True) # 作为基表的Model不能在数据库中形成对应的表，设置 abstract = True class Meta: abstract = True class Book(BaseModel): \u0026quot;\u0026quot;\u0026quot;name、price、img、authors、publish、is_delete、create_time\u0026quot;\u0026quot;\u0026quot; name = models.CharField(max_length=64) price = models.DecimalField(max_digits=5, decimal_places=2) img = models.ImageField(upload_to='img', default='img/default.jpg') publish = models.ForeignKey( to='Publish', db_constraint=False, # 断关联 related_name='books', # 反向查询字段：publish_obj.books 就能访问所有出版的书 on_delete=models.DO_NOTHING, # 设置连表操作关系 ) authors = models.ManyToManyField( to='Author', db_constraint=False, related_name='books' ) # 序列化插拔式属性 - 完成自定义字段名完成连表查询 @property def publish_name(self): return self.publish.name @property def author_list(self): return self.authors.values('name', 'age', 'detail__mobile').all() class Meta: db_table = 'book' verbose_name = '书籍' verbose_name_plural = verbose_name def __str__(self): return self.name class Publish(BaseModel): \u0026quot;\u0026quot;\u0026quot;name、address、is_delete、create_time\u0026quot;\u0026quot;\u0026quot; name = models.CharField(max_length=64) address = models.CharField(max_length=64) class Meta: db_table = 'publish' verbose_name = '出版社' verbose_name_plural = verbose_name def __str__(self): return self.name class Author(BaseModel): \u0026quot;\u0026quot;\u0026quot;name、age、is_delete、create_time\u0026quot;\u0026quot;\u0026quot; name = models.CharField(max_length=64) age = models.IntegerField() class Meta: db_table = 'author' verbose_name = '作者' verbose_name_plural = verbose_name def __str__(self): return self.name class AuthorDetail(BaseModel): \u0026quot;\u0026quot;\u0026quot;mobile, author、is_delete、create_time\u0026quot;\u0026quot;\u0026quot; mobile = models.CharField(max_length=11) author = models.OneToOneField( to='Author', db_constraint=False, related_name='detail', on_delete=models.CASCADE, ) class Meta: db_table = 'author_detail' verbose_name = '作者详情' verbose_name_plural = verbose_name def __str__(self): return '%s的详情' % self.author.name 序列化\n序列化层：api/serializers.py\nfrom rest_framework.serializers import ModelSerializer, SerializerMethodField from rest_framework.exceptions import ValidationError from . import models # 可以单独作为Publish接口的序列化类，也可以作为Book序列化外键publish辅助的序列化组件 class PublishModelSerializer(ModelSerializer): class Meta: model = models.Publish fields = ('name', 'address') class BookModelSerializer(ModelSerializer): # 了解: 该方式设置的序列化字段，必须在fields中声明 # publish_address = SerializerMethodField() # def get_publish_address(self, obj): # return obj.publish.address # 自定义连表深度 - 子序列化方式 - 该方式不能参与反序列化，使用在序列化反序列化共存时，不能书写 publish = PublishModelSerializer() class Meta: # 序列化类关联的model类 model = models.Book # 参与序列化的字段 fields = ('name', 'price', 'img', 'author_list', 'publish') # 了解知识点 # 所有字段 # fields = '__all__' # 与fields不共存，exclude排除哪些字段 # exclude = ('id', 'is_delete', 'create_time') # 自动连表深度 # depth = 1 视图层：api/views.py\nclass Book(APIView): def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: try: book_obj = models.Book.objects.get(pk=pk, is_delete=False) book_data = serializers.BookModelSerializer(book_obj).data except: return Response({ 'status': 1, 'msg': '书籍不存在' }) else: book_query = models.Book.objects.filter(is_delete=False).all() book_data = serializers.BookModelSerializer(book_query, many=True).data return Response({ 'status': 0, 'msg': 'ok', 'results': book_data }) 路由层：api/urls.py\nurlpatterns = [ url(r'^books/$', views.Book.as_view()), url(r'^books/(?P\u0026lt;pk\u0026gt;.*)/$', views.Book.as_view()), ] 反序列化\n序列化层：api/serializers.py\nclass BookModelDeserializer(ModelSerializer): class Meta: model = models.Book fields = ('name', 'price', 'publish', 'authors') # extra_kwargs 用来完成反序列化字段的 系统校验规则 extra_kwargs = { 'name': { 'required': True, 'min_length': 1, 'error_messages': { 'required': '必填项', 'min_length': '太短', } } } # 局部钩子 def validate_name(self, value): # 书名不能包含 g 字符 if 'g' in value.lower(): raise ValidationError('该g书不能出版') return value # 全局钩子 def validate(self, attrs): publish = attrs.get('publish') name = attrs.get('name') if models.Book.objects.filter(name=name, publish=publish): raise ValidationError({'book': '该书已存在'}) return attrs # ModelSerializer类已经帮我们实现了 create 与 update 方法 视图层：api/views.py\nclass Book(APIView): def post(self, request, *args, **kwargs): request_data = request.data book_ser = serializers.BookModelDeserializer(data=request_data) # raise_exception=True：当校验失败，马上终止当前视图方法，抛异常返回给前台 book_ser.is_valid(raise_exception=True) book_obj = book_ser.save() return Response({ 'status': 0, 'msg': 'ok', 'results': serializers.BookModelSerializer(book_obj).data }) 路由层：api/ursl.py\nurlpatterns = [ url(r'^books/$', views.Book.as_view()), url(r'^books/(?P\u0026lt;pk\u0026gt;.*)/$', views.Book.as_view()), ] 序列化与反序列整合(重点)\n序列化层：api/serializers.py\n\u0026quot;\u0026quot;\u0026quot; 1) fields中设置所有序列化与反序列化字段 2) extra_kwargs划分只序列化或只反序列化字段 write_only：只反序列化 read_only：只序列化 自定义字段默认只序列化(read_only) 3) 设置反序列化所需的 系统、局部钩子、全局钩子 等校验规则 \u0026quot;\u0026quot;\u0026quot; class V2BookModelSerializer(ModelSerializer): class Meta: model = models.Book fields = ('name', 'price', 'img', 'author_list', 'publish_name', 'publish', 'authors') extra_kwargs = { 'name': { 'required': True, 'min_length': 1, 'error_messages': { 'required': '必填项', 'min_length': '太短', } }, 'publish': { 'write_only': True }, 'authors': { 'write_only': True }, 'img': { 'read_only': True, }, 'author_list': { 'read_only': True, }, 'publish_name': { 'read_only': True, } } def validate_name(self, value): # 书名不能包含 g 字符 if 'g' in value.lower(): raise ValidationError('该g书不能出版') return value def validate(self, attrs): publish = attrs.get('publish') name = attrs.get('name') if models.Book.objects.filter(name=name, publish=publish): raise ValidationError({'book': '该书已存在'}) return attrs 视图层：api/views.py\nclass V2Book(APIView): # 单查：有pk # 群查：无pk def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: try: book_obj = models.Book.objects.get(pk=pk, is_delete=False) book_data = serializers.V2BookModelSerializer(book_obj).data except: return Response({ 'status': 1, 'msg': '书籍不存在' }) else: book_query = models.Book.objects.filter(is_delete=False).all() book_data = serializers.V2BookModelSerializer(book_query, many=True).data return Response({ 'status': 0, 'msg': 'ok', 'results': book_data }) # 单增：传的数据是与model对应的字典 # 群增：传的数据是 装多个 model对应字典 的列表 def post(self, request, *args, **kwargs): request_data = request.data if isinstance(request_data, dict): many = False elif isinstance(request_data, list): many = True else: return Response({ 'status': 1, 'msg': '数据有误', }) book_ser = serializers.V2BookModelSerializer(data=request_data, many=many) # 当校验失败，马上终止当前视图方法，抛异常返回给前台 book_ser.is_valid(raise_exception=True) book_result = book_ser.save() return Response({ 'status': 0, 'msg': 'ok', 'results': serializers.V2BookModelSerializer(book_result, many=many).data }) # 单删：有pk # 群删：有pks | {\u0026quot;pks\u0026quot;: [1, 2, 3]} def delete(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: pks = [pk] else: pks = request.data.get('pks') if models.Book.objects.filter(pk__in=pks, is_delete=False).update(is_delete=True): return Response({ 'status': 0, 'msg': '删除成功', }) return Response({ 'status': 1, 'msg': '删除失败', }) 路由层：api/urls.py\nurlpatterns = [ url(r'^v2/books/$', views.V2Book.as_view()), url(r'^v2/books/(?P\u0026lt;pk\u0026gt;.*)/$', views.V2Book.as_view()), ] 整体单改\n路由层、模型层、序列化层不需要修改，只需要处理视图层：views.py\n\u0026quot;\u0026quot;\u0026quot; 1) 单整体改，说明前台要提供修改的数据，那么数据就需要校验，校验的数据应该在实例化“序列化类对象”时，赋值给data 2）修改，就必须明确被修改的模型类对象，并在实例化“序列化类对象”时，赋值给instance 3）整体修改，所有校验规则有required=True的字段，都必须提供，因为在实例化“序列化类对象”时，参数partial默认为False 注：如果partial值设置为True，就是可以局部改 1）单整体修改，一般用put请求： V2BookModelSerializer( instance=要被更新的对象, data=用来更新的数据, partial=默认False，必须的字段全部参与校验 ) 2）单局部修改，一般用patch请求： V2BookModelSerializer( instance=要被更新的对象, data=用来更新的数据, partial=设置True，必须的字段都变为选填字段 ) 注：partial设置True的本质就是使字段 required=True 校验规则失效 \u0026quot;\u0026quot;\u0026quot; class V2Book(APIView): # 单整体改: 对 v2/books/(pk)/ 传的数据是与model对应的字典{name|price|publish|authors} def put(self, request, *args, **kwargs): request_data = request.data pk = kwargs.get('pk') old_book_obj = models.Book.objects.filter(pk=pk).first() # 目的：将众多数据的校验交给序列化类来处理 - 让序列化类扮演反序列化角色，校验成功后，序列化类来帮你入库 book_ser = serializers.V2BookModelSerializer(instance=old_book_obj, data=request_data, partial=False) book_ser.is_valid(raise_exception=True) # 校验通过，完成数据的更新：要更新的目标，用来更新的新数据 book_obj = book_ser.save() return Response({ 'status': 0, 'msg': 'ok', 'results': serializers.V2BookModelSerializer(book_obj).data }) 单与整体局部修改\n序列化层：serializers.py\n# 重点：ListSerializer与ModelSerializer建立关联的是： # ModelSerializer的Meta类的 - list_serializer_class class V2BookListSerializer(ListSerializer): def update(self, instance, validated_data): # print(instance) # 要更新的对象们 # print(validated_data) # 更新的对象对应的数据们 # print(self.child) # 服务的模型序列化类 - V2BookModelSerializer for index, obj in enumerate(instance): self.child.update(obj, validated_data[index]) return instance # 原模型序列化类变化 class V2BookModelSerializer(ModelSerializer): class Meta: # ... # 群改，需要设置 自定义ListSerializer，重写群改的 update 方法 list_serializer_class = V2BookListSerializer # ... 视图层:views.py\nclass V2Book(APIView): # 单局部改：对 v2/books/(pk)/ 传的数据，数据字段key都是选填 # 群局部改：对 v2/books/ # 请求数据 - [{pk:1, name:123}, {pk:3, price:7}, {pk:7, publish:2}] def patch(self, request, *args, **kwargs): request_data = request.data pk = kwargs.get('pk') # 将单改，群改的数据都格式化成 pks=[要需要的对象主键标识] | request_data=[每个要修改的对象对应的修改数据] if pk and isinstance(request_data, dict): # 单改 pks = [pk, ] request_data = [request_data, ] elif not pk and isinstance(request_data, list): # 群改 pks = [] for dic in request_data: # 遍历前台数据[{pk:1, name:123}, {pk:3, price:7}, {pk:7, publish:2}]，拿一个个字典 pk = dic.pop('pk', None) if pk: pks.append(pk) else: return Response({ 'status': 1, 'msg': '数据有误', }) else: return Response({ 'status': 1, 'msg': '数据有误', }) # pks与request_data数据筛选， # 1）将pks中的没有对应数据的pk与数据已删除的pk移除，request_data对应索引位上的数据也移除 # 2）将合理的pks转换为 objs objs = [] new_request_data = [] for index, pk in enumerate(pks): try: # pk对应的数据合理，将合理的对象存储 obj = models.Book.objects.get(pk=pk) objs.append(obj) # 对应索引的数据就需要保存下来 new_request_data.append(request_data[index]) except: # 重点：反面教程 - pk对应的数据有误，将对应索引的data中request_data中移除 # index = pks.index(pk) # request_data.pop(index) continue book_ser = serializers.V2BookModelSerializer(instance=objs, data=new_request_data, partial=True, many=True) book_ser.is_valid(raise_exception=True) book_objs = book_ser.save() return Response({ 'status': 0, 'msg': 'ok', 'results': serializers.V2BookModelSerializer(book_objs, many=True).data }) END\n","permalink":"https://zhong-er.com/posts/drf%E5%BA%8F%E5%88%97%E5%8C%96%E5%8F%8D%E5%BA%8F%E5%88%97%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%8E%E5%8F%8D%E5%BA%8F%E5%88%97%E6%95%B4%E5%90%88/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003e\u003cimg alt=\"drf.jpg\" loading=\"lazy\" src=\"/assets/201910177473_80_1.jpg\"\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e环境配置\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e配置：settings.py\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eINSTALLED_APPS = [\n    # ...\n    'rest_framework',\n]\n\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.mysql',\n        'NAME': 'dg_proj',\n        'USER': 'root',\n        'PASSWORD': '123',\n    }\n}\n\u0026quot;\u0026quot;\u0026quot;\n任何__init__文件\nimport pymysql\npymysql.install_as_MySQLdb()\n\u0026quot;\u0026quot;\u0026quot;\n\nLANGUAGE_CODE = 'zh-hans'\nTIME_ZONE = 'Asia/Shanghai'\nUSE_I18N = True\nUSE_L10N = True\nUSE_TZ = False\n\nMEDIA_URL = '/media/'\nMEDIA_ROOT = os.path.join(BASE_DIR, 'media')\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e路由\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 主\nfrom django.conf.urls import url, include\nfrom django.contrib import admin\nfrom django.views.static import serve\nfrom django.conf import settings\nurlpatterns = [\n    url(r'^admin/', admin.site.urls),\n    url(r'^api/', include('api.urls')),\n    url(r'^media/(?P\u0026lt;path\u0026gt;.*)', serve, {'document_root': settings.MEDIA_ROOT}),\n]\n\n# 子\nfrom django.conf.urls import url\nfrom . import views\nurlpatterns = [\n    \n]\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e多表设计\u003c/strong\u003e\u003c/p\u003e","title":"【DRF】序列化/反序列/序列化与反序列整合"},{"content":"\nvue项目环境搭建\nnode ~~ python: node是用c++编写用来运行js代码的\nnpm(cnpm) ~~ pip: npm是一个终端应用商城，可以换国内源cnpm\nvue ~~ django: vue是用来搭建vue前端项目的\n1）安装node\n官网下载安装包，傻瓜式安装：https://nodejs.org/zh-cn/\n2）换源安装cnpm\n: npm install -g cnpm \u0026ndash; registry=https://registry.npm.taobao.org\n3）安装vue项目脚手架\n: cnpm install -g @vue/cli\n注：2或3终端安装失败时，可以清空 npm缓存 再重复执行失败的步骤\nnpm cache clean \u0026ndash;force\nvue项目创建\n进入存放项目的目录 : cd ***\n创建项目 : vue create 项目名\n项目初始化 pycharm配置并启动vue项目\n用pycharm打开vue项目\n添加配置npm启动\nvue项目目录结构分析\n├── v-proj |\t├── node_modules // 当前项目所有依赖，一般不可以移植给其他电脑环境 |\t├── public\t|\t|\t├── favicon.ico\t// 标签图标 |\t|\t└── index.html\t// 当前项目唯一的页面 |\t├── src |\t|\t├── assets\t// 静态资源img、css、js |\t|\t├── components\t// 小组件 |\t|\t├── views\t// 页面组件 |\t|\t├── App.vue\t// 根组件 |\t|\t├── main.js\t// 全局脚本文件（项目的入口） |\t|\t├── router.js\t// 路由脚本文件（配置路由 url链接 与 页面组件的映射关系） |\t|\t└── store.js\t// 仓库脚本文件（vuex插件的配置文件，数据仓库） |\t├── README.md └\t└── **配置文件 vue组件(.vue文件)\ntemplate：有且只有一个根标签\nscript：必须将组件对象导出 export default {}\nstyle: style标签明确scoped属性，代表该样式只在组件内部起作用(样式的组件化)\n\u0026lt;style scoped\u0026gt;\n全局脚本文件main.js(项目入口)\nimport Vue from 'vue' import App from './App.vue' import router from './router' import store from './store' Vue.config.productionTip = false new Vue({ router, store, render: h =\u0026gt; h(App) }).$mount('#app') 改写\nimport Vue from 'vue' // 加载vue环境 import App from './App.vue' // 加载根组件 import router from './router' // 加载路由环境 import store from './store' // 加载数据仓库环境 Vue.config.productionTip = false new Vue({ el: '#app', router, store, render: function (readFn) { return readFn(App); }, }); vue项目启动生命周期与页面组件的运用\n请求过程\n加载mian.js启动项目 i) import Vue from \u0026lsquo;vue\u0026rsquo; 为项目加载vue环境\nii) import App from \u0026lsquo;./App.vue\u0026rsquo; 加载根组件用于渲染替换挂载点\niii) import router from \u0026lsquo;./router\u0026rsquo; 加载路由脚本文件，进入路由相关配置\n加载router.js文件，为项目提供路由服务，并加载已配置的路由(链接与页面组件的映射关系) 注：不管当前渲染的是什么路由，页面渲染的一定是根组件，链接匹配到的页面组件只是替换根组件中的\n如果请求链接改变(路由改变)，就会匹配新链接对应的页面组件，新页面组件会替换渲染router-view标签，替换掉之前的页面标签(就是完成了页面跳转) 参与文件\nmain.js：该文件内容不变\nApp.vue\n\u0026lt;template\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;!-- url路径会加载不同的页面组件 eg：/red =\u0026gt; RegPage | /blue =\u0026gt; BluePage 来替换router-view标签，完成页面的切换 --\u0026gt; \u0026lt;router-view\u0026gt;\u0026lt;/router-view\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; views/RedPage.vue\n\u0026lt;template\u0026gt; \u0026lt;div class=\u0026quot;red-page\u0026quot;\u0026gt; \u0026lt;Nav\u0026gt;\u0026lt;/Nav\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import Nav from '@/components/Nav' export default { name: \u0026quot;RedPage\u0026quot;, components: { Nav }, } \u0026lt;/script\u0026gt; \u0026lt;style scoped\u0026gt; .red-page { width: 100vw; height: 100vh; background-color: red; } \u0026lt;/style\u0026gt; views/BluePage.vue\n\u0026lt;template\u0026gt; \u0026lt;div class=\u0026quot;blue-page\u0026quot;\u0026gt; \u0026lt;Nav\u0026gt;\u0026lt;/Nav\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import Nav from '@/components/Nav' export default { name: \u0026quot;BluePage\u0026quot;, components: { Nav } } \u0026lt;/script\u0026gt; \u0026lt;style scoped\u0026gt; .blue-page { width: 100vw; height: 100vh; background-color: blue; } \u0026lt;/style\u0026gt; router.js\nimport Vue from 'vue' import Router from 'vue-router' import Home from './views/Home.vue' import RedPage from \u0026quot;./views/RedPage\u0026quot;; import BluePage from \u0026quot;./views/BluePage\u0026quot;; Vue.use(Router); export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ { path: '/', name: 'home', component: Home }, { path: '/red', name: 'red', component: RedPage }, { path: '/blue', name: 'blue', component: BluePage } ] }) 全局样式文件配置\nassets/css/global.css\nhtml, body, h1, h2, ul, p { margin: 0; padding: 0; } ul { list-style: none; } a { color: black; text-decoration: none; } main.js中新增\n// 配置全局样式 import '@/assets/css/global.css' 封装小组件-Nav导航栏组件\ncomponents/Nav.vue\n\u0026lt;template\u0026gt; \u0026lt;div class=\u0026quot;nav\u0026quot;\u0026gt; \u0026lt;!--采用vue-router完成页面跳转，不能采用a标签（会发生页面刷新，本质就是重新加载了一次项目界面）--\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li\u0026gt; \u0026lt;!--\u0026lt;a href=\u0026quot;/\u0026quot;\u0026gt;主页\u0026lt;/a\u0026gt;--\u0026gt; \u0026lt;router-link to=\u0026quot;/\u0026quot;\u0026gt;主页\u0026lt;/router-link\u0026gt; \u0026lt;/li\u0026gt; \u0026lt;li\u0026gt; \u0026lt;router-link to=\u0026quot;/red\u0026quot;\u0026gt;红页\u0026lt;/router-link\u0026gt; \u0026lt;/li\u0026gt; \u0026lt;li\u0026gt; \u0026lt;router-link to=\u0026quot;/blue\u0026quot;\u0026gt;蓝页\u0026lt;/router-link\u0026gt; \u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026quot;Nav\u0026quot;, } \u0026lt;/script\u0026gt; \u0026lt;style scoped\u0026gt; .nav { width: 100%; height: 60px; background-color: orange; } .nav li { float: left; font: normal 20px/60px '微软雅黑'; padding: 0 30px; } .nav li:hover { cursor: pointer; background-color: aquamarine; } .nav li.active { cursor: pointer; background-color: aquamarine; } \u0026lt;/style\u0026gt; views/HomePage.vue、RedPage.vue与BluePage都添加下方三个步骤代码\n\u0026lt;template\u0026gt; \u0026lt;div class=\u0026quot;home\u0026quot;\u0026gt; \u0026lt;!-- 3）使用Nav组件 --\u0026gt; \u0026lt;Nav\u0026gt;\u0026lt;/Nav\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; // 1）导入Nav组件 import Nav from '@/components/Nav' export default { // 2）注册Nav组件 components: { Nav, } } \u0026lt;/script\u0026gt; 新增页面三步骤\n在views文件夹中创建视图组件\n在router.js文件中配置路由\n设置路由跳转，在指定路由下渲染该页面组件(替换根组件中的router-view标签)\nviews/TanPage.vue\n\u0026lt;template\u0026gt; \u0026lt;div class=\u0026quot;tan-page\u0026quot;\u0026gt; \u0026lt;Nav\u0026gt;\u0026lt;/Nav\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import Nav from '@/components/Nav' export default { name: \u0026quot;TanPage\u0026quot;, components: { Nav } } \u0026lt;/script\u0026gt; \u0026lt;style scoped\u0026gt; .tan-page { width: 100vw; height: 100vh; background-color: tan; } \u0026lt;/style\u0026gt; router.js\n// ... import TanPage from \u0026quot;./views/TanPage\u0026quot;; export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [ // ... { path: '/tan', name: 'tan', component: TanPage } ] }) components/Nav.vue\n... \u0026lt;li\u0026gt; \u0026lt;router-link to=\u0026quot;/tan\u0026quot;\u0026gt;土页\u0026lt;/router-link\u0026gt; \u0026lt;/li\u0026gt; ... 组件生命周期钩子(官网API****)\n1）一个组件从创建到销毁的整个过程，就称之为组件的生命周期\n2）在组件创建到销毁的过程中，会出现众多关键的时间节点，如 组件要创建了、组件创建完毕了、组件数据渲染完毕了、组件要被销毁了、组件销毁完毕了 等等时间节点，每一个时间节点，vue都为其提供了一个回调函数(在该组件到达该时间节点时，就会触发对应的回调函数，在函数中就可以完成该节点需要完成的业务逻辑)\n3）生命周期钩子函数就是 vue实例 成员\n任何一个组件：在vue组件的script的export default导出字典中直接写钩子函数\nexport default { // ... beforeCreate() { console.log('组件创建了，但数据和方法还未提供'); // console.log(this.$data); // console.log(this.$options.methods); console.log(this.title); console.log(this.alterTitle); }, // 该钩子需要掌握，一般该组件请求后台的数据，都是在该钩子中完成 // 1）请求来的数据可以给页面变量进行赋值 // 2）该节点还只停留在虚拟DOM范畴，如果数据还需要做二次修改再渲染到页面， // 可以在beforeMount、mounted钩子中添加逻辑处理 created() { console.log('组件创建了，数据和方法已提供'); // console.log(this.$data); // console.log(this.$options.methods); console.log(this.title); console.log(this.alterTitle); console.log(this.$options.name); }, destroyed() { console.log('组件销毁完毕') } } 根据请求路径高亮路由标签\nrouter-link会被解析为a标签，用to完成指定路径跳转，但是不能添加系统事件(因为是组件标签)\n在js方法中可以用 this.$router.push(\u0026lsquo;路径\u0026rsquo;) 完成逻辑跳转\n在js方法中可以用 this.$route.path 拿到当前请求的页面路由\ncomponents/Nav.vue\n\u0026lt;template\u0026gt; \u0026lt;div class=\u0026quot;nav\u0026quot;\u0026gt; \u0026lt;!--采用vue-router完成页面跳转，不能采用a标签（会发生页面刷新，本质就是重新加载了一次项目界面）--\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li @click=\u0026quot;changePage('/')\u0026quot; :class=\u0026quot;{active: currentPage === '/'}\u0026quot;\u0026gt; \u0026lt;!--\u0026lt;a href=\u0026quot;/\u0026quot;\u0026gt;主页\u0026lt;/a\u0026gt;--\u0026gt; \u0026lt;!--\u0026lt;router-link to=\u0026quot;/\u0026quot;\u0026gt;主页\u0026lt;/router-link\u0026gt;--\u0026gt; 主页 \u0026lt;/li\u0026gt; \u0026lt;li @click=\u0026quot;changePage('/red')\u0026quot; :class=\u0026quot;{active: currentPage === '/red'}\u0026quot;\u0026gt; \u0026lt;!--\u0026lt;router-link to=\u0026quot;/red\u0026quot;\u0026gt;红页\u0026lt;/router-link\u0026gt;--\u0026gt; 红页 \u0026lt;/li\u0026gt; \u0026lt;li @click=\u0026quot;changePage('/blue')\u0026quot; :class=\u0026quot;{active: currentPage === '/blue'}\u0026quot;\u0026gt; \u0026lt;!--\u0026lt;router-link to=\u0026quot;/blue\u0026quot;\u0026gt;蓝页\u0026lt;/router-link\u0026gt;--\u0026gt; 蓝页 \u0026lt;/li\u0026gt; \u0026lt;li @click=\u0026quot;changePage('/tan')\u0026quot; :class=\u0026quot;{active: currentPage === '/tan'}\u0026quot;\u0026gt; \u0026lt;!--\u0026lt;router-link to=\u0026quot;/tan\u0026quot;\u0026gt;土页\u0026lt;/router-link\u0026gt;--\u0026gt; 土页 \u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; export default { name: \u0026quot;Nav\u0026quot;, data() { return { // 没渲染一个页面，都会出现加载Nav组件，currentPage就会被重置， // 1）在点击跳转事件中，将跳转的页面用 数据库 保存，在钩子函数中对currentPage进行数据更新 // currentPage: localStorage.currentPage ? localStorage.currentPage: '' // 2）直接在created钩子函数中，获取当前的url路径，根据路径更新currentPage currentPage: '' } }, methods: { changePage(page) { // console.log(page); // 当Nav出现渲染，该语句就无意义，因为在data中将currentPage重置为空 // this.currentPage = page; // 有bug，用户不通过点击，直接修改请求路径完成页面跳转，数据库就不会更新数据 // localStorage.currentPage = page; // 任何一个标签的事件中，都可以通过router完成逻辑条件 // console.log(this.$route); // 管理路由数据 // console.log(this.$router); // 管理路由跳转 this.$router.push(page); // 路由的逻辑跳转 } }, // 当前组件加载成功，要根据当前实际所在的路径，判断单选激活标签 created() { // console.log(this.$route.path); this.currentPage = this.$route.path; } } \u0026lt;/script\u0026gt; \u0026lt;style scoped\u0026gt; .nav { width: 100%; height: 60px; background-color: orange; } .nav li { float: left; font: normal 20px/60px '微软雅黑'; padding: 0 30px; } .nav li:hover { cursor: pointer; background-color: aquamarine; } .nav li.active { cursor: pointer; background-color: aquamarine; } \u0026lt;/style\u0026gt; ","permalink":"https://zhong-er.com/posts/vue%E9%A1%B9%E7%9B%AE%E9%85%8D%E7%BD%AE/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003e\u003cstrong\u003e\u003cimg alt=\"vue.jpg\" loading=\"lazy\" src=\"/assets/201910086430_9431_1.jpg\"\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003evue项目环境搭建\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003enode ~~ python: node是用c++编写用来运行js代码的\u003c/p\u003e\n\u003cp\u003enpm(cnpm) ~~ pip: npm是一个终端应用商城，可以换国内源cnpm\u003c/p\u003e\n\u003cp\u003evue ~~ django: vue是用来搭建vue前端项目的\u003c/p\u003e\n\u003cp\u003e1）安装node\u003c/p\u003e\n\u003cp\u003e官网下载安装包，傻瓜式安装：https://nodejs.org/zh-cn/\u003c/p\u003e\n\u003cp\u003e2）换源安装cnpm\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e: npm install -g cnpm \u0026ndash; registry=https://registry.npm.taobao.org\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e3）安装vue项目脚手架\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e: cnpm install -g @vue/cli\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e注：2或3终端安装失败时，可以清空 npm缓存 再重复执行失败的步骤\u003c/p\u003e\n\u003cp\u003enpm cache clean \u0026ndash;force\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003evue项目创建\u003c/strong\u003e\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e进入存放项目的目录\u003c/li\u003e\n\u003c/ol\u003e\n\u003cblockquote\u003e\n\u003cp\u003e: cd ***\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003col start=\"2\"\u003e\n\u003cli\u003e创建项目\u003c/li\u003e\n\u003c/ol\u003e\n\u003cblockquote\u003e\n\u003cp\u003e: vue create 项目名\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003col start=\"3\"\u003e\n\u003cli\u003e项目初始化\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cimg alt=\"vue项目创建.png\" loading=\"lazy\" src=\"/assets/201910104859_8974_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003epycharm配置并启动vue项目\u003c/strong\u003e\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e用pycharm打开vue项目\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e添加配置npm启动\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e\u003cimg alt=\"配置vue启动.png\" loading=\"lazy\" src=\"/assets/201910101130_940_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003evue项目目录结构分析\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e├── v-proj\n|\t├── node_modules  \t// 当前项目所有依赖，一般不可以移植给其他电脑环境\n|\t├── public\t\t\t\n|\t|\t├── favicon.ico\t// 标签图标\n|\t|\t└── index.html\t// 当前项目唯一的页面\n|\t├── src\n|\t|\t├── assets\t\t// 静态资源img、css、js\n|\t|\t├── components\t// 小组件\n|\t|\t├── views\t\t// 页面组件\n|\t|\t├── App.vue\t\t// 根组件\n|\t|\t├── main.js\t\t// 全局脚本文件（项目的入口）\n|\t|\t├── router.js\t// 路由脚本文件（配置路由 url链接 与 页面组件的映射关系）\n|\t|\t└── store.js\t// 仓库脚本文件（vuex插件的配置文件，数据仓库）\n|\t├── README.md\n└\t└── **配置文件\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003evue组件(.vue文件)\u003c/strong\u003e\u003c/p\u003e","title":"【vue】项目配置"},{"content":"\n条件指令\n1）两种都可以控制标签的显隐，绑定的值是布尔类型值，当都隐藏标签时\nv-if是不渲染标签\nv-show以 display:none 方式渲染\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;p v-if=\u0026quot;isShow\u0026quot;\u0026gt;if条件指令\u0026lt;/p\u0026gt; \u0026lt;p v-show=\u0026quot;isShow\u0026quot;\u0026gt;show条件指令\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { isShow: false, } }) \u0026lt;/script\u0026gt; 2）v-if/v-else-if/v-else\n一组分支上成立，分支会屏蔽下方所有分支，else分支没有条件，在所有分支不成立后才显示\n\u0026lt;style\u0026gt; .box { width: 400px; height: 300px; } .r { background-color: red; } .y { background-color: yellow } .b { background-color: blue; } .active { background-color: pink; } \u0026lt;/style\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;em\u0026quot;\u0026gt; \u0026lt;p\u0026gt; \u0026lt;button @click=\u0026quot;changeBox('rBox')\u0026quot; :class=\u0026quot;{active: showName == 'rBox'}\u0026quot;\u0026gt;红\u0026lt;/button\u0026gt; \u0026lt;button @click=\u0026quot;changeBox('yBox')\u0026quot; :class=\u0026quot;{active: showName == 'yBox'}\u0026quot;\u0026gt;黄\u0026lt;/button\u0026gt; \u0026lt;button @click=\u0026quot;changeBox('bBox')\u0026quot; :class=\u0026quot;{active: showName == 'bBox'}\u0026quot;\u0026gt;蓝\u0026lt;/button\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;p\u0026gt; \u0026lt;button @click=\u0026quot;changeBox('rBox')\u0026quot; :class=\u0026quot;showName == 'rBox' ? 'active' : ''\u0026quot;\u0026gt;红\u0026lt;/button\u0026gt; \u0026lt;button @click=\u0026quot;changeBox('yBox')\u0026quot; :class=\u0026quot;showName == 'yBox' ? 'active' : ''\u0026quot;\u0026gt;黄\u0026lt;/button\u0026gt; \u0026lt;button @click=\u0026quot;changeBox('bBox')\u0026quot; :class=\u0026quot;showName == 'bBox' ? 'active' : ''\u0026quot;\u0026gt;蓝\u0026lt;/button\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;div class=\u0026quot;box r\u0026quot; v-if=\u0026quot;showName == 'rBox'\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;box y\u0026quot; v-else-if=\u0026quot;showName == 'yBox'\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;box b\u0026quot; v-else\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { showName: 'rBox' }, methods: { changeBox(name) { this.showName = name; } } }) \u0026lt;/script\u0026gt; v-pre指令\nv-pre指令可以在vue控制范围内，形成局部vue不控制区域(局部不解析vue语法)\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;p\u0026gt;{{ msg }}\u0026lt;/p\u0026gt; \u0026lt;!-- v-pre 指令可以在vue控制范围内，形成局部vue不控制区域 {{ }} 和 v-if 都会原样输出，不会被解析 --\u0026gt; \u0026lt;p v-pre\u0026gt; {{ }} \u0026lt;span v-if=\u0026quot;hehe\u0026quot;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { msg: 'message' } }) \u0026lt;/script\u0026gt; 循环指令\n1）遍历字符串：可以只逐一遍历字符，也可以连同遍历索引\n| 2）遍历数组：可以只逐一遍历成员元素，也可以连同遍历索引\n| 3）遍历对象：可以只逐一遍历成员元素，也可以连同遍历成员健(key)，还可以遍历成员key索引\n| | \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;p\u0026gt;{{ str }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ str[0] }}\u0026lt;/p\u0026gt; \u0026lt;div\u0026gt; \u0026lt;span v-for=\u0026quot;ch in str\u0026quot;\u0026gt;{{ ch }}\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;!--针对循环遍历的标签，通过会提供key属性来优化渲染速度，但key的值必须唯一（key可以不用提供） --\u0026gt; \u0026lt;span v-for=\u0026quot;(ch, i) in str\u0026quot; :key=\u0026quot;ch + i\u0026quot;\u0026gt;{{ i }}{{ ch }}\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;p v-for=\u0026quot;(ele, i) in arr\u0026quot;\u0026gt;{{ i }}{{ ele }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;p v-for=\u0026quot;ele in dic\u0026quot;\u0026gt;{{ ele }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;p v-for=\u0026quot;(ele, k) in dic\u0026quot;\u0026gt;{{ k }}:{{ ele }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;p v-for=\u0026quot;(ele, k, i) in dic\u0026quot;\u0026gt;{{ i }}{{ k }}:{{ ele }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { str: 'abc123呵呵', arr: [3, 4, 1, 2, 5], dic: { name: 'Tank', age: 80, gender: '哇塞', } } }) \u0026lt;/script\u0026gt; todolist留言板案例\n1）留言就是往留言数组中添加数据，删除留言就是从留言数组中移除数据\n2）前台数据库：localStorage和sessionStorage\nlocalStorage永久保存数据\nsessionStorage临时保存数据(当所属页面标签被关闭，数据清空)\n3）前台localStorage和sessionStorage数据库存储的值是字符串类型，所以要存放arr、dic等复杂数据需要JSON参与\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; li:hover { color: red; cursor: pointer; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;p\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; v-model=\u0026quot;userMsg\u0026quot;\u0026gt; \u0026lt;button type=\u0026quot;button\u0026quot; @click=\u0026quot;sendMsg\u0026quot;\u0026gt;留言\u0026lt;/button\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li v-for=\u0026quot;(msg, index) in msgs\u0026quot; @click=\u0026quot;deleteMsg(index)\u0026quot;\u0026gt; {{ msg }} \u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { msgs: localStorage.msgs ? JSON.parse(localStorage.msgs) : [], // 所有留言 userMsg: '', // 用户留言 }, methods: { sendMsg() { // 留言事件 // 尾增 // this.msgs.push(this.userMsg); // 首增 // this.msgs.unshift(this.userMsg); let userMsg = this.userMsg; if (userMsg) { this.msgs.unshift(userMsg); // 渲染给页面 localStorage.msgs = JSON.stringify(this.msgs); // 同步到数据库 this.userMsg = ''; // 清空留言框 } }, deleteMsg(index) { // 开始索引 操作长度 操作的结果们 this.msgs.splice(index, 1) } } }) \u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; // localStorage['num'] = 10; // sessionStorage.num = 888; // console.log(localStorage.num); // localStorage.msgs = JSON.stringify(['1111', '2222']); // console.log(JSON.parse(localStorage.msgs)); // console.log(JSON.parse(localStorage.msgs)[0]); \u0026lt;/script\u0026gt; \u0026lt;/html\u0026gt; 实例成员-插值表达式符号\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; {{ msg }} {[ msg ]} \u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { msg: '12345' }, // delimiters: ['{{', '}}'], delimiters: ['{[', ']}'], }) \u0026lt;/script\u0026gt; 计算属性\n1）computed是用来声明方法属性的\n2）声明的方法属性不能在 data中重复定义\n3）方法属性必须在页面中渲染使用，才会对内部出现的所有变量进行监听\n4）计算属性的值来源于监听方法的返回值\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; 姓：\u0026lt;input type=\u0026quot;text\u0026quot; v-model=\u0026quot;fName\u0026quot;\u0026gt; 名：\u0026lt;input type=\u0026quot;text\u0026quot; v-model=\u0026quot;lName\u0026quot;\u0026gt; 姓名：\u0026lt;b\u0026gt;{{ flName }}\u0026lt;/b\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { fName: '', lName: '', }, computed: { flName(){ // this.fName和this.lName有值发送改变，该方法都会被调用 // 变量flName的值是由函数的返回值决定 return this.fName + this.lName; } } }) \u0026lt;/script\u0026gt; 属性监听\n1）watch为data中已存在的属性设置监听事件\n2）监听的属性值发生改变，就会触发监听事件\n3）监听事件的方法返回值没有任何意义\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; 姓名：\u0026lt;input type=\u0026quot;text\u0026quot; v-model=\u0026quot;fullName\u0026quot;\u0026gt; 姓：\u0026lt;b\u0026gt;{{ firstName }}\u0026lt;/b\u0026gt; 名：\u0026lt;b\u0026gt;{{ lastName }}\u0026lt;/b\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { fullName: '', firstName: '', lastName: '', }, watch: { // fullName值改变，就会调用绑定的事件方法 fullName() { nameArr = this.fullName.split(''); this.firstName = nameArr[0]; this.lastName = nameArr[1]; } } }) \u0026lt;/script\u0026gt; ","permalink":"https://zhong-er.com/posts/vue%E6%9D%A1%E4%BB%B6%E5%BE%AA%E7%8E%AF%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7%E5%B1%9E%E6%80%A7%E7%9B%91%E5%90%AC/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003e\u003cstrong\u003e\u003cimg alt=\"vue.jpg\" loading=\"lazy\" src=\"/assets/201910086430_9431_1.jpg\"\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e条件指令\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1）两种都可以控制标签的显隐，绑定的值是布尔类型值，当都隐藏标签时\u003c/p\u003e\n\u003cp\u003ev-if是不渲染标签\u003c/p\u003e\n\u003cp\u003ev-show以 display:none 方式渲染\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt;\n    \u0026lt;p v-if=\u0026quot;isShow\u0026quot;\u0026gt;if条件指令\u0026lt;/p\u0026gt;\n    \u0026lt;p v-show=\u0026quot;isShow\u0026quot;\u0026gt;show条件指令\u0026lt;/p\u0026gt;\n\u0026lt;/div\u0026gt;\n\u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u0026lt;script\u0026gt;\n    new Vue({\n        el: '#app',\n        data: {\n            isShow: false,\n        }\n    })\n\u0026lt;/script\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2）v-if/v-else-if/v-else\u003c/p\u003e\n\u003cp\u003e一组分支上成立，分支会屏蔽下方所有分支，else分支没有条件，在所有分支不成立后才显示\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;style\u0026gt;\n    .box {\n        width: 400px;\n        height: 300px;\n    }\n    .r { background-color: red; }\n    .y { background-color: yellow }\n    .b { background-color: blue; }\n    .active {\n        background-color: pink;\n    }\n\u0026lt;/style\u0026gt;\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt;\n    \u0026lt;div class=\u0026quot;em\u0026quot;\u0026gt;\n        \u0026lt;p\u0026gt;\n            \u0026lt;button @click=\u0026quot;changeBox('rBox')\u0026quot; :class=\u0026quot;{active: showName == 'rBox'}\u0026quot;\u0026gt;红\u0026lt;/button\u0026gt;\n            \u0026lt;button @click=\u0026quot;changeBox('yBox')\u0026quot; :class=\u0026quot;{active: showName == 'yBox'}\u0026quot;\u0026gt;黄\u0026lt;/button\u0026gt;\n            \u0026lt;button @click=\u0026quot;changeBox('bBox')\u0026quot; :class=\u0026quot;{active: showName == 'bBox'}\u0026quot;\u0026gt;蓝\u0026lt;/button\u0026gt;\n        \u0026lt;/p\u0026gt;\n        \u0026lt;p\u0026gt;\n            \u0026lt;button @click=\u0026quot;changeBox('rBox')\u0026quot; :class=\u0026quot;showName == 'rBox' ? 'active' : ''\u0026quot;\u0026gt;红\u0026lt;/button\u0026gt;\n            \u0026lt;button @click=\u0026quot;changeBox('yBox')\u0026quot; :class=\u0026quot;showName == 'yBox' ? 'active' : ''\u0026quot;\u0026gt;黄\u0026lt;/button\u0026gt;\n            \u0026lt;button @click=\u0026quot;changeBox('bBox')\u0026quot; :class=\u0026quot;showName == 'bBox' ? 'active' : ''\u0026quot;\u0026gt;蓝\u0026lt;/button\u0026gt;\n        \u0026lt;/p\u0026gt;\n        \u0026lt;div class=\u0026quot;box r\u0026quot; v-if=\u0026quot;showName == 'rBox'\u0026quot;\u0026gt;\u0026lt;/div\u0026gt;\n        \u0026lt;div class=\u0026quot;box y\u0026quot; v-else-if=\u0026quot;showName == 'yBox'\u0026quot;\u0026gt;\u0026lt;/div\u0026gt;\n        \u0026lt;div class=\u0026quot;box b\u0026quot; v-else\u0026gt;\u0026lt;/div\u0026gt;\n    \u0026lt;/div\u0026gt;\n\u0026lt;/div\u0026gt;\n\u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u0026lt;script\u0026gt;\n    new Vue({\n        el: '#app',\n        data: {\n            showName: 'rBox'\n        },\n        methods: {\n            changeBox(name) {\n                this.showName = name;\n            }\n        }\n    })\n\u0026lt;/script\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cimg alt=\"001.png\" loading=\"lazy\" src=\"/assets/201910098559_2432.png-zhonger.com\"\u003e\u003c/p\u003e","title":"【vue】条件循环/计算属性/属性监听"},{"content":"\n********组件\n1）组件：一个包含html、css、js独立的集合体，这样的集合体可以完成页面结构的代码复用\n2）组件分为根组件、全局组件与局部组件\n根组件：所有被new Vue()产生的组件，在项目开发阶段，一个项目只会出现一个根组件\n全局组件：不用注册，就可以成为任何一个组件的字组件\n局部组件：必须注册，才可以成为注册该局部组件的子组件\n3）每一个组件都有自身的html结构，css样式，js逻辑\n每一个组件其实都有自己的template，就是用来标识自己html结构的\ntemplate模板中有且只有一个根标签\n根组件一般不提供template，就由挂载点的真实DOM提供html结构\n4）除根组件的其他组件，数据要有局部作用域，保证组件复用是，各组件间数据的独立性\n5）在多组件共处时，在哪个组件模板中出现的变量，由当前组件提供\n局部组件\n1）创建局部组件\n2）在父组件中注册该局部组件\n3）在父组件的template模板中渲染该局部组件\n\u0026lt;style\u0026gt; .box { box-shadow: 0 3px 5px 0 #666; width: 240px; height: 300px; text-align: center; padding: 20px 0; float: left; margin: 5px; } .box img { width: 200px; } \u0026lt;/style\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;!--\u0026lt;mcc\u0026gt;\u0026lt;/mcc\u0026gt;--\u0026gt; \u0026lt;local-tag\u0026gt;\u0026lt;/local-tag\u0026gt; \u0026lt;local-tag\u0026gt;\u0026lt;/local-tag\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; let localTag = { template: ` \u0026lt;div class=\u0026quot;box\u0026quot;\u0026gt; \u0026lt;img src=\u0026quot;img/666.jpg\u0026quot; alt=\u0026quot;\u0026quot;\u0026gt; \u0026lt;h3\u0026gt;凤哥\u0026lt;/h3\u0026gt; \u0026lt;p\u0026gt;马叉虫❤马叉虫\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; ` }; new Vue({ el: '#app', components: { // mcc: localTag, // localTag, 'local-tag': localTag, } }) \u0026lt;/script\u0026gt; 全局组件\n1）创建全局组件\n2）在父组件的template模板中直接渲染该全局组件\n\u0026lt;style\u0026gt; .box { box-shadow: 0 3px 5px 0 #666; width: 240px; height: 300px; text-align: center; padding: 20px 0; float: left; margin: 5px; } .box img { width: 200px; } \u0026lt;/style\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;global-tag\u0026gt;\u0026lt;/global-tag\u0026gt; \u0026lt;global-tag\u0026gt;\u0026lt;/global-tag\u0026gt; \u0026lt;global-tag\u0026gt;\u0026lt;/global-tag\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; Vue.component('global-tag', { template: ` \u0026lt;div class=\u0026quot;box\u0026quot; @click=\u0026quot;action\u0026quot;\u0026gt; \u0026lt;img src=\u0026quot;img/666.jpg\u0026quot; alt=\u0026quot;\u0026quot;\u0026gt; \u0026lt;h3\u0026gt;凤哥\u0026lt;/h3\u0026gt; \u0026lt;p\u0026gt;马叉虫❤{{ num }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; `, data () { return { num: 0 } }, methods: { action() { this.num++; } } }); // 数据局部化分析导入 // a = function () { // return {num: 10} // }; // b1 = a(); // b2 = a(); // b3 = a(); // 这样处理b1，b2，b3的数据就彼此独立的，但是都来源于a new Vue({ el: '#app', }) \u0026lt;/script\u0026gt; 组件交互-父传子\n数据交互-父传子-通过绑定属性的方式\n1）父组件提供数据\n2）在父组件模板中，为子组件标签设置自定义属性，绑定的值由父组件提供\n3）在子组件实例中，通过props实例成员获得自定义属性\n\u0026lt;style\u0026gt; .info { text-align: center; width: 200px; padding: 3px; box-shadow: 0 3px 5px 0 pink; float: left; margin: 5px; } .info img { width: 200px; } \u0026lt;/style\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;!-- 2) 在父组件模板中，为子组件标签设置自定义属性，绑定的值由父组件提供 --\u0026gt; \u0026lt;info v-for=\u0026quot;info in infos\u0026quot; :key=\u0026quot;info.image\u0026quot; :myinfo=\u0026quot;info\u0026quot;\u0026gt;\u0026lt;/info\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; // 伪代码：模拟数据从后台请求 /* let infos = ''; document.onload = function () { $.ajax({ url: '/images/', type: 'get', success (response) { infos = response.data } }) */ let infos = [ { image: 'img/001.png', title: '小猫' }, { image: 'img/002.png', title: '蛋糕' }, { image: 'img/003.png', title: '蓝糕' }, { image: 'img/004.png', title: '恶犬' }, ]; let info = { template: ` \u0026lt;div class=\u0026quot;info\u0026quot;\u0026gt; \u0026lt;img :src=\u0026quot;myinfo.image\u0026quot; alt=\u0026quot;\u0026quot;\u0026gt; \u0026lt;p\u0026gt;\u0026lt;b\u0026gt;{{ myinfo.title }}\u0026lt;/b\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; `, // 3) 在子组件实例中，通过props实例成员获得自定义属性 props: ['myinfo'] }; new Vue({ el: '#app', components: { info, }, data: { infos, // 1) 父组件提供数据 } }) \u0026lt;/script\u0026gt; 组件交互-子传父\n组件交互-子传父\n1）数据由子组件提供\n2）子组件内部通过触发系统事件，发送一个自定义事件，将数据携带出来\n3）父组件位子组件标签的自定义属性通过方法实现，就可以通过参数拿到子组件传递处理的参数\n\u0026lt;style\u0026gt; .close:hover { cursor: pointer; color: red; } \u0026lt;/style\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;p\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; v-model=\u0026quot;userMsg\u0026quot;\u0026gt; \u0026lt;button @click=\u0026quot;sendMsg\u0026quot;\u0026gt;留言\u0026lt;/button\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;!-- 2) 子组件内部通过触发系统事件，发送一个自定义事件，将数据携带出来 --\u0026gt; \u0026lt;msg-li @remove_msg=\u0026quot;removeAction\u0026quot; v-for=\u0026quot;(msg, i) in msgs\u0026quot; :msg=\u0026quot;msg\u0026quot; :index=\u0026quot;i\u0026quot; :key=\u0026quot;msg\u0026quot;\u0026gt;\u0026lt;/msg-li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; let msgLi = { template: ` \u0026lt;li\u0026gt; \u0026lt;span class=\u0026quot;close\u0026quot; @click=\u0026quot;deleteMsg(index)\u0026quot;\u0026gt;x \u0026lt;/span\u0026gt; \u0026lt;span\u0026gt;第{{ index + 1 }}条：\u0026lt;/span\u0026gt; \u0026lt;span\u0026gt;{{ msg }}\u0026lt;/span\u0026gt; \u0026lt;/li\u0026gt; `, props: ['msg', 'index'], methods: { // 系统的click事件 deleteMsg(i) { // 1) 数据由子组件提供 // $emit('自定义事件名', 参数们) this.$emit('remove_msg', i); } } }; new Vue({ el: '#app', data: { msgs: [], userMsg: '' }, methods: { sendMsg() { if (this.userMsg) { this.msgs.push(this.userMsg); this.userMsg = \u0026quot;\u0026quot;; } }, // 3) 父组件位子组件标签的自定义属性通过方法实现，就可以通过参数拿到子组件传递处理的参数 removeAction(i) { this.msgs.splice(i, 1) } }, components: { msgLi } }) \u0026lt;/script ","permalink":"https://zhong-er.com/posts/vue%E7%BB%84%E4%BB%B6/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cimg alt=\"vue.jpg\" loading=\"lazy\" src=\"/assets/201910086430_9431_1.jpg\"\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e********\u003cstrong\u003e组件\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1）组件：一个包含html、css、js独立的集合体，这样的集合体可以完成页面结构的代码复用\u003c/p\u003e\n\u003cp\u003e2）组件分为根组件、全局组件与局部组件\u003c/p\u003e\n\u003cp\u003e根组件：所有被new Vue()产生的组件，在项目开发阶段，一个项目只会出现一个根组件\u003c/p\u003e\n\u003cp\u003e全局组件：不用注册，就可以成为任何一个组件的字组件\u003c/p\u003e\n\u003cp\u003e局部组件：必须注册，才可以成为注册该局部组件的子组件\u003c/p\u003e\n\u003cp\u003e3）每一个组件都有自身的html结构，css样式，js逻辑\u003c/p\u003e\n\u003cp\u003e每一个组件其实都有自己的template，就是用来标识自己html结构的\u003c/p\u003e\n\u003cp\u003etemplate模板中有且只有一个根标签\u003c/p\u003e\n\u003cp\u003e根组件一般不提供template，就由挂载点的真实DOM提供html结构\u003c/p\u003e\n\u003cp\u003e4）除根组件的其他组件，数据要有局部作用域，保证组件复用是，各组件间数据的独立性\u003c/p\u003e\n\u003cp\u003e5）在多组件共处时，在哪个组件模板中出现的变量，由当前组件提供\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e局部组件\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1）创建局部组件\u003c/p\u003e\n\u003cp\u003e2）在父组件中注册该局部组件\u003c/p\u003e\n\u003cp\u003e3）在父组件的template模板中渲染该局部组件\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;style\u0026gt;\n    .box {\n        box-shadow: 0 3px 5px 0 #666;\n        width: 240px;\n        height: 300px;\n        text-align: center;\n        padding: 20px 0;\n        float: left;\n        margin: 5px;\n    }\n    .box img {\n        width: 200px;\n    }\n\u0026lt;/style\u0026gt;\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt;\n    \u0026lt;!--\u0026lt;mcc\u0026gt;\u0026lt;/mcc\u0026gt;--\u0026gt;\n    \u0026lt;local-tag\u0026gt;\u0026lt;/local-tag\u0026gt;\n    \u0026lt;local-tag\u0026gt;\u0026lt;/local-tag\u0026gt;\n\u0026lt;/div\u0026gt;\n\u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u0026lt;script\u0026gt;\n    let localTag = {\n        template: `\n        \u0026lt;div class=\u0026quot;box\u0026quot;\u0026gt;\n            \u0026lt;img src=\u0026quot;img/666.jpg\u0026quot; alt=\u0026quot;\u0026quot;\u0026gt;\n            \u0026lt;h3\u0026gt;凤哥\u0026lt;/h3\u0026gt;\n            \u0026lt;p\u0026gt;马叉虫❤马叉虫\u0026lt;/p\u0026gt;\n        \u0026lt;/div\u0026gt;\n        `\n    };\n\n    new Vue({\n        el: '#app',\n        components: {\n            // mcc: localTag,\n            // localTag,\n            'local-tag': localTag,\n        }\n    })\n\u0026lt;/script\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e全局组件\u003c/strong\u003e\u003c/p\u003e","title":"【vue】组件"},{"content":"\nvue导读\n1）vue框架是前台框架，常见的前台框架有：Angular、React、Vue\n2）vue是结合其他框架的优点：轻量级、中文API、数据驱动、双向绑定、MVVM设计模式、支持组件化开发、单页面应用\nMVVM设计模式：MV数据到视图一层数据处理，向后台请求数据渲染页面；VM视图再到数据一层数据处理，前台获取的用户数据到后台。\n3）vue环境，本地导入与cdn导入\n4）vue是js渐进式框架\n根据开发需求，可以决定vue框架控制项目的具体方位：可以为一个标签，也可以为一个页面，甚至可以为整个项目\n实例成员-挂载点\n1）vue通过挂载点与html页面结构建立关联\n2）html与body不能作为挂载点\n3）一个vue对象挂载点只会索引一个匹配结果，所以挂载点一般用id标识\nel：实例\n\u0026lt;div id=\u0026quot;wrap\u0026quot;\u0026gt; {{ }} \u0026lt;hr\u0026gt; \u0026lt;div class=\u0026quot;main\u0026quot;\u0026gt; {{ }} \u0026lt;/div\u0026gt; \u0026lt;hr\u0026gt; \u0026lt;div class=\u0026quot;main\u0026quot;\u0026gt; {{ }} \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#wrap' // el表示挂载点 }) \u0026lt;/script\u0026gt; //实例与页面挂载点一一对应 //一个页面中可以出现多个实例对应多个挂载点 //实例只操作挂载点内部内容 js对象(字典)补充\nlet b = 20; let dic = { a: 10, // 字典本身就是对象，key都是字符串形式可以省略引号 b // 值为变量时，且与key同名，可以简写 }; console.log(dic) 实例成员-数据\n1）用实例成员data为vue环境提供数据，数据采用字典{}形式\n2）在插值表达式{{}}中，直接书写数据的key来访问数据\n{{}}是插值表达式，中间出现的info是vue变量\n插值表达式内可以直接做简单的数学运算\n复杂的运算就需要借助过滤器\n3）在外部通过接受实例的变量app，访问实例成员(实例成员都用$开头)，间接访问到数据\napp.$data.info\n4）在外部也可以通过实例变量app直接访问数据\napp.info\n5）在vue实例内部的方法methods中，使用变量，this.info(this其实就是等价于app)\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;!-- {{}}是插值表达式，中间出现的info是vue变量 --\u0026gt; \u0026lt;p\u0026gt;{{ info }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ info }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ num }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ arr }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ dic }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; let app = new Vue({ el: '#app', data: { info: 'message', num: 100, result: true, arr: [1, 2, 3, 4, 5], dic: { name: 'Owen', age: 17.5 } } }) \u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; console.log(app); console.log(app.$data.info); console.log(app.info); \u0026lt;/script\u0026gt; 实例成员-过滤器\n1）过滤器本身就是数据处理函数，可以将插值 表达式中的数据作为参数进行处理，得到的函数返回值就是处理后的结果\n2）过滤器使用语法{{ \u0026hellip;变量 | 过滤器(\u0026hellip;变量) }}\n3）过滤器在实例中用filters成员提供\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;!--1）插值表达式可以直接做简单运算--\u0026gt; \u0026lt;p\u0026gt;{{ num + 3.5 }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ msg.split('')[4] }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ num | f1 }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ 10, 20, 30, 40 | f2 }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ 10, 20 | f2(50, 80) }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ 120 | f2 }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { num: 100, msg: 'message' }, filters: { // 总结：{{ num | f1 }} f1: function (num) { return num * 2 }, // 总结：{{ a,b,c,d | f2 }} {{ a,b,c | f2(d) }} { a,b| f2(c,d) }} { a| f2(b,c,d) }} f2: function (a, b, c, d) { console.log(a, b, c, d); return a + b + c + d } } }) \u0026lt;/script\u0026gt; 文本指令\n1）插值表达式 同 v-text指令，渲染普通文本\n2）v-html指令可以渲染有html语法的文本，能够解析html语法\n3）文本指令中可以渲染变量也可以渲染常量\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;p\u0026gt;{{ info }}\u0026lt;/p\u0026gt; \u0026lt;!--1）v-指令名=\u0026quot;变量\u0026quot;，变量是需要data提供数据值的 --\u0026gt; \u0026lt;p v-text=\u0026quot;info\u0026quot;\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;!--2）v-指令名=\u0026quot;常量\u0026quot;，常量采用常量基本的语法，数字与布尔类型等可以直接书写的直接书写，字符串等需要特殊符号的需要添加符号--\u0026gt; \u0026lt;p v-text=\u0026quot;123\u0026quot;\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;p v-text=\u0026quot;true\u0026quot;\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;p v-text=\u0026quot;'abc' + info\u0026quot;\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;p v-text=\u0026quot;[1, 2, 3]\u0026quot;\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;!--3）v-html可以解析html语法--\u0026gt; \u0026lt;p v-html=\u0026quot;`\u0026lt;b style='color:red'\u0026gt;好的\u0026lt;/b\u0026gt;`\u0026quot;\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;p v-text=\u0026quot;'\u0026lt;b\u0026gt;好的\u0026lt;/b\u0026gt;'\u0026quot;\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { info: 'data提供的数据', } }) \u0026lt;/script\u0026gt; 反引号补充\n// 1) js多行字符串 反引号 `` // 2) 反引号字符串中可以直接填充变量，语法为 `${变量名}` let s1 = `第一行 第二行 结束行`; console.log(s1); let name = 'Owen'; let age = 17.5; let s2 = ` name: ${name} age: ${age} `; console.log(s2); 事件指令\n1）语法：v-on:事件名=\u0026ldquo;函数名(参数们)\u0026rdquo;\n2）简写：@事件名=\u0026ldquo;函数名(参数们)\u0026rdquo;\n3）用methods实例成员提供 事件函数 的实现\n4）事件传参：函数名 | 函数名() | 函数名(自定义参数) | 函数名($event,自定义参数)\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; body { /*页面文本不允许选择*/ user-select: none; } .low-num { cursor: pointer; } .low-num:hover { color: red; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;!-- 事件指令：v-on:事件名=\u0026quot;事件函数名\u0026quot; --\u0026gt; \u0026lt;p v-on:click=\u0026quot;lowNum\u0026quot;\u0026gt; \u0026lt;span\u0026gt;点击减一：\u0026lt;/span\u0026gt; \u0026lt;span\u0026gt;{{ num }}\u0026lt;/span\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;p v-on:dblclick=\u0026quot;dblAction\u0026quot;\u0026gt;双击\u0026lt;/p\u0026gt; \u0026lt;!-- v-on:事件名=\u0026quot;事件函数\u0026quot; 可以简写为 @事件名=\u0026quot;事件函数\u0026quot; --\u0026gt; \u0026lt;p @mouseover=\u0026quot;overAction()\u0026quot;\u0026gt;悬浮\u0026lt;/p\u0026gt; \u0026lt;p @mouseover=\u0026quot;overAction(10)\u0026quot;\u0026gt;悬浮1\u0026lt;/p\u0026gt; \u0026lt;p @mouseover=\u0026quot;overAction(10, 20)\u0026quot;\u0026gt;悬浮2\u0026lt;/p\u0026gt; \u0026lt;p @mouseover=\u0026quot;overAction(10, 20, $event)\u0026quot;\u0026gt;悬浮3\u0026lt;/p\u0026gt; \u0026lt;!-- 事件传参： @事件='方法' 默认传入事件对象 $event @事件='方法()' 不传递任何参数 @事件='方法(参数...)' 只传递自定义参数 @事件='方法($event, 参数...)' 自定义传参时传递事件对象 --\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; let app = new Vue({ el: '#app', data: { num: 99 }, methods: { lowNum: function () { // 在vue实例内部的方法中，使用变量 // alert(this.num) this.num -= 1; }, dblAction (ev) { // alert('双击事件'); console.log(ev) }, overAction(a, b, c) { console.log(a, b, c) } } }) \u0026lt;/script\u0026gt; \u0026lt;/html\u0026gt; 属性指令\n1）语法：v-bind:属性名=\u0026ldquo;变量\u0026rdquo;\n2）v-bind:属性名=\u0026ldquo;变量\u0026rdquo; 简写 :属性名=\u0026ldquo;变量\u0026rdquo;\n3）单值属性绑定： :title=\u0026ldquo;变量\u0026rdquo; | :id=\u0026ldquo;变量\u0026rdquo; | :自定义属性=\u0026ldquo;变量\u0026rdquo;\n4）style属性绑定： :style=\u0026ldquo;字典变量\u0026rdquo; | :style=\u0026quot;{css属性1:变量1, \u0026hellip;, css属性n:变量n}\u0026quot;\n5）class属性绑定： :class=\u0026ldquo;变量\u0026rdquo; | :class=\u0026quot;[变量1, \u0026hellip;, 变量n]\u0026quot; | :class=\u0026quot;{类名:布尔变量}\u0026quot;\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;属性指令\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; [index] { color: orange; } .ccc { background-color: yellowgreen; color: greenyellow; border-radius: 50%; } .ccc1 { background-color: yellowgreen; } .ccc2 { color: greenyellow; } .ccc3 { border-radius: 50%; } .ttt { background-color: gold; color: orange; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;!--1) 语法：v-bind:属性名=\u0026quot;变量\u0026quot; --\u0026gt; \u0026lt;p id=\u0026quot;p1\u0026quot; style=\u0026quot;\u0026quot; title=\u0026quot;\u0026quot; index=\u0026quot;\u0026quot;\u0026gt;属性指令1\u0026lt;/p\u0026gt; \u0026lt;p id=\u0026quot;p2\u0026quot; v-bind:class=\u0026quot;p1\u0026quot; style=\u0026quot;\u0026quot; title=\u0026quot;\u0026quot; index=\u0026quot;\u0026quot;\u0026gt;属性指令2\u0026lt;/p\u0026gt; \u0026lt;p v-bind:index=\u0026quot;p1\u0026quot;\u0026gt;自定义属性也可以被vue绑定1\u0026lt;/p\u0026gt; \u0026lt;!--2) v-bind:属性名=\u0026quot;变量\u0026quot; 简写 :属性名=\u0026quot;变量\u0026quot; --\u0026gt; \u0026lt;p :index=\u0026quot;p1\u0026quot;\u0026gt;自定义属性也可以被vue绑定2\u0026lt;/p\u0026gt; \u0026lt;p :title=\u0026quot;'文本提示'\u0026quot;\u0026gt;悬浮文本提示\u0026lt;/p\u0026gt; \u0026lt;!--3) style样式属性绑定 --\u0026gt; \u0026lt;p :style=\u0026quot;myStyle\u0026quot;\u0026gt;样式绑定1\u0026lt;/p\u0026gt; \u0026lt;p :style=\u0026quot;{backgroundColor: c1, color: c2, 'border-radius': '50%'}\u0026quot;\u0026gt;样式绑定2\u0026lt;/p\u0026gt; \u0026lt;!--4) class类属性绑定 --\u0026gt; \u0026lt;p :class=\u0026quot;myc1\u0026quot;\u0026gt;样式绑定3\u0026lt;/p\u0026gt; \u0026lt;p :class=\u0026quot;[myc2,myc3,myc4,'bbb']\u0026quot;\u0026gt;样式绑定4\u0026lt;/p\u0026gt; \u0026lt;!--xxx是类名，是否起作用有布尔类型变量yyy值决定 --\u0026gt; \u0026lt;p :class=\u0026quot;{xxx:yyy}\u0026quot;\u0026gt;样式绑定5\u0026lt;/p\u0026gt; \u0026lt;!--案例：点击切换类名是否起作用--\u0026gt; \u0026lt;p @click=\u0026quot;clickAction\u0026quot; :class=\u0026quot;{ttt:yyy}\u0026quot;\u0026gt;点击切换类\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { p1: 'q1', myStyle: { backgroundColor: 'pink', color: 'deeppink', 'border-radius': '50%' }, c1: 'cyan', c2: 'tan', myc1: 'ccc ddd eee', myc2: 'ccc1', myc3: 'ccc2', myc4: 'ccc3', yyy: true, // false }, methods: { clickAction() { this.yyy = !this.yyy; } } }) \u0026lt;/script\u0026gt; \u0026lt;/html\u0026gt; 动态修改文本样式案例\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; .box { width: 200px; height: 200px; background-color: orange; } .center { text-align: center; line-height: 200px; } .rcenter { text-align: right; line-height: 200px; } .right { text-align: right; } .top { line-height: 21px; } .bottom { line-height: calc(400px - 21px); } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;div :class=\u0026quot;[c1, {right:r, top:t, bottom:b}]\u0026quot; @mouseover=\u0026quot;a1\u0026quot; @mousedown=\u0026quot;a2\u0026quot; @mouseup=\u0026quot;a3\u0026quot; @mouseout=\u0026quot;a4\u0026quot;\u0026gt;{{ msg }}\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', methods: { a1(){ this.msg = '被悬浮'; // 正中 this.c1 = 'center'; this.r = 0; this.t = 0; this.b = 0; }, a2(){ this.msg = '被按下'; // 右上 this.r = 1; this.t = 1; this.b = 0; }, a3(){ this.msg = '被抬起'; // 右中 this.c1 = 'rcenter'; this.r = 0; this.t = 0; this.b = 0; }, a4(){ this.msg = '被移开'; // 右下 this.r = 1; this.t = 0; this.b = 1; }, }, data: { msg: '', c1: '', r:0, t:0, b:0, } }) \u0026lt;/script\u0026gt; \u0026lt;/html\u0026gt; 表单指令\n1）语法：v-model=\u0026ldquo;控制value值的变量\u0026rdquo;\n2）:value=\u0026ldquo;变量\u0026rdquo; 直接绑定数据不会实时更新(修改表单标签值，值不会实时映射给绑定的变量)\n3）v-model=\u0026ldquo;变量\u0026rdquo; 绑定数据会实时更新(修改表单标签值，值会实时映射给绑定的变量)\n4）单独复选框作为确认框时，v-model绑定的变量为布尔类型变量\n5）多复选框，v-model绑定的变量值是一个列表(数组)，存放复选框选项值(谁被选中就存放了谁)\n6）单选框，v-model绑定的变量值是某一个选项的值(值是哪个选项的值，那个选项就被选中)\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; .p1 { width: 500px; height: 21px; background-color: orange; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;form action=\u0026quot;\u0026quot;\u0026gt; \u0026lt;!--1) 对表单标签value进行绑定操作变量，不能时时检测绑定的变量--\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; :value=\u0026quot;info\u0026quot;\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; :value=\u0026quot;info\u0026quot;\u0026gt; \u0026lt;p\u0026gt;{{ info }}\u0026lt;/p\u0026gt; \u0026lt;hr\u0026gt; \u0026lt;!--2) 表单标签的值有 v-model=\u0026quot;变量\u0026quot; 来绑定控制，操作的还是value，但是拥有时时变量值的检测 --\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; v-model=\u0026quot;info\u0026quot;\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; v-model=\u0026quot;info\u0026quot;\u0026gt; \u0026lt;p\u0026gt;{{ info }}\u0026lt;/p\u0026gt; \u0026lt;hr\u0026gt; \u0026lt;!-- 2) v-model操作单独复选框 - 确认框 --\u0026gt; 是否同意：\u0026lt;input type=\u0026quot;checkbox\u0026quot; name=\u0026quot;agree\u0026quot; v-model=\u0026quot;isAgree\u0026quot;\u0026gt; \u0026lt;!--是否同意：\u0026lt;input type=\u0026quot;checkbox\u0026quot; name=\u0026quot;agree\u0026quot; true-value=\u0026quot;yes\u0026quot; false-value=\u0026quot;no\u0026quot; v-model=\u0026quot;isAgree\u0026quot;\u0026gt;--\u0026gt; \u0026lt;p\u0026gt;{{ isAgree }}\u0026lt;/p\u0026gt; \u0026lt;!-- 3) 单选框--\u0026gt; 性取向： 男 \u0026lt;input type=\u0026quot;radio\u0026quot; name=\u0026quot;sex\u0026quot; value=\u0026quot;male\u0026quot; v-model=\u0026quot;mysex\u0026quot;\u0026gt; 女 \u0026lt;input type=\u0026quot;radio\u0026quot; name=\u0026quot;sex\u0026quot; value=\u0026quot;female\u0026quot; v-model=\u0026quot;mysex\u0026quot;\u0026gt; 哇塞 \u0026lt;input type=\u0026quot;radio\u0026quot; name=\u0026quot;sex\u0026quot; value=\u0026quot;others\u0026quot; v-model=\u0026quot;mysex\u0026quot;\u0026gt; \u0026lt;p\u0026gt;{{ mysex }}\u0026lt;/p\u0026gt; \u0026lt;!-- 4) 复选框--\u0026gt; 兴趣爱好： 男 \u0026lt;input type=\u0026quot;checkbox\u0026quot; name=\u0026quot;hobbies\u0026quot; value=\u0026quot;male\u0026quot; v-model=\u0026quot;myhobbies\u0026quot;\u0026gt; 女 \u0026lt;input type=\u0026quot;checkbox\u0026quot; name=\u0026quot;hobbies\u0026quot; value=\u0026quot;female\u0026quot; v-model=\u0026quot;myhobbies\u0026quot;\u0026gt; 哇塞 \u0026lt;input type=\u0026quot;checkbox\u0026quot; name=\u0026quot;hobbies\u0026quot; value=\u0026quot;others\u0026quot; v-model=\u0026quot;myhobbies\u0026quot;\u0026gt; \u0026lt;p\u0026gt;{{ myhobbies }}\u0026lt;/p\u0026gt; \u0026lt;hr\u0026gt; \u0026lt;input type=\u0026quot;submit\u0026quot;\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { info: '123', isAgree: 0, // isAgree: 'no', mysex: 'others', myhobbies: ['male', 'female'] } }) \u0026lt;/script\u0026gt; \u0026lt;/html\u0026gt; v-once指令\nv-once：单独使用，限制的标签内容一旦赋值，便不可被动更改(如果是输入框，可以主动修改)\n\u0026lt;div id=\u0026quot;app\u0026quot;\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; v-model=\u0026quot;msg\u0026quot;\u0026gt; \u0026lt;!-- 一旦赋值，只可主动更改 --\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; v-model=\u0026quot;msg\u0026quot; v-once\u0026gt; \u0026lt;p\u0026gt;{{ msg }}\u0026lt;/p\u0026gt; \u0026lt;!-- 一旦赋值，便不可更改 --\u0026gt; \u0026lt;p v-once\u0026gt;{{ msg }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', data: { msg: '初始值' } }) \u0026lt;/script\u0026gt; v-cloak指令\nv-cloak:防止页面闪烁\n\u0026lt;style\u0026gt; [v-cloak] { display: none; } \u0026lt;/style\u0026gt; \u0026lt;!-- 不处理的情况下，每次新价值该页面，都会先渲染{{}}，当vue环境加载成功，{{}}有会被解析消失 --\u0026gt; \u0026lt;!-- 处理后，vue环境没加载好时，#app是被隐藏的，vue环境加载成功，会依次#app的v-cloak属性，就不会出现{{}}渲染闪烁问题 --\u0026gt; \u0026lt;div id=\u0026quot;app\u0026quot; v-cloak\u0026gt; {{ }} {{ }} {{ }} \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; new Vue({ el: '#app', }) \u0026lt;/script\u0026gt; ","permalink":"https://zhong-er.com/posts/vue%E6%A8%A1%E6%9D%BF%E8%AF%AD%E6%B3%95/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cstrong\u003e\u003cimg alt=\"vue.jpg\" loading=\"lazy\" src=\"/assets/201910086430_9431_1.jpg\"\u003e\u003c/strong\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003evue导读\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1）vue框架是前台框架，常见的前台框架有：Angular、React、Vue\u003c/p\u003e\n\u003cp\u003e2）vue是结合其他框架的优点：轻量级、中文API、数据驱动、双向绑定、MVVM设计模式、支持组件化开发、单页面应用\u003c/p\u003e\n\u003cp\u003eMVVM设计模式：MV数据到视图一层数据处理，向后台请求数据渲染页面；VM视图再到数据一层数据处理，前台获取的用户数据到后台。\u003c/p\u003e\n\u003cp\u003e3）vue环境，本地导入与cdn导入\u003c/p\u003e\n\u003cp\u003e4）vue是js渐进式框架\u003c/p\u003e\n\u003cp\u003e根据开发需求，可以决定vue框架控制项目的具体方位：可以为一个标签，也可以为一个页面，甚至可以为整个项目\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e实例成员-挂载点\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1）vue通过挂载点与html页面结构建立关联\u003c/p\u003e\n\u003cp\u003e2）html与body不能作为挂载点\u003c/p\u003e\n\u003cp\u003e3）一个vue对象挂载点只会索引一个匹配结果，所以挂载点一般用id标识\u003c/p\u003e\n\u003cp\u003eel：实例\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;div id=\u0026quot;wrap\u0026quot;\u0026gt;\n    {{ }}\n    \u0026lt;hr\u0026gt;\n    \u0026lt;div class=\u0026quot;main\u0026quot;\u0026gt;\n        {{ }}\n    \u0026lt;/div\u0026gt;\n    \u0026lt;hr\u0026gt;\n    \u0026lt;div class=\u0026quot;main\u0026quot;\u0026gt;\n        {{ }}\n    \u0026lt;/div\u0026gt;\n\u0026lt;/div\u0026gt;\n\u0026lt;script src=\u0026quot;js/vue.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u0026lt;script\u0026gt;\n    new Vue({\n        el: '#wrap'  // el表示挂载点\n    })\n\u0026lt;/script\u0026gt;\n//实例与页面挂载点一一对应\n//一个页面中可以出现多个实例对应多个挂载点\n//实例只操作挂载点内部内容\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003ejs对象(字典)补充\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003elet b = 20;\nlet dic = {\n    a: 10,  // 字典本身就是对象，key都是字符串形式可以省略引号\n    b  // 值为变量时，且与key同名，可以简写\n};\nconsole.log(dic)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e实例成员-数据\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1）用实例成员data为vue环境提供数据，数据采用字典{}形式\u003c/p\u003e\n\u003cp\u003e2）在插值表达式{{}}中，直接书写数据的key来访问数据\u003c/p\u003e\n\u003cp\u003e{{}}是插值表达式，中间出现的info是vue变量\u003c/p\u003e\n\u003cp\u003e插值表达式内可以直接做简单的数学运算\u003c/p\u003e\n\u003cp\u003e复杂的运算就需要借助过滤器\u003c/p\u003e","title":"【vue】模板语法"},{"content":"Django中间件\nDjango中间件类似于Django的保安。请求的时候需要先经过中间件才能到达Django后端(urls,views,templates,models),响应走的时候也需要经过中间件才能到达web服务网关接口。\nDjango默认的七个中间件\nMIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] 我们点进去几个组件查看源码：\nclass SecurityMiddleware(MiddlewareMixin): def __init__(self, get_response=None): pass def process_request(self, request): path = request.path.lstrip(\u0026quot;/\u0026quot;) if (self.redirect and not request.is_secure() and not any(pattern.search(path) for pattern in self.redirect_exempt)): host = self.redirect_host or request.get_host() return HttpResponsePermanentRedirect(\u0026quot;https://%s%s\u0026quot; % (host, request.get_full_path())) def process_response(self, request, response): ... return response class CsrfViewMiddleware(MiddlewareMixin): def process_request(self, request): pass def process_view(self, request, callback, callback_args, callback_kwargs): ... return self._accept(request) def process_response(self, request, response): ... return response class AuthenticationMiddleware(MiddlewareMixin): def process_request(self, request): assert hasattr(request, 'session'), ( \u0026quot;The Django authentication middleware requires session middleware \u0026quot; \u0026quot;to be installed. Edit your MIDDLEWARE%s setting to insert \u0026quot; \u0026quot;'django.contrib.sessions.middleware.SessionMiddleware' before \u0026quot; \u0026quot;'django.contrib.auth.middleware.AuthenticationMiddleware'.\u0026quot; ) % (\u0026quot;_CLASSES\u0026quot; if settings.MIDDLEWARE is None else \u0026quot;\u0026quot;) request.user = SimpleLazyObject(lambda: get_user(request)) 不难发现一些规律：\n首先 主要的两个方法process_request和process_response，这两个方法也是需要我们重点掌握的方法，其实Django中间件一共有五个用户可以自定义的方法：\nprocess_request(self,request) process_view(self, request, view_func, view_args, view_kwargs) process_template_response(self,request,response) process_exception(self, request, exception) process_response(self, request, response) 以上方法的返回值可以是None或一个HttpResponse对象，如果是None,则继续按照Django定义的规则向后继续执行，如果是HttpResponse对象，则直接将改对象返回给用户。\nDjango的中间件可以用来做什么呢？\n网站全局身份校验，访问频率限制，权限校验\u0026hellip;只要是涉及到全局的校验你都可以在中间件中完。Django的中间件是所有web框架中做的最好。\n其次 process_request和process_response这连个方法的绿\nprocess_request()方法\n规律\n1.请求来的时候会经过每个中间件里面的process_request方法(从上往下)\n2.如果方法里面直接返回了HttpResponse对象 那么会直接返回 不再往下执行\n基于该特点就可以做访问频率限制,身份校验,权限校验\nprocess_response()方法\n规律\n1.必须将response形参返回 因为这个形参指代的就是要返回给前端的数据\n2.响应走的时候，会依次经过每一个中间件里面的process_response方法(从下往上)\n然后 需要了解的方法\nprocess_view()\n在路由匹配成功执行视图函数之前 触发\nprocess_exception()\n当你的视图函数报错时 就会自动执行\nprocess_template_response()\n当你返回的HttpResponse对象中必须包含render属性才会触发\ndef index(request): print('我是index视图函数') def render(): return HttpResponse('什么鬼玩意') obj = HttpResponse('index') obj.render = render return obj 总结 :你在书写中间件的时候 只要形参中有repsonse 你就顺手将其返回，这个reponse就是要给前端的消息。\n如何自定义我们自己的中间件？研究这上面五个方法都有哪些特点\n1.如果你想让你写的中间件生效 就必须要先继承MiddlewareMixin\n2.在注册自定义中间件的时候 一定要确保路径不要写错\ncsrf跨站请求伪造\n钓鱼网站：\n通过制作一个跟正儿八经的网站一模一样的页面,骗取用户输入信息，从而做手脚。\n内部原理：\n在让用户输入对方账户的那个input上面做手脚，给这个input不设置name属性,在内部隐藏一个实现写好的name和value属性的input框，这个value的值 就是钓鱼网站受益人账号。\n防止钓鱼网站的思路：\n网站会给返回给用户的form表单页面 偷偷塞一个随机字符串。请求到来的时候，会先比对随机字符串是否一致，如果不一致，直接拒绝(403)。\n该随机字符串有以下特点：\n1.同一个浏览器每一次访问都不一样\n2.不同浏览器绝对不会重复\n代码如何实现添加随机字符串：\nform表单发送post请求的时候，需要你做得仅仅书写一句话\n{% csrf_token %} ajax发送post请求 如何避免csrf校验：\n1.现在页面上写{% csrf_token %},利用标签查找 获取到该input键值信息\n{'username':'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()} $('[name=csrfmiddlewaretoken]').val() 2.直接书写\u0026rsquo;{{ csrf_token }}'\n{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'} {{ csrf_token }} 3.你可以将该获取随机键值对的方法 写到一个js文件中,之后只需要导入该文件即可\n新建一个js文件 存放以下代码 之后导入即可\nfunction getCookie(name) { var cookieValue = null; if (document.cookie \u0026amp;\u0026amp; document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i \u0026lt; cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } var csrftoken = getCookie('csrftoken'); function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $.ajaxSetup({ beforeSend: function (xhr, settings) { if (!csrfSafeMethod(settings.type) \u0026amp;\u0026amp; !this.crossDomain) { xhr.setRequestHeader(\u0026quot;X-CSRFToken\u0026quot;, csrftoken); } } }); 当你网站全局都需要校验csrf的时候 有几个不需要校验该如何处理？当你网站全局不校验csrf的时候 有几个需要校验又该如何处理？\nfrom django.utils.decorators import method_decorator\tfrom django.views.decorators.csrf import csrf_exempt,csrf_protect # 这两个装饰器在给CBV装饰的时候 有一定的区别 # 如果是csrf_protect 那么有三种方式 # 第一种方式 # @method_decorator(csrf_protect,name='post') # 有效的 class MyView(View): # 第三种方式 # @method_decorator(csrf_protect) def dispatch(self, request, *args, **kwargs): res = super().dispatch(request, *args, **kwargs) return res def get(self,request): return HttpResponse('get') # 第二种方式 # @method_decorator(csrf_protect) # 有效的 def post(self,request): return HttpResponse('post') 如果是csrf_exempt 只有两种(只能给dispatch装) 特例\n@method_decorator(csrf_exempt,name='dispatch') # 第二种可以不校验的方式 class MyView(View): # @method_decorator(csrf_exempt) # 第一种可以不校验的方式 def dispatch(self, request, *args, **kwargs): res = super().dispatch(request, *args, **kwargs) return res def get(self,request): return HttpResponse('get') def post(self,request): return HttpResponse('post') 总结：装饰器中只有csrf_exempt是特例,其他的装饰器在给CBV装饰的时候，都可以有三种方式。\nauth模块\nauth模块是跟用户相关的功能模块。执行数据库迁移命令之后，会生成很多表，其中的auth_user是一张用户相关的表格。\n添加数据：createsuperuser 创建超级用户\n这个超级用户就可以拥有登陆django admin后台管理的权限\nauth模块的功能\n# 查询用户 from django.contrib import auth user_obj = auth.authenticate(username=username,password=password) # 必须要用 因为数据库中的密码字段是密文的 而你获取的用户输入的是明文 # 记录用户状态 auth.login(request,user_obj) # 将用户状态记录到session中 # 判断用户是否登录 print(request.user.is_authenticated) # 判断用户是否登录 如果是你们用户会返回False # 用户登录之后 获取用户对象 print(request.user) # 如果没有执行auth.login那么拿到的是匿名用户 # 校验用户是否登录 from django.contrib.auth.decorators import login_required @login_required(login_url='/xxx/') # 局部配置 def index(request): pass # 全局配置 settings文件中 LOGIN_URL = '/xxx/' # 验证密码是否正确 request.user.check_password(old_password) # 修改密码 request.user.set_password(new_password) request.user.save() # 修改密码的时候 一定要save保存 否则无法生效 # 退出登陆 auth.logout(request) # request.session.flush() # 注册用户 # User.objects.create(username =username,password=password) # 创建用户名的时候 千万不要再使用create 了 # User.objects.create_user(username =username,password=password) # 创建普通用户 User.objects.create_superuser(username =username,password=password,email='123@qq.com') # 创建超级用户 邮箱必填 自定义auth_user表\nfrom django.contrib.auth.models import AbstractUser # Create your models here. # 第一种 使用一对一关系 不考虑 # 第二种方式 使用类的继承 class Userinfo(AbstractUser): # 千万不要跟原来表中的字段重复 只能创新 phone = models.BigIntegerField() avatar = models.CharField(max_length=32) # 一定要在配置文件中 告诉django # 告诉django orm不再使用auth默认的表 而是使用你自定义的表 AUTH_USER_MODEL = 'app01.Userinfo' # '应用名.类名' 执行数据库迁移命令，\n所有的auth模块功能 全部都基于你创建的表，而不再使用auth_user\n","permalink":"https://zhong-er.com/posts/django%E4%B8%AD%E9%97%B4%E4%BB%B6auth%E6%A8%A1%E5%9D%97/","summary":"\u003cp\u003e\u003cstrong\u003eDjango中间件\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eDjango中间件类似于Django的保安。请求的时候需要先经过中间件才能到达Django后端(urls,views,templates,models),响应走的时候也需要经过中间件才能到达web服务网关接口。\u003c/p\u003e\n\u003cp\u003eDjango默认的七个中间件\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e我们点进去几个组件查看源码：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass SecurityMiddleware(MiddlewareMixin):\n    def __init__(self, get_response=None):\n        pass\n\n    def process_request(self, request):\n        path = request.path.lstrip(\u0026quot;/\u0026quot;)\n        if (self.redirect and not request.is_secure() and not any(pattern.search(path) for pattern in self.redirect_exempt)):\n            host = self.redirect_host or request.get_host()\n            return HttpResponsePermanentRedirect(\u0026quot;https://%s%s\u0026quot; % (host, request.get_full_path()))\n\n    def process_response(self, request, response):\n        ...\n        return response\n\n\nclass CsrfViewMiddleware(MiddlewareMixin):\n    def process_request(self, request):\n        pass\n\n    def process_view(self, request, callback, callback_args, callback_kwargs):\n        ...\n        return self._accept(request)\n\n    def process_response(self, request, response):\n        ...\n        return response\n\nclass AuthenticationMiddleware(MiddlewareMixin):    \n    def process_request(self, request):\n        assert hasattr(request, 'session'), (\n            \u0026quot;The Django authentication middleware requires session middleware \u0026quot;\n            \u0026quot;to be installed. Edit your MIDDLEWARE%s setting to insert \u0026quot;\n            \u0026quot;'django.contrib.sessions.middleware.SessionMiddleware' before \u0026quot;\n            \u0026quot;'django.contrib.auth.middleware.AuthenticationMiddleware'.\u0026quot;\n        ) % (\u0026quot;_CLASSES\u0026quot; if settings.MIDDLEWARE is None else \u0026quot;\u0026quot;)\n        request.user = SimpleLazyObject(lambda: get_user(request))\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e不难发现一些规律：\u003c/p\u003e","title":"【Django】中间件/Auth模块"},{"content":"**多对多三种创建方式\n**\n1.全自动\n优势：不需要你手动创建第三张表\n不足：由于第三张表不是你手动创建的，也就意味着第三张表字段时固定的无法做扩展\nclass Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) authors = models.ManyToManyField(to='Author') class Author(models.Model): name = models.CharField(max_length=32) 2.纯手动\n自己创建第三张表\n优势：第三张表可以任意的扩展字段\n不足：orm查询不方便\nclass Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) class Author(models.Model): name = models.CharField(max_length=32) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') create_time = models.DateField(auto_now_add=True) 3.半自动(推荐使用)\n优势：结合了全自动和纯手动的两个优点\n不足：不再支持多对多字段的add(),set(),remove(),clear()方法\nclass Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) authors = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('book','author')) # through 告诉django orm 书籍表和作者表的多对多关系是通过Book2Author来记录的 # through_fields 告诉django orm记录关系时用过Book2Author表中的book字段和author字段来记录的 class Author(models.Model): name = models.CharField(max_length=32) # books = models.ManyToManyField(to='Book', through='Book2Author', through_fields=('author', 'book')) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') create_time = models.DateField(auto_now_add=True) forms组件\n需求：注册页面当用户输入的用户名包含特殊字符时提示非法字符，当用户密码短于3位时提示密码太短了不符合要求\n基本步骤：1.前端页面搭建 \u0026raquo;\u0026gt; 渲染页面\n2.将数据传到后端做校验 \u0026raquo;\u0026gt; 校验数据\n3.展示错误信息 \u0026raquo;\u0026gt; 展示信息\nforms组建能够直接帮你完成上面3步\n1.forms组建基本用法\n(1).写一个基础的forms.Form的类\nfrom django import forms class LoginForm(forms.Form): username = forms.CharField(max_length=8,min_length=3) # 用户名最长八位最短三位 password = forms.CharField(max_length=8,min_length=5) # 密码最长八位最短五位 email = forms.EmailField() # email必须是邮箱格式 (2).基本使用\nfrom app01 import views # 1.将需要校验的数据 以字典的方式传递给自定义的类 实例化产生对象 form_obj = views.LoginForm({'username':'jason','password':'123','email':'123'}) # 2.如何查看数据是否全部合法 form_obj.is_valid() # 只有所有的数据都符合要求 才会是True False # 3.如何查看错误原因 form_obj.errors { 'password': ['Ensure this value has at least 5 characters (it has 3).'], 'email': ['Enter a valid email address.'] } # 4.如何查看通过校验的数据 form_obj.cleaned_data {'username': 'jason'} 注意事项:\n-1.自定义类中所有的字段默认都是必须要传值的\n-2.可以额外传入类中没有定义的字段名 forms组件不会去校验 也就意味着多传一点关系没有\nform_obj = views.LoginForm({'username':'jason','password':'123456','email':'123@qq.com'}) form_obj.is_valid() True form_obj = views.LoginForm({'username':'jason','password':'123456'}) form_obj.is_valid() False form_obj = views.LoginForm({'username':'jason','password':'123456','email':'123@qq.com','hobby':'read'}) form_obj.is_valid() True 2.渲染页面\n三种方式\n\u0026lt;p\u0026gt;第一种渲染页面的方式(封装程度太高 一般只用于本地测试 通常不适用)\u0026lt;/p\u0026gt; {{ form_obj.as_p }} {{ form_obj.as_ul }} {{ form_obj.as_table }} \u0026lt;p\u0026gt;第二种渲染页面的方式(可扩展性较高 书写麻烦)\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ form_obj.username.label }}{{ form_obj.username }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ form_obj.password.label }}{{ form_obj.password }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{{ form_obj.email.label }}{{ form_obj.email }}\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;第三种渲染页面的方式(推荐)\u0026lt;/p\u0026gt; {% for foo in form_obj %} \u0026lt;p\u0026gt;{{ foo.label }}{{ foo }}\u0026lt;/p\u0026gt; {% endfor %} 注意事项：\n-1.forms组件在帮你渲染页面的时候 只会渲染获取用户输入的标签 提交按钮需要你手动添加\n-2.input框的label注释 不指定的情况下 默认用的类中字段的首字母大写\n校验数据的时候可以前后端都校验，做一个双重的校验。但是前端的校验可有可无，而后端的校验则必须要有,因为前端的校验可以通过爬虫直接避开。\n前端取消浏览器校验功能 form标签指定novalidate属性即可\n\u0026lt;form action=\u0026quot;\u0026quot; method='post' novalidate\u0026gt;\u0026lt;/form\u0026gt; 3.展示错误信息\n{% for foo in form_obj %} \u0026lt;p\u0026gt;{{ foo.label }}:{{ foo }} \u0026lt;span\u0026gt;{{ foo.errors.0 }}\u0026lt;/span\u0026gt; \u0026lt;/p\u0026gt; {% endfor %} 后端代码\npassword = forms.CharField( max_length=8, min_length=5, label='密码', error_messages={ 'max_length':'密码最大八位', 'min_length':'密码最小五位', 'required':'密码不能为空' }, required=False, validators=[ RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头') ] ) # 密码最长八位最短五位 4.钩子函数(HOOK)\nforms组件暴露给用户,可以自定义的校验规则\n局部钩子：我们在Form类中定义clean_字段名()方法，就能够实现对特定字段进行校验。\n# 局部钩子(针对某一个字段做额外的校验) 校验用户名中不能包含666 一旦包含 提示 def clean_username(self): username = self.cleaned_data.get('username') if '666' in username: self.add_error('username','光喊666是不行的 你得自己上') return username 全局钩子：我们在Fom类中定义 clean() 方法，就能够实现对字段进行全局校验。\n# 全局钩子(针对多个字段做额外的校验) 校验用户两次密码是否一致 def clean(self): password = self.cleaned_data.get('password') confirm_password = self.cleaned_data.get('confirm_password') if not password == confirm_password: self.add_error('confirm_password','两次密码不一致') return self.cleaned_data 5.forms组建其他字段及操作方式\nrequired 是否必填\nlabel 注释信息\nerror_messages 报错信息\ninitial 默认值\nwidget 控制标签属性和样式\nwidget=widgets.PasswordInput() # 控制标签属性 widget=widgets.PasswordInput(attrs={'class':'form-control c1 c2','username':'jason'}) 其他字段\n# 单选的radio框 gender = forms.ChoiceField( choices=((1, \u0026quot;男\u0026quot;), (2, \u0026quot;女\u0026quot;), (3, \u0026quot;保密\u0026quot;)), label=\u0026quot;性别\u0026quot;, initial=3, widget=forms.widgets.RadioSelect() ) # 单选select hobby = forms.ChoiceField( choices=((1, \u0026quot;篮球\u0026quot;), (2, \u0026quot;足球\u0026quot;), (3, \u0026quot;双色球\u0026quot;),), label=\u0026quot;爱好\u0026quot;, initial=3, widget=forms.widgets.Select() ) # 多选的select框 hobby1 = forms.MultipleChoiceField( choices=((1, \u0026quot;篮球\u0026quot;), (2, \u0026quot;足球\u0026quot;), (3, \u0026quot;双色球\u0026quot;),), label=\u0026quot;爱好\u0026quot;, initial=[1, 3], widget=forms.widgets.SelectMultiple() ) # 单选的checkbox keep = forms.ChoiceField( label=\u0026quot;是否记住密码\u0026quot;, initial=\u0026quot;checked\u0026quot;, widget=forms.widgets.CheckboxInput() ) # 多选的checkbox hobby2 = forms.MultipleChoiceField( choices=((1, \u0026quot;篮球\u0026quot;), (2, \u0026quot;足球\u0026quot;), (3, \u0026quot;双色球\u0026quot;),), label=\u0026quot;爱好\u0026quot;, initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() ) cookie与session\n由于http协议是无状态的，无法记录用户状态。\ncookie就是保存在客户端浏览器上的键值对。\n工作原理:当你登陆成功之后，浏览器上会保存一些信息。下次再访问的时候，就会带着这些信息去访问服务端，服务端通过这些信息来识别出你的身份。\ncookie虽然是写在客户端浏览器上的，但是是服务端设置的。浏览器可以选择不服从命令，禁止写cookie。\nsession就是保存在服务器上的键值对。\nsession虽然是保存在服务器上的键值对，但是它是依赖于cookie工作的。\n服务端返回给浏览器一个随机的字符串，浏览器以键值对的形式保存：\nsessionid:随机字符串\n浏览器在访问服务端的时候，就会将随机字符串携带上。后端获取随机串与后端的记录的做比对。\n随机字符串1:数据1\n随机字符串2:数据2\nCookie\ndjango返回给客户端浏览器的都必须是HttpResponse对象\n之前我们这样写返回\nreturn HttpResponse() return render() return redirect() 现在我们可以这样写返回\nobj1 = HttpResponse() return obj1 obj2 = render() return obj2 obj3 = redirect() return obj3 设置cookie利用的就是HttpResponse对象\n# 设置cookie obj1.set_cookie('k1','v1') # 获取cookie request.COOKIE.get() # 删除cookie obj1.delete_cookie(\u0026quot;k1\u0026quot;) # 设置超时时间 max_age=None, # 超时时间 expires=None, # 超时时间(IE requires expires, so set it if hasn't been already.) session\n# 设置session request.session['name'] = 'jason' \u0026quot;\u0026quot;\u0026quot; 上面这一句话发生了三件事 1.django 内部自动生成一个随机字符串 2.将随机字符串和你要保存的数据 写入django_session表中(现在内存中生成一个缓存记录 等到经过中间件的时候才会执行) 3.将产生的随机字符串发送给浏览器写入cookie sessionid:随机字符串 \u0026quot;\u0026quot;\u0026quot; # 获取session request.session.get('name') \u0026quot;\u0026quot;\u0026quot; 上面这一句话发生了三件事 1.django内部会自动从请求信息中获取到随机字符串 2.拿着随机字符串去django_session表中比对 3.一旦对应上了就将对应的数据解析出来放到request.session中 \u0026quot;\u0026quot;\u0026quot; django session默认的超时时间是14天\ndjango_session表中的一条记录针对一个浏览器\n# 删除当前会话的所有Session数据 request.session.delete() # 删除的是浏览器的sessionid信息 # 删除当前的会话数据并删除会话的Cookie。 request.session.flush() # 将浏览器和服务端全部删除 # 这用于确保前面的会话数据不可以再次被用户的浏览器访问 例如： django.contrib.auth.logout() # 就会调用它 # 设置会话Session和Cookie的超时时间 request.session.set_expiry(value) # * 如果value是个整数，session会在些秒数后失效。 # * 如果value是个datatime或timedelta，session就会在这个时间后失效。 # * 如果value是0,用户关闭浏览器session就会失效。 # * 如果value是None,session会依赖全局session失效策略。 总结:在后期可以将一些数据保存到session表中,保存的数据 可以在后端任意位置获取到\n","permalink":"https://zhong-er.com/posts/djangoform%E7%BB%84%E4%BB%B6cookie%E4%B8%8Esession/","summary":"\u003cp\u003e**多对多三种创建方式\u003cbr\u003e\n**\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e1.全自动\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e优势：不需要你手动创建第三张表\u003c/p\u003e\n\u003cp\u003e不足：由于第三张表不是你手动创建的，也就意味着第三张表字段时固定的无法做扩展\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass Book(models.Model):\n    title = models.CharField(max_length=32)\n    price = models.DecimalField(max_digits=8,decimal_places=2)\n    authors = models.ManyToManyField(to='Author')\n\n \nclass Author(models.Model):\n    name = models.CharField(max_length=32)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e2.纯手动\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e自己创建第三张表\u003c/p\u003e\n\u003cp\u003e优势：第三张表可以任意的扩展字段\u003c/p\u003e\n\u003cp\u003e不足：orm查询不方便\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass Book(models.Model):\n    title = models.CharField(max_length=32)\n    price = models.DecimalField(max_digits=8,decimal_places=2)\n\nclass Author(models.Model):\n    name = models.CharField(max_length=32)\n\nclass Book2Author(models.Model):\n    book = models.ForeignKey(to='Book')\n    author = models.ForeignKey(to='Author')\n    create_time = models.DateField(auto_now_add=True)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e3.半自动(推荐使用)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e优势：结合了全自动和纯手动的两个优点\u003c/p\u003e\n\u003cp\u003e不足：不再支持多对多字段的add(),set(),remove(),clear()方法\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass Book(models.Model):\n    title = models.CharField(max_length=32)\n    price = models.DecimalField(max_digits=8,decimal_places=2)\n    authors = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('book','author'))\n    # through 告诉django orm 书籍表和作者表的多对多关系是通过Book2Author来记录的\n    # through_fields 告诉django orm记录关系时用过Book2Author表中的book字段和author字段来记录的\n\nclass Author(models.Model):\n    name = models.CharField(max_length=32)\n    # books = models.ManyToManyField(to='Book', through='Book2Author', through_fields=('author', 'book'))\n\nclass Book2Author(models.Model):\n    book = models.ForeignKey(to='Book')\n    author = models.ForeignKey(to='Author')\n    create_time = models.DateField(auto_now_add=True)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003eforms组件\u003c/strong\u003e\u003c/p\u003e","title":"【Django】form组件/cookie与session"},{"content":"choices参数\n我们存用户信息性别时，写入数据库时，写入的是数字1或2，\n但从数据库读取时，我们又不能直接将数字展示给用户，\n我们需要将1和2对应的男女展示给用户\nchoices的定义\nchoices参数是在models中定义\nfrom django.db import models class User(models.Model): username = models.CharField(max_length=32) age = models.IntegerField() choices = ( (1,'男'),(2,'女'),(3,'其他') ) gender = models.IntegerField(choices=choices) choices怎么用\nprint(user_obj.get_gender_display()) # 只要是choices字段 在获取数字对应的值 # 固定语法：get_choices字段名_display() choices容错\n比如数据库中某个用户的gender存的是4， 但是choices中没有4对应的值那么不会报错，\n而是展示的是数据库中存的这个值4\nuser_obj = models.User.objects.filter(name='jason').first() print(user_obj.get_gender_display()) # 男user_obj = models.User.objects.filter(name='tank').first() print(user_obj.get_gender_display()) # 4 MTV和MVC模型\ndjango框架 自称为是MTV框架\nM:models\nT:templates\nV:views\nMVC\nM:models\nV:views\nC:controller控制器(urls)\n本质:MTV其实也是MVC\nAjax\n特点： 异步提交 局部刷新（比如注册页面，实时检测用户名是否已存在）\n目前发送请求的方式 GET POST\na标签href属性 GET请求\n浏览器窗口输入url GET请求\nform表单 GET/POST\najax GET/POST\n首先ajax这门技术 是js中的\n但是原生的js操作ajax比较繁琐,我们这里为了提高效率\n直接使用jQuery封装版本的ajax\najax优点\nAJAX 最大的优点是 在不重新加载整个页面的情况下， 可以与服务器交换数据并更新部分网页内容。\n（这一特点给用户的感受 是在不知不觉中 完成请求和响应过程）\najax的使用\najax是js的，所以使用ajax之前一定要先导入js 这里我们使用jquery，所以先导入jQuery\n小例子：\n页面上有三个input框\n在前两个input框中输入数字\n点击按钮 发送ajax请求 不刷新页面的情况下\n第三个框中自动算出两数之和\n前端：\n$('#b1').on('click',function () { {#alert(123)#} // 点击按钮 朝后端发送post请求 $.ajax({ url:'', // 控制发送给谁 不写就是朝当前地址提交 type:'post', // 发送方式是post请求，注意要把settings中crsf注释掉 data:{'i1':$('#i1').val(),'i2':$('#i2').val()}, // 发送的数据 success:function (data) { // data形参用来接收异步提交的结果 {#alert(data)#} // 将后端计算好的结果 通过DOM操作 渲染到第三个input矿中 $('#i3').val(data) } }) }) 后端：\n可以通过 request.is_ajax() 判断是否是ajax提交的请求\n结果是True或False\ncontentType\ncontentType前后端传输数据编码格式\n前后端传输数据编码格式\n1.urlencoded\n2.formdata\n3.json\nform表单\n1. 默认使用的编码格式是urlencoded 数据格式:name=jason\u0026amp;pwd=123（数据格式可以在F12 的headers的 view sourse查看） django后端针对urlencoded编码格式的数据会自动解析并放到request.POST中供用户获取\n2. 编码格式可以修改为formdata 传文件 django后端针对只要是符合urlencoded编码格式的数据(name=jason\u0026amp;pwd=123) 数据都会自动解析并放到request.POST中供用户获取 如果是文件 只要你指定的编码是formdata 就会自动解析并放到request.FILES中\n总结:前后端传输数据的时候 一定要保证数据格式 和你的编码格式是一致的 不能骗人家!!!这个指的什么意思呢：你在data中写的数据是你处理过的数据格式 contentType中可以声明一下数据格式 说的是这两个一直一致\najax提交数据\n1.ajax默认数据提交方式也是urlencoded\n2. ajax如何发送json格式数据\ndjango后端针对json格式的数据 并不会自动解析放到request.POST 或者request.FILES里面 它并不会解析json格式数据，而是将它原封不动的放在request.body中了 可以自己处理一下（解码\u0026ndash;反序列化）\n$('#b1').on('click',function () { alert(123) // 点击按钮 朝后端发送post请求 $.ajax({ url:'', // 控制发送给谁 不写就是朝当前地址提交 type:'post', // 发送方式是post请求 data:JSON.stringify({'username':'jason','password':123}), // 发送的数据 contentType:'application/json', //在浏览器中查看headers时，就可以看到contentype // 告诉后端你这次的数据是json格式，所以上一行的data，要用stringify序列化成json格式数据 success:function (data) { // data形参用来接收异步提交的结果 alert(data) // 将后端计算好的结果 通过DOM操作 渲染到第三个input框中 $('#i3').val(data) } }) ajax传输文件\n// ajax传输文件 $('#b1').on('click',function () { // ajax传输文件 建议使用内置对象formdata var formData = new FormData(); // 既可以传普通的键值对 也可以传文件 // 添加普通键值，多个键值可以for循环 formData.append('username','jason'); formData.append('password','123'); // 传文件 // 如何获取文件标签所存储的文件对象? 固定语法 // 1.先用jQuery查找到存储文件的input标签 // 2.将jQuery对象转成原生js对象 // 3.利用原生js对象的方法 .files[0]获取到标签内部存储的文件对象 // 4.一定要指定两个参数都为false formData.append('my_file',$('#d1')[0].files[0]); $.ajax({ url:'', // 控制发送给谁 不写就是朝当前地址提交 type:'post', // 发送方式是post请求 data:formData, // 发送的数据 // ajax发送文件需要指定两个额外的参数 processData:false, // 告诉前端不要处理数据 contentType:false, // 不使用任何编码 因为formdata对象自身自带编码 django后端也能够识别formdata对象 success:function (data) { // data形参用来接收异步提交的结果 {#alert(data)#} // 将后端计算好的结果 通过DOM操作 渲染到第三个input矿中 $('#i3').val(data) } }) }) 序列化组件\n需求：从数据库拿所有数据展示到前端\n正常情况下：是通过jquery列表，拿到一个个jQuery对象，对象点属性的方式拿到值， 在后端组织成字典，并序列化之后render给前端：\n后端：\ndef reg(request): user_list = models.User.objects.all() # 将所有的数据都组织成一个符合json的一个个的字典 # user_l = [] # for user_obj in user_list: # user_l.append( # json.dumps({'username':user_obj.username, # 'age':user_obj.age, # 'gender':user_obj.get_gender_display() # }) # ) return render(request,'index.html',locals()) ****通过django自带的一个小型的序列化工具： 直接可以将数据包装成一个大字典\n后端：\nfrom django.core import serializers def reg(request): user_list = models.User.objects.all() res = serializers.serialize('json',user_list) return render(request,'index.html',locals()) ****前端：\n｛{res}｝ # 结果如下：[{ \u0026quot;model\u0026quot;: \u0026quot;app01.user\u0026quot;, \u0026quot;pk\u0026quot;: 1, \u0026quot;fields\u0026quot;: { \u0026quot;username\u0026quot;: \u0026quot;jason\u0026quot;, \u0026quot;age\u0026quot;: 18, \u0026quot;gender\u0026quot;: 1 } }, { \u0026quot;model\u0026quot;: \u0026quot;app01.user\u0026quot;, \u0026quot;pk\u0026quot;: 2, \u0026quot;fields\u0026quot;: { \u0026quot;username\u0026quot;: \u0026quot;tank\u0026quot;, \u0026quot;age\u0026quot;: 24, \u0026quot;gender\u0026quot;: 3 } } }] sweetalert（先弹窗，确认后再提交请求）\n1.先下载源码（本例子只用sweetalert/dist下的 js 和 css）\n2. 放在静态文件static下\n3. 需要用到bootstrap，所以也要导入\n4. 遮盖中文的改一下，style标签里边写一个padding-top: 10px\n5. 通过点击事件.click的function想后端发送ajax请求\n6. 后端接到删除请求，执行删除， 然后在data里边写了code=100，通过jsonReponse将data传给前端\n7. ajax的回调函数success:function(data)接收后端返回来的data 并在回调函数内做一个判断，若data.code==100，则将这一行remove\n8. 删除这一行，指的是删除这一行所在的标签\nvar $btnEle = $(this);\n$btnEle.parent().parent().remove();\n自定义分页器\n1. 常规create插入数据\n# for i in range(1000): # models.Book.objects.create(title='第%s本书'%i) # 上面这种方式 效率极低 2. 对比bulk_create() 批量插入数据 非常快\nl = [] for i in range(10000): l.append(models.Book(title='第%s本书'%i)) models.Book.objects.bulk_create(l) # 批量插入数据 自定义分页器的使用\n后端代码\nbook_list = models.Book.objects.all() current_page = request.GET.get(\u0026quot;page\u0026quot;,1) all_count = book_list.count() page_obj = Pagination(current_page=current_page,all_count=all_count,per_page_num=10,pager_count=5) page_queryset = book_list[page_obj.start:page_obj.end] 前端代码\n{% for book in page_queryset %} \u0026lt;p\u0026gt;{{ book.title }}\u0026lt;/p\u0026gt; {% endfor %} {{ page_obj.page_html|safe }} 思路：\n1.就是将切分的结果返回到前端即可 models.Book.objects.all()[start_page:end_page]\n2. 页码展示部分，使用bootstrap； 实现对所选页码编号高亮，则在后端写好页面，通过 | safe在前端转义 代码的逻辑是判断是否是\nDjango引用第三方功能\n项目下新建一个utils文件夹 然后正常的导入使用即可\n","permalink":"https://zhong-er.com/posts/djangoajax%E5%BA%8F%E5%88%97%E5%8C%96%E7%BB%84%E4%BB%B6sweetalert%E5%88%86%E9%A1%B5%E5%99%A8/","summary":"\u003cp\u003e\u003cstrong\u003echoices参数\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e我们存用户信息性别时，写入数据库时，写入的是数字1或2，\u003c/p\u003e\n\u003cp\u003e但从数据库读取时，我们又不能直接将数字展示给用户，\u003c/p\u003e\n\u003cp\u003e我们需要将1和2对应的男女展示给用户\u003c/p\u003e\n\u003cp\u003echoices的定义\u003c/p\u003e\n\u003cp\u003echoices参数是在models中定义\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom django.db import models\nclass User(models.Model):\n    username = models.CharField(max_length=32)\n    age = models.IntegerField()    choices = (\n        (1,'男'),(2,'女'),(3,'其他')\n    )\n    gender = models.IntegerField(choices=choices)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003echoices怎么用\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eprint(user_obj.get_gender_display())\n# 只要是choices字段 在获取数字对应的值 \n# 固定语法：get_choices字段名_display()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003echoices容错\u003c/p\u003e\n\u003cp\u003e比如数据库中某个用户的gender存的是4， 但是choices中没有4对应的值那么不会报错，\u003c/p\u003e\n\u003cp\u003e而是展示的是数据库中存的这个值4\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003euser_obj = models.User.objects.filter(name='jason').first()\nprint(user_obj.get_gender_display())    # 男user_obj = models.User.objects.filter(name='tank').first()\nprint(user_obj.get_gender_display())    # 4\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003eMTV和MVC模型\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003edjango框架 自称为是MTV框架\u003c/p\u003e\n\u003cp\u003eM:models\u003c/p\u003e\n\u003cp\u003eT:templates\u003c/p\u003e\n\u003cp\u003eV:views\u003c/p\u003e\n\u003cp\u003eMVC\u003c/p\u003e\n\u003cp\u003eM:models\u003c/p\u003e\n\u003cp\u003eV:views\u003c/p\u003e\n\u003cp\u003eC:controller控制器(urls)\u003c/p\u003e\n\u003cp\u003e本质:MTV其实也是MVC\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAjax\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e特点： 异步提交 局部刷新（比如注册页面，实时检测用户名是否已存在）\u003c/p\u003e\n\u003cp\u003e目前发送请求的方式 GET POST\u003c/p\u003e\n\u003cp\u003ea标签href属性 GET请求\u003c/p\u003e","title":"【Django】Ajax/序列化组件/sweetalert/分页器"},{"content":"\n常用字段\n常见字段\nAutoField\nint自增列，必须填入参数primary_key=True。当model中如果没有自增列，则会自动创建一个列名为id的列。\nIntegerField\n一个整数类型，范围在-214748368to214748367。(一般不用它来存手机号，位数也不够，直接用字符串存)\nCharField\n字符类型，必须提供max_length参数，max_length表示字符长度。\n这里需要知道的是Django中的CharField对应的MySQL数据库中的varchar类型，没有设置对应char类型的字段，但是Django允许我们自定义新的字段，下面我们来自定义对应于数据库的char类型\nEmailField()\nvarchar(254)内部是个varchar字段**\n**\nBooleanField(Field)\nis_delete = BooleanField()\n给该字段传值的时候 你只需要传布尔值即可\n但是对应到数据库 它存的是0和1\nTextField(Field)\n- 文本类型\n用来存大段文本\nFileField(Field)\n- 字符串，路径保存在数据库，文件上传到指定目录\n- 参数：\nupload_to = \u0026quot;\u0026quot; 用户上传的文件会自动放到等号后面指定的文件路径中\nstorage = None 存储组件，默django.core.files.storage.FileSystemStorage\n自定义char字段\nfrom django.db import models # Create your models here. #Django中没有对应的char类型字段，但是我们可以自己创建 class FixCharField(models.Field): ''' 自定义的char类型的字段类 ''' def __init__(self,max_length,*args,**kwargs): self.max_length=max_length super().__init__(max_length=max_length,*args,**kwargs) def db_type(self, connection): ''' 限定生成的数据库表字段类型char，长度为max_length指定的值 :param connection: :return: ''' return 'char(%s)'%self.max_length #应用上面自定义的char类型 class Class(models.Model): id=models.AutoField(primary_key=True) title=models.CharField(max_length=32) class_name=FixCharField(max_length=16) gender_choice=((1,'男'),(2,'女'),(3,'保密')) gender=models.SmallIntegerField(choices=gender_choice,default=3) DateField\n日期字段，日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例。\nDateTimeField\n日期时间字段，格式YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]，相当于Python中的datetime.datetime()实例。\n字段合集\nAutoField(Field) - int自增列，必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列，必须填入参数 primary_key=True 注：当model中如果没有自增列，则自动会创建一个列名为id的列 from django.db import models class UserInfo(models.Model): # 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32) class Group(models.Model): # 自定义自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整数 -32768 ～ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ～ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ～ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ～ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ～ 9223372036854775807 BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数， max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField)： - 字符串类型，Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型，Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型，Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数： protocol，用于指定Ipv4或Ipv6， 'both',\u0026quot;ipv4\u0026quot;,\u0026quot;ipv6\u0026quot; unpack_ipv4， 如果指定为True，则输入::ffff:192.0.2.1时候，可解析为192.0.2.1，开启此功能，需要protocol=\u0026quot;both\u0026quot; URLField(CharField) - 字符串类型，Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型，Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符（减号） CommaSeparatedIntegerField(CharField) - 字符串类型，格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型，Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串，Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数： path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field) - 字符串，路径保存在数据库，文件上传到指定目录 - 参数： upload_to = \u0026quot;\u0026quot; 上传文件的保存路径 storage = None 存储组件，默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串，路径保存在数据库，文件上传到指定目录 - 参数： upload_to = \u0026quot;\u0026quot; 上传文件的保存路径 storage = None 存储组件，默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名（字符串） height_field=None 上传图片的宽度保存的数据库字段名（字符串） DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数，时间间隔，数据库中按照bigint存储，ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10进制小数 - 参数： max_digits，小数总长度 decimal_places，小数位长度 BinaryField(Field) - 二进制类型 ORM字段与MySQL字段对应关系\n对应关系： 'AutoField': 'integer AUTO_INCREMENT', 'BigAutoField': 'bigint AUTO_INCREMENT', 'BinaryField': 'longblob', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 'DateField': 'date', 'DateTimeField': 'datetime', 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'DurationField': 'bigint', 'FileField': 'varchar(%(max_length)s)', 'FilePathField': 'varchar(%(max_length)s)', 'FloatField': 'double precision', 'IntegerField': 'integer', 'BigIntegerField': 'bigint', 'IPAddressField': 'char(15)', 'GenericIPAddressField': 'char(39)', 'NullBooleanField': 'bool', 'OneToOneField': 'integer', 'PositiveIntegerField': 'integer UNSIGNED', 'PositiveSmallIntegerField': 'smallint UNSIGNED', 'SlugField': 'varchar(%(max_length)s)', 'SmallIntegerField': 'smallint', 'TextField': 'longtext', 'TimeField': 'time', 'UUIDField': 'char(32)', 字段参数\nnull 用于表示某个字段可以为空。\nunique 如果设置为unique=True 则该字段在此表中必须是唯一的。\ndb_index 如果db_index=True则代表着为此字段设置索引。\ndefault\n为该字段设置默认值\nDateField和DateTimeField\nauto_now_add\n配置auto_now_add=True，创建数据记录的时候会把当前时间添加到数据库。\nauto_now\n配置上auto_now=True,每次更新数据记录的时候会自动更新改字段。\n关系字段\nForeignKey\n外键类型在ORM中用来表示外键关联关系，一般把ForeignKey字段设置在 \u0026lsquo;一对多\u0026rsquo;中\u0026rsquo;多\u0026rsquo;的一方。\nForeignKey可以和其他表做关联关系同时也可以和自身做关联关系。\n字段参数\nto 设置要关联的表\nto_field 设置要关联的表的字段\non_delete 当删除关联表中的数据时，当前表与其关联的行的行为。\nmodels.CASCADE 删除关联数据，与之关联也删除\ndb_constraint 是否在数据库中创建外键约束，默认为True。\n其他字段\nmodels.DO_NOTHING 删除关联数据，引发错误IntegrityError\nmodels.PROTECT 删除关联数据，引发错误ProtectedError\nmodels.SET_NULL 删除关联数据，与之关联的值设置为null（前提FK字段需要设置为可空）\nmodels.SET_DEFAULT 删除关联数据，与之关联的值设置为默认值（前提FK字段需要设置默认值）\nmodels.SET 删除关联数据，\na. 与之关联的值设置为指定值，设置：models.SET(值)\nb. 与之关联的值设置为可执行对象的返回值，设置：models.SET(可执行对象)\ndef func(): return 10class MyModel(models.Model): user = models.ForeignKey( to=\u0026quot;User\u0026quot;, to_field=\u0026quot;id\u0026quot;， on_delete=models.SET(func) ) OneToOneField\n一对一字段。\n通常一对一字段用来扩展已有字段。(通俗的说就是一个人的所有信息不是放在一张表里面的，简单的信息一张表，隐私的信息另一张表，之间通过一对一外键关联)\n字段参数\nto 设置要关联的表。\nto_field 设置要关联的字段。\non_delete 当删除关联表中的数据时，当前表与其关联的行的行为。(参考上面的例子)\nManyToManyField\nclass RelatedManager\n\u0026ldquo;关联管理器\u0026quot;是在一对多或者多对多的关联上下文中使用的管理器。\n它存在于下面两种情况：\n1.外键关系的反向查询\n2.多对多关联关系\n简单来说就是在多对多表关系并且这一张多对多的关系表是有Django自动帮你建的情况下，下面的方法才可使用。\n数据库查询优化\nonly与defer\norm内所有的语句操作都是惰性查询：\n只会在你真正需要数据的时候才会走数据库，如果你单单只写orm语句时不会走数据库的\n这样设计的好处在于减轻数据库的压力\nres = models.Book.objects.all() # 这一步不查询数据库，惰性查询 print(res) # 查询数据库 # 例：我们想拿到所有书籍的书名 res = models.Book.objects.values('title') # 这一步不查询数据库 .values返回的是一个列表套字典 for i in res : print(i.title) #字典没有点取值方法报错 # 我们想通过点取值的方法取所有书名，就需要用到only res = models.Book.objects.only('title') for r in res: print(r.title) # 只走一次数据库查询 print(r.price) # 每循环一次走一次数据库查询，频繁查询 \u0026quot;\u0026quot;\u0026quot; only会将括号内的所有的字段信息封装成一个对象，支持点取值 当点only括号内指定的字段取值时，只走一次查询, 当点only括号外其他的字段取值时，不会报错，会频繁走数据库查询 \u0026quot;\u0026quot;\u0026quot; # defer与only相反 res = models.Book.objects.defer('title') for r in res1: print(r.price) \u0026quot;\u0026quot;\u0026quot; defer会将不是括号内的所有的字段信息封装成一个对象，支持点取值 当点defer括号内指定的字段取值时，会频繁走数据库查询 当点defer括号外其他的字段取值时，只走一次查询 \u0026quot;\u0026quot;\u0026quot; select_related与prefetch_related\nselect_related帮你直接连表操作 查询数据 括号内只能放外键字段\nres = models.Book.objects.all() for r in res: print(r.publish.name) # 每for循环一次跨表一次走一次数据库，效率低 res = models.Book.objects.all().select_related('publish') for r in res: print(r.publish.name) # 只走一次数据库 res = models.Book.objects.all().select_related('publish__xxx__yyy__ttt') print(res) \u0026quot;\u0026quot;\u0026quot; select_related:会将括号内外键字段所关联的那张表 直接全部拿过来(可以一次性拿多张表)跟当前表拼接操作 从而降低你跨表查询 数据库的压力 注意select_related括号只能放外键字段(一对一和一对多) res = models.Book.objects.all().select_related('外键字段1__外键字段2__外键字段3__外键字段4') \u0026quot;\u0026quot;\u0026quot; prefetch_related 不主动连表\nres = models.Book.objects.prefetch_related('publish') for r in res: print(r.publish.name) \u0026quot;\u0026quot;\u0026quot; 不主动连表操作(但是内部给你的感觉像是连表操作了) 而是将book表中的publish全部拿出来 在取publish表中将id对应的所有的数据取出 res = models.Book.objects.prefetch_related('publish') 括号内有几个外键字段 就会走几次数据库查询操作 \u0026quot;\u0026quot;\u0026quot; select_related需要连表，虽然跨表只走一次数据库操作，但是连表需要时间，速度慢\nprefetch_related不需要连表，每次都只需要两次查询，先查一张表，然后拿着这个表查的数据再查另一张表，内部做了一次拼接，感觉像是连表\n事务\n事务的定义:将多个sql语句操作变成原子性操作，要么同时成功，有一个失败则里面回滚到原来的状态，保证数据的完整性和一致性(NoSQL数据库对于事务则是部分支持)\nACID\n原子性\n一致性\n隔离性\n持久性\nfrom django.db import transaction with transaction.atomic(): \u0026quot;\u0026quot;\u0026quot;数据库操作 在该代码块中书写的操作 同属于一个事务 \u0026quot;\u0026quot;\u0026quot; models.Book.objects.create() models.Publish.objects.create() # 添加书籍和出版社 就是同一个事务 要么一起成功要么一起失败 print('出了 代码块 事务就结束') ","permalink":"https://zhong-er.com/posts/djangoorm%E4%B8%AD%E5%B8%B8%E7%94%A8%E5%AD%97%E6%AE%B5%E5%92%8C%E5%8F%82%E6%95%B0/","summary":"\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/assets/201909169981_9838.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e常用字段\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e常见字段\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eAutoField\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eint自增列，必须填入参数primary_key=True。当model中如果没有自增列，则会自动创建一个列名为id的列。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eIntegerField\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e一个整数类型，范围在-214748368to214748367。(一般不用它来存手机号，位数也不够，直接用字符串存)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eCharField\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e字符类型，必须提供max_length参数，max_length表示字符长度。\u003c/p\u003e\n\u003cp\u003e这里需要知道的是Django中的CharField对应的MySQL数据库中的varchar类型，没有设置对应char类型的字段，但是Django允许我们自定义新的字段，下面我们来自定义对应于数据库的char类型\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"1.png\" loading=\"lazy\" src=\"/assets/201909207986_7402.png-zhonger.com\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eEmailField()\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003evarchar(254)内部是个varchar字段**\u003cbr\u003e\n**\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eBooleanField(Field)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eis_delete = BooleanField()\u003c/p\u003e\n\u003cp\u003e给该字段传值的时候 你只需要传布尔值即可\u003c/p\u003e\n\u003cp\u003e但是对应到数据库 它存的是0和1\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eTextField(Field)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e- 文本类型\u003c/p\u003e\n\u003cp\u003e用来存大段文本\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eFileField(Field)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e- 字符串，路径保存在数据库，文件上传到指定目录\u003c/p\u003e\n\u003cp\u003e- 参数：\u003c/p\u003e\n\u003cp\u003eupload_to = \u0026quot;\u0026quot; 用户上传的文件会自动放到等号后面指定的文件路径中\u003c/p\u003e\n\u003cp\u003estorage = None 存储组件，默django.core.files.storage.FileSystemStorage\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e自定义char字段\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom django.db import models\n\n# Create your models here.\n#Django中没有对应的char类型字段，但是我们可以自己创建\nclass FixCharField(models.Field):\n    '''\n    自定义的char类型的字段类\n    '''\n    def __init__(self,max_length,*args,**kwargs):\n        self.max_length=max_length\n        super().__init__(max_length=max_length,*args,**kwargs)\n\n    def db_type(self, connection):\n        '''\n        限定生成的数据库表字段类型char，长度为max_length指定的值\n        :param connection:\n        :return:\n        '''\n        return 'char(%s)'%self.max_length\n#应用上面自定义的char类型\nclass Class(models.Model):\n    id=models.AutoField(primary_key=True)\n    title=models.CharField(max_length=32)\n    class_name=FixCharField(max_length=16)\n    gender_choice=((1,'男'),(2,'女'),(3,'保密'))\n    gender=models.SmallIntegerField(choices=gender_choice,default=3)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003eDateField\u003c/strong\u003e\u003c/p\u003e","title":"【Django】ORM中常用字段和参数"},{"content":"ORM单表操作\n单表操作演示表\nclass Book(models.Model): name = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_place=2) publish = models.CharField(max_length=32) author = models.CharField(max_length=32) create_time = models.DateField(null=True) # 配置auto_now_add=True，创建数据记录的时候会把当前时间添加到数据库。 # 配置上auto_now=True，每次更新数据记录的时候会更新该字段。 新增数据\n# 第一种：有返回值，并且就是当前被创建的数据对象 modles.Book.objects.create(name='',price='',publish='',author='',create_time='2019-5-1') # 第二种：先实例化产生对象，然后调用save方法保存 book_obj = models.Book(name='',price='',publish='',author='',create_time='2019-5-1') book_obj.save() # 2.验证时间格式字段即可以传字符串也可以传时间对象 import datetime ctime = datetime.datetime.now() book = models.Book.objects.create(name='',price='',author='',create_time=ctime) 删除数据\n\u0026quot;\u0026quot;\u0026quot;删除数据\u0026quot;\u0026quot;\u0026quot; # 1.删除书名为xxx的这本书 queryset方法 res = models.Book.objects.filter(name='').delete() print(res) # 2.删除书名为xxx的这本书 queryset方法 res = models.Book.objects.filter(name='').first() res.delete() 修改数据\n# 1.queryset修改 models.Book.objects.filter(name='').update(price='') # 2.对象修改 book = models.Book.objects.filter(name='').first() book.price = 66.66 book.save() # 对象只有保存方法 这样也能实现修改需求 查询数据的13个方法\n返回QuerySet对象的方法有：\nall() 查询所有结果\nfilter() 它包含了与所给筛选条件相匹配的对象\nexclude() 它包含了与所给筛选条件不匹配的对象\norder_by() 对查询结果排序\nreverse() 对查询结果反向排序\u0026raquo;\u0026gt;前提是要先有排序才能反向\ndistinct() 从返回结果中剔除重复记录(必须是完全相同的两条记录)\n特殊的QuerySet：\nvalues() 返回一个可迭代的字典序列\nvalues_list() 返回一个可迭代的元组序列\n返回具体对象的：\nget() 返回与所给筛选条件相匹配的对象，返回结果有且只有一个，如果符合筛选条件的对象超过一个或者没有都会抛出错误。\nfirst() 返回第一条记录\nlast() 返回最后一条记录\n返回布尔值的方法有：\nexists() 如果QuerySet包含数据，就返回True，否则返回False\n返回数字的方法有：\ncount() 返回数据库中匹配查询\n查看orm内部sql语句的方法有哪些\n1.如果是queryset对象，那么可以点query直接查看该queryset的内部sql语句\n2.在Django项目的配置文件中，配置以下参数即可实现所有的orm在查询的时候自动打印对应的sql语句\nLOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } } 神奇的双下划线查询\n# 价格 大于 小于 大于等于 小于等于 filter(price__gt='90') filter(price__lt='90') filter(price_gte='90') filter(price_lte='90') # 存在与某几个条件中 filter(price__in=['11','22','33']) # 在某个范围内 filter(price__range=[50,90]) # 模糊查询 filter(title__contains='西') filter(title__icontains='P') # 以什么开头 以什么结尾 filter(title__startswith='三') filter(title__endswith='p') # 按年查询 filter(create_time__year='2017') 多表操作\n一对一查询，一对一关联其实就是外健关联再加一个唯一性约束\nForeignKey(unique=Ture) \u0026gt;\u0026gt;\u0026gt; OneToOneField() # 即一对一可以用ForeignKey来做，但是需要设唯一性约束并且会报警告信息，不建议使用，建议用OneToOneField # 用了OneToOneField和用ForeignKey会自动在字段后面加_id # 用了ManyToMany会自动创建第三张表 一对多增删改查\n# 增：针对外键关联的字段 两种添加方式 # 第一种通过publish_id models.Book.objects.create(publish_id=1,) # 第二种通过publish传出版社对象 publish_obj = models.Publish.objects.filter(pk=2).first() models.Book.objects.create(publish=publish_obj,) # 删：删除书籍直接查询删除即可，删除出版社会级联删除 models.Publish.objects.filter(pk=2).delete() # 默认都是级联更新 级联删除 # 改：编辑数据也是两种对应的方式(对象点的方式(这里能点publish和publish_id)最后点save(),queryset方式update()) # 传数字的 models.Book.objects.filter(pk=1).update(publish_id=3) # 传对象的 publish_obj = models.Publish.objects.filter(pk=2).first() models.Book.objects.filter(pk=1).update(publish=publish_obj) 多对多增删改查\n\u0026quot;\u0026quot;\u0026quot;前提：先获取书籍对象，再通过书籍对象点authors来进行书籍作者的增删改查\u0026quot;\u0026quot;\u0026quot; # 增：给书籍新增作者add # add可以传作者id，也可以直接传作者对象，并且支持传多个位置参数(不要混着用) # 删：给书籍删除作者remove # remove同样可以传id，对象，并且支持传多个位置参数(不要混着用) # 清空：直接清空书籍对象所有的作者数据 # clear()不用传任何参数 # 改修改书籍对象所关联的作者信息set，注意点set括号内必须传可迭代对象，里面可以传id，对象 \u0026quot;\u0026quot;\u0026quot;总结:一对多增删改，多对多add，remove，clear，set\u0026quot;\u0026quot;\u0026quot; create() 创建一个关联对象，并自动写入数据库，且在第三张双方的关联表中自动新建上双方对应关系。 add() 把指定的model对象添加到第三张关联表中。 set() 更新某个对象在第三张表中的关联对象。不同于上面的add是添加,set相当于重置 remove() 从关联对象集中移除执行的model对象(移除对象在第三张表中与某个关联对象的关系) clear() 从关联对象集中移除一切对象。(移除所有与对象相关的关系信息) \u0026quot;\u0026quot;\u0026quot; 注意：1.对于ForeignKey对象，clear()和remove()方法仅在null=True时存在。 2.对于所有类型的关联字段，add()、create()、remove()和clear(),set()都会马上更新数据库。换句话说，在关联的任何一端，都不需要再调用save()方法。 \u0026quot;\u0026quot;\u0026quot; 正向反向查找\n# 正向与方向的概念解释 # 一对一 # 正向：author---关联字段在author表里---\u0026gt;authordetail 按字段 # 反向：authordetail---关联字段在author表里---\u0026gt;author 按表名小写 # 查询jason作者的手机号 正向查询 # 查询地址是 :山东 的作者名字 反向查询 # 一对多 # 正向：book---关联字段在book表里---\u0026gt;publish 按字段 # 反向：publish---关联字段在book表里---\u0026gt;book 按表名小写_set.all() 因为一个出版社对应着多个图书 # 多对多 # 正向：book---关联字段在book表里---\u0026gt;author 按字段 # 反向：author---关联字段在book表里---\u0026gt;book 按表名小写_set.all() 因为一个作者对应着多个图书 # 连续跨表 # 查询图书是三国演义的作者的手机号，先查书，再正向查到作者，在正向查手机号 \u0026quot;\u0026quot;\u0026quot;基于对象的跨表查询(子查询:将一张表的查询结果当做另外一个查询语句的条件)\u0026quot;\u0026quot;\u0026quot; 多表查询\n# 1.查询书籍id是1 的出版社名称 book_obj = models.Book.objects.filter(pk=1).first() print(book_obj.publish.name) 2.查询书籍id是2 的作者姓名 book_obj = models.Book.objects.filter(pk=2).first() # print(book_obj.authors) # app01.Author.None print(book_obj.authors.all()) res = book_obj.authors.all() for r in res: print(r.name) # 3.查询作者是jason的家庭住址 author_obj = models.Author.objects.filter(name='jason').first() print(author_obj.author_detail.addr) # 4.查询出版社是东方出版社出版的书籍 publish_obj = models.Publish.objects.filter(name='东方出版社').first() print(publish_obj.book_set.all()) # 5.查询作者是jason的写过的所有的书籍 author_obj = models.Author.objects.filter(name='jason').first() print(author_obj.book_set.all()) # 6.查询电话号码是130的作者姓名 author_detail_obj = models.AuthorDetail.objects.filter(phone=130).first() print(author_detail_obj.author.name) \u0026quot;\u0026quot;\u0026quot; 当你反向查询的结果是多个的时候 就需要加_set 否则直接表明小写即可 \u0026quot;\u0026quot;\u0026quot; # 7.查询书籍id为1 的作者的电话号码 book_obj = models.Book.objects.filter(pk=1).first() author_list = book_obj.authors.all() for author_obj in author_list: print(author_obj.author_detail.phone) 基于双下划线的跨表查询\n\u0026quot;\u0026quot;\u0026quot;基于双下划线的跨表查询(连表操作) -连表查询 -一对一双下划线查询 -正向:按字段,跨表可以在filter,也可以在values中 -反向:按表名小写,跨表可以在filter,也可以在values中 \u0026quot;\u0026quot;\u0026quot; # 查询jason作者的手机号 正向查询 跨表的话,按字段 # ret=Author.objects.filter(name='jason').values('authordetail__phone') # 以authordetail作为基表 反向查询,按表名小写 跨表的话,用表名小写 # ret=AuthorDetail.objects.filter(author__name='jason').values('phone') # 查询jason这个作者的性别和手机号 # 正向 # ret=Author.objects.filter(name='jason').values('sex','authordetail__phone') # 查询手机号是13888888的作者性别 # ret=Author.objects.filter(authordetail__phone='13888888').values('sex') # ret=AuthorDetail.objects.filter(phone='13888888').values('author__sex') \u0026quot;\u0026quot;\u0026quot; 总结 其实你在查询的时候先把orm查询语句写出来，再看用到的条件是否在当前表内，在就直接获取，不在就按照正向按字段反向按表名来查即可 比如： 1.查询出版社为北方出版社的所有图书的名字和价格 res1 = Publish.objects.filter(name='').values('book__name','book__price') res2 = Book.objects.filter(publish__name='').values('name','price') 2.查询北方出版社出版的价格大于19的书 res1 = Publish.objects.filter(name='',book__price__gt=19).values('book__name','book__price) \u0026quot;\u0026quot;\u0026quot; 聚合查询(aggregate)\n# 1.统计所有书的总价格 from django.db.models import Max,Min,Count,Avg,Sum res = models.Book.objects.aggregate(Sum('price')) res1 = models.Book.objects.aggregate(Avg('price')) res2 = models.Book.objects.aggregate(Count('price')) res3 = models.Book.objects.aggregate(Max('price')) res4 = models.Book.objects.aggregate(Min('price')) res5 = models.Book.objects.aggregate(Max('price'),Min('price'),Count('pk'),Avg('price'),Sum('price')) print(res,res1,res2,res3,res4,res5) 分组查询\n# 统计每一本书的作者个数 from django.db.models import Max, Min, Count, Avg, Sum res = models.Book.objects.annotate(author_num = Count('authors')).values('author_num','title') print(res) # 统计出每个出版社卖的最便宜的书的价格 res = models.Publish.objects.annotate(mmp = Min('book__price')).values('name','mmp') print(res) # 统计不止一个作者的图书 res = models.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=1) print(res) \u0026quot;\u0026quot;\u0026quot; 只要是queryset对象 就可以无限制的调用queryset对象的方法!!! 最最常用的就是对一个已经filter过滤完的数据 再进行更细化的筛选 \u0026quot;\u0026quot;\u0026quot; # 查询各个作者出的书的总价格 res = models.Author.objects.annotate(sp=Sum('book__price')).values('name','sp') print(res) F查询\n#F查询的本质就是从数据库中获取某个字段的值 # 查询库存数大于卖出数的书籍 \u0026quot;\u0026quot;\u0026quot;之前查询等号后面的条件都是我们认为输入的 现在变成了需要从数据库中获取数据放在等号后面 \u0026quot;\u0026quot;\u0026quot; from django.db.models import F res = models.Book.objects.filter(kucun__gt=F('maichu')) print(res) # 将书籍库存数全部增加1000 models.Book.objects.update(kucun=F('kucun')+1000) # 把所有书名后面加上'新款' from django.db.models.functions import Concat from django.db.models import Value res = models.Book.objects.update(title=Concat(F('title'), Value('新款'))) # models.Book.objects.update(title = F('title')+'新款') # 不能这么写 Q查询\nfrom django.db.models import Q # 查询书籍名称是三国演义或者价格是444.44 res = models.Book.objects.filter(title='三国演义',price=444.44) # filter只支持and关系 res1 = models.Book.objects.filter(Q(title='三国演义'),Q(price=444)) # 如果用逗号 那么还是and关系 res2 = models.Book.objects.filter(Q(title='三国演义')|Q(price=444)) res3 = models.Book.objects.filter(~Q(title='三国演义')|Q(price=444)) print(res) # Q高级用法 q = Q() q.connector = 'or' # 修改查询条件的关系 默认是and q.children.append(('title__contains','三国演义')) # 往列表中添加筛选条件 q.children.append(('price__gt',444)) # 往列表中添加筛选条件 res = models.Book.objects.filter(q) # filter支持你直接传q对象 但是默认还是and关系 print(res) ","permalink":"https://zhong-er.com/posts/django%E6%A8%A1%E5%9E%8B%E5%B1%82/","summary":"\u003cp\u003e\u003cstrong\u003eORM单表操作\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e单表操作演示表\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass Book(models.Model):\n      name = models.CharField(max_length=32)\n    price = models.DecimalField(max_digits=8,decimal_place=2)\n    publish = models.CharField(max_length=32)\n    author = models.CharField(max_length=32)\n    create_time = models.DateField(null=True)\n    # 配置auto_now_add=True，创建数据记录的时候会把当前时间添加到数据库。\n    # 配置上auto_now=True，每次更新数据记录的时候会更新该字段。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e新增数据\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 第一种：有返回值，并且就是当前被创建的数据对象\nmodles.Book.objects.create(name='',price='',publish='',author='',create_time='2019-5-1')\n# 第二种：先实例化产生对象，然后调用save方法保存\nbook_obj = models.Book(name='',price='',publish='',author='',create_time='2019-5-1')\nbook_obj.save()\n# 2.验证时间格式字段即可以传字符串也可以传时间对象\nimport datetime\nctime = datetime.datetime.now()\nbook = models.Book.objects.create(name='',price='',author='',create_time=ctime)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e删除数据\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;删除数据\u0026quot;\u0026quot;\u0026quot;\n# 1.删除书名为xxx的这本书  queryset方法\nres = models.Book.objects.filter(name='').delete()\nprint(res)\n# 2.删除书名为xxx的这本书  queryset方法\nres = models.Book.objects.filter(name='').first()\nres.delete()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e修改数据\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 1.queryset修改\nmodels.Book.objects.filter(name='').update(price='')\n# 2.对象修改\nbook = models.Book.objects.filter(name='').first()\nbook.price = 66.66\nbook.save()  # 对象只有保存方法 这样也能实现修改需求\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e查询数据的13个方法\u003c/strong\u003e\u003c/p\u003e","title":"【Django】模型层"},{"content":"模版语法\n两大类\n{{}} 变量相关\n{%%} 逻辑相关\n给模版传值\npython所有的数据类型都可以被传递到前端页面\n注意在传递函数的时候，会自动加括号调用，前端展示的是函数调用的之后的返回值(ps:前端在调用函数的时候，是不支持给函数传递额外参数的)\n如果你想在前端获取后端传递的某个容器类型中的具体元素\n那么你可以通过句点符来获取具体的元素\n.索引\n.键\n变量\n在Django的模板语言中按此语法使用：{{ 变量名 }}。\n当模版引擎遇到一个变量，它将计算这个变量，然后用结果替换掉它本身。 变量的命名包括任何字母数字以及下划线 (\u0026quot;_\u0026quot;)的组合。 变量名称中不能有空格或标点符号。\n点（.）在模板语言中有特殊的含义。当模版系统遇到点(\u0026quot;.\u0026quot;)，它将以这样的顺序查询：\n字典查询（Dictionary lookup）\n属性或方法查询（Attribute or method lookup）\n数字索引查询（Numeric index lookup）\n注意事项：\n如果计算结果的值是可调用的，它将被无参数的调用。 调用的结果将成为模版的值。\n如果使用的变量不存在， 模版系统将插入 string_if_invalid 选项的值， 它被默认设置为\u0026rsquo;\u0026rsquo; (空字符串) 。\n几个例子：\nview中代码：\ndef template_test(request): l = [11, 22, 33] d = {\u0026quot;name\u0026quot;: \u0026quot;alex\u0026quot;} class Person(object): def __init__(self, name, age): self.name = name self.age = age def dream(self): return \u0026quot;{} is dream...\u0026quot;.format(self.name) Alex = Person(name=\u0026quot;Alex\u0026quot;, age=34) Egon = Person(name=\u0026quot;Egon\u0026quot;, age=9000) Eva_J = Person(name=\u0026quot;Eva_J\u0026quot;, age=18) person_list = [Alex, Egon, Eva_J] return render(request, \u0026quot;template_test.html\u0026quot;, {\u0026quot;l\u0026quot;: l, \u0026quot;d\u0026quot;: d, \u0026quot;person_list\u0026quot;: person_list}) 模板中支持的写法：\n{# 取l中的第一个参数 #} {{ l.0 }} {# 取字典中key的值 #} {{ d.name }} {# 取对象的name属性 #} {{ person_list.0.name }} {# .操作只能调用不带参数的方法 #} {{ person_list.0.dream }} Filter(过滤器)\n在Django的模板语言中，通过使用 过滤器 来改变变量的显示。\n过滤器的语法： {{ value|filter_name:参数 }}\n使用管道符\u0026quot;|\u0026ldquo;来应用过滤器。过滤器会将|左边的对象当做过滤器第一个参数传入，或将右侧的对象当做第二个参数传入\n例如：{{ name|lower }}会将name变量应用lower过滤器之后再显示它的值。lower在这里的作用是将文本全都变成小写。\n注意事项：\n1.过滤器支持“链式”操作。即一个过滤器的输出作为另一个过滤器的输入。\n2.过滤器可以接受参数，例如：{{ sss|truncatewords:30 }}，这将显示sss的前30个词。\n3.过滤器参数包含空格的话，必须用引号包裹起来。比如使用逗号和空格去连接一个列表中的元素，如：{{ list|join:\u0026rsquo;, \u0026rsquo; }}\n4.\u0026rsquo;|\u0026lsquo;左右没有空格没有空格没有空格\nDjango的模板语言中提供了大约六十个内置过滤器。\ndefault\n如果一个变量是false或者为空，使用给定的默认值。 否则，使用变量的值。\n{{ value|default:\u0026quot;nothing\u0026quot;}} 如果value没有传值或者值为空的话就显示nothing\nlength\n返回值的长度，作用于字符串和列表。\n{{ value|length }}\n返回value的长度，如 value=[\u0026lsquo;a\u0026rsquo;, \u0026lsquo;b\u0026rsquo;, \u0026lsquo;c\u0026rsquo;, \u0026rsquo;d\u0026rsquo;]的话，就显示4.\nfilesizeformat\n将值格式化为一个 “人类可读的” 文件尺寸 （例如 \u0026lsquo;13 KB\u0026rsquo;, \u0026lsquo;4.1 MB\u0026rsquo;, \u0026lsquo;102 bytes\u0026rsquo;, 等等）。例如：\n{{ value|filesizeformat }} 如果 value 是 123456789，输出将会是 117.7 MB。\nslice\n切片\n{{value|slice:\u0026quot;2:-1\u0026quot;}} date\n格式化\n{{ value|date:\u0026quot;Y-m-d H:i:s\u0026quot;}} 可用的参数：\n格式化字符 描述 示例输出 a 'a.m.'或'p.m.'（请注意，这与PHP的输出略有不同，因为这包括符合Associated Press风格的期间） 'a.m.' A 'AM'或'PM'。 'AM' b 月，文字，3个字母，小写。 'jan' B 未实现。 c| ISO 8601格式。 （注意：与其他格式化程序不同，例如“Z”，“O”或“r”，如果值为naive datetime，则“c”格式化程序不会添加时区偏移量（请参阅datetime.tzinfo） 。| 2008-01-02T10:30:00.000123+02:00或2008-01-02T10:30:00.000123如果datetime是天真的\nd| 月的日子，带前导零的2位数字。| '01'到'31'\nD| 一周中的文字，3个字母。| “星期五”\ne| 时区名称 可能是任何格式，或者可能返回一个空字符串，具体取决于datetime。| ''、'GMT'、'-500'、'US/Eastern'等\nE| 月份，特定地区的替代表示通常用于长日期表示。| 'listopada'（对于波兰语区域，而不是'Listopad'）\nf| 时间，在12小时的小时和分钟内，如果它们为零，则分钟停留。 专有扩展。| '1'，'1:30'\nF| 月，文，长。| '一月'\ng| 小时，12小时格式，无前导零。| '1'到'12'\nG| 小时，24小时格式，无前导零。| '0'到'23'\nh| 小时，12小时格式。| '01'到'12'\nH| 小时，24小时格式。| '00'到'23'\ni| 分钟。| '00'到'59'\nI| 夏令时间，无论是否生效。| '1'或'0'\nj| 没有前导零的月份的日子。| '1'到'31'\nl| 星期几，文字长。| '星期五'\nL| 布尔值是否是一个闰年。| True或False\nm| 月，2位数字带前导零。| '01'到'12'\nM| 月，文字，3个字母。| “扬”\nn| 月无前导零。| '1'到'12'\nN| 美联社风格的月份缩写。 专有扩展。| 'Jan.'，'Feb.'，'March'，'May'\no| ISO-8601周编号，对应于使用闰年的ISO-8601周数（W）。 对于更常见的年份格式，请参见Y。| '1999年'\nO| 与格林威治时间的差异在几小时内。| '+0200'\nP| 时间为12小时，分钟和\u0026rsquo;a.m。\u0026rsquo;/\u0026lsquo;p.m。\u0026rsquo;，如果为零，分钟停留，特殊情况下的字符串“午夜”和“中午”。 专有扩展。| '1 am'，'1:30 pm' / t3\u0026gt;，\u0026lsquo;midnight\u0026rsquo;，\u0026lsquo;noon\u0026rsquo;，\u0026lsquo;12：30 pm\u0026rsquo; / T10\u0026gt;``\nr| RFC 5322格式化日期。| 'Thu, 21 Dec 2000 16:01:07 +0200'\ns| 秒，带前导零的2位数字。| '00'到'59'\nS| 一个月的英文序数后缀，2个字符。| 'st'，'nd'，'rd'或'th'\nt| 给定月份的天数。| 28 to 31\nT| 本机的时区。| 'EST'，'MDT'\nu| 微秒。| 000000 to 999999\nU| 自Unix Epoch以来的二分之一（1970年1月1日00:00:00 UTC）。|\nw| 星期几，数字无前导零。| '0'（星期日）至'6'（星期六）\nW| ISO-8601周数，周数从星期一开始。| 1，53\ny| 年份，2位数字。| '99'\nY| 年，4位数。| '1999年'\nz| 一年中的日子| 0到365\nZ| 时区偏移量，单位为秒。 UTC以西时区的偏移量总是为负数，对于UTC以东时，它们总是为正。| -43200到43200\nsafe\nDjango的模板中会对HTML标签和JS等语法标签进行自动转义，原因显而易见，这样是为了安全。但是有的时候我们可能不希望这些HTML元素被转义，比如我们做一个内容管理系统，后台添加的文章中是经过修饰的，这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本，如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式，如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。\n比如：\nvalue = \u0026ldquo;点我\u0026rdquo;\n{{ value|safe}}\ntruncatechars\n按照字符切分 三个点也算字符数\n如果字符串字符多于指定的字符数量，那么会被截断。截断的字符串将以可翻译的省略号序列（“\u0026hellip;”）结尾。\n参数：截断的字符数\n{{ value|truncatechars:9}}\ntruncatewords\n按照空格切分 不包含三个点\n在一定数量的字后截断字符串。\n{{ value|truncatewords:9}}\ncut\n移除value中所有的与给出的变量相同的字符串\n{{ value|cut:\u0026rsquo; \u0026rsquo; }}\n如果value为\u0026rsquo;i love you\u0026rsquo;，那么将输出\u0026rsquo;iloveyou\u0026rsquo;.\njoin\n使用字符串连接列表，例如Python的str.join(list)\ntimesince\n将日期格式设为自该日期起的时间（例如，“4天，6小时”）。\n采用一个可选参数，它是一个包含用作比较点的日期的变量（不带参数，比较点为现在）。 例如，如果blog_date是表示2006年6月1日午夜的日期实例，并且comment_date是2006年6月1日08:00的日期实例，则以下将返回“8小时”：\n{{ blog_date|timesince:comment_date }}\n分钟是所使用的最小单位，对于相对于比较点的未来的任何日期，将返回“0分钟”。\ntimeuntil\n似于timesince，除了它测量从现在开始直到给定日期或日期时间的时间。 例如，如果今天是2006年6月1日，而conference_date是保留2006年6月29日的日期实例，则{{ conference_date | timeuntil }}将返回“4周”。\n使用可选参数，它是一个包含用作比较点的日期（而不是现在）的变量。 如果from_date包含2006年6月22日，则以下内容将返回“1周”：\n{{ conference_date|timeuntil:from_date }}\n自定义filter\n自定义过滤器只是带有一个或两个参数的Python函数:\n变量（输入）的值 - -不一定是一个字符串\n参数的值 - 这可以有一个默认值，或完全省略\n例如，在过滤器{{var | foo:\u0026lsquo;bar\u0026rsquo;}}中，过滤器foo将传递变量var和参数“bar”。\n自定义filter代码文件摆放位置：\napp01/ __init__.py models.py templatetags/ # 在app01下面新建一个package package __init__.py app01_filters.py # 建一个存放自定义filter的文件 views.py 编写自定义filter\nfrom django import template register = template.Library() @register.filter(name=\u0026quot;cut\u0026quot;) def cut(value, arg): return value.replace(arg, \u0026quot;\u0026quot;) @register.filter(name=\u0026quot;addSB\u0026quot;) def add_sb(value): return \u0026quot;{} SB\u0026quot;.format(value) 使用自定义filter\n{# 先导入我们自定义filter那个文件 #} {% load app01_filters %} {# 使用我们自定义的filter #} {{ somevariable|cut:\u0026quot;0\u0026quot; }} {{ d.name|addSB }} Tags标签\nfor循环\n普通for循环\n\u0026lt;ul\u0026gt; {% for user in user_list %} \u0026lt;li\u0026gt;{{ user.name }}\u0026lt;/li\u0026gt; {% endfor %} \u0026lt;/ul\u0026gt; for循环可用的一些参数：\nVariable Description forloop.counter 当前循环的索引值（从1开始） forloop.counter0 当前循环的索引值（从0开始） forloop.revcounter 当前循环的倒序索引值（从1开始） forloop.revcounter0 当前循环的倒序索引值（从0开始） forloop.first 当前循环是不是第一次循环（布尔值） forloop.last 当前循环是不是最后一次循环（布尔值） forloop.parentloop 本层循环的外层循环 for \u0026hellip; empty\n\u0026lt;ul\u0026gt; {% for user in user_list %} \u0026lt;li\u0026gt;{{ user.name }}\u0026lt;/li\u0026gt; {% empty %} \u0026lt;li\u0026gt;空空如也\u0026lt;/li\u0026gt; {% endfor %} \u0026lt;/ul\u0026gt; if判断 if,elif和``else\n{% if user_list %} 用户人数：{{ user_list|length }} {% elif black_list %} 黑名单数：{{ black_list|length }} {% else %} 没有用户 {% endif %} 当然也可以只有if和else\n{% if user_list|length \u0026gt; 5 %} 七座豪华SUV {% else %} 黄包车 {% endif %} if语句支持 and 、or、==、\u0026gt;、\u0026lt;、!=、\u0026lt;=、\u0026gt;=、in、not in、is、is not判断。\nwith\n定义一个中间变量，多用于给一个复杂的变量起别名。\n注意等号左右不要加空格。\n{% with total=business.employees.count %} {{ total }} employee{{ total|pluralize }} {% endwith %} 或\n{% with business.employees.count as total %} {{ total }} employee{{ total|pluralize }} {% endwith %} csrf_token\n这个标签用于跨站请求伪造保护。\n在页面的form表单里面写上{% csrf_token %}\n注释\n{# \u0026hellip; #}\n注意事项\n1. Django的模板语言不支持连续判断，即不支持以下写法：\n{% if a \u0026gt; b \u0026gt; c %} ... {% endif %} 2. Django的模板语言中属性的优先级大于方法\ndef xx(request): d = {\u0026quot;a\u0026quot;: 1, \u0026quot;b\u0026quot;: 2, \u0026quot;c\u0026quot;: 3, \u0026quot;items\u0026quot;: \u0026quot;100\u0026quot;} return render(request, \u0026quot;xx.html\u0026quot;, {\u0026quot;data\u0026quot;: d}) 如上，我们在使用render方法渲染一个页面的时候，传的字典d有一个key是items并且还有默认的 d.items() 方法，此时在模板语言中:\n{{ data.items }}\n默认会取d的items key的值。\n母板\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; {% block page-css %} {% endblock %} \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;这是母板的标题\u0026lt;/h1\u0026gt; {% block page-main %} {% endblock %} \u0026lt;h1\u0026gt;母板底部内容\u0026lt;/h1\u0026gt; {% block page-js %} {% endblock %} \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 注意：我们通常会在母板中定义页面专用的CSS块和JS块，方便子页面替换。\n继承母板 在子页面中在页面最上方使用下面的语法来继承母板。\n{% extends 'layouts.html' %} 块（block） 通过在母板中使用{% block xxx %}来定义\u0026quot;块\u0026rdquo;。\n在子页面中通过定义母板中的block名来对应替换母板中相应的内容。\n{% block page-main %} \u0026lt;p\u0026gt;世情薄\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;人情恶\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;雨送黄昏花易落\u0026lt;/p\u0026gt; {% endblock %} 组件 可以将常用的页面内容如导航条，页尾信息等组件保存在单独的文件中，然后在需要使用的地方按如下语法导入即可。\n{% include 'navbar.html' %} 静态文件相关 {% static %} {% load static %} \u0026lt;img src=\u0026quot;{% static \u0026quot;images/hi.jpg\u0026quot; %}\u0026quot; alt=\u0026quot;Hi!\u0026quot; /\u0026gt; 引用JS文件时使用：\n{% load static %} \u0026lt;script src=\u0026quot;{% static \u0026quot;mytest.js\u0026quot; %}\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; 某个文件多处被用到可以存为一个变量\n{% load static %} {% static \u0026quot;images/hi.jpg\u0026quot; as myphoto %} \u0026lt;img src=\u0026quot;{{ myphoto }}\u0026quot;\u0026gt;\u0026lt;/img\u0026gt; {% get_static_prefix %} {% load static %} \u0026lt;img src=\u0026quot;{% get_static_prefix %}images/hi.jpg\u0026quot; alt=\u0026quot;Hi!\u0026quot; /\u0026gt; 或者\n{% load static %} {% get_static_prefix as STATIC_PREFIX %} \u0026lt;img src=\u0026quot;{{ STATIC_PREFIX }}images/hi.jpg\u0026quot; alt=\u0026quot;Hi!\u0026quot; /\u0026gt; \u0026lt;img src=\u0026quot;{{ STATIC_PREFIX }}images/hi2.jpg\u0026quot; alt=\u0026quot;Hello!\u0026quot; /\u0026gt; simple_tag 和自定义filter类似，只不过接收更灵活的参数。\n定义注册simple tag\n@register.simple_tag(name=\u0026quot;plus\u0026quot;) def plus(a, b, c): return \u0026quot;{} + {} + {}\u0026quot;.format(a, b, c) 使用自定义simple tag\n{% load app01_demo %} {# simple tag #} {% plus \u0026quot;1\u0026quot; \u0026quot;2\u0026quot; \u0026quot;abc\u0026quot; %} inclusion_tag 多用于返回html代码片段\n示例：\ntemplatetags/my_inclusion.py\nfrom django import template register = template.Library() @register.inclusion_tag('result.html') def show_results(n): n = 1 if n \u0026lt; 1 else int(n) data = [\u0026quot;第{}项\u0026quot;.format(i) for i in range(1, n+1)] return {\u0026quot;data\u0026quot;: data} templates/snippets/result.html\n\u0026lt;ul\u0026gt; {% for choice in data %} \u0026lt;li\u0026gt;{{ choice }}\u0026lt;/li\u0026gt; {% endfor %} \u0026lt;/ul\u0026gt; templates/index.html\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;inclusion_tag test\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; {% load inclusion_tag_test %} {% show_results 10 %} \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; ","permalink":"https://zhong-er.com/posts/django%E6%A8%A1%E6%9D%BF%E5%B1%82/","summary":"\u003cp\u003e\u003cstrong\u003e模版语法\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e两大类\u003c/p\u003e\n\u003cp\u003e{{}} 变量相关\u003c/p\u003e\n\u003cp\u003e{%%} 逻辑相关\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e给模版传值\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003epython所有的数据类型都可以被传递到前端页面\u003c/p\u003e\n\u003cp\u003e注意在传递函数的时候，会自动加括号调用，前端展示的是函数调用的之后的返回值(ps:前端在调用函数的时候，是不支持给函数传递额外参数的)\u003c/p\u003e\n\u003cp\u003e如果你想在前端获取后端传递的某个容器类型中的具体元素\u003c/p\u003e\n\u003cp\u003e那么你可以通过句点符来获取具体的元素\u003c/p\u003e\n\u003cp\u003e.索引\u003c/p\u003e\n\u003cp\u003e.键\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e变量\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e在Django的模板语言中按此语法使用：{{ 变量名 }}。\u003c/p\u003e\n\u003cp\u003e当模版引擎遇到一个变量，它将计算这个变量，然后用结果替换掉它本身。 变量的命名包括任何字母数字以及下划线 (\u0026quot;_\u0026quot;)的组合。 变量名称中不能有空格或标点符号。\u003c/p\u003e\n\u003cp\u003e点（.）在模板语言中有特殊的含义。当模版系统遇到点(\u0026quot;.\u0026quot;)，它将以这样的顺序查询：\u003c/p\u003e\n\u003cp\u003e字典查询（Dictionary lookup）\u003cbr\u003e\n属性或方法查询（Attribute or method lookup）\u003cbr\u003e\n数字索引查询（Numeric index lookup）\u003c/p\u003e\n\u003cp\u003e注意事项：\u003c/p\u003e\n\u003cp\u003e如果计算结果的值是可调用的，它将被无参数的调用。 调用的结果将成为模版的值。\u003c/p\u003e\n\u003cp\u003e如果使用的变量不存在， 模版系统将插入 string_if_invalid 选项的值， 它被默认设置为\u0026rsquo;\u0026rsquo; (空字符串) 。\u003c/p\u003e\n\u003cp\u003e几个例子：\u003c/p\u003e\n\u003cp\u003eview中代码：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef template_test(request):\n    l = [11, 22, 33]\n    d = {\u0026quot;name\u0026quot;: \u0026quot;alex\u0026quot;}\n\n    class Person(object):\n        def __init__(self, name, age):\n            self.name = name\n            self.age = age\n\n        def dream(self):\n            return \u0026quot;{} is dream...\u0026quot;.format(self.name)\n\n    Alex = Person(name=\u0026quot;Alex\u0026quot;, age=34)\n    Egon = Person(name=\u0026quot;Egon\u0026quot;, age=9000)\n    Eva_J = Person(name=\u0026quot;Eva_J\u0026quot;, age=18)\n\n    person_list = [Alex, Egon, Eva_J]\n    return render(request, \u0026quot;template_test.html\u0026quot;, {\u0026quot;l\u0026quot;: l, \u0026quot;d\u0026quot;: d, \u0026quot;person_list\u0026quot;: person_list})\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e模板中支持的写法：\u003c/p\u003e","title":"【Django】模板层"},{"content":"\n1.Django orm中表与表之间建关系\n数据库表与表之间建关系\n图书管理系统为例，书籍表，作者表，出版社表，作者详情表\n书籍和出版社是一对多 外键字段建在书籍表中\n书籍和作者是多对多 需要第三个表记录多对多关系\n作者和作者详情是一对一 记录在最常用的那张表\nDjango orm中\n一对多 ForeignKey(to=\u0026lsquo;Publish\u0026rsquo;)\n一对一 OneToOneField(to=\u0026lsquo;AuthorDetail\u0026rsquo;)\n多对多 ManyToManyField(to=\u0026lsquo;Author\u0026rsquo;)\n注意：前面两个关键字会自动在字段后面加id\n最后一个关键字 并不会产生实际字段 只是告诉django orm自动创建第三张表\n2.路由层\n2.1 URL配置\n基本格式【1.0版本】\nfrom django.conf.urls import url urlpatterns = [ url(正则表达式, views视图函数，参数，别名), ] 基本格式【2.0版本】\nfrom django.urls import path urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/\u0026lt;int:year\u0026gt;/', views.year_archive), path('articles/\u0026lt;int:year\u0026gt;/\u0026lt;int:month\u0026gt;/', views.month_archive), path('articles/\u0026lt;int:year\u0026gt;/\u0026lt;int:month\u0026gt;/\u0026lt;slug:slug\u0026gt;/', views.article_detail), ] 2.0版本已经不能使用正则表达式，只能精准匹配\n1.0版本参数说明\n正则表达式：一个正则表达式字符串\nviews视图函数：一个可调用对象，通常为一个视图函数或一个指定视图函数路径的字符串\n参数：可选的要传递给视图函数的默认参数（字典形式）\n别名：一个可选的name参数\nDjango版本区别\ndjango1.X跟django2.X版本区别\n1.路由层1.X用的是url，而2.X用的是path\n2.路由层2.X中的path第一个参数不再是正则表达式,而是写什么就匹配什么 是精准匹配,\n3.当你使用2.X不习惯的时候,2.X还有一个叫re_path，2.x中的re_path就是你1.X的url\n4.虽然2.X中path不支持正则表达式 但是它提供了五种默认的转换器\n\u0026quot;\u0026quot;\u0026quot; 1.0版本的url和2.0版本的re_path分组出来的数据都是字符串类型 默认有五个转换器，感兴趣的自己可以课下去试一下 str,匹配除了路径分隔符（/）之外的非空字符串，这是默认的形式 int,匹配正整数，包含0。 slug,匹配字母、数字以及横杠、下划线组成的字符串。 uuid,匹配格式化的uuid，如 075194d3-6885-417e-a8a8-6c931e272f00。 path,匹配任何非空字符串，包含了路径分隔符（/）（不能用？） \u0026quot;\u0026quot;\u0026quot; 5.版本2.X中还支持自定义转换器**\n**\nclass FourDigitYearConverter: regex = '[0-9]{4}' def to_python(self, value): return int(value) def to_url(self, value): return '%04d' % value 占四位，不够用0填满，超了则就按超了的位数来！ register_converter(FourDigitYearConverter, 'yyyy') urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/\u0026lt;yyyy:year\u0026gt;/', views.year_archive), ... ] 2.2正则表达式详解\n基本配置\nfrom django.conf.urls import urlfrom . import views urlpatterns = [ url(r'^articles/2003/$', views.special_case_2003), url(r'^articles/([0-9]{4})/$', views.year_archive), url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), ] 注意事项\n1.urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式，一旦匹配成功则不再继续。\n2.若要从URL中捕获一个值，只需要在它周围放置一对圆括号（分组匹配）。\n3.不需要添加一个前导的反斜杠，因为每个URL 都有。例如，应该是^articles 而不是 ^/articles。\n4.每个正则表达式前面的\u0026rsquo;r\u0026rsquo; 是可选的但是建议加上。\n补充说明\n是否开启URL访问地址后面不为/跳转至带有/的路径的配置项 APPEND_SLASH=True Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数，但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加\u0026rsquo;/'\n其效果就是：django会先拿着你没有敲斜杠的结果取匹配，如果都没有匹配上，会让浏览器在末尾加斜杠再发一次请求，再匹配一次 如果还匹配不上才会报错\n2.3分组命名匹配\n无名分组\nurl(r'^test/([0-9]{4})/', views.test) # 路由匹配的时候 会将括号内正则表达式匹配到的内容 当做位置参数传递给视图函数 test(request,2019) 有名分组\nurl(r'^test/(?P\u0026lt;year\u0026gt;\\d+)/', views.test) # 路由匹配的时候 会将括号内正则表达式匹配到的内容 当做关键字参数传递给视图函数 test(request,year=2019) # 无名有名不能混合使用 !!! url(r'^test/(\\d+)/(?P\u0026lt;year\u0026gt;\\d+)/', views.test) # 但是用一种分组下 可以使用多个 ## 无名分组支持多个 url(r'^test/(\\d+)/(\\d+)/', views.test), ## 有名分组支持多个 url(r'^test/(?P\u0026lt;year\u0026gt;\\d+)/(?P\u0026lt;xx\u0026gt;\\d+)/', views.test), 捕获的参数永远都是字符串\n每个在URLconf中捕获的参数都作为一个普通的Python字符串传递给视图，无论正则表达式使用的是什么匹配方式。例如，下面这行URLconf 中：\nurl(r'^articles/(?P\u0026lt;year\u0026gt;[0-9]{4})/$', views.year_archive), 传递到视图函数views.year_archive() 中的year 参数永远是一个字符串类型。\n视图函数中指定默认值\n# urls.py中 from django.conf.urls import url from . import views urlpatterns = [ url(r'^blog/$', views.page), url(r'^blog/page(?P\u0026lt;num\u0026gt;[0-9]+)/$', views.page), ] # views.py中，可以为num指定默认值 def page(request, num=\u0026quot;1\u0026quot;): pass 在上面的例子中，两个URL模式指向相同的view - views.page - 但是第一个模式并没有从URL中捕获任何东西。\n如果第一个模式匹配上了，page()函数将使用其默认参数num=“1”,如果第二个模式匹配，page()将使用正则表达式捕获到的num值。\n2.3反向解析\nDjango 提供一个办法是让URL 映射是URL 设计唯一的地方。你填充你的URLconf，然后可以双向使用它：\n1.根据用户/浏览器发起的URL 请求，它调用正确的Django 视图，并从URL 中提取它的参数需要的值。\n2.根据Django 视图的标识和将要传递给它的参数的值，获取与之关联的URL。\n第一种方式是我们在前面的章节中一直讨论的用法。\n第二种方式叫做反向解析URL、反向URL 匹配、反向URL 查询或者简单的URL 反查。\n在需要URL 的地方，对于不同层级，Django 提供不同的工具用于URL 反查：\n1.在模板中：使用url模板标签。\n2.在Python 代码中：使用django.core.urlresolvers.reverse() 函数。\n3.在更高层的与处理Django 模型实例相关的代码中：使用get_absolute_url() 方法。\n本质:其实就是给你返回一个能够返回对应url的地址\n1.先给url和视图函数对应关系起别名\nurl(r'^index/$',views.index,name='kkk') 2.反向解析\n后端反向解析\n后端可以在任意位置通过reverse反向解析出对应的url\nfrom django.shortcuts import render,HttpResponse,redirect,reverse reverse('kkk') 前端反向解析\n{% url 'kkk' %} 无名分组反向解析\nurl(r'^index/(\\d+)/$',views.index,name='kkk') 后端反向解析\nreverse('kkk',args=(1,)) # 后面的数字通常都是数据的id值 前端反向解析\n{% url 'kkk' 1%} # 后面的数字通常都是数据的id值 有名分组反向解析\n同无名分组反向解析意义的用法\nurl(r'^index/(?P\u0026lt;year\u0026gt;\\d+)/$',views.index,name='kkk') 后端反向解析\nprint(reverse('kkk',args=(1,))) # 推荐你使用上面这种 减少你的脑容量消耗 print(reverse('kkk',kwargs={'year':1})) 前端反向解析\n\u0026lt;a href=\u0026quot;{% url 'kkk' 1 %}\u0026quot;\u0026gt;1\u0026lt;/a\u0026gt; # 推荐你使用上面这种 减少你的脑容量消耗 \u0026lt;a href=\u0026quot;{% url 'kkk' year=1 %}\u0026quot;\u0026gt;1\u0026lt;/a\u0026gt; 注意:在同一个应用下 别名千万不能重复!!!\n2.4路由分发\n当你的django项目特别庞大的时候 路由与视图函数对应关系特别特别多**\n**\n那么你的总路由urls.py代码太过冗长 不易维护\n每一个应用都可以有自己的urls.py,static文件夹,templates文件夹(******)\n正是基于上述条件 可以实现多人分组开发 等多人开发完成之后 我们只需要创建一个空的django项目\n然后将多人开发的app全部注册进来 在总路由实现一个路由分发 而不再做路由匹配(来了之后 我只给你分发到对应的app中)\n当你的应用下的视图函数特别特别多的时候 你可以建一个views文件夹 里面根据功能的细分再建不同的py文件(******)\nurlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^app01/',include('app01.urls')), url(r'^app02/',include('app02.urls')), ] 2.5名称空间(了解)\n多个app起了相同的别名 这个时候用反向解析 并不会自动识别应用前缀\n如果想避免这种问题的发生\n方式1:\n总路由\nurl(r'^app01/',include('app01.urls',namespace='app01')) url(r'^app02/',include('app02.urls',namespace='app02')) 后端解析的时候\nreverse('app01:index') reverse('app02:index') 前端解析的时候\n{% url 'app01:index' %} {% url 'app02:index' %} 方式2:\n起别名的时候不要冲突即可 一般情况下在起别名的时候通常建议以应用名作为前缀\nname = 'app01_index' name = 'app02_index' 2.6伪静态\n静态网页:数据是写死的 万年不变\n伪静态网页的设计是为了增加百度等搜索引擎seo查询力度\n所有的搜索引擎其实都是一个巨大的爬虫程序\n网站优化相关 通过伪静态确实可以提高你的网站被查询出来的概率\n但是再怎么优化也抵不过RMB玩家\n2.7虚拟环境\n一般情况下 我们会给每一个项目 配备该项目所需要的模块 不需要的一概不装\n虚拟环境 就类似于为每个项目量身定做的解释器环境\n如何创建虚拟环境\n每创建一个虚拟环境 就类似于你又下载了一个全新的python解释器\n3.视图层\n如果一个应用下的视图函数特别特别多，你可以新建一个views文件夹,然后根据功能的不同起不同名字的py文件存放不同的功能\n实现持续的解耦、可读性。\n3.1小白必会三板斧\n1.HttpResponse\n2.render\n3.redirect\ndjango视图函数必须要给返回一个HttpResponse对象\n前后端分离\n前端一个人干(前端转成自定义对象)\nJSON.stringify() json.dumps()\nJSON.parse() json.loads()\n后端另一个干(python后端用字典)\n只要涉及到数据交互,一般情况下都是用的json格式\n后端只负责产生接口,前端调用该接口能拿到一个大字典\n后端只需要写一个接口文档 里面描述字典的详细信息以及参数的传递\n3.2JsonReponse\nfrom django.http import JsonResponse def index(request): data = {'name':'jason好帅哦 我好喜欢','password':123} l = [1,2,3,4,5,6,7,8] # res = json.dumps(data,ensure_ascii=False) # return HttpResponse(res) # return JsonResponse(data,json_dumps_params={'ensure_ascii':False}) return JsonResponse(l,safe=False) # 如果返回的不是字典 只需要修改safe参数为false即可 3.3上传文件\nform表单上传文件需要注意的事项\n1.enctype需要由默认的urlencoded变成formdata\n2.method需要由默认的get变成post\n3.目前还需要考虑的是 提交post请求需要将配置文件中的csrf中间件注释\n如果form表单上传文件 后端需要从request.FILES中获取文件数据 而不再是POST里面\nfile_obj = request.FILES.get('myfile') file_obj.name # 文件名 with open(file_obj.name,'wb') as f: for chunk in file_obj.chunks(): f.write(chunk) request的一些方法\nrequest.method request.GET request.POST request.FILES request.body # 原生的二进制数据 request.path # 只回去url后缀 不获取?后面的参数 request.get_full_path() # 后缀和参数全部获取 \u0026quot;\u0026quot;\u0026quot; RBAC (role based access control) 基于角色的权限管理 当你在做权限管理的时候 需要用到 在web领域权限就是一个个的url 简单判断用户是否有权限访问某个url思路 获取用户想要访问的url 与数据库中该用户可以访问的url进行对比 \u0026quot;\u0026quot;\u0026quot; 3.4FBV与CBV\n视图函数并不只是指函数 也可以是类\nFBV(基于函数的视图) 面向函数式编程\nCBV(基于类的视图) 面向对象式编程\n问题 :\n基于CBV的视图函数get请求来就会走类里面get方法,post请求来就会走类里面post方法 为什么???\nurls.py中\nurl(r'^login/',views.MyLogin.as_view()) views.py中\nfrom django.views import View class MyLogin(View): def get(self,request): print(\u0026quot;from MyLogin get方法\u0026quot;) return render(request,'login.html') def post(self,request): return HttpResponse(\u0026quot;from MyLogin post方法\u0026quot;) 研究方向\n1.从url入手\nurl(r'^login/',views.MyLogin.as_view()) # 由于函数名加括号执行优先级最高,所以这一句话一写完会立刻执行as_view()方法 @classonlymethod def as_view(cls, **initkwargs): # cls就是我们自己的写的类 MyLogin def view(request, *args, **kwargs): self = cls(**initkwargs) # 实例化产生MyLogin的对象 self = MyLogin(**ininkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs # 上面的几句话都仅仅是在给对象新增属性 return self.dispatch(request, *args, **kwargs) # dispatch返回什么 浏览器就会收到什么 # 对象在查找属性或者方法的时候 你一定要默念 先从对象自己这里找 然后从产生对象的类里面找 最后类的父类依次往后 return view 通过源码发现url匹配关系可以变形成\nurl(r'^login/',views.view) # FBV和CBV在路由匹配上是一致的 都是url后面跟函数的内存地址 2.当浏览器中输入login 会立刻触发view函数的运行\ndef dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. # 我们先以GET为例 if request.method.lower() in self.http_method_names: # 判断当前请求方法是否在默认的八个方法内 # 反射获取我们自己写的类产生的对象的属性或者方法 # 以GET为例 handler = getattr(self,'get','取不到报错的信息') # handler = get(request) handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) # 直接调用我们自己的写类里面的get方法 # 源码中先通过判断请求方式是否符合默认的八个请求方法 然后通过反射获取到自定义类中的对应的方法执行 django settings源码\n前提:\n1.django除了暴露给用户一个settings.py配置文件之外 自己内部还有一个全局的配置文件\n2.我们在使用配置文件的时候 可以直接直接导入暴露给用户的settings.py也可以使用django全局的配置文件 并且后者居多\nfrom django.conf import settings\n3.django的启动入口是manage.py\nimport os import sys if __name__ == \u0026quot;__main__\u0026quot;: # django在启动的时候 就会往全局的大字典中设置一个键值对 值是暴露给用户的配置文件的路径字符串 os.environ.setdefault(\u0026quot;DJANGO_SETTINGS_MODULE\u0026quot;, \u0026quot;day54.settings\u0026quot;) class Settings(object): def __init__(self, settings_module): # settings_module = 'day54.settings' # update this dict from global settings (but only for ALL_CAPS settings) for setting in dir(global_settings): # django全局配置文件 # dir获取django全局配置文件中所有的变量名 if setting.isupper(): # 判断文件中的变量名是否是大写 如果是大写才会执行/生效 setattr(self, setting, getattr(global_settings, setting)) # 给settings对象设置键值对 # 给settings对象设置键值对 settings[配置文件中大写的变量名] = 配置文件中大写的变量名所对应的值 # store the settings module in case someone later cares self.SETTINGS_MODULE = settings_module # 'day54.settings' mod = importlib.import_module(self.SETTINGS_MODULE) # mod = 模块settings(暴露给用户的配置文件) for setting in dir(mod): # for循环获取暴露给用户的配置文件中所有的变量名 if setting.isupper(): # 判断变量名是否是大写 setting_value = getattr(mod, setting) # 获取大写的变量名所对应的值 setattr(self, setting, setting_value) # 给settings对象设置键值对 \u0026quot;\u0026quot;\u0026quot; d = {} d['username'] = 'jason' d['username'] = 'egon' 用户如果配置了就用用户的 用户如果没有配置就用系统默认的 其实本质就是利用字典的键存在就是替换的原理 实现了用户配置就用用户的用户没配置就用默认的 \u0026quot;\u0026quot;\u0026quot; class LazySettings(LazyObject): def _setup(self, name=None): # os.environ你可以把它看成是一个全局的大字典 settings_module = os.environ.get(ENVIRONMENT_VARIABLE) # 从大字典中取值键为DJANGO_SETTINGS_MODULE所对应的值:day54.settings # settings_module = 'day54.settings' self._wrapped = Settings(settings_module) # Settings('day54.settings') settings = LazySettings() # 单例模式 ","permalink":"https://zhong-er.com/posts/django%E8%B7%AF%E7%94%B1%E5%B1%82%E8%A7%86%E5%9B%BE%E5%B1%82/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cimg loading=\"lazy\" src=\"/assets/201909169981_9838.jpg\"\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e1.Django orm中表与表之间建关系\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e数据库表与表之间建关系\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e图书管理系统为例，书籍表，作者表，出版社表，作者详情表\u003c/p\u003e\n\u003cp\u003e书籍和出版社是一对多 外键字段建在书籍表中\u003c/p\u003e\n\u003cp\u003e书籍和作者是多对多 需要第三个表记录多对多关系\u003c/p\u003e\n\u003cp\u003e作者和作者详情是一对一 记录在最常用的那张表\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDjango orm中\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e一对多 ForeignKey(to=\u0026lsquo;Publish\u0026rsquo;)\u003c/p\u003e\n\u003cp\u003e一对一 OneToOneField(to=\u0026lsquo;AuthorDetail\u0026rsquo;)\u003c/p\u003e\n\u003cp\u003e多对多 ManyToManyField(to=\u0026lsquo;Author\u0026rsquo;)\u003c/p\u003e\n\u003cp\u003e注意：前面两个关键字会自动在字段后面加id\u003c/p\u003e\n\u003cp\u003e最后一个关键字 并不会产生实际字段 只是告诉django orm自动创建第三张表\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2.路由层\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2.1 URL配置\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e基本格式【1.0版本】\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom django.conf.urls import url\n\nurlpatterns = [\n     url(正则表达式, views视图函数，参数，别名),\n]\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e基本格式【2.0版本】\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom django.urls import path\n\nurlpatterns = [\n    path('articles/2003/', views.special_case_2003),\n    path('articles/\u0026lt;int:year\u0026gt;/', views.year_archive),\n    path('articles/\u0026lt;int:year\u0026gt;/\u0026lt;int:month\u0026gt;/', views.month_archive),\n    path('articles/\u0026lt;int:year\u0026gt;/\u0026lt;int:month\u0026gt;/\u0026lt;slug:slug\u0026gt;/', views.article_detail),\n]\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2.0版本已经不能使用正则表达式，只能精准匹配\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e1.0版本参数说明\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e正则表达式：一个正则表达式字符串\u003c/p\u003e\n\u003cp\u003eviews视图函数：一个可调用对象，通常为一个视图函数或一个指定视图函数路径的字符串\u003c/p\u003e\n\u003cp\u003e参数：可选的要传递给视图函数的默认参数（字典形式）\u003c/p\u003e\n\u003cp\u003e别名：一个可选的name参数\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDjango版本区别\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003edjango1.X跟django2.X版本区别\u003c/p\u003e\n\u003cp\u003e1.路由层1.X用的是url，而2.X用的是path\u003c/p\u003e\n\u003cp\u003e2.路由层2.X中的path第一个参数不再是正则表达式,而是写什么就匹配什么 是精准匹配,\u003c/p\u003e\n\u003cp\u003e3.当你使用2.X不习惯的时候,2.X还有一个叫re_path，2.x中的re_path就是你1.X的url\u003c/p\u003e\n\u003cp\u003e4.虽然2.X中path不支持正则表达式 但是它提供了五种默认的转换器\u003c/p\u003e","title":"【Django】路由层、视图层"},{"content":"Django简易版\n1. 纯手撸一个简易版的web框架\n1.1 手动处理socket请求\n1.2 手动处理http请求\n1.3 根据用户访问url的不同，响应不同的结果\n2. wsgireg模块(web服务端网关接口)\n2.1 封装socket代码\n2.2 拆分请求发来时的http格式的数据为一个大字典\n2.3 打包数据为响应发回时的http格式的数据\n2.4 启动\nserver = make_server('127.0.0.1',8080,run) # 实时监测，一旦有客户端接入，自动加括号调用run方法 server.server_forever() # 启动服务端 3. 如何给html页面传递数据\n3.1 字符串替换\n3.2 jinja2模块\n1. 模版的渲染(传数据)\n2. 模版语法(极其贴近python语法)\nfrom jinja2 import Template # 将文本格式的html数据传入Template render() # 模板语法 {{ data }} {% for user in user_list %} {% endfor %} # 模板语法 只有两种情况 {{}}:变量相关 {%%}:逻辑相关 4.对不同的功能拆封成不同的文件\ntemplates文件夹(专门放html文件)\nurls.py\nviews.py\nDjango总览版\n1.创建Django项目\ndjango-admin startproject 项目名\n跟项目同名的文件夹\ninit.py\nsettings.py(暴露给用户可配置的配置文件)\nurls.py(路由系统，本质是URL与要为该URL调用的视图函数之间的映射表)\nwsgi.py\nmanage.py\n2.创建app\ndjango-admin startapp app名\npython3 manage.py startapp app名\napp名的文件夹\ninit.py\nmigrations文件夹(数据库操作记录)\napps.py(注册app相关文件)\nadmin.py(django后台管理相关)\nmodels.py(orm模型类相关)\nviews.py(视图函数，接受Web请求并且返回Web响应)\ntests.py(测试相关)\n3.启动django项目\npython3 manage.py runserver # django默认是在8000端口启动项目\n创建tempelates文件夹(专门放html文件)\n注意:用命令行创建项目 不会自动创建templates文件夹，\n也就意味着需要你手动创建并且需要在配置文件中将该路径添加上\n创建的app也需要在settings文件中注册\napp概念\ndjango项目就类似于是一所大学,app就是大学里面不同的学院，每个学院都有自己独立的功能\ndjango小白必会三板斧\nHttpResponse 返回字符串\nrender 返回html页面\nredirect 重定向\n以登录功能为例走一遍Django流程\n静态文件配置\nform表单提交数据后端如何获取\nrequest方法\npycharm连接数据库 django使用mysql数据库\ndjango orm简介\n表字段的增删改查\n表数据的增删改查\n1.新创建的app一定要先去settings.py注册\nINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 'app01' # 简写 'app01.apps.App01Config' # 全称 ] 2.html文件默认全都放在templates文件夹下\n对于前段已经写好了的文件 我们只是拿过来使用 那么这些文件都可以称之为叫\u0026quot;静态文件\u0026quot;\n静态文件可以是\nbootstrap一类的前段框架 已经写好了的\n图片\ncss\njs\n静态文件默认全都放在static文件夹下\nstatic文件夹中默认会默认创建的子文件夹\ncss文件夹 当前网站所有的样式文件\njs文件 当前网站所有的js文件\nimg文件 当前网站所有的图片文件\n其他(前端框架代码 第三方插件代码\u0026hellip;)\n3.用户访问的资源都在URL中\n只有url中开设相关的资源你才能访问到(******)\n后端资源一般都需要手动指定是否需要暴露给用户\n静态文件配置\nSTATICFILES_DIRS = [ os.path.join(BASE_DIR,'static') ] 你只要输入static文件夹内具体文件的路径就能够访问到\nSTATIC_URL = '/static/' # 这个static不是文件夹的名字 而是接口前缀 \u0026quot;\u0026quot;\u0026quot;只要你想访问静态文件中的资源 文件路径就必须用static开头\u0026quot;\u0026quot;\u0026quot; # 手动将static文件夹中所有的资源暴露给用户 STATICFILES_DIRS = [ os.path.join(BASE_DIR,'static'), # 真正的文件夹路径 os.path.join(BASE_DIR,'static1'), # 真正的文件夹路径 os.path.join(BASE_DIR,'static2'), # 真正的文件夹路径 os.path.join(BASE_DIR,'static3') # 真正的文件夹路径 ] 静态文件接口前缀\u0026quot;动态解析\u0026quot;\n{% load static %} \u0026lt;link rel=\u0026quot;stylesheet\u0026quot; href=\u0026quot;{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}\u0026quot;\u0026gt; \u0026lt;script src=\u0026quot;{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; 利用解析器 动态获取接口前缀\n4.form表单默认是get请求\n携带数据的方式是URL问号后跟数据\nhttp://127.0.0.1:8000/login/?username=zekai\u0026amp;password=123\n可以通过method改为post请求\n改成post请求之后 需要去settings文件中注释掉一个中间件\nMIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] form表单提交数据目的地由action\n1.不写的情况下 默认往当前地址提交\n2.还可以写后缀/index/(将项目常用这种)\n3.还可以写全路径\n视图函数一般主要会先处理get请求\ndef login(request): # 视图函数针对不同的请求方式 应该有不同的处理逻辑 # if request.method == 'GET': # print('收到了') # print(request.method) # 能够获取前端请求方式 并且是全大写的字符串 # print(type(request.method)) # return render(request,'login.html') # elif request.method == 'POST': # # 获取用户输入 做相应的逻辑判断 # return HttpResponse(\u0026quot;拿到了 老弟\u0026quot;) if request.method == 'POST': return HttpResponse('来啦 宝贝') return render(request,'login.html') 5.获取前端数据\nrequest.method获取请求方法\n对数据的处理 不单单只有wsgiref模块 django后端也进行了大量的数据处理\nGET\nrequest.GET获取前端get提交的数据(就类似于是一个大字典)\n取值\nrequest.GET.get(\u0026lsquo;username\u0026rsquo;) # 虽然value是一个列表 但是默认只取列表最后一个元素\n强烈不建议你使用中括号的形式取值 如果想直接把列表全部取出(******) request.GET.getlist(\u0026lsquo;hobby\u0026rsquo;)\nPOST\nrequest.POST获取前端post提交的数据(就类似于是一个大字典)\n取值\nrequest.POST.get(\u0026lsquo;username\u0026rsquo;) # 虽然value是一个列表 但是默认只取列表最后一个元素\n强烈不建议你使用中括号的形式取值 如果想直接把列表全部取出(******) request.POST.getlist(\u0026lsquo;hobby\u0026rsquo;)\n6.Django默认使用自带sqlite数据库\n如果你想让它其他的数据库 需要在settings配置文件中 配置\n1.settings文件中配置\nDATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'test', 'HOST':'127.0.0.1', 'PORT':3306, 'USER':'root', 'PASSWORD':'123', 'CHARSET':'utf8' } } 2.还要在项目名下的init文件或者是应用名下的init文件中告诉django不要使用默认的mysqldb连接mysql\n而是使用pymysql\nimport pymysql\npymysql.install_as_MySQLdb()\n7.Django ORM\nORM对象关系映射\n表 类\n一条条记录对象\n字段对应的值对象的属性\n首先需要在应用下的models.py中书写模型类\nclass User(models.Model): # 将id字段设置为User表主键字段 在django orm中 你可以不写主键字典 django会默认给你的表创建一个名为id的主键字段 # id = models.AutoField(primary_key=True) # 一旦你自己指定了主键字段 那么django就不会自动再帮你创建了 username = models.CharField(max_length=32) # username varchar(32) CharField必须要指定max_length参数 password = models.IntegerField() # password int 需要执行数据库迁移(同步)命令\npython3 manage.py makemigrations # 仅仅是在小本本上(migrations文件夹)记录数据库的修改 并不会直接操作数据 python3 manage.py migrate # 将数据库修改记录 真正同步到数据库 注意:只要动了models中跟数据库相关的代码 就必须重新执行上面的两条命令 缺一不可(******)\n8.表字段的增删改查\n增\n# 当一张表已经创建出来之后 后续还想添加字段,可以有两种方式 # 1.给新增的字段设置默认值 addr = models.CharField(max_length=32,default='China') # default该字段默认值 # 2.给新增的字段设置成可以为空 age = models.IntegerField(null=True) # 该字段允许为空 删(慎用)\n\u0026quot;\u0026quot;\u0026quot; 删除字段 直接在models.py中注释该字段 然后重新执行两条命令即可 注意:执行完之后 表中该字段所对应的所有的数据全部删除 并且一般情况下 基本是不会用到真正意义上的删除 \u0026quot;\u0026quot;\u0026quot; orm操作需要使用models中的类的名字\n9.数据的增删改查\n数据的查\nfrom app01 import models models.User.objects.all() # 直接拿所有的数据 models.User.objects.get(username=username) res = models.User.objects.filter(username=username) res.query user_obj = res.first() 数据的增\n# 1. models.User.objects.create(username=username,password=password) # 2. user_obj = models.User(username=username,password=password) user_obj.save() 删\nmodels.User.objects.filter(条件).delete() 改\nmodels.User.objects.filter(条件).update() ","permalink":"https://zhong-er.com/posts/django%E6%80%BB%E8%A7%88/","summary":"\u003cp\u003e\u003cstrong\u003eDjango简易版\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1. 纯手撸一个简易版的web框架\u003c/p\u003e\n\u003cp\u003e1.1 手动处理socket请求\u003c/p\u003e\n\u003cp\u003e1.2 手动处理http请求\u003c/p\u003e\n\u003cp\u003e1.3 根据用户访问url的不同，响应不同的结果\u003c/p\u003e\n\u003cp\u003e2. wsgireg模块(web服务端网关接口)\u003c/p\u003e\n\u003cp\u003e2.1 封装socket代码\u003c/p\u003e\n\u003cp\u003e2.2 拆分请求发来时的http格式的数据为一个大字典\u003c/p\u003e\n\u003cp\u003e2.3 打包数据为响应发回时的http格式的数据\u003c/p\u003e\n\u003cp\u003e2.4 启动\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eserver = make_server('127.0.0.1',8080,run)  # 实时监测，一旦有客户端接入，自动加括号调用run方法\nserver.server_forever()  # 启动服务端\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e3. 如何给html页面传递数据\u003c/p\u003e\n\u003cp\u003e3.1 字符串替换\u003c/p\u003e\n\u003cp\u003e3.2 jinja2模块\u003c/p\u003e\n\u003cp\u003e1. 模版的渲染(传数据)\u003c/p\u003e\n\u003cp\u003e2. 模版语法(极其贴近python语法)\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom  jinja2 import Template\n# 将文本格式的html数据传入Template\nrender()\n\n# 模板语法\n{{ data }}\n{% for user in user_list %}\n{% endfor %}\n\n# 模板语法 只有两种情况 \n{{}}:变量相关\n{%%}:逻辑相关\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e4.对不同的功能拆封成不同的文件\u003c/p\u003e\n\u003cp\u003etemplates文件夹(专门放html文件)\u003c/p\u003e\n\u003cp\u003eurls.py\u003c/p\u003e\n\u003cp\u003eviews.py\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDjango总览版\u003c/strong\u003e\u003c/p\u003e","title":"【Django】总览"},{"content":"\nWeb框架本质\n我们可以这样理解：所有的Web应用本质上就是一个socket服务端，而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。\n半成品自定义web框架\nimport socket sk = socket.socket() sk.bind((\u0026quot;127.0.0.1\u0026quot;, 80)) sk.listen() while True: conn, addr = sk.accept() data = conn.recv(8096) conn.send(b\u0026quot;OK\u0026quot;) conn.close() 可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。\n用户的浏览器一输入网址，会给服务端发送数据，那浏览器会发送什么数据？怎么发？这个谁来定？ 你这个网站是这个规定，他那个网站按照他那个规定，这互联网还能玩么？\n所以，必须有一个统一的规则，让大家发送消息、接收消息的时候有个格式依据，不能随便写。\n这个规则就是HTTP协议，以后浏览器发送请求信息也好，服务器回复响应信息也罢，都要按照这个规则来。\nHTTP协议主要规定了客户端和服务器之间的通信格式，那HTTP协议是怎么规定消息格式的呢？\n让我们首先打印下我们在服务端接收到的消息是什么。\nimport socket sk = socket.socket() sk.bind((\u0026quot;127.0.0.1\u0026quot;, 80)) sk.listen() while True: conn, addr = sk.accept() data = conn.recv(8096) print(data) # 将浏览器发来的消息打印出来 conn.send(b\u0026quot;OK\u0026quot;) conn.close() 输出：\nb'GET / HTTP/1.1\\r\\nHost: 127.0.0.1:8080\\r\\nConnection: keep-alive\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\\r\\nDNT: 1\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: zh-CN,zh;q=0.9\\r\\nCookie: csrftoken=RKBXh1d3M97iz03Rpbojx1bR6mhHudhyX5PszUxxG3bOEwh1lxFpGOgWN93ZH3zv\\r\\n\\r\\n' 然后我们再看一下我们访问博客园官网时浏览器收到的响应信息是什么。\n响应相关信息可以在浏览器调试窗口的network标签页中看到。\n点击view source之后显示如下图：\n我们发现收发的消息需要按照一定的格式来，这里就需要了解一下HTTP协议了。\nHTTP协议介绍\nHTTP协议对收发消息的格式要求\n每个HTTP请求和响应都遵循相同的格式，一个HTTP包含Header和Body两部分，其中Body是可选的。 HTTP响应的Header中有一个 Content-Type表明响应的内容格式。如 text/html表示HTML网页。\nHTTP GET请求的格式：\nHTTP 响应的格式：\n处女版自定义web框架\n经过上面的补充学习，我们知道了要想让我们自己写的web server端正经起来，必须要让我们的Web server在给客户端回复消息的时候按照HTTP协议的规则加上响应状态行，这样我们就实现了一个正经的Web框架了。\nimport socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('127.0.0.1', 8000)) sock.listen()while True: conn, addr = sock.accept() data = conn.recv(8096) # 给回复的消息加上响应状态行 conn.send(b\u0026quot;HTTP/1.1 200 OK\\r\\n\\r\\n\u0026quot;) conn.send(b\u0026quot;OK\u0026quot;) conn.close() 我们通过十几行代码简单地演示了web 框架的本质。\n接下来就让我们继续完善我们的自定义web框架吧！\n根据不同的路径返回不同的内容\n这样就结束了吗？ 如何让我们的Web服务根据用户请求的URL不同而返回不同的内容呢？\n小事一桩，我们可以从请求相关数据里面拿到请求URL的路径，然后拿路径做一个判断\u0026hellip;\n\u0026quot;\u0026quot;\u0026quot;根据URL中不同的路径返回不同的内容\u0026quot;\u0026quot;\u0026quot;import socket sk = socket.socket() sk.bind((\u0026quot;127.0.0.1\u0026quot;, 8080)) # 绑定IP和端口sk.listen() # 监听while 1: # 等待连接 conn, add = sk.accept() data = conn.recv(8096) # 接收客户端发来的消息 # 从data中取到路径 data = str(data, encoding=\u0026quot;utf8\u0026quot;) # 把收到的字节类型的数据转换成字符串 # 按\\r\\n分割 data1 = data.split(\u0026quot;\\r\\n\u0026quot;)[0] url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径 conn.send(b'HTTP/1.1 200 OK\\r\\n\\r\\n') # 因为要遵循HTTP协议，所以回复的消息也要加状态行 # 根据不同的路径返回不同内容 if url == \u0026quot;/index/\u0026quot;: response = b\u0026quot;index\u0026quot; elif url == \u0026quot;/home/\u0026quot;: response = b\u0026quot;home\u0026quot; else: response = b\u0026quot;404 not found!\u0026quot; conn.send(response) conn.close() 根据不同的路径返回不同的内容\u0026ndash;函数版\n上面的代码解决了不同URL路径返回不同内容的需求。\n但是问题又来了，如果有很多很多路径要判断怎么办？难道要挨个写if判断？ 当然不用，我们有更聪明的办法。\n\u0026quot;\u0026quot;\u0026quot;根据URL中不同的路径返回不同的内容--函数版\u0026quot;\u0026quot;\u0026quot;import socket sk = socket.socket() sk.bind((\u0026quot;127.0.0.1\u0026quot;, 8080)) # 绑定IP和端口sk.listen() # 监听# 将返回不同的内容部分封装成函数def index(url): s = \u0026quot;这是{}页面！\u0026quot;.format(url) return bytes(s, encoding=\u0026quot;utf8\u0026quot;)def home(url): s = \u0026quot;这是{}页面！\u0026quot;.format(url) return bytes(s, encoding=\u0026quot;utf8\u0026quot;)while 1: # 等待连接 conn, add = sk.accept() data = conn.recv(8096) # 接收客户端发来的消息 # 从data中取到路径 data = str(data, encoding=\u0026quot;utf8\u0026quot;) # 把收到的字节类型的数据转换成字符串 # 按\\r\\n分割 data1 = data.split(\u0026quot;\\r\\n\u0026quot;)[0] url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径 conn.send(b'HTTP/1.1 200 OK\\r\\n\\r\\n') # 因为要遵循HTTP协议，所以回复的消息也要加状态行 # 根据不同的路径返回不同内容，response是具体的响应体 if url == \u0026quot;/index/\u0026quot;: response = index(url) elif url == \u0026quot;/home/\u0026quot;: response = home(url) else: response = b\u0026quot;404 not found!\u0026quot; conn.send(response) conn.close() 根据不同的路径返回不同的内容\u0026ndash;函数进阶版\n看起来上面的代码还是要挨个写if判断，怎么办？我们还是有办法！（只要思想不滑坡，方法总比问题多！）\n\u0026quot;\u0026quot;\u0026quot;根据URL中不同的路径返回不同的内容--函数进阶版\u0026quot;\u0026quot;\u0026quot;import socket sk = socket.socket() sk.bind((\u0026quot;127.0.0.1\u0026quot;, 8080)) # 绑定IP和端口sk.listen() # 监听# 将返回不同的内容部分封装成函数def index(url): s = \u0026quot;这是{}页面！\u0026quot;.format(url) return bytes(s, encoding=\u0026quot;utf8\u0026quot;)def home(url): s = \u0026quot;这是{}页面！\u0026quot;.format(url) return bytes(s, encoding=\u0026quot;utf8\u0026quot;)# 定义一个url和实际要执行的函数的对应关系list1 = [ (\u0026quot;/index/\u0026quot;, index), (\u0026quot;/home/\u0026quot;, home), ]while 1: # 等待连接 conn, add = sk.accept() data = conn.recv(8096) # 接收客户端发来的消息 # 从data中取到路径 data = str(data, encoding=\u0026quot;utf8\u0026quot;) # 把收到的字节类型的数据转换成字符串 # 按\\r\\n分割 data1 = data.split(\u0026quot;\\r\\n\u0026quot;)[0] url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径 conn.send(b'HTTP/1.1 200 OK\\r\\n\\r\\n') # 因为要遵循HTTP协议，所以回复的消息也要加状态行 # 根据不同的路径返回不同内容 func = None # 定义一个保存将要执行的函数名的变量 for i in list1: if i[0] == url: func = i[1] break if func: response = func(url) else: response = b\u0026quot;404 not found!\u0026quot; # 返回具体的响应消息 conn.send(response) conn.close() 返回具体的HTML文件\n完美解决了不同URL返回不同内容的问题。 但是我不想仅仅返回几个字符串，我想给浏览器返回完整的HTML内容，这又该怎么办呢？\n没问题，不管是什么内容，最后都是转换成字节数据发送出去的。 我们可以打开HTML文件，读取出它内部的二进制数据，然后再发送给浏览器。\n\u0026quot;\u0026quot;\u0026quot;根据URL中不同的路径返回不同的内容--函数进阶版 返回独立的HTML页面\u0026quot;\u0026quot;\u0026quot;import socket sk = socket.socket() sk.bind((\u0026quot;127.0.0.1\u0026quot;, 8080)) # 绑定IP和端口sk.listen() # 监听# 将返回不同的内容部分封装成函数def index(url): # 读取index.html页面的内容 with open(\u0026quot;index.html\u0026quot;, \u0026quot;r\u0026quot;, encoding=\u0026quot;utf8\u0026quot;) as f: s = f.read() # 返回字节数据 return bytes(s, encoding=\u0026quot;utf8\u0026quot;)def home(url): with open(\u0026quot;home.html\u0026quot;, \u0026quot;r\u0026quot;, encoding=\u0026quot;utf8\u0026quot;) as f: s = f.read() return bytes(s, encoding=\u0026quot;utf8\u0026quot;)# 定义一个url和实际要执行的函数的对应关系list1 = [ (\u0026quot;/index/\u0026quot;, index), (\u0026quot;/home/\u0026quot;, home), ]while 1: # 等待连接 conn, add = sk.accept() data = conn.recv(8096) # 接收客户端发来的消息 # 从data中取到路径 data = str(data, encoding=\u0026quot;utf8\u0026quot;) # 把收到的字节类型的数据转换成字符串 # 按\\r\\n分割 data1 = data.split(\u0026quot;\\r\\n\u0026quot;)[0] url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径 conn.send(b'HTTP/1.1 200 OK\\r\\n\\r\\n') # 因为要遵循HTTP协议，所以回复的消息也要加状态行 # 根据不同的路径返回不同内容 func = None # 定义一个保存将要执行的函数名的变量 for i in list1: if i[0] == url: func = i[1] break if func: response = func(url) else: response = b\u0026quot;404 not found!\u0026quot; # 返回具体的响应消息 conn.send(response) conn.close() 让网页动态起来\n这网页能够显示出来了，但是都是静态的啊。页面的内容都不会变化的，我想要的是动态网站。\n没问题，我也有办法解决。我选择使用字符串替换来实现这个需求。（这里使用时间戳来模拟动态的数据）\n\u0026quot;\u0026quot;\u0026quot; 根据URL中不同的路径返回不同的内容--函数进阶版 返回HTML页面 让网页动态起来 \u0026quot;\u0026quot;\u0026quot; import socket import time sk = socket.socket() sk.bind((\u0026quot;127.0.0.1\u0026quot;, 8080)) # 绑定IP和端口 sk.listen() # 监听 # 将返回不同的内容部分封装成函数 def index(url): with open(\u0026quot;index.html\u0026quot;, \u0026quot;r\u0026quot;, encoding=\u0026quot;utf8\u0026quot;) as f: s = f.read() now = str(time.time()) s = s.replace(\u0026quot;@@oo@@\u0026quot;, now) # 在网页中定义好特殊符号，用动态的数据去替换提前定义好的特殊符号 return bytes(s, encoding=\u0026quot;utf8\u0026quot;) def home(url): with open(\u0026quot;home.html\u0026quot;, \u0026quot;r\u0026quot;, encoding=\u0026quot;utf8\u0026quot;) as f: s = f.read() return bytes(s, encoding=\u0026quot;utf8\u0026quot;) # 定义一个url和实际要执行的函数的对应关系 list1 = [ (\u0026quot;/index/\u0026quot;, index), (\u0026quot;/home/\u0026quot;, home), ] while 1: # 等待连接 conn, add = sk.accept() data = conn.recv(8096) # 接收客户端发来的消息 # 从data中取到路径 data = str(data, encoding=\u0026quot;utf8\u0026quot;) # 把收到的字节类型的数据转换成字符串 # 按\\r\\n分割 data1 = data.split(\u0026quot;\\r\\n\u0026quot;)[0] url = data1.split()[1] # url是我们从浏览器发过来的消息中分离出的访问路径 conn.send(b'HTTP/1.1 200 OK\\r\\n\\r\\n') # 因为要遵循HTTP协议，所以回复的消息也要加状态行 # 根据不同的路径返回不同内容 func = None # 定义一个保存将要执行的函数名的变量 for i in list1: if i[0] == url: func = i[1] break if func: response = func(url) else: response = b\u0026quot;404 not found!\u0026quot; # 返回具体的响应消息 conn.send(response) conn.close() 服务器程序和应用程序\n对于真实开发中的python web程序来说，一般会分为两部分：服务器程序和应用程序。\n服务器程序负责对socket服务器进行封装，并在请求到来时，对请求的各种数据进行整理。\n应用程序则负责具体的逻辑处理。为了方便应用程序的开发，就出现了众多的Web框架，例如：Django、Flask、web.py 等。不同的框架有不同的开发方式，但是无论如何，开发出的应用程序都要和服务器程序配合，才能为用户提供服务。\n这样，服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架，都是不好的。对服务器来说，需要支持各种不同框架，对框架来说，只有支持它的服务器才能被开发出的应用使用。\n这时候，标准化就变得尤为重要。我们可以设立一个标准，只要服务器程序支持这个标准，框架也支持这个标准，那么他们就可以配合使用。一旦标准确定，双方各自实现。这样，服务器可以支持更多支持标准的框架，框架也可以使用更多支持标准的服务器。\nWSGI（Web Server Gateway Interface）就是一种规范，它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式，实现web应用程序与web服务器程序间的解耦。\n常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref，Django开发环境用的就是这个模块来做服务器。\nwsgiref\n我们利用wsgiref模块来替换我们自己写的web框架的socket server部分：\n\u0026quot;\u0026quot;\u0026quot; 根据URL中不同的路径返回不同的内容--函数进阶版 返回HTML页面 让网页动态起来 wsgiref模块版 \u0026quot;\u0026quot;\u0026quot; import time from wsgiref.simple_server import make_server # 将返回不同的内容部分封装成函数 def index(url): with open(\u0026quot;index.html\u0026quot;, \u0026quot;r\u0026quot;, encoding=\u0026quot;utf8\u0026quot;) as f: s = f.read() now = str(time.time()) s = s.replace(\u0026quot;@@oo@@\u0026quot;, now) return bytes(s, encoding=\u0026quot;utf8\u0026quot;) def home(url): with open(\u0026quot;home.html\u0026quot;, \u0026quot;r\u0026quot;, encoding=\u0026quot;utf8\u0026quot;) as f: s = f.read() return bytes(s, encoding=\u0026quot;utf8\u0026quot;) # 定义一个url和实际要执行的函数的对应关系 list1 = [ (\u0026quot;/index/\u0026quot;, index), (\u0026quot;/home/\u0026quot;, home), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息 url = environ['PATH_INFO'] # 取到用户输入的url func = None for i in list1: if i[0] == url: func = i[1] break if func: response = func(url) else: response = b\u0026quot;404 not found!\u0026quot; return [response, ] if __name__ == '__main__': httpd = make_server('127.0.0.1', 8090, run_server) print(\u0026quot;我在8090等你哦...\u0026quot;) httpd.serve_forever() jinja2\n上面的代码实现了一个简单的动态，我完全可以从数据库中查询数据，然后去替换我html中的对应内容，然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的，其实模板渲染有个现成的工具： jinja2\n下载jinja2:\npip install jinja2 index2.html文件\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;姓名：{{name}}\u0026lt;/h1\u0026gt; \u0026lt;h1\u0026gt;爱好：\u0026lt;/h1\u0026gt; \u0026lt;ul\u0026gt; {% for hobby in hobby_list %} \u0026lt;li\u0026gt;{{hobby}}\u0026lt;/li\u0026gt; {% endfor %} \u0026lt;/ul\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; index2.html文件 使用jinja2渲染index2.html文件：\nfrom wsgiref.simple_server import make_server from jinja2 import Template def index(): with open(\u0026quot;index2.html\u0026quot;, \u0026quot;r\u0026quot;) as f: data = f.read() template = Template(data) # 生成模板文件 ret = template.render({\u0026quot;name\u0026quot;: \u0026quot;Alex\u0026quot;, \u0026quot;hobby_list\u0026quot;: [\u0026quot;烫头\u0026quot;, \u0026quot;泡吧\u0026quot;]}) # 把数据填充到模板里面 return [bytes(ret, encoding=\u0026quot;utf8\u0026quot;), ] def home(): with open(\u0026quot;home.html\u0026quot;, \u0026quot;rb\u0026quot;) as f: data = f.read() return [data, ] # 定义一个url和函数的对应关系 URL_LIST = [ (\u0026quot;/index/\u0026quot;, index), (\u0026quot;/home/\u0026quot;, home), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息 url = environ['PATH_INFO'] # 取到用户输入的url func = None # 将要执行的函数 for i in URL_LIST: if i[0] == url: func = i[1] # 去之前定义好的url列表里找url应该执行的函数 break if func: # 如果能找到要执行的函数 return func() # 返回函数的执行结果 else: return [bytes(\u0026quot;404没有该页面\u0026quot;, encoding=\u0026quot;utf8\u0026quot;), ] if __name__ == '__main__': httpd = make_server('', 8000, run_server) print(\u0026quot;Serving HTTP on port 8000...\u0026quot;) httpd.serve_forever() 现在的数据是我们自己手写的，那可不可以从数据库中查询数据，来填充页面呢？\n使用pymysql连接数据库：\nconn = pymysql.connect(host=\u0026quot;127.0.0.1\u0026quot;, port=3306, user=\u0026quot;root\u0026quot;, passwd=\u0026quot;xxx\u0026quot;, db=\u0026quot;xxx\u0026quot;, charset=\u0026quot;utf8\u0026quot;) cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.execute(\u0026quot;select name, age, department_id from userinfo\u0026quot;) user_list = cursor.fetchall() cursor.close() conn.close() 创建一个测试的user表：\nCREATE TABLE user( id int auto_increment PRIMARY KEY, name CHAR(10) NOT NULL, hobby CHAR(20) NOT NULL )engine=innodb DEFAULT charset=UTF8; 按 Ctrl+C 复制代码\n模板的原理就是字符串替换，我们只要在HTML页面中遵循jinja2的语法规则写上，其内部就会按照指定的语法进行相应的替换，从而达到动态的返回内容。Django\n安装（安装最新LTS版）：\npip3 install django==1.11.11 创建一个django项目：\n下面的命令创建了一个名为\u0026quot;mysite\u0026quot;的Django 项目：\ndjango-admin startproject mysite 目录介绍：\nmysite/ ├── manage.py # 管理文件 └── mysite # 项目目录 ├── __init__.py ├── settings.py # 配置 ├── urls.py # 路由 --\u0026gt; URL和函数的对应关系 └── wsgi.py # runserver命令就使用wsgiref模块做简单的web server 运行Django项目：\npython manage.py runserver 127.0.0.1:8000 模板文件配置：\nTEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, \u0026quot;template\u0026quot;)], # template文件夹位置 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] 静态文件配置：\nSTATIC_URL = '/static/' # HTML中使用的静态文件夹前缀 STATICFILES_DIRS = [ os.path.join(BASE_DIR, \u0026quot;static\u0026quot;), # 静态文件存放位置 ] 看不明白？有图有真相：\n刚开始学习时可在配置文件中暂时禁用csrf中间件，方便表单提交测试。\nMIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] Django基础必备三件套：\nfrom django.shortcuts import HttpResponse, render, redirect HttpResponse\n内部传入一个字符串参数，返回给浏览器。\n例如：\ndef index(request): # 业务逻辑代码 return HttpResponse(\u0026quot;OK\u0026quot;) render\n除request参数外还接受一个待渲染的模板文件和一个保存具体数据的字典参数。\n将数据填充进模板文件，最后把结果返回给浏览器。（类似于我们上面用到的jinja2）\n例如：\ndef index(request): # 业务逻辑代码 return render(request, \u0026quot;index.html\u0026quot;, {\u0026quot;name\u0026quot;: \u0026quot;alex\u0026quot;, \u0026quot;hobby\u0026quot;: [\u0026quot;烫头\u0026quot;, \u0026quot;泡吧\u0026quot;]}) redirect\n接受一个URL参数，表示跳转到指定的URL。\n例如：\ndef index(request): # 业务逻辑代码 return redirect(\u0026quot;/home/\u0026quot;) 重定向是怎么回事？\n启动Django报错：\nDjango 启动时报错 “UnicodeEncodeError \u0026hellip;”\n报这个错误通常是因为计算机名为中文，改成英文的计算机名重启下电脑就可以了。\nDjango 启动报错“SyntaxError: Generator expression must be parenthesized”\n报这个错很大可能是因为使用了Python3.7.0，而目前(2018-06-12)Python3.7.0和Django还有点兼容性问题，换回Python3.6的环境即可。\n","permalink":"https://zhong-er.com/posts/django%E6%A1%86%E6%9E%B6%E7%AE%80%E4%BB%8B/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cimg loading=\"lazy\" src=\"/assets/201909169981_9838.jpg\"\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eWeb框架本质\u003c/p\u003e\n\u003cp\u003e我们可以这样理解：所有的Web应用本质上就是一个socket服务端，而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。\u003c/p\u003e\n\u003cp\u003e半成品自定义web框架\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eimport socket\n\nsk = socket.socket()\nsk.bind((\u0026quot;127.0.0.1\u0026quot;, 80))\nsk.listen()\n\n\nwhile True:\n    conn, addr = sk.accept()\n    data = conn.recv(8096)\n    conn.send(b\u0026quot;OK\u0026quot;)\n    conn.close()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e可以说Web服务本质上都是在这十几行代码基础上扩展出来的。这段代码就是它们的祖宗。\u003c/p\u003e\n\u003cp\u003e用户的浏览器一输入网址，会给服务端发送数据，那浏览器会发送什么数据？怎么发？这个谁来定？ 你这个网站是这个规定，他那个网站按照他那个规定，这互联网还能玩么？\u003c/p\u003e\n\u003cp\u003e所以，必须有一个统一的规则，让大家发送消息、接收消息的时候有个格式依据，不能随便写。\u003c/p\u003e\n\u003cp\u003e这个规则就是HTTP协议，以后浏览器发送请求信息也好，服务器回复响应信息也罢，都要按照这个规则来。\u003c/p\u003e\n\u003cp\u003eHTTP协议主要规定了客户端和服务器之间的通信格式，那HTTP协议是怎么规定消息格式的呢？\u003c/p\u003e\n\u003cp\u003e让我们首先打印下我们在服务端接收到的消息是什么。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eimport socket\n\nsk = socket.socket()\nsk.bind((\u0026quot;127.0.0.1\u0026quot;, 80))\nsk.listen()\n\n\nwhile True:\n    conn, addr = sk.accept()\n    data = conn.recv(8096)\n    print(data)  # 将浏览器发来的消息打印出来\n    conn.send(b\u0026quot;OK\u0026quot;)\n    conn.close()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e输出：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eb'GET / HTTP/1.1\\r\\nHost: 127.0.0.1:8080\\r\\nConnection: keep-alive\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\\r\\nDNT: 1\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: zh-CN,zh;q=0.9\\r\\nCookie: csrftoken=RKBXh1d3M97iz03Rpbojx1bR6mhHudhyX5PszUxxG3bOEwh1lxFpGOgWN93ZH3zv\\r\\n\\r\\n'\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e然后我们再看一下我们访问博客园官网时浏览器收到的响应信息是什么。\u003c/p\u003e","title":"【Django】框架简介"},{"content":"前戏 到目前为止，我们已经学过了JavaScript的一些简单的语法。但是这些简单的语法，并没有和浏览器有任何交互。\n也就是我们还不能制作一些我们经常看到的网页的一些交互，我们需要继续学习BOM和DOM相关知识。\nJavaScript分为 ECMAScript，DOM，BOM。\nBOM（Browser Object Model）是指浏览器对象模型，它使 JavaScript 有能力与浏览器进行“对话”。\nDOM （Document Object Model）是指文档对象模型，通过它，可以访问HTML文档的所有元素。\nWindow对象是客户端JavaScript最高层对象之一，由于window对象是其它大部分对象的共同祖先，在调用window对象的方法和属性时，可以省略window对象的引用。例如：window.document.write()可以简写成：document.write()。\nwindow对象 所有浏览器都支持 window 对象。它表示浏览器窗口。\n*如果文档包含框架（frame 或 iframe 标签），浏览器会为 HTML 文档创建一个 window 对象，并为每个框架创建一个额外的 window 对象。\n*没有应用于 window 对象的公开标准，不过所有浏览器都支持该对象。\n所有 JavaScript 全局对象、函数以及变量均自动成为 window 对象的成员。\n全局变量是 window 对象的属性。全局函数是 window 对象的方法。\n接下来要讲的HTML DOM 的 document 也是 window 对象的属性之一。\n一些常用的Window方法：\nwindow.innerHeight - 浏览器窗口的内部高度\nwindow.innerWidth - 浏览器窗口的内部宽度\nwindow.open() - 打开新窗口\nwindow.close() - 关闭当前窗口\nwindow的子对象 navigator对象（了解即可） 浏览器对象，通过这个对象可以判定用户所使用的浏览器，包含了浏览器相关信息。\nnavigator.appName　// Web浏览器全称 navigator.appVersion　// Web浏览器厂商和版本的详细字符串 navigator.userAgent　// 客户端绝大部分信息 navigator.platform　// 浏览器运行所在的操作系统 screen对象（了解即可） 屏幕对象，不常用。\n一些属性：\nscreen.availWidth - 可用的屏幕宽度\nscreen.availHeight - 可用的屏幕高度\nhistory对象（了解即可） window.history 对象包含浏览器的历史。\n浏览历史对象，包含了用户对当前页面的浏览历史，但我们无法查看具体的地址，可以简单的用来前进或后退一个页面。\nhistory.forward() // 前进一页 history.back() // 后退一页 location对象 window.location 对象用于获得当前页面的地址 (URL)，并把浏览器重定向到新的页面。\n常用属性和方法：\nlocation.href 获取URL location.href=\u0026quot;URL\u0026quot; // 跳转到指定页面 location.reload() 重新加载页面 弹出框 可以在 JavaScript 中创建三种消息框：警告框、确认框、提示框。\n警告框\n警告框经常用于确保用户可以得到某些信息。\n当警告框出现后，用户需要点击确定按钮才能继续进行操作。\n语法：\nalert(\u0026quot;你看到了吗？\u0026quot;); 确认框（了解即可）\n确认框用于使用户可以验证或者接受某些信息。\n当确认框出现后，用户需要点击确定或者取消按钮才能继续进行操作。\n如果用户点击确认，那么返回值为 true。如果用户点击取消，那么返回值为 false。\n语法：\nconfirm(\u0026quot;你确定吗？\u0026quot;) 提示框（了解即可）\n提示框经常用于提示用户在进入页面前输入某个值。\n当提示框出现后，用户需要输入某个值，然后点击确认或取消按钮才能继续操纵。\n如果用户点击确认，那么返回值为输入的值。如果用户点击取消，那么返回值为 null。\n语法：\nprompt(\u0026quot;请在下方输入\u0026quot;,\u0026quot;你的答案\u0026quot;) 计时相关 通过使用 JavaScript，我们可以在一定时间间隔之后来执行代码，而不是在函数被调用后立即执行。我们称之为计时事件。\nsetTimeout()\n语法：\nvar t=setTimeout(\u0026quot;JS语句\u0026quot;,毫秒) setTimeout() 方法会返回某个值。在上面的语句中，值被储存在名为 t 的变量中。假如你希望取消这个 setTimeout()，你可以使用这个变量名来指定它。\nsetTimeout() 的第一个参数是含有 JavaScript 语句的字符串。这个语句可能诸如 \u0026ldquo;alert(\u0026lsquo;5 seconds!\u0026rsquo;)\u0026quot;，或者对函数的调用，诸如 alertMsg()\u0026quot;。\n第二个参数指示从当前起多少毫秒后执行第一个参数（1000 毫秒等于一秒）。\nclearTimeout()\n语法：\nclearTimeout(setTimeout_variable) 举个例子**：**\n// 在指定时间之后执行一次相应函数 var timer = setTimeout(function(){alert(123);}, 3000) // 取消setTimeout设置 clearTimeout(timer); setInterval()\nsetInterval() 方法可按照指定的周期（以毫秒计）来调用函数或计算表达式。\nsetInterval() 方法会不停地调用函数，直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。\n语法：\nsetInterval(\u0026quot;JS语句\u0026quot;,时间间隔) 返回值\n一个可以传递给 Window.clearInterval() 从而取消对 code 的周期性执行的值。\nclearInterval()\nclearInterval() 方法可取消由 setInterval() 设置的 timeout。\nclearInterval() 方法的参数必须是由 setInterval() 返回的 ID 值。\n语法：\nclearInterval(setinterval返回的ID值) 举个例子：\n// 每隔一段时间就执行一次相应函数 var timer = setInterval(function(){console.log(123);}, 3000) // 取消setInterval设置 clearInterval(timer); DOM DOM（Document Object Model）是一套对文档的内容进行抽象和概念化的方法。\n当网页被加载时，浏览器会创建页面的文档对象模型（Document Object Model）。\nHTML DOM 模型被构造为对象的树。\nHTML DOM 树 DOM标准规定HTML文档中的每个成分都是一个节点(node)：\n文档节点(document对象)：代表整个文档\n元素节点(element 对象)：代表一个元素（标签）\n文本节点(text对象)：代表元素（标签）中的文本\n属性节点(attribute对象)：代表一个属性，元素（标签）才有属性\n注释是注释节点(comment对象)\nJavaScript 可以通过DOM创建动态的 HTML：\nJavaScript 能够改变页面中的所有 HTML 元素\nJavaScript 能够改变页面中的所有 HTML 属性\nJavaScript 能够改变页面中的所有 CSS 样式\nJavaScript 能够对页面中的所有事件做出反应\n查找标签 直接查找 document.getElementById 根据ID获取一个标签 document.getElementsByClassName 根据class属性获取 document.getElementsByTagName 根据标签名获取标签合集 注意：\n涉及到DOM操作的JS代码应该放在文档的哪个位置。\n间接查找 parentElement 父节点标签元素 children 所有子标签 firstElementChild 第一个子标签元素 lastElementChild 最后一个子标签元素 nextElementSibling 下一个兄弟标签元素 previousElementSibling 上一个兄弟标签元素 节点操作 创建节点 语法：\ncreateElement(标签名)\n示例：\nvar divEle = document.createElement(\u0026quot;div\u0026quot;); 添加节点 语法：\n追加一个子节点（作为最后的子节点）\nsomenode.appendChild(newnode)；\n把增加的节点放到某个节点的前边。\nsomenode.insertBefore(newnode,某个节点);\n示例：\nvar imgEle=document.createElement(\u0026quot;img\u0026quot;); imgEle.setAttribute(\u0026quot;src\u0026quot;, \u0026quot;assets/m1905_20100205.jpg\u0026quot;); var d1Ele = document.getElementById(\u0026quot;d1\u0026quot;); d1Ele.appendChild(imgEle); 删除节点： 语法：\n获得要删除的元素，通过父元素调用该方法删除。\nsomenode.removeChild(要删除的节点)\n替换节点： 语法：\nsomenode.replaceChild(newnode, 某个节点);\n属性节点 获取文本节点的值：\nvar divEle = document.getElementById(\u0026quot;d1\u0026quot;) divEle.innerText divEle.innerHTML 设置文本节点的值：\nvar divEle = document.getElementById(\u0026quot;d1\u0026quot;) divEle.innerText=\u0026quot;1\u0026quot; divEle.innerHTML=\u0026quot;\u0026lt;p\u0026gt;2\u0026lt;/p\u0026gt;\u0026quot; attribute操作\nvar divEle = document.getElementById(\u0026quot;d1\u0026quot;); divEle.setAttribute(\u0026quot;age\u0026quot;,\u0026quot;18\u0026quot;) divEle.getAttribute(\u0026quot;age\u0026quot;) divEle.removeAttribute(\u0026quot;age\u0026quot;) // 自带的属性还可以直接.属性名来获取和设置 imgEle.src imgEle.src=\u0026quot;...\u0026quot; 获取值操作 语法：\nelementNode.value\n适用于以下标签：\n.input\n.select\n.textarea\nvar iEle = document.getElementById(\u0026ldquo;i1\u0026rdquo;); console.log(iEle.value); var sEle = document.getElementById(\u0026ldquo;s1\u0026rdquo;); console.log(sEle.value); var tEle = document.getElementById(\u0026ldquo;t1\u0026rdquo;); console.log(tEle.value);\nclass的操作 className 获取所有样式类名(字符串) classList.remove(cls) 删除指定类 classList.add(cls) 添加类 classList.contains(cls) 存在返回true，否则返回false classList.toggle(cls) 存在就删除，否则添加 指定CSS操作 obj.style.backgroundColor=\u0026quot;red\u0026quot; JS操作CSS属性的规律：\n1.对于没有中横线的CSS属性一般直接使用style.属性名即可。如：\nobj.style.margin obj.style.width obj.style.left obj.style.position 2.对含有中横线的CSS属性，将中横线后面的第一个字母换成大写即可。如：\nobj.style.marginTop obj.style.borderLeftWidth obj.style.zIndex obj.style.fontFamily 事件 HTML 4.0 的新特性之一是有能力使 HTML 事件触发浏览器中的动作（action），比如当用户点击某个 HTML 元素时启动一段 JavaScript。下面是一个属性列表，这些属性可插入 HTML 标签来定义事件动作。\n常用事件 onclick 当用户点击某个对象时调用的事件句柄。 ondblclick 当用户双击某个对象时调用的事件句柄。 onfocus 元素获得焦点。 // 练习：输入框 onblur 元素失去焦点。 应用场景：用于表单验证,用户离开某个输入框时,代表已经输入完了,我们可以对它进行验证. onchange 域的内容被改变。 应用场景：通常用于表单元素,当元素内容被改变时触发.（select联动） onkeydown 某个键盘按键被按下。 应用场景: 当用户在最后一个输入框按下回车按键时,表单提交. onkeypress 某个键盘按键被按下并松开。 onkeyup 某个键盘按键被松开。 onload 一张页面或一幅图像完成加载。 onmousedown 鼠标按钮被按下。 onmousemove 鼠标被移动。 onmouseout 鼠标从某元素移开。 onmouseover 鼠标移到某元素之上。 onselect 在文本框中的文本被选中时发生。 onsubmit 确认按钮被点击，使用的对象是form。 绑定方式： 方式一：\n\u0026lt;div id=\u0026quot;d1\u0026quot; onclick=\u0026quot;changeColor(this);\u0026quot;\u0026gt;点我\u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; function changeColor(ths) { ths.style.backgroundColor=\u0026quot;green\u0026quot;; } \u0026lt;/script\u0026gt; 注意：\nthis是实参，表示触发事件的当前元素。\n函数定义过程中的ths为形参。\n方式二：\n\u0026lt;div id=\u0026quot;d2\u0026quot;\u0026gt;点我\u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; var divEle2 = document.getElementById(\u0026quot;d2\u0026quot;); divEle2.onclick=function () { this.innerText=\u0026quot;呵呵\u0026quot;; } \u0026lt;/script\u0026gt; 定时器示例\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; id=\u0026quot;i1\u0026quot;\u0026gt; \u0026lt;button id=\u0026quot;b1\u0026quot;\u0026gt;开始\u0026lt;/button\u0026gt; \u0026lt;button id=\u0026quot;b2\u0026quot;\u0026gt;结束\u0026lt;/button\u0026gt; \u0026lt;script\u0026gt; var t; function showTime() { var i1Ele = document.getElementById('i1'); var time = new Date(); i1Ele.value = time.toLocaleString(); } showTime(); var b1Ele = document.getElementById('b1'); b1Ele.onclick = function (ev) { if (!t){ t = setInterval(showTime,1000) } }; var b2Ele = document.getElementById('b2'); b2Ele.onclick = function (ev) { clearInterval(t); t = undefined }; \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 搜索框示例\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;搜索框示例\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;input id=\u0026quot;d1\u0026quot; type=\u0026quot;text\u0026quot; value=\u0026quot;请输入关键字\u0026quot; onblur=\u0026quot;blur()\u0026quot; onfocus=\u0026quot;focus()\u0026quot;\u0026gt; \u0026lt;script\u0026gt; function focus(){ var inputEle=document.getElementById(\u0026quot;d1\u0026quot;); if (inputEle.value===\u0026quot;请输入关键字\u0026quot;){ inputEle.value=\u0026quot;\u0026quot;; } } function blur(){ var inputEle=document.getElementById(\u0026quot;d1\u0026quot;); var val=inputEle.value; if(!val.trim()){ inputEle.value=\u0026quot;请输入关键字\u0026quot;; } } \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; select联动\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;select联动\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;select id=\u0026quot;province\u0026quot;\u0026gt; \u0026lt;option\u0026gt;请选择省:\u0026lt;/option\u0026gt; \u0026lt;/select\u0026gt; \u0026lt;select id=\u0026quot;city\u0026quot;\u0026gt; \u0026lt;option\u0026gt;请选择市:\u0026lt;/option\u0026gt; \u0026lt;/select\u0026gt; \u0026lt;script\u0026gt; data = {\u0026quot;河北省\u0026quot;: [\u0026quot;廊坊\u0026quot;, \u0026quot;邯郸\u0026quot;], \u0026quot;北京\u0026quot;: [\u0026quot;朝阳区\u0026quot;, \u0026quot;海淀区\u0026quot;], \u0026quot;山东\u0026quot;: [\u0026quot;威海市\u0026quot;, \u0026quot;烟台市\u0026quot;]}; var p = document.getElementById(\u0026quot;province\u0026quot;); var c = document.getElementById(\u0026quot;city\u0026quot;); for (var i in data) { var optionP = document.createElement(\u0026quot;option\u0026quot;); optionP.innerHTML = i; p.appendChild(optionP); } p.onchange = function () { var pro = (this.options[this.selectedIndex]).innerHTML; var citys = data[pro]; // 清空option c.innerHTML = \u0026quot;\u0026quot;; for (var i=0;i\u0026lt;citys.length;i++) { var option_city = document.createElement(\u0026quot;option\u0026quot;); option_city.innerHTML = citys[i]; c.appendChild(option_city); } } \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; window.onload 当我们给页面上的元素绑定事件的时候，必须等到文档加载完毕。因为我们无法给一个不存在的元素绑定事件。\nwindow.onload事件在文件加载过程结束的时候触发。此时，文档中的所有对象都位于DOM中，并且所有图像，脚本，链接和子框架都已完成加载。\n注意：.onload()函数存在覆盖现象。\n","permalink":"https://zhong-er.com/posts/%E5%89%8D%E7%AB%AFbom%E5%92%8Cdom/","summary":"\u003ch1 id=\"前戏\"\u003e前戏\u003c/h1\u003e\n\u003cp\u003e到目前为止，我们已经学过了JavaScript的一些简单的语法。但是这些简单的语法，并没有和浏览器有任何交互。\u003c/p\u003e\n\u003cp\u003e也就是我们还不能制作一些我们经常看到的网页的一些交互，我们需要继续学习BOM和DOM相关知识。\u003c/p\u003e\n\u003cp\u003eJavaScript分为 ECMAScript，DOM，BOM。\u003c/p\u003e\n\u003cp\u003eBOM（Browser Object Model）是指浏览器对象模型，它使 JavaScript 有能力与浏览器进行“对话”。\u003c/p\u003e\n\u003cp\u003eDOM （Document Object Model）是指文档对象模型，通过它，可以访问HTML文档的所有元素。\u003c/p\u003e\n\u003cp\u003eWindow对象是客户端JavaScript最高层对象之一，由于window对象是其它大部分对象的共同祖先，在调用window对象的方法和属性时，可以省略window对象的引用。例如：window.document.write()可以简写成：document.write()。\u003c/p\u003e\n\u003ch2 id=\"window对象\"\u003ewindow对象\u003c/h2\u003e\n\u003cp\u003e所有浏览器都支持 window 对象。它表示浏览器窗口。\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e*如果文档包含框架（frame 或 iframe 标签），浏览器会为 HTML 文档创建一个 window 对象，并为每个框架创建一个额外的 window 对象。\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e*没有应用于 window 对象的公开标准，不过所有浏览器都支持该对象。\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e所有 JavaScript 全局对象、函数以及变量均自动成为 window 对象的成员。\u003c/p\u003e\n\u003cp\u003e全局变量是 window 对象的属性。全局函数是 window 对象的方法。\u003c/p\u003e\n\u003cp\u003e接下来要讲的HTML DOM 的 document 也是 window 对象的属性之一。\u003c/p\u003e\n\u003cp\u003e一些常用的Window方法：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003ewindow.innerHeight - 浏览器窗口的内部高度\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ewindow.innerWidth - 浏览器窗口的内部宽度\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ewindow.open() - 打开新窗口\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ewindow.close() - 关闭当前窗口\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"window的子对象\"\u003ewindow的子对象\u003c/h2\u003e\n\u003ch3 id=\"navigator对象了解即可\"\u003enavigator对象（了解即可）\u003c/h3\u003e\n\u003cp\u003e浏览器对象，通过这个对象可以判定用户所使用的浏览器，包含了浏览器相关信息。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003enavigator.appName　　// Web浏览器全称\nnavigator.appVersion　　// Web浏览器厂商和版本的详细字符串\nnavigator.userAgent　　// 客户端绝大部分信息\nnavigator.platform　　　// 浏览器运行所在的操作系统\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"screen对象了解即可\"\u003escreen对象（了解即可）\u003c/h3\u003e\n\u003cp\u003e屏幕对象，不常用。\u003c/p\u003e","title":"【前端】BOM和DOM"},{"content":"JavaScript引入方式 Script标签内写代码 \u0026lt;script\u0026gt; // 在这里写你的JS代码 \u0026lt;/script\u0026gt; 引入额外的JS文件 \u0026lt;script src=\u0026quot;myscript.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; JavaScript语言规范 注释（注释是代码之母） // 这是单行注释 /* 这是 多行注释 */ 结束符 JavaScript中的语句要以分号（;）为结束符。\nJavaScript语言基础 变量声明 JavaScript的变量名可以使用_，数字，字母，$组成，不能以数字开头。\n声明变量使用 var 变量名; 的格式来进行声明\nvar name = \u0026quot;Alex\u0026quot;; var age = 18; 注意：\n变量名是区分大小写的。\n推荐使用驼峰式命名规则。\n保留字不能用做变量名。\n补充：\nES6新增了let命令，用于声明变量。其用法类似于var，但是所声明的变量只在let命令所在的代码块内有效。例如：for循环的计数器就很适合使用let命令。\nfor (let i=0;i\u0026lt;arr.length;i++){...} ES6新增const用来声明常量。一旦声明，其值就不能改变。\nconst PI = 3.1415; PI // 3.1415 PI = 3 // TypeError: \u0026quot;PI\u0026quot; is read-only JavaScript数据类型 JavaScript拥有动态类型\nvar x; // 此时x是undefined var x = 1; // 此时x是数字 var x = \u0026quot;Alex\u0026quot; // 此时x是字符串 数值(Number) JavaScript不区分整型和浮点型，就只有一种数字类型。\nvar a = 12.34; var b = 20; var c = 123e5; // 12300000 var d = 123e-5; // 0.00123 还有一种NaN，表示不是一个数字（Not a Number）。\n常用方法：\nparseInt(\u0026quot;123\u0026quot;) // 返回123 parseInt(\u0026quot;ABC\u0026quot;) // 返回NaN,NaN属性是代表非数字值的特殊值。该属性用于指示某个值不是数字。 parseFloat(\u0026quot;123.456\u0026quot;) // 返回123.456 字符串(String) var a = \u0026quot;Hello\u0026quot; var b = \u0026quot;world; var c = a + b; console.log(c); // 得到Helloworld 常用方法：\n方法\n说明 .length 返回长度 .trim() 移除空白 .trimLeft() 移除左边的空白 .timRight() 移除右边的空白 .charAt(n) 返回第n个字符 .concat(value,\u0026hellip;) 拼接 .indexOf(substring,start) 子序列位置 substring(from,to) 根据索引获取子序列 .slice(start,end) 切片 .toLowerCase() 小写 .toUpperCase()| 大写\n.split(delimiter,limit)| 分割\n拼接字符串一般使用“+”\nslice和substring的区别\nstring.slice(start, stop)和string.substring(start, stop)： 两者的相同点： 如果start等于end，返回空字符串 如果stop参数省略，则取到字符串末 如果某个参数超过string的长度，这个参数会被替换为string的长度 substirng()的特点： 如果 start \u0026gt; stop ，start和stop将被交换 如果参数是负数或者不是数字，将会被0替换 silce()的特点： 如果 start \u0026gt; stop 不会交换两者 如果start小于0，则切割从字符串末尾往前数的第abs(start)个的字符开始(包括该位置的字符) 如果stop小于0，则切割在从字符串末尾往前数的第abs(stop)个字符结束(不包含该位置字符) 补充：\nES6中引入了模板字符串。模板字符串（template string）是增强版的字符串，用反引号（`）标识。它可以当做普通字符串使用，也可以用来定义多行字符串，或者在字符串中嵌入变量。\n// 普通字符串`这是普通字符串！`// 多行文本`这是多行的 文本`// 字符串中嵌入变量var name = \u0026quot;jason\u0026quot;, time = \u0026quot;today\u0026quot;; `Hello ${name}, how are you ${time}?` 注意：\n如果模板字符串中需要使用反引号，则在其前面要用反斜杠转义。\nJSHint启用ES6语法支持:/* jshint esversion: 6 */\n布尔值(Boolean) 区别于Python，true和false都是小写。\nvar a = true; var b = false; \u0026ldquo;\u0026quot;(空字符串)、0、null、undefined、NaN都是false。\nnull和undefined null表示值是空，一般在需要指定或清空一个变量时才会使用，如 name=null;\nundefined表示当声明一个变量但未初始化时，该变量的默认值是undefined。还有就是函数无明确的返回值时，返回的也是undefined。\nnull表示变量的值是空(null可以手动清空一个变量的值，使得该变量变为object类型，值为null)，undefined则表示只声明了变量，但还没有赋值。\n还不明白，下图吧！\n对象(Object) JavaScript 中的所有事物都是对象：字符串、数值、数组、函数\u0026hellip;此外，JavaScript 允许自定义对象。\nJavaScript 提供多个内建对象，比如 String、Date、Array 等等。\n对象只是带有属性和方法的特殊数据类型。\n数组\n数组对象的作用是：使用单独的变量名来存储一系列的值。类似于Python中的列表。\nvar a = [123, \u0026quot;ABC\u0026quot;]; console.log(a[1]); // 输出\u0026quot;ABC\u0026quot; 常用方法：\n方法 说明 .length 数组的大小 .push 尾部追加元素 .pop() 获取尾部的元素 .unshift(ele) 头部插入元素 .shift(ele) 头部移除元素 .slice(start,end) 切片 .reverse() 反转 .join(seq) 将数组元素连接成字符串 .concat(val,\u0026hellip;) 连接数组 .sort() 排序 .forEach() 将数组的每个元素传递给回调函数 .splice()| 删除元素，并向数组添加新元素\n.map()| 返回一个数组元素调用函数处理后的值的新数组\nforEach() 语法：\nforEach(function(currentValue, index, arr), thisValue)**\n**\n参数：\nsplice() 语法：\nsplice(index,howmany,item1,\u0026hellip;..,itemX)\n参数：\nmap() 语法：\nmap(function(currentValue,index,arr), thisValue)\n参数：\n关于sort()需要注意：\n如果调用该方法时没有使用参数，将按字母顺序对数组中的元素进行排序，说得更精确点，是按照字符编码的顺序进行排序。要实现这一点，首先应把数组的元素都转换成字符串（如有必要），以便进行比较。 如果想按照其他标准进行排序，就需要提供比较函数，该函数要比较两个值，然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b，其返回值如下： 若 a 小于 b，在排序后的数组中 a 应该出现在 b 之前，则返回一个小于 0 的值。 若 a 等于 b，则返回 0。 若 a 大于 b，则返回一个大于 0 的值。 function sortNumber(a,b){ return a - b } var arr1 = [11, 100, 22, 55, 33, 44] arr1.sort(sortNumber) 可以使用以下方式遍历数组中的元素：\nvar a = [10, 20, 30, 40]; for (var i=0;i\u0026lt;a.length;i++) { console.log(i); } 补充：\nES6新引入了一种新的原始数据类型（Symbol），表示独一无二的值。它是JavaScript语言的第7种数据类型。\n类型查询 typeof \u0026quot;abc\u0026quot; // \u0026quot;string\u0026quot; typeof null // \u0026quot;object\u0026quot; typeof true // \u0026quot;boolean\u0026quot; typeof 123 // \u0026quot;number\u0026quot; typeof是一个一元运算符（就像++，\u0026ndash;，！，- 等一元运算符），不是一个函数，也不是一个语句。\n对变量或值调用 typeof 运算符将返回下列值之一：\nundefined - 如果变量是 Undefined 类型的\nboolean - 如果变量是 Boolean 类型的\nnumber - 如果变量是 Number 类型的\nstring - 如果变量是 String 类型的\nobject - 如果变量是一种引用类型或 Null 类型的\n运算符 算数运算符 + - * / % ++ -- var x=10; var res1=x++; var res2=++x; res1; 10 res2; 12 这里由于的x++和++x在出现赋值运算式，x++会先赋值再进行自增1运算，而++x会先进行自增运算再赋值！ 比较运算符 \u0026gt; \u0026gt;= \u0026lt; \u0026lt;= != == === !== 注意：\n1 == “1” // true 弱等于 1 === \u0026quot;1\u0026quot; // false 强等于 //上面这张情况出现的原因在于JS是一门弱类型语言(会自动转换数据类型)，所以当你用两个等号进行比较时，JS内部会自动先将 //数值类型的1转换成字符串类型的1再进行比较，所以我们以后写JS涉及到比较时尽量用三等号来强制限制类型，防止判断错误 逻辑运算符 \u0026amp;\u0026amp; || ! 赋值运算符 = += -= *= /= 流程控制 if-else var a = 10; if (a \u0026gt; 5){ console.log(\u0026quot;yes\u0026quot;); }else { console.log(\u0026quot;no\u0026quot;); } if-else if-else switch\nswitch中的case子句通常都会加break语句，否则程序会继续执行后续case中的语句。\nfor\nwhile\n三元运算 函数 函数定义 JavaScript中的函数和Python中的非常类似，只是定义方式有点区别。\n无参函数与有参函数\n带返回值函数\n匿名函数与立即执行函数\n补充：\nES6中允许使用“箭头”（=\u0026gt;）定义函数。\nvar f = v =\u0026gt; v; // 等同于 var f = function(v){ return v; } 如果箭头函数不需要参数或需要多个参数，就是用圆括号代表参数部分：\nvar f = () =\u0026gt; 5; // 等同于 var f = function(){return 5}; var sum = (num1, num2) =\u0026gt; num1 + num2; // 等同于 var sum = function(num1, num2){ return num1 + num2; //这里的return只能返回一个值，如果想返回多个值需要自己手动给他们包一个数组或对象中 } 函数中的arguments参数 function add(a,b){ console.log(a+b); console.log(arguments.length); console.log(arguments[0]);//arguments相当于将出传入的参数全部包含，这里取得就是第一个元素1} add(1,2) 输出：\n3 2 1 注意：\n函数只能返回一个值，如果要返回多个值，只能将其放在数组或对象中返回。\n函数的全局变量和局部变量 局部变量 ：\n在JavaScript函数内部声明的变量（使用 var）是局部变量，所以只能在函数内部访问它（该变量的作用域是函数内部）。只要函数运行完毕，本地变量就会被删除。\n全局变量：\n在函数外声明的变量是 全局 变量，网页上的所有脚本和函数都能访问它。\n变量生存周期：\nJavaScript变量的生命期从它们被声明的时间开始。\n局部变量会在函数运行以后被删除。\n全局变量会在页面关闭后被删除。\n作用域 首先在函数内部查找变量，找不到则到外层函数查找，逐步找到最外层。与python作用域关系查找一模一样！\n闭包函数\nvar city = \u0026quot;BeiJing\u0026quot;; function f(){ var city = \u0026quot;ShangHai\u0026quot;; function inner(){ console.log(city); } return inner; } var ret = f(); ret(); 词法分析（尝试理解） JavaScript中在调用函数的那一瞬间，会先进行词法分析。\n词法分析的过程：\n当函数调用的前一瞬间，会先形成一个激活对象：Avtive Object（AO），并会分析以下3个方面：\n1:函数参数，如果有，则将此参数赋值给AO，且值为undefined。如果没有，则不做任何操作。\n2:函数局部变量，如果AO上有同名的值，则不做任何操作。如果没有，则将此变量赋值给AO，并且值为undefined。\n3:函数声明，如果AO上有，则会将AO上的对象覆盖。如果没有，则不做任何操作。\n函数内部无论是使用参数还是使用局部变量都到AO上找。\n看两个例子：\nvar age = 18; function foo(){ console.log(age); var age = 22; console.log(age); } foo(); // 问：执行foo()之后的结果是？ 第二题：\nvar age = 18; function foo(){ console.log(age); var age = 22; console.log(age); function age(){ console.log(\u0026quot;呵呵\u0026quot;); } console.log(age); } foo(); // 执行后的结果是？ 答案解析\n词法分析过程： 1、分析参数，有一个参数，形成一个 AO.age=undefine; 2、分析变量声明，有一个 var age, 发现 AO 上面已经有一个 AO.age，因此不做任何处理 3、分析函数声明，有一个 function age(){...} 声明， 则把原有的 age 覆盖成 AO.age=function(){...}; 最终，AO上的属性只有一个age，并且值为一个函数声明 执行过程： 注意：执行过程中所有的值都是从AO对象上去寻找 1、执行第一个 console.log(age) 时，此时的 AO.age 是一个函数，所以第一个输出的一个函数 2、这句 var age=22; 是对 AO.age 的属性赋值， 此时AO.age=22 ，所以在第二个输出的是 2 3、同理第三个输出的还是22, 因为中间再没有改变age值的语句了 内置对象和方法 JavaScript中的所有事物都是对象：字符串、数字、数组、日期，等等。在JavaScript中，对象是拥有属性和方法的数据。\n我们在学习基本数据类型的时候已经带大家了解了，JavaScript中的Number对象、String对象、Array对象等。\n注意var s1 = \u0026ldquo;abc\u0026quot;和var s2 = new String(\u0026ldquo;abc\u0026rdquo;)的区别：typeof s1 \u0026ndash;\u0026gt; string而 typeof s2 \u0026ndash;\u0026gt; Object\n自定义对象\n自定义对象 JavaScript的对象（Object）本质上是键值对的集合（Hash结构），但是只能用字符串作为键。\nvar a = {\u0026quot;name\u0026quot;: \u0026quot;Alex\u0026quot;, \u0026quot;age\u0026quot;: 18}; console.log(a.name); console.log(a[\u0026quot;age\u0026quot;]); 遍历对象中的内容：\nvar a = {\u0026quot;name\u0026quot;: \u0026quot;Alex\u0026quot;, \u0026quot;age\u0026quot;: 18}; for (var i in a){ console.log(i, a[i]); } 事情并没有那么简单\u0026hellip;\n创建对象：\nvar person=new Object(); // 创建一个person对象 person.name=\u0026quot;Alex\u0026quot;; // person对象的name属性 person.age=18; // person对象的age属性 _\n_\n注意：\nES6中提供了Map数据结构。它类似于对象，也是键值对的集合，但是“键”的范围不限于字符串，各种类型的值（包括对象）都可以当做键。\n也就是说，Object结构提供了“字符串\u0026ndash;值”的对应，Map结构提供了“值\u0026ndash;值”的对应，是一种更完善的Hash结构实现。\nvar m = new Map();var o = {p: \u0026quot;Hello World\u0026quot;} m.set(o, \u0026quot;content\u0026quot;} m.get(o) // \u0026quot;content\u0026quot; m.has(o) // true m.delete(o) // true m.has(o) // false 扩展：\nJavaScript面向对象之继承\n// 父类构造函数 var Car = function (loc) { this.loc = loc; }; // 父类方法 Car.prototype.move = function () { this.loc ++; }; // 子类构造函数 var Van = function (loc) { Car.call(this, loc); }; // 继承父类的方法 Van.prototype = Object.create(Car.prototype); // 修复 constructor Van.prototype.constructor = Van; // 扩展方法 Van.prototype.grab = function () { /* ... */ }; Date对象 创建Date对象\n//方法1：不指定参数 var d1 = new Date(); console.log(d1.toLocaleString()); //方法2：参数为日期字符串var d2 = new Date(\u0026quot;2004/3/20 11:12\u0026quot;); console.log(d2.toLocaleString()); var d3 = new Date(\u0026quot;04/03/20 11:12\u0026quot;); console.log(d3.toLocaleString()); //方法3：参数为毫秒数 var d3 = new Date(5000); console.log(d3.toLocaleString()); console.log(d3.toUTCString()); //方法4：参数为年月日小时分钟秒毫秒 var d4 = new Date(2004,2,20,11,12,0,300); console.log(d4.toLocaleString()); //毫秒并不直接显示 Date对象的方法：\nvar d = new Date(); //getDate() 获取日 //getDay () 获取星期 //getMonth () 获取月（0-11） //getFullYear () 获取完整年份 //getYear () 获取年 //getHours () 获取小时 //getMinutes () 获取分钟 //getSeconds () 获取秒 //getMilliseconds () 获取毫秒 //getTime () 返回累计毫秒数(从1970/1/1午夜) 练习：\n编写代码，将当前日期按“2017-12-27 11:11 星期三”格式输出。\nconst WEEKMAP = { 0:\u0026quot;星期天\u0026quot;, 1:\u0026quot;星期一\u0026quot;, 2:\u0026quot;星期二\u0026quot;, 3:\u0026quot;星期三\u0026quot;, 4:\u0026quot;星期四\u0026quot;, 5:\u0026quot;星期五\u0026quot;, 6:\u0026quot;星期六\u0026quot; }; //定义一个数字与星期的对应关系对象 function showTime() { var d1 = new Date(); var year = d1.getFullYear(); var month = d1.getMonth() + 1; //注意月份是从0~11 var day = d1.getDate(); var hour = d1.getHours(); var minute = d1.getMinutes() \u0026lt; 10?\u0026quot;0\u0026quot;+d1.getMinutes():d1.getMinutes(); //三元运算 var week = WEEKMAP[d1.getDay()]; //星期是从0~6 var strTime = ` ${year}-${month}-${day} ${hour}:${minute} ${week} `; console.log(strTime) }; showTime(); 详细Date对象方法：点我\nJSON对象 var str1 = '{\u0026quot;name\u0026quot;: \u0026quot;Alex\u0026quot;, \u0026quot;age\u0026quot;: 18}'; var obj1 = {\u0026quot;name\u0026quot;: \u0026quot;Alex\u0026quot;, \u0026quot;age\u0026quot;: 18}; // JSON字符串转换成对象 var obj = JSON.parse(str1); // 对象转换成JSON字符串 var str = JSON.stringify(obj1); RegExp对象 // 定义正则表达式两种方式 var reg1 = new RegExp(\u0026quot;^[a-zA-Z][a-zA-Z0-9]{5,11}\u0026quot;); var reg2 = /^[a-zA-Z][a-zA-Z0-9]{5,9}$/; // 正则校验数据 reg1.test('jason666') reg2.test('jason666') /*第一个注意事项，正则表达式中不能有空格*/ // 全局匹配 var s1 = 'egondsb dsb dsb'; s1.match(/s/) s1.match(/s/g)var reg2 = /^[a-zA-Z][a-zA-Z0-9]{5,9}$/g reg2.test('egondsb'); reg2.test('egondsb'); reg2.lastIndex; /*第二个注意事项，全局匹配时有一个lastIndex属性*/ // 校验时不传参数 var reg2 = /^[a-zA-Z][a-zA-Z0-9]{5,9}$/ reg2.test(); reg2.test(undefined); var reg3 = /undefined/; reg3.test(); 扩展阅读\nMath对象 abs(x) 返回数的绝对值。 exp(x) 返回 e 的指数。 floor(x) 对数进行下舍入。 log(x) 返回数的自然对数（底为e）。 max(x,y) 返回 x 和 y 中的最高值。 min(x,y) 返回 x 和 y 中的最低值。 pow(x,y) 返回 x 的 y 次幂。 random() 返回 0 ~ 1 之间的随机数。 round(x) 把数四舍五入为最接近的整数。 sin(x) 返回数的正弦。 sqrt(x) 返回数的平方根。 tan(x) 返回角的正切。 ","permalink":"https://zhong-er.com/posts/%E5%89%8D%E7%AB%AFjavascript/","summary":"\u003ch2 id=\"javascript引入方式\"\u003eJavaScript引入方式\u003c/h2\u003e\n\u003ch3 id=\"script标签内写代码\"\u003eScript标签内写代码\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;script\u0026gt;\n  // 在这里写你的JS代码\n\u0026lt;/script\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"引入额外的js文件\"\u003e引入额外的JS文件\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;script src=\u0026quot;myscript.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"javascript语言规范\"\u003eJavaScript语言规范\u003c/h2\u003e\n\u003ch3 id=\"注释注释是代码之母\"\u003e注释（注释是代码之母）\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003e// 这是单行注释\n\n/*\n这是\n多行注释\n*/\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"结束符\"\u003e结束符\u003c/h3\u003e\n\u003cp\u003eJavaScript中的语句要以分号（;）为结束符。\u003c/p\u003e\n\u003ch2 id=\"javascript语言基础\"\u003eJavaScript语言基础\u003c/h2\u003e\n\u003ch3 id=\"变量声明\"\u003e变量声明\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eJavaScript的变量名可以使用_，数字，字母，$组成，不能以数字开头。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e声明变量使用 var 变量名; 的格式来进行声明\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre\u003e\u003ccode\u003evar name = \u0026quot;Alex\u0026quot;;\nvar age = 18;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e注意：\u003c/p\u003e\n\u003cp\u003e变量名是区分大小写的。\u003c/p\u003e\n\u003cp\u003e推荐使用驼峰式命名规则。\u003c/p\u003e\n\u003cp\u003e保留字不能用做变量名。\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e补充：\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eES6新增了let命令，用于声明变量。其用法类似于var，但是所声明的变量只在let命令所在的代码块内有效。例如：for循环的计数器就很适合使用let命令。\u003c/em\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efor (let i=0;i\u0026lt;arr.length;i++){...}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cem\u003eES6新增const用来声明常量。一旦声明，其值就不能改变。\u003c/em\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003econst PI = 3.1415;\nPI // 3.1415\n\nPI = 3\n// TypeError: \u0026quot;PI\u0026quot; is read-only\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"javascript数据类型\"\u003eJavaScript数据类型\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eJavaScript拥有动态类型\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003evar x;  // 此时x是undefined\nvar x = 1;  // 此时x是数字\nvar x = \u0026quot;Alex\u0026quot;  // 此时x是字符串\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"数值number\"\u003e\u003cstrong\u003e数值(Number)\u003c/strong\u003e\u003c/h3\u003e\n\u003cp\u003eJavaScript不区分整型和浮点型，就只有一种数字类型。\u003c/p\u003e","title":"【前端】JavaScript"},{"content":" jQuery jQuery介绍 jQuery是一个轻量级的、兼容多浏览器的JavaScript库。\njQuery使用户能够更方便地处理HTML Document、Events、实现动画效果、方便地进行Ajax交互，能够极大地简化JavaScript编程。它的宗旨就是：“Write less, do more.“\njQuery的优势 一款轻量级的JS框架。jQuery核心js文件才几十kb，不会影响页面加载速度。\n丰富的DOM选择器,jQuery的选择器用起来很方便，比如要找到某个DOM对象的相邻元素，JS可能要写好几行代码，而jQuery一行代码就搞定了，再比如要将一个表格的隔行变色，jQuery也是一行代码搞定。\n链式表达式。jQuery的链式操作可以把多个操作写在一行代码里，更加简洁。\n事件、样式、动画支持。jQuery还简化了js操作css的代码，并且代码的可读性也比js要强。\nAjax操作支持。jQuery简化了AJAX操作，后端只需返回一个JSON格式的字符串就能完成与前端的通信。\n跨浏览器兼容。jQuery基本兼容了现在主流的浏览器，不用再为浏览器的兼容问题而伤透脑筋。\n插件扩展开发。jQuery有着丰富的第三方的插件，例如：树形菜单、日期控件、图片切换插件、弹出窗口等等基本前端页面上的组件都有对应插件，并且用jQuery插件做出来的效果很炫，并且可以根据自己需要去改写和封装插件，简单实用。\njQuery内容： 选择器\n筛选器\n样式操作\n文本操作\n属性操作\n文档处理\n事件\n动画效果\n插件\neach、data、Ajax\n下载链接：jQuery官网\n中文文档：jQuery AP中文文档\njQuery版本 1.x：兼容IE678,使用最为广泛的，官方只做BUG维护，功能不再新增。因此一般项目来说，使用1.x版本就可以了，最终版本：1.12.4 (2016年5月20日)\n2.x：不兼容IE678，很少有人使用，官方只做BUG维护，功能不再新增。如果不考虑兼容低版本的浏览器可以使用2.x，最终版本：2.2.4 (2016年5月20日)\n3.x：不兼容IE678，只支持最新的浏览器。需要注意的是很多老的jQuery插件不支持3.x版。目前该版本是官方主要更新维护的版本。\n维护IE678是一件让人头疼的事情，一般我们都会额外加载一个CSS和JS单独处理。值得庆幸的是使用这些浏览器的人也逐步减少，PC端用户已经逐步被移动端用户所取代，如果没有特殊要求的话，一般都会选择放弃对678的支持。\njQuery对象 jQuery对象就是通过jQuery包装DOM对象后产生的对象。jQuery对象是 jQuery独有的。如果一个对象是 jQuery对象，那么它就可以使用jQuery里的方法：例如$(“#i1”).html()。 $(\u0026quot;#i1\u0026quot;).html()的意思是：获取id值为 i1的元素的html代码。其中 html()是jQuery里的方法。 相当于： document.getElementById(\u0026quot;i1\u0026quot;).innerHTML; 虽然 jQuery对象是包装 DOM对象后产生的，但是 jQuery对象无法使用 DOM对象的任何方法，同理 DOM对象也没不能使用 jQuery里的方法。 一个约定，我们在声明一个jQuery对象变量的时候在变量名前面加上$： var $variable = jQuery对像 var variable = DOM对象 $variable[0]//jQuery对象转成DOM对象 拿上面那个例子举例，jQuery对象和DOM对象的使用：\n$(\u0026quot;#i1\u0026quot;).html();//jQuery对象可以使用jQuery的方法 $(\u0026quot;#i1\u0026quot;)[0].innerHTML;// DOM对象使用DOM的方法 jQuery基础语法 $(selector).action() 查找标签 基本选择器 id选择器：\n$(\u0026quot;#id\u0026quot;) 标签选择器：\n$(\u0026quot;tagName\u0026quot;) class选择器：\n$(\u0026quot;.className\u0026quot;) 配合使用：\n$(\u0026quot;div.c1\u0026quot;) // 找到有c1 class类的div标签 ****所有元素选择器：\n$(\u0026quot;*\u0026quot;) ****组合选择器：\n$(\u0026quot;#id, .className, tagName\u0026quot;) 层级选择器： x和y可以为任意选择器\n$(\u0026quot;x y\u0026quot;);// x的所有后代y（子子孙孙） $(\u0026quot;x\u0026gt; y\u0026quot;);// x的所有儿子y（儿子） $(\u0026quot;x + y\u0026quot;)// 找到所有紧挨在x后面的y $(\u0026quot;x ~ y\u0026quot;)// x之后所有的兄弟y 基本筛选器： :first // 第一个 :last // 最后一个 :eq(index)// 索引等于index的那个元素 :even // 匹配所有索引值为偶数的元素，从 0 开始计数 :odd // 匹配所有索引值为奇数的元素，从 0 开始计数 :gt(index)// 匹配所有大于给定索引值的元素 :lt(index)// 匹配所有小于给定索引值的元素 :not(元素选择器)// 移除所有满足not条件的标签 :has(元素选择器)// 选取所有包含一个或多个标签在其内的标签(指的是从后代元素找) 例子：\n$(\u0026quot;div:has(h1)\u0026quot;)// 找到所有后代中有h1标签的div标签 $(\u0026quot;div:has(.c1)\u0026quot;)// 找到所有后代中有c1样式类的div标签 $(\u0026quot;li:not(.c1)\u0026quot;)// 找到所有不包含c1样式类的li标签 $(\u0026quot;li:not(:has(a))\u0026quot;)// 找到所有后代中不含a标签的li标签 练习：\n自定义模态框，使用jQuery实现弹出和隐藏功能。\njQuery版自定义模态框\n\u0026lt;!DOCTYPE html\u0026gt;\u0026lt;html lang=\u0026quot;zh-CN\u0026quot;\u0026gt;\u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;自定义模态框\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; .cover { position: fixed; left: 0; right: 0; top: 0; bottom: 0; background-color: darkgrey; z-index: 999; } .modal { width: 600px; height: 400px; background-color: white; position: fixed; left: 50%; top: 50%; margin-left: -300px; margin-top: -200px; z-index: 1000; } .hide { display: none; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;input type=\u0026quot;button\u0026quot; value=\u0026quot;弹\u0026quot; id=\u0026quot;i0\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;cover hide\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;modal hide\u0026quot;\u0026gt; \u0026lt;label for=\u0026quot;i1\u0026quot;\u0026gt;姓名\u0026lt;/label\u0026gt; \u0026lt;input id=\u0026quot;i1\u0026quot; type=\u0026quot;text\u0026quot;\u0026gt; \u0026lt;label for=\u0026quot;i2\u0026quot;\u0026gt;爱好\u0026lt;/label\u0026gt; \u0026lt;input id=\u0026quot;i2\u0026quot; type=\u0026quot;text\u0026quot;\u0026gt; \u0026lt;input type=\u0026quot;button\u0026quot; id=\u0026quot;i3\u0026quot; value=\u0026quot;关闭\u0026quot;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; var tButton = $(\u0026quot;#i0\u0026quot;)[0]; tButton.onclick=function () { var coverEle = $(\u0026quot;.cover\u0026quot;)[0]; var modalEle = $(\u0026quot;.modal\u0026quot;)[0]; $(coverEle).removeClass(\u0026quot;hide\u0026quot;); $(modalEle).removeClass(\u0026quot;hide\u0026quot;); }; var cButton = $(\u0026quot;#i3\u0026quot;)[0]; cButton.onclick=function () { var coverEle = $(\u0026quot;.cover\u0026quot;)[0]; var modalEle = $(\u0026quot;.modal\u0026quot;)[0]; $(coverEle).addClass(\u0026quot;hide\u0026quot;); $(modalEle).addClass(\u0026quot;hide\u0026quot;); } \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 属性选择器： [attribute] [attribute=value]// 属性等于 [attribute!=value]// 属性不等于 例子：\n// 示例\u0026lt;input type=\u0026quot;text\u0026quot;\u0026gt; \u0026lt;input type=\u0026quot;password\u0026quot;\u0026gt; \u0026lt;input type=\u0026quot;checkbox\u0026quot;\u0026gt; $(\u0026quot;input[type='checkbox']\u0026quot;);// 取到checkbox类型的input标签 $(\u0026quot;input[type!='text']\u0026quot;);// 取到类型不是text的input标签 表单筛选器 ： :text :password :file:radio :checkbox :submit :reset :button 例子：\n$(\u0026quot;:checkbox\u0026quot;) // 找到所有的checkbox 表单对象属性:\n:enabled :disabled :checked :selected 例子：\n找到可用的input标签\n\u0026lt;form\u0026gt; \u0026lt;input name=\u0026quot;email\u0026quot; disabled=\u0026quot;disabled\u0026quot; /\u0026gt; \u0026lt;input name=\u0026quot;id\u0026quot; /\u0026gt; \u0026lt;/form\u0026gt; $(\u0026quot;input:enabled\u0026quot;) // 找到可用的input标签 找到被选中的option：\n\u0026lt;select id=\u0026quot;s1\u0026quot;\u0026gt; \u0026lt;option value=\u0026quot;beijing\u0026quot;\u0026gt;北京市\u0026lt;/option\u0026gt; \u0026lt;option value=\u0026quot;shanghai\u0026quot;\u0026gt;上海市\u0026lt;/option\u0026gt; \u0026lt;option selected value=\u0026quot;guangzhou\u0026quot;\u0026gt;广州市\u0026lt;/option\u0026gt; \u0026lt;option value=\u0026quot;shenzhen\u0026quot;\u0026gt;深圳市\u0026lt;/option\u0026gt; \u0026lt;/select\u0026gt;$(\u0026quot;:selected\u0026quot;) // 找到所有被选中的option 筛选器方法 下一个元素：\n$(\u0026quot;#id\u0026quot;).next() $(\u0026quot;#id\u0026quot;).nextAll() $(\u0026quot;#id\u0026quot;).nextUntil(\u0026quot;#i2\u0026quot;) 上一个元素：\n$(\u0026quot;#id\u0026quot;).prev() $(\u0026quot;#id\u0026quot;).prevAll() $(\u0026quot;#id\u0026quot;).prevUntil(\u0026quot;#i2\u0026quot;) 父亲元素：\n$(\u0026quot;#id\u0026quot;).parent() $(\u0026quot;#id\u0026quot;).parents() // 查找当前元素的所有的父辈元素 $(\u0026quot;#id\u0026quot;).parentsUntil() // 查找当前元素的所有的父辈元素，直到遇到匹配的那个元素为止。 儿子和兄弟元素：\n$(\u0026quot;#id\u0026quot;).children();// 儿子们 $(\u0026quot;#id\u0026quot;).siblings();// 兄弟们 查找\n搜索所有与指定表达式匹配的元素。这个函数是找出正在处理的元素的后代元素的好方法。\n$(\u0026quot;div\u0026quot;).find(\u0026quot;p\u0026quot;) 等价于$(\u0026ldquo;div p\u0026rdquo;)\n筛选\n筛选出与指定表达式匹配的元素集合。这个方法用于缩小匹配的范围。用逗号分隔多个表达式。\n$(\u0026quot;div\u0026quot;).filter(\u0026quot;.c1\u0026quot;) // 从结果集中过滤出有c1样式类的 等价于 $(\u0026ldquo;div.c1\u0026rdquo;)\n补充：\n.first() // 获取匹配的第一个元素 .last() // 获取匹配的最后一个元素 .not() // 从匹配元素的集合中删除与指定表达式匹配的元素 .has() // 保留包含特定后代的元素，去掉那些不含有指定后代的元素 .eq() // 索引值等于指定值的元素 尺寸： height() width() innerHeight() innerWidth() outerHeight() outerWidth() 文本操作 HTML代码：\nhtml()// 取得第一个匹配元素的html内容 html(val)// 设置所有匹配元素的html内容 文本值：\ntext()// 取得所有匹配元素的内容 text(val)// 设置所有匹配元素的内容 值：\nval()// 取得第一个匹配元素的当前值 val(val)// 设置所有匹配元素的值 val([val1, val2])// 设置多选的checkbox、多选select的值 例如：\n\u0026lt;input type=\u0026quot;checkbox\u0026quot; value=\u0026quot;basketball\u0026quot; name=\u0026quot;hobby\u0026quot;\u0026gt;篮球 \u0026lt;input type=\u0026quot;checkbox\u0026quot; value=\u0026quot;football\u0026quot; name=\u0026quot;hobby\u0026quot;\u0026gt;足球 \u0026lt;select multiple id=\u0026quot;s1\u0026quot;\u0026gt; \u0026lt;option value=\u0026quot;1\u0026quot;\u0026gt;1\u0026lt;/option\u0026gt; \u0026lt;option value=\u0026quot;2\u0026quot;\u0026gt;2\u0026lt;/option\u0026gt; \u0026lt;option value=\u0026quot;3\u0026quot;\u0026gt;3\u0026lt;/option\u0026gt; \u0026lt;/select\u0026gt; 设置值：\n$(\u0026quot;[name='hobby']\u0026quot;).val(['basketball', 'football']); $(\u0026quot;#s1\u0026quot;).val([\u0026quot;1\u0026quot;, \u0026quot;2\u0026quot;]) 示例：\n获取被选中的checkbox或radio的值：\n\u0026lt;label for=\u0026quot;c1\u0026quot;\u0026gt;女\u0026lt;/label\u0026gt; \u0026lt;input name=\u0026quot;gender\u0026quot; id=\u0026quot;c1\u0026quot; type=\u0026quot;radio\u0026quot; value=\u0026quot;0\u0026quot;\u0026gt; \u0026lt;label for=\u0026quot;c2\u0026quot;\u0026gt;男\u0026lt;/label\u0026gt; \u0026lt;input name=\u0026quot;gender\u0026quot; id=\u0026quot;c2\u0026quot; type=\u0026quot;radio\u0026quot; value=\u0026quot;1\u0026quot;\u0026gt; 可以使用：\n$(\u0026quot;input[name='gender']:checked\u0026quot;).val() 自定义登录校验示例\n\u0026lt;!DOCTYPE html\u0026gt;\u0026lt;html lang=\u0026quot;zh-CN\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;文本操作之登录验证\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; .error { color: red; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;form action=\u0026quot;\u0026quot;\u0026gt; \u0026lt;div\u0026gt; \u0026lt;label for=\u0026quot;input-name\u0026quot;\u0026gt;用户名\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; id=\u0026quot;input-name\u0026quot; name=\u0026quot;name\u0026quot;\u0026gt; \u0026lt;span class=\u0026quot;error\u0026quot;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;label for=\u0026quot;input-password\u0026quot;\u0026gt;密码\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026quot;password\u0026quot; id=\u0026quot;input-password\u0026quot; name=\u0026quot;password\u0026quot;\u0026gt; \u0026lt;span class=\u0026quot;error\u0026quot;\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;input type=\u0026quot;button\u0026quot; id=\u0026quot;btn\u0026quot; value=\u0026quot;提交\u0026quot;\u0026gt; \u0026lt;/div\u0026gt;\u0026lt;/form\u0026gt; \u0026lt;script src=\u0026quot;https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; $(\u0026quot;#btn\u0026quot;).click(function () { var username = $(\u0026quot;#input-name\u0026quot;).val(); var password = $(\u0026quot;#input-password\u0026quot;).val(); if (username.length === 0) { $(\u0026quot;#input-name\u0026quot;).siblings(\u0026quot;.error\u0026quot;).text(\u0026quot;用户名不能为空\u0026quot;); } if (password.length === 0) { $(\u0026quot;#input-password\u0026quot;).siblings(\u0026quot;.error\u0026quot;).text(\u0026quot;密码不能为空\u0026quot;); } } \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 属性操作 用于ID等或自定义属性：\nattr(attrName)// 返回第一个匹配元素的属性值 attr(attrName, attrValue)// 为所有匹配元素设置一个属性值 attr({k1: v1, k2:v2})// 为所有匹配元素设置多个属性值 removeAttr()// 从每一个匹配的元素中删除一个属性 用于checkbox和radio\nprop() // 获取属性 removeProp() // 移除属性 注意：\n在1.x及2.x版本的jQuery中使用attr对checkbox进行赋值操作时会出bug，在3.x版本的jQuery中则没有这个问题。为了兼容性，我们在处理checkbox和radio的时候尽量使用特定的prop()，不要使用attr(\u0026ldquo;checked\u0026rdquo;, \u0026ldquo;checked\u0026rdquo;)。\n\u0026lt;input type=\u0026quot;checkbox\u0026quot; value=\u0026quot;1\u0026quot;\u0026gt;\u0026lt;input type=\u0026quot;radio\u0026quot; value=\u0026quot;2\u0026quot;\u0026gt;\u0026lt;script\u0026gt; $(\u0026quot;:checkbox[value='1']\u0026quot;).prop(\u0026quot;checked\u0026quot;, true); $(\u0026quot;:radio[value='2']\u0026quot;).prop(\u0026quot;checked\u0026quot;, true);\u0026lt;/script\u0026gt; prop和attr的区别：\nattr全称attribute(属性)\nprop全称property(属性)\n虽然都是属性，但他们所指的属性并不相同，attr所指的属性是HTML标签属性，而prop所指的是DOM对象属性，可以认为attr是显式的，而prop是隐式的。\n举个例子：\n\u0026lt;input type=\u0026quot;checkbox\u0026quot; id=\u0026quot;i1\u0026quot; value=\u0026quot;1\u0026quot;\u0026gt; 针对上面的代码，\n$(\u0026quot;#i1\u0026quot;).attr(\u0026quot;checked\u0026quot;) // undefined $(\u0026quot;#i1\u0026quot;).prop(\u0026quot;checked\u0026quot;) // false 可以看到attr获取一个标签内没有的东西会得到undefined，而prop获取的是这个DOM对象的属性，因此checked为false。\n如果换成下面的代码：\n\u0026lt;input type=\u0026quot;checkbox\u0026quot; checked id=\u0026quot;i1\u0026quot; value=\u0026quot;1\u0026quot;\u0026gt; 再执行：\n$(\u0026quot;#i1\u0026quot;).attr(\u0026quot;checked\u0026quot;) // checked $(\u0026quot;#i1\u0026quot;).prop(\u0026quot;checked\u0026quot;) // true 这已经可以证明attr的局限性，它的作用范围只限于HTML标签内的属性，而prop获取的是这个DOM对象的属性，选中返回true，没选中返回false。\n接下来再看一下针对自定义属性，attr和prop又有什么区别：\n\u0026lt;input type=\u0026quot;checkbox\u0026quot; checked id=\u0026quot;i1\u0026quot; value=\u0026quot;1\u0026quot; me=\u0026quot;自定义属性\u0026quot;\u0026gt; 执行以下代码：\n$(\u0026quot;#i1\u0026quot;).attr(\u0026quot;me\u0026quot;) // \u0026quot;自定义属性\u0026quot; $(\u0026quot;#i1\u0026quot;).prop(\u0026quot;me\u0026quot;) // undefined 可以看到prop不支持获取标签的自定义属性。\n总结一下：\n对于标签上有的能看到的属性和自定义属性都用attr\n对于返回布尔值的比如checkbox、radio和option的是否被选中都用prop。\n练习题：全选、反选、取消\n文档处理 添加到指定元素内部 的后面\n$(A).append(B) // 把B追加到A $(A).appendTo(B) // 把A追加到B 添加到指定元素内部 的前面\n$(A).prepend(B) // 把B前置到A $(A).prependTo(B) // 把A前置到B 添加到指定元素外部 的后面\n$(A).after(B) // 把B放到A的后面 $(A).insertAfter(B) // 把A放到B的后面 添加到指定元素外部 的前面\n$(A).before(B) // 把B放到A的前面 $(A).insertBefore(B) // 把A放到B的前面 移除和清空元素\nremove() // 从DOM中删除所欲匹配的元素 empty() // 删除匹配的元素集合中所有的字节点 例子：\n点击按钮在表格添加一行数据。\n点击每一行的删除按钮删除当期行数据。\n替换\nreplaceWith() replaceALL() 克隆\nclone() // 参数 true 填写时，委托时间可复制js 克隆示例：\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width,initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;克隆\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; #b1 { background-color:deeppink; padding:5px; color:white; margin:5px; } #b2 { background-color:dodgerblue; padding:5px; color:white; margin:5px; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;button id=\u0026quot;b1\u0026quot;\u0026gt;屠龙宝刀，点击就送\u0026lt;/button\u0026gt; \u0026lt;hr\u0026gt; \u0026lt;button id=\u0026quot;b1\u0026quot;\u0026gt;屠龙宝刀，点击就送\u0026lt;/button\u0026gt; \u0026lt;script src=\u0026quot;jquery-3.2.1.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt;\u0026lt;script\u0026gt; // clone方法不加参数true，克隆标签但不克隆标签带的事件 $(\u0026quot;#b1\u0026quot;).on(\u0026quot;click\u0026quot;, function () { $(this).clone().insertAfter(this); }); // clone方法加参数true，克隆标签并且克隆标签带的事件 $(\u0026quot;#b2\u0026quot;).on(\u0026quot;click\u0026quot;, function () { $(this).clone(true).insertAfter(this); });\u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 事件 常用事件 click(function(){...}) hover(function(){...}) blur(function(){...}) focus(function(){...}) change(function(){...}) keyup(function(){...}) hover事件示例：\n\u0026lt;!DOCTYPE html\u0026gt;\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p\u0026gt;苍茫的天涯是我的哎，绵绵的青山脚下一片海！\u0026lt;/p\u0026gt; \u0026lt;script src=\u0026quot;jQuery-3.3.1.js\u0026quot;\u0026gt; \u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; $('p').hover( function () { alert('来啦，老弟') }, function () { alert('慢走哦～') } ) \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 实时监听input输入值变化示例：\n\u0026lt;!DOCTYPE html\u0026gt;\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;实时监听input输入值变化\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; id=\u0026quot;i1\u0026quot;\u0026gt; \u0026lt;script src=\u0026quot;jquery-3.2.1.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; /* * oninput是HTML5的标准事件 * 能够检测textarea,input:text,input:password和input:search这几个元素的内容变化， * 在内容修改后立即被触发，不像onchange事件需要失去焦点才触发 * oninput事件在IE9以下版本不支持，需要使用IE特有的onpropertychange事件替代 * 使用jQuery库的话直接使用on同时绑定这两个事件即可。 * */ $(\u0026quot;#i1\u0026quot;).on(\u0026quot;input propertychange\u0026quot;, function () { alert($(this).val()); }) \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 事件绑定 .on( events [, selector ],function(){}) events： 事件\nselector: 选择器（可选的）\nfunction: 事件处理函数\n移除事件 .off( events [, selector ][,function(){}]) off() 方法移除用 .on()绑定的事件处理程序。\nevents： 事件\nselector: 选择器（可选的）\nfunction: 事件处理函数\n阻止后续事件执行 return false; // 常见阻止表单提交等\ne.preventDefault();\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;阻止默认事件\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;form action=\u0026quot;\u0026quot;\u0026gt; \u0026lt;button id=\u0026quot;b1\u0026quot;\u0026gt;点我\u0026lt;/button\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;script src=\u0026quot;jquery-3.3.1.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; $(\u0026quot;#b1\u0026quot;).click(function (e) { alert(123); //return false; e.preventDefault(); }); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 注意：\n像click、keydown等DOM中定义的事件，我们都可以使用.on()方法来绑定事件，但是hover这种jQuery中定义的事件就不能用.on()方法来绑定了。\n想使用事件委托的方式绑定hover事件处理函数，可以参照如下代码分两步绑定事件：\n$('ul').on('mouseenter', 'li', function() {//绑定鼠标进入事件 $(this).addClass('hover'); }); $('ul').on('mouseleave', 'li', function() {//绑定鼠标划出事件 $(this).removeClass('hover'); }); 阻止事件冒泡 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;阻止事件冒泡\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div\u0026gt; \u0026lt;p\u0026gt; \u0026lt;span\u0026gt;点我\u0026lt;/span\u0026gt; \u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;jquery-3.3.1.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; $(\u0026quot;span\u0026quot;).click(function (e) { alert(\u0026quot;span\u0026quot;); e.stopPropagation(); }); $(\u0026quot;p\u0026quot;).click(function () { alert(\u0026quot;p\u0026quot;); }); $(\u0026quot;div\u0026quot;).click(function () { alert(\u0026quot;div\u0026quot;); }) \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 页面载入 当DOM载入就绪可以查询及操纵时绑定一个要执行的函数。这是事件模块中最重要的一个函数，因为它可以极大地提高web应用程序的响应速度。\n两种写法：\n$(document).ready(function(){ // 在这里写你的JS代码... }) 简写：\n$(function(){ // 你在这里写你的代码 }) 文档加载完绑定事件，并且阻止默认事件发生：\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;登录注册示例\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; .error { color: red; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;form id=\u0026quot;myForm\u0026quot;\u0026gt; \u0026lt;label for=\u0026quot;name\u0026quot;\u0026gt;姓名\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026quot;text\u0026quot; id=\u0026quot;name\u0026quot;\u0026gt; \u0026lt;span\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;label for=\u0026quot;passwd\u0026quot;\u0026gt;密码\u0026lt;/label\u0026gt; \u0026lt;input type=\u0026quot;password\u0026quot; id=\u0026quot;passwd\u0026quot;\u0026gt; \u0026lt;span\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;input type=\u0026quot;submit\u0026quot; id=\u0026quot;modal-submit\u0026quot; value=\u0026quot;登录\u0026quot;\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;script src=\u0026quot;jquery-3.2.1.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script src=\u0026quot;s7validate.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; function myValidation() { // 多次用到的jQuery对象存储到一个变量，避免重复查询文档树 var $myForm = $(\u0026quot;#myForm\u0026quot;); $myForm.find(\u0026quot;:submit\u0026quot;).on(\u0026quot;click\u0026quot;, function () { // 定义一个标志位，记录表单填写是否正常 var flag = true; $myForm.find(\u0026quot;:text, :password\u0026quot;).each(function () { var val = $(this).val(); if (val.length \u0026lt;= 0 ){ var labelName = $(this).prev(\u0026quot;label\u0026quot;).text(); $(this).next(\u0026quot;span\u0026quot;).text(labelName + \u0026quot;不能为空\u0026quot;); flag = false; } }); // 表单填写有误就会返回false，阻止submit按钮默认的提交表单事件 return flag; }); // input输入框获取焦点后移除之前的错误提示信息 $myForm.find(\u0026quot;input[type!='submit']\u0026quot;).on(\u0026quot;focus\u0026quot;, function () { $(this).next(\u0026quot;.error\u0026quot;).text(\u0026quot;\u0026quot;); }) } // 文档树就绪后执行 $(document).ready(function () { myValidation(); }); \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 与window.onload的区别 window.onload()函数有覆盖现象，必须等待着图片资源加载完成之后才能调用\njQuery的这个入口函数没有函数覆盖现象，文档加载完成之后就可以调用（建议使用此函数）\n事件委托 事件委托是通过事件冒泡的原理，利用父标签去捕获子标签的事件。\n示例：\n表格中每一行的编辑和删除按钮都能触发相应的事件。\n$(\u0026quot;table\u0026quot;).on(\u0026quot;click\u0026quot;, \u0026quot;.delete\u0026quot;, function () { // 删除按钮绑定的事件 }) 动画效果 // 基本 show([s,[e],[fn]]) hide([s,[e],[fn]]) toggle([s],[e],[fn]) // 滑动 slideDown([s],[e],[fn]) slideUp([s,[e],[fn]]) slideToggle([s],[e],[fn]) // 淡入淡出 fadeIn([s],[e],[fn]) fadeOut([s],[e],[fn]) fadeTo([[s],o,[e],[fn]]) fadeToggle([s,[e],[fn]]) // 自定义（了解即可） animate(p,[s],[e],[fn]) 自定义动画示例：\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;点赞动画示例\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; div { position: relative; display: inline-block; } div\u0026gt;i { display: inline-block; color: red; position: absolute; right: -16px; top: -5px; opacity: 1; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div id=\u0026quot;d1\u0026quot;\u0026gt;点赞\u0026lt;/div\u0026gt; \u0026lt;script src=\u0026quot;jquery-3.2.1.min.js\u0026quot;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; $(\u0026quot;#d1\u0026quot;).on(\u0026quot;click\u0026quot;, function () { var newI = document.createElement(\u0026quot;i\u0026quot;); newI.innerText = \u0026quot;+1\u0026quot;; $(this).append(newI); $(this).children(\u0026quot;i\u0026quot;).animate({ opacity: 0 }, 1000) }) \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 补充 each jQuery.each(collection, callback(indexInArray, valueOfElement))：\n描述：一个通用的迭代函数，它可以用来无缝迭代对象和数组。数组和类似数组的对象通过一个长度属性（如一个函数的参数对象）来迭代数字索引，从0到length - 1。其他对象通过其属性名进行迭代。\nli =[10,20,30,40] $.each(li,function(i, v){ console.log(i, v);//index是索引，ele是每次循环的具体元素。 }) 输出：\n010 120 230 340 .each(function(index, Element))：\n描述：遍历一个jQuery对象，为每个匹配元素执行一个函数。\n.each() 方法用来迭代jQuery对象中的每一个DOM元素。每次回调函数执行时，会传递当前循环次数作为参数(从0开始计数)。由于回调函数是在当前DOM元素为上下文的语境中触发的，所以关键字 this 总是指向这个元素。\n// 为每一个li标签添加foo $(\u0026quot;li\u0026quot;).each(function(){ $(this).addClass(\u0026quot;c1\u0026quot;); }); 注意: jQuery的方法返回一个jQuery对象，遍历jQuery集合中的元素 - 被称为隐式 迭代 的过程。当这种情况发生时，它通常不需要显式地循环的 .each()方法：\n也就是说，上面的例子没有必要使用each()方法，直接像下面这样写就可以了：\n$(\u0026quot;li\u0026quot;).addClass(\u0026quot;c1\u0026quot;); // 对所有标签做统一操作 注意：\n在遍历过程中可以使用 return false提前结束each循环。\n终止each循环\nreturn false； 伏笔\u0026hellip;\n.data() 在匹配的元素集合中的所有元素上存储任意相关数据或返回匹配的元素集合中的第一个元素的给定名称的数据存储的值。\n.data(key, value):\n描述：在匹配的元素上存储任意相关数据。\n$(\u0026quot;div\u0026quot;).data(\u0026quot;k\u0026quot;,100);//给所有div标签都保存一个名为k，值为100 .data(key):\n描述: 返回匹配的元素集合中的第一个元素的给定名称的数据存储的值—通过 .data(name, value)或 HTML5 data-*属性设置。\n$(\u0026quot;div\u0026quot;).data(\u0026quot;k\u0026quot;);//返回第一个div标签中保存的\u0026quot;k\u0026quot;的值 .removeData(key):\n描述：移除存放在元素上的数据，不加key参数表示移除所有保存的数据。\n$(\u0026quot;div\u0026quot;).removeData(\u0026quot;k\u0026quot;); //移除元素上存放k对应的数据 示例：\n模态框编辑的数据回填表格\n插件(了解即可) jQuery.extend(object)\njQuery的命名空间下添加新的功能。多用于插件开发者向 jQuery 中添加新函数时使用。\n示例：\n\u0026lt;script\u0026gt; jQuery.extend({ min:function(a, b){return a \u0026lt; b ? a : b;}, max:function(a, b){return a \u0026gt; b ? a : b;} }); jQuery.min(2,3);// =\u0026gt; 2 jQuery.max(4,5);// =\u0026gt; 5 \u0026lt;/script\u0026gt; jQuery.fn.extend(object)\n一个对象的内容合并到jQuery的原型，以提供新的jQuery实例方法。\n\u0026lt;script\u0026gt; jQuery.fn.extend({ check:function(){ return this.each(function(){this.checked =true;}); }, uncheck:function(){ return this.each(function(){this.checked =false;}); } }); // jQuery对象可以使用新添加的check()方法了。 $(\u0026quot;input[type='checkbox']\u0026quot;).check(); \u0026lt;/script\u0026gt; 单独写在文件中的扩展：\n(function(jq){ jq.extend({ funcName:function(){ ... }, }); })(jQuery); ","permalink":"https://zhong-er.com/posts/%E5%89%8D%E7%AB%AFjquery/","summary":"\u003ch1 id=\"jquerypng\"\u003e\u003cimg alt=\"jQuery.png\" loading=\"lazy\" src=\"/assets/201909106733_5460.png\"\u003e\u003c/h1\u003e\n\u003ch1 id=\"jquery\"\u003ejQuery\u003c/h1\u003e\n\u003ch2 id=\"jquery介绍\"\u003ejQuery介绍\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003ejQuery是一个轻量级的、兼容多浏览器的JavaScript库。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ejQuery使用户能够更方便地处理HTML Document、Events、实现动画效果、方便地进行Ajax交互，能够极大地简化JavaScript编程。它的宗旨就是：“Write less, do more.“\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"jquery的优势\"\u003ejQuery的优势\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e一款轻量级的JS框架。jQuery核心js文件才几十kb，不会影响页面加载速度。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e丰富的DOM选择器,jQuery的选择器用起来很方便，比如要找到某个DOM对象的相邻元素，JS可能要写好几行代码，而jQuery一行代码就搞定了，再比如要将一个表格的隔行变色，jQuery也是一行代码搞定。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e链式表达式。jQuery的链式操作可以把多个操作写在一行代码里，更加简洁。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e事件、样式、动画支持。jQuery还简化了js操作css的代码，并且代码的可读性也比js要强。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eAjax操作支持。jQuery简化了AJAX操作，后端只需返回一个JSON格式的字符串就能完成与前端的通信。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e跨浏览器兼容。jQuery基本兼容了现在主流的浏览器，不用再为浏览器的兼容问题而伤透脑筋。\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e插件扩展开发。jQuery有着丰富的第三方的插件，例如：树形菜单、日期控件、图片切换插件、弹出窗口等等基本前端页面上的组件都有对应插件，并且用jQuery插件做出来的效果很炫，并且可以根据自己需要去改写和封装插件，简单实用。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"jquery内容\"\u003ejQuery内容：\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e选择器\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e筛选器\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e样式操作\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e文本操作\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e属性操作\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e文档处理\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e事件\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e动画效果\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e插件\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eeach、data、Ajax\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e下载链接：\u003ca href=\"https://jquery.com/\"\u003ejQuery官网\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e中文文档：\u003ca href=\"http://jquery.cuishifeng.cn/\"\u003ejQuery AP中文文档\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"jquery版本\"\u003ejQuery版本\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e1.x：兼容IE678,使用最为广泛的，官方只做BUG维护，功能不再新增。因此一般项目来说，使用1.x版本就可以了，最终版本：1.12.4 (2016年5月20日)\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e2.x：不兼容IE678，很少有人使用，官方只做BUG维护，功能不再新增。如果不考虑兼容低版本的浏览器可以使用2.x，最终版本：2.2.4 (2016年5月20日)\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e3.x：不兼容IE678，只支持最新的浏览器。需要注意的是很多老的jQuery插件不支持3.x版。目前该版本是官方主要更新维护的版本。\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cem\u003e维护IE678是一件让人头疼的事情，一般我们都会额外加载一个CSS和JS单独处理。值得庆幸的是使用这些浏览器的人也逐步减少，PC端用户已经逐步被移动端用户所取代，如果没有特殊要求的话，一般都会选择放弃对678的支持。\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"jquery对象\"\u003ejQuery对象\u003c/h2\u003e\n\u003cpre\u003e\u003ccode\u003ejQuery对象就是通过jQuery包装DOM对象后产生的对象。jQuery对象是 jQuery独有的。如果一个对象是 jQuery对象，那么它就可以使用jQuery里的方法：例如$(“#i1”).html()。\n$(\u0026quot;#i1\u0026quot;).html()的意思是：获取id值为 i1的元素的html代码。其中 html()是jQuery里的方法。\n相当于： document.getElementById(\u0026quot;i1\u0026quot;).innerHTML;\n虽然 jQuery对象是包装 DOM对象后产生的，但是 jQuery对象无法使用 DOM对象的任何方法，同理 DOM对象也没不能使用 jQuery里的方法。\n一个约定，我们在声明一个jQuery对象变量的时候在变量名前面加上$：\n\n\nvar $variable = jQuery对像\nvar variable = DOM对象\n$variable[0]//jQuery对象转成DOM对象\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e拿上面那个例子举例，jQuery对象和DOM对象的使用：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e$(\u0026quot;#i1\u0026quot;).html();//jQuery对象可以使用jQuery的方法\n$(\u0026quot;#i1\u0026quot;)[0].innerHTML;// DOM对象使用DOM的方法\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"jquery基础语法\"\u003ejQuery基础语法\u003c/h2\u003e\n\u003cpre\u003e\u003ccode\u003e$(selector).action()\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"查找标签\"\u003e查找标签\u003c/h2\u003e\n\u003ch3 id=\"基本选择器\"\u003e基本选择器\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003eid选择器：\u003c/strong\u003e\u003c/p\u003e","title":"【前端】JQuery"},{"content":"1. CSS介绍\nCSS（Cascading Style Sheet，层叠样式表)定义如何显示HTML元素。\n当浏览器读到一个样式表，它就会按照这个样式表来对文档进行格式化（渲染）。\n2. CSS语法\n2.1 CSS实例\n每个CSS样式由两个组成部分：选择器和声明。声明又包括属性和属性值。每个声明之后用分号结束。\n2.2 CSS注释\n/*这是注释*/ 3. CSS的几种引入方式\n3.1 行内样式\n行内式是在标记的style属性中设定CSS样式。不推荐大规模使用。\n\u0026lt;p style=\u0026quot;color: red\u0026quot;\u0026gt;Hello world.\u0026lt;/p\u0026gt; 3.2内部样式\n嵌入式是将CSS样式集中写在网页的标签对的标签对中。格式如下：\n\u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; p{ background-color: #2b99ff; } \u0026lt;/style\u0026gt;\u0026lt;/head\u0026gt; 3.3 外部样式\n外部样式就是将css写在一个单独的文件中，然后在页面进行引入即可。推荐使用此方式。\n\u0026lt;link href=\u0026quot;mystyle.css\u0026quot; rel=\u0026quot;stylesheet\u0026quot; type=\u0026quot;text/css\u0026quot;/\u0026gt; 4. CSS选择器\n4.1 基本选择器\n元素选择器\n/*元素选择器*/ p {color: \u0026quot;red\u0026quot;;} /*ID选择器*/ #i1 { background-color: red; } /*类选择器*/ .c1 { font-size: 14px; } p.c1 { color: red; } /*注意： 样式类名不要用数字开头（有的浏览器不认）。 标签中的class属性如果有多个，要用空格分隔。*/ /*通用选择器*/ * { color: white; } 4.2 组合选择器\n/*后代选择器*/ /*li内部的a标签设置字体颜色*/li a { color: green; } /*儿子选择器*/ /*选择所有父级是 \u0026lt;div\u0026gt; 元素的 \u0026lt;p\u0026gt; 元素*/div\u0026gt;p { font-family: \u0026quot;Arial Black\u0026quot;, arial-black, cursive; } /*毗邻选择器*/ /*选择所有紧接着\u0026lt;div\u0026gt;元素之后的\u0026lt;p\u0026gt;元素*/div+p { margin: 5px; } /*弟弟选择器*/ /*i1后面所有的兄弟p标签*/#i1~p { border: 2px solid royalblue; } 4.3 属性选择器\n/*用于选取带有指定属性的元素。*/p[title] { color: red; }/*用于选取带有指定属性和值的元素。*/p[title=\u0026quot;213\u0026quot;] { color: green; } /*不怎么常用的属性选择器*/ /*找到所有title属性以hello开头的元素*/ [title^=\u0026quot;hello\u0026quot;] { color: red; } /*找到所有title属性以hello结尾的元素*/ [title$=\u0026quot;hello\u0026quot;] { color: yellow; } /*找到所有title属性中包含（字符串包含）hello的元素*/ [title*=\u0026quot;hello\u0026quot;] { color: red; } /*找到所有title属性(有多个值或值以空格分割)中有一个值为hello的元素：*/ [title~=\u0026quot;hello\u0026quot;] { color: green; } 4.4 分组和嵌套\n分组\n当多个元素的样式相同的时候，我们没有必要重复地为每个元素都设置样式，我们可以通过在多个选择器之间使用逗号分隔的分组选择器来统一设置元素样式。\ndiv, p { color: red; } 上面的代码为div标签和p标签统一设置字体为红色。\n通常，我们会分两行来写，更清晰:\ndiv, p { color: red; } 嵌套\n多种选择器可以混合起来使用，比如：.c1类内部所有p标签设置字体颜色为红色。\n.c1 p { color: red; } 4|5伪类选择器 4.5 伪类选择器\n/* 未访问的链接 */ a:link { color: #FF0000 } /* 鼠标移动到链接上 */ a:hover { color: #FF00FF } /* 选定的链接 */ a:active { color: #0000FF } /* 已访问的链接 */ a:visited { color: #00FF00 } /*input输入框获取焦点时样式*/ input:focus { outline: none; background-color: #eee; } 4.6 伪元素选择器\nfirst_letter 常用的给首字母设置特殊样式\np:first-letter { font-size: 48px; color: red; } before\n/*在每个\u0026lt;p\u0026gt;元素之前插入内容*/p:before { content:\u0026quot;*\u0026quot;; color:red; } after\n/*在每个\u0026lt;p\u0026gt;元素之后插入内容*/p:after { content:\u0026quot;[?]\u0026quot;; color:blue; } before和after多用于清除浮动。\n4.7 选择器的优先级\nCSS继承\n继承是CSS的一个主要特征，它是依赖于祖先-后代的关系的。继承是一种机制，它允许样式不仅可以应用于某个特定的元素，还可以应用于它的后代。例如一个body定义了的字体颜色值也会应用到段落的文本中。\nbody { color: red; } 此时页面上所有标签都会继承body的字体颜色。然而CSS继承性的权重是非常低的，是比普通元素的权重还要低的0。\n我们只要给对应的标签设置字体颜色就可覆盖掉它继承的样式。\np { color: green; } 此外，继承是CSS重要的一部分，我们甚至不用去考虑它为什么能够这样，但CSS继承也是有限制的。有一些属性不能被继承，如：border, margin, padding, background等。\n选择器的优先级\n我们上面学了很多的选择器，也就是说在一个HTML页面中有很多种方式找到一个元素并且为其设置样式，那浏览器根据什么来决定应该应用哪个样式呢？\n其实是按照不同选择器的权重来决定的，具体的选择器权重计算方式如下图：\n除此之外还可以通过添加 !important方式来强制让样式生效，但并不推荐使用。因为如果过多的使用!important会使样式文件混乱不易维护。\n万不得已可以使用!important\n5. CSS属性相关\n5.1宽和高\nwidth属性可以为元素设置宽度。\nheight属性可以为元素设置高度。\n块级标签才能设置宽度，内联标签的宽度由内容来决定。\n5.2字体属性\n文字字体\nfont-family可以把多个字体名称作为一个“回退”系统来保存。如果浏览器不支持第一个字体，则会尝试下一个。浏览器会使用它可识别的第一个值。\n简单实例\nbody { font-family: \u0026quot;Microsoft Yahei\u0026quot;, \u0026quot;微软雅黑\u0026quot;, \u0026quot;Arial\u0026quot;, sans-serif} 字体大小\np { font-size: 14px; } 如果设置成inherit表示继承父元素的字体大小值。\n字重(粗细)\nfont-weight用来设置字体的字重（粗细）。\n值 描述 normal 默认值，标准粗细 bold 粗体 bolder 更粗 lighter 更细 100~900 设置具体粗细，400等同于normal，而700等同于bold inherit 继承父元素字体的粗细值 文本颜色\n颜色属性被用来设置文字的颜色。\n颜色是通过CSS最经常的指定：\n十六进制值 - 如: ＃FF0000\n一个RGB值 - 如: RGB(255,0,0)\n颜色的名称 - 如: red\n还有rgba(255,0,0,0.3)，第四个值为alpha, 指定了色彩的透明度/不透明度，它的范围为0.0到1.0之间。\n**5.3文字属性\n**\n文字对齐\ntext-align 属性规定元素中的文本的水平对齐方式。\n值 描述 left 左边对齐 默认值 right 右对齐 center 居中对齐 justify 两端对齐 文字装饰\ntext-decoration 属性用来给文字添加特殊效果。\n值 描述 none 默认。定义标准的文本。 underline 定义文本下的一条线。 overline 定义文本上的一条线。 line-through 定义穿过文本下的一条线。 inherit 继承父元素的text-decoration属性的值。 常用的为去掉a标签默认的自划线：\na { text-decoration: none; } 首行缩进\n将段落的第一行缩进 32像素：\np { text-indent: 32px; } 5.4背景属性\n/*背景颜色*/ background-color: red; /*背景图片*/ background-image: url('1.jpg'); /* 背景重复 repeat(默认):背景图片平铺排满整个网页 repeat-x：背景图片只在水平方向上平铺 repeat-y：背景图片只在垂直方向上平铺 no-repeat：背景图片不平铺 */ background-repeat: no-repeat; /*背景位置*/ background-position: left top; /*background-position: 200px 200px;*/ 支持简写：\nbackground:#336699 url('1.png') no-repeat left top; 使用背景图片的一个常见案例就是很多网站会把很多小图标放在一张图片上，然后根据位置去显示图片。减少频繁的图片请求。\n参考链接点我\n一个有趣的例子：\n\u0026lt;!DOCTYPE html\u0026gt;\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt;\u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1.0\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;X-UA-Compatible\u0026quot; content=\u0026quot;ie=edge\u0026quot;\u0026gt; \u0026lt;title\u0026gt;滚动背景图示例\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; * { margin: 0; } .box { width: 100%; height: 500px; background: url(\u0026quot;assets/baidu_zhidao_450.png3aa0dd4ea/4a36acaf2edda3cc5c5bdd6409e93901213f9232.jpg\u0026quot;) center center; background-attachment: fixed; } .d1 { height: 500px; background-color: tomato; } .d2 { height: 500px; background-color: steelblue; } .d3 { height: 500px; background-color: mediumorchid; } \u0026lt;/style\u0026gt;\u0026lt;/head\u0026gt;\u0026lt;body\u0026gt; \u0026lt;div class=\u0026quot;d1\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;box\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;d2\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;d3\u0026quot;\u0026gt;\u0026lt;/div\u0026gt;\u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt; 5.5边框\n边框属性\nborder-width\nborder-style\nborder-color\n#i1 { border-width: 2px; border-style: solid; border-color: red; }\n通常使用简写方式：\n#i1 { border: 2px solid red; } 边框样式\n值 描述 none 无边框。 dotted 点状虚线边框。 dashed 矩形虚线边框。 solid 实线边框。 除了可以统一设置边框外还可以单独为某一个边框设置样式，如下所示：\n#i1 { border-top-style:dotted; border-top-color: red; border-right-style:solid; border-bottom-style:dotted; border-left-style:none; } 5.6 border-radius\n用这个属性能实现圆角边框的效果。\n将border-radius设置为长或高的一半即可得到一个圆形。\n5.7 display属性\n用于控制HTML元素的显示效果。\n值 意义 display:\u0026ldquo;none\u0026rdquo; HTML文档中元素存在，但是在浏览器中不显示。一般用于配合JavaScript代码使用。 display:\u0026ldquo;block\u0026rdquo; 默认占满整个页面宽度，如果设置了指定宽度，则会用margin填充剩下的部分。 display:\u0026ldquo;inline\u0026rdquo; 按行内元素显示，此时再设置元素的width、height、margin-top、margin-bottom和float属性都不会有什么影响。 display:\u0026ldquo;inline-block\u0026rdquo; 使元素同时具有行内元素和块级元素的特点。 display:\u0026ldquo;none\u0026quot;与visibility:hidden的区别：\nvisibility:hidden: 可以隐藏某个元素，但隐藏的元素仍需占用与未隐藏之前一样的空间。也就是说，该元素虽然被隐藏了，但仍然会影响布局。\ndisplay:none: 可以隐藏某个元素，且隐藏的元素不会占用任何空间。也就是说，该元素不但被隐藏了，而且该元素原本占用的空间也会从页面布局中消失。\n6. CSS盒子模型**\n**\nmargin: 用于控制元素与元素之间的距离；margin的最基本用途就是控制元素周围空间的间隔，从视觉角度上达到相互隔开的目的。\npadding: 用于控制内容与边框之间的距离；\nBorder(边框): 围绕在内边距和内容外的边框。\nContent(内容): 盒子的内容，显示文本和图像。\n看图吧：\n6.1 margin外边框\n.margin-test { margin-top:5px; margin-right:10px; margin-bottom:15px; margin-left:20px; } 推荐使用简写：\n.margin-test { margin: 5px 10px 15px 20px; } 顺序：上右下左\n常见居中：\n.mycenter { margin: 0 auto; } 6.2 padding内填充\n.padding-test { padding-top: 5px; padding-right: 10px; padding-bottom: 15px; padding-left: 20px; } 推荐使用简写：\n.padding-test { padding: 5px 10px 15px 20px; } 顺序：上右下左\n补充padding的常用简写方式：\n提供一个，用于四边；\n提供两个，第一个用于上－下，第二个用于左－右；\n如果提供三个，第一个用于上，第二个用于左－右，第三个用于下；\n提供四个参数值，将按上－右－下－左的顺序作用于四边；\n6.3 float\n在 CSS 中，任何元素都可以浮动。\n浮动元素会生成一个块级框，而不论它本身是何种元素。\n关于浮动的两个特点：\n浮动的框可以向左或向右移动，直到它的外边缘碰到包含框或另一个浮动框的边框为止。\n由于浮动框不在文档的普通流中，所以文档的普通流中的块框表现得就像浮动框不存在一样。\n三种取值\nleft：向左浮动\nright：向右浮动\nnone：默认值，不浮动\n参考示例\n6.4 clear\nclear属性规定元素的哪一侧不允许其他浮动元素。\n值 描述 left 在左侧不允许浮动元素。 right 在右侧不允许浮动元素。 both 在左右两侧均不允许浮动元素。 none 默认值。允许浮动元素出现在两侧。 inherit 规定应该从父元素继承 clear 属性的值。 注意：clear属性只会对自身 起作用，而不会影响其他元素。**\n**\n清除浮动 清除浮动的副作用（父标签塌陷问题）\n主要有三种方式：\n固定高度\n伪元素清除法\noverflow:hidden\n伪元素清除法（使用较多）：\n.clearfix:after { content: \u0026quot;\u0026quot;; display: block; clear: both; } 6.5 overflow溢出属性\n值 描述 visible 默认值。内容不会被修剪，会呈现在元素框之外。 hidden 内容会被修剪，并且其余内容是不可见的。 scroll 内容会被修剪，但是浏览器会显示滚动条以便查看其余的内容。 auto 如果内容被修剪，则浏览器会显示滚动条以便查看其余的内容。 inherit 规定应该从父元素继承 overflow 属性的值。 overflow（水平和垂直均设置）\noverflow-x（设置水平方向）\noverflow-y（设置垂直方向）\n圆形头像示例 \u0026lt;!DOCTYPE HTML\u0026gt;\u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;圆形的头像示例\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; * { margin: 0; padding: 0; background-color: #eeeeee; } .header-img { width: 150px; height: 150px; border: 3px solid white; border-radius: 50%; overflow: hidden; } .header-img\u0026gt;img { width: 100%; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div class=\u0026quot;header-img\u0026quot;\u0026gt; \u0026lt;img src=\u0026quot;assets/cnblogs_avatar_1342004.png\u0026quot; alt=\u0026quot;\u0026quot;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 6.6定位(position)\nstatic\nstatic 默认值，无定位，不能当作绝对定位的参照物，并且设置标签对象的left、top等值是不起作用的的。\nrelative（相对定位） 相对定位是相对于该元素在文档流中的原始位置，即以自己原始位置为参照物。有趣的是，即使设定了元素的相对定位以及偏移值，元素还占有着原来的位置，即占据文档流空间。对象遵循正常文档流，但将依据top，right，bottom，left等属性在正常文档流中偏移位置。而其层叠通过z-index属性定义。\n注意：position：relative的一个主要用法：方便绝对定位元素找到参照物。\nabsolute（绝对定位） 定义：设置为绝对定位的元素框从文档流完全删除，并相对于最近的已定位祖先元素定位，如果元素没有已定位的祖先元素，那么它的位置相对于最初的包含块（即body元素）。元素原先在正常文档流中所占的空间会关闭，就好像该元素原来不存在一样。元素定位后生成一个块级框，而不论原来它在正常流中生成何种类型的框。\n重点：如果父级设置了position属性，例如position:relative;，那么子元素就会以父级的左上角为原始点进行定位。这样能很好的解决自适应网站的标签偏离问题，即父级为自适应的，那我子元素就设置position:absolute;父元素设置position:relative;，然后Top、Right、Bottom、Left用百分比宽度表示。\n另外，对象脱离正常文档流，使用top，right，bottom，left等属性进行绝对定位。而其层叠通过z-index属性定义。\n\u0026lt;!DOCTYPE html\u0026gt;\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt;\u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;绝对定位\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; .c1 { height: 100px; width: 100px; background-color: red; float: left; } .c2 { height: 50px; width: 50px; background-color: #ff6700; float: right; margin-right: 400px; position: relative; } .c3 { height: 200px; width: 200px; background-color: green; position: absolute; top: 50px; } \u0026lt;/style\u0026gt;\u0026lt;/head\u0026gt;\u0026lt;body\u0026gt;\u0026lt;div class=\u0026quot;c1\u0026quot;\u0026gt;\u0026lt;/div\u0026gt;\u0026lt;div class=\u0026quot;c2\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;c3\u0026quot;\u0026gt;\u0026lt;/div\u0026gt;\u0026lt;/div\u0026gt;\u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt; fixed（固定）\nfixed：对象脱离正常文档流，使用top，right，bottom，left等属性以窗口为参考点进行定位，当出现滚动条时，对象不会随着滚动。而其层叠通过z-index属性 定义。 注意点： 一个元素若设置了 position:absolute | fixed; 则该元素就不能设置float。这 是一个常识性的知识点，因为这是两个不同的流，一个是浮动流，另一个是“定位流”。但是 relative 却可以。因为它原本所占的空间仍然占据文档流。\n在理论上，被设置为fixed的元素会被定位于浏览器窗口的一个指定坐标，不论窗口是否滚动，它都会固定在这个位置。\n示例代码：\n\u0026lt;!DOCTYPE html\u0026gt;\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt;\u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;返回顶部示例\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; * { margin: 0; } .d1 { height: 1000px; background-color: #eeee; } .scrollTop { background-color: darkgrey; padding: 10px; text-align: center; position: fixed; right: 10px; bottom: 20px; } \u0026lt;/style\u0026gt;\u0026lt;/head\u0026gt;\u0026lt;body\u0026gt;\u0026lt;div class=\u0026quot;d1\u0026quot;\u0026gt;111\u0026lt;/div\u0026gt;\u0026lt;div class=\u0026quot;scrollTop\u0026quot;\u0026gt;返回顶部\u0026lt;/div\u0026gt;\u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt; 是否脱离文档流\n相对定位\n\u0026lt;!DOCTYPE html\u0026gt;\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;style\u0026gt; .c1 { height: 50px; width: 100px; background-color: dodgerblue; } .c2 { height: 100px; width: 50px; background-color: orange; position: relative; left: 100px; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div class=\u0026quot;c1\u0026quot;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;c2\u0026quot;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div style=\u0026quot;height: 100px;width: 200px;background-color: black\u0026quot;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 绝对定位\n\u0026lt;!DOCTYPE html\u0026gt;\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;style\u0026gt; .c1 { height: 50px; width: 100px; background-color: red; position: relative; } .c2 { height: 50px; width: 200px; background-color: green; position: absolute; left: 50px; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div class=\u0026quot;c1\u0026quot;\u0026gt;购物车 \u0026lt;div class=\u0026quot;c2\u0026quot;\u0026gt;空空如也~\u0026lt;/div\u0026gt; \u0026lt;div style=\u0026quot;height: 50px;width: 100px;background-color: deeppink\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 固定定位\n\u0026lt;!DOCTYPE html\u0026gt;\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div class=\u0026quot;c1\u0026quot; style=\u0026quot;height: 50px;width: 500px;background-color: black\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;c2\u0026quot; style=\u0026quot;height: 50px;width: 100px;background-color: deeppink;position: fixed;right: 10px;bottom: 20px\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;c3\u0026quot; style=\u0026quot;height: 10px;width: 100px;background-color: green\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 上述例子可知：\n脱离文档流：\n绝对定位\n固定定位\n不脱离文档流：\n相对定位\n6.7 z-index\n#i2 { z-index: 999; } 设置对象的层叠顺序。\nz-index 值表示谁压着谁，数值大的压盖住数值小的，\n只有定位了的元素，才能有z-index,也就是说，不管相对定位，绝对定位，固定定位，都可以使用z-index，而浮动元素不能使用z-index\nz-index值没有单位，就是一个正整数，默认的z-index值为0如果大家都没有z-index值，或者z-index值一样，那么谁写在HTML后面，谁在上面压着别人，定位了元素，永远压住没有定位的元素。\n从父现象：父亲怂了，儿子再牛逼也没用\n自定义模态框示例\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1\u0026quot;\u0026gt; \u0026lt;title\u0026gt;自定义模态框\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; .cover { background-color: rgba(0,0,0,0.65); position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 998; } .modal { background-color: white; position: fixed; width: 600px; height: 400px; left: 50%; top: 50%; margin: -200px 0 0 -300px; z-index: 1000; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 6.8 opacity\n用来定义透明效果。取值范围是0~1，0是完全透明，1是完全不透明。\n","permalink":"https://zhong-er.com/posts/%E5%89%8D%E7%AB%AFcss/","summary":"\u003cp\u003e\u003cstrong\u003e1. CSS介绍\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eCSS（Cascading Style Sheet，层叠样式表)定义如何显示HTML元素。\u003c/p\u003e\n\u003cp\u003e当浏览器读到一个样式表，它就会按照这个样式表来对文档进行格式化（渲染）。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. CSS语法\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2.1 CSS实例\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e每个CSS样式由两个组成部分：选择器和声明。声明又包括属性和属性值。每个声明之后用分号结束。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"867021-20171215115756808-909989248.png\" loading=\"lazy\" src=\"/assets/201909042855_2638.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2.2 CSS注释\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e/*这是注释*/\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e3. CSS的几种引入方式\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3.1 行内样式\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e行内式是在标记的style属性中设定CSS样式。不推荐大规模使用。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;p style=\u0026quot;color: red\u0026quot;\u0026gt;Hello world.\u0026lt;/p\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e3.2内部样式\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e嵌入式是将CSS样式集中写在网页的\u003chead\u003e\u003c/head\u003e标签对的\u003cstyle\u003e\u003c/style\u003e标签对中。格式如下：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;head\u0026gt;\n    \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt;\n    \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt;\n    \u0026lt;style\u0026gt;\n        p{\n            background-color: #2b99ff;\n        }\n    \u0026lt;/style\u0026gt;\u0026lt;/head\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e3.3 外部样式\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e外部样式就是将css写在一个单独的文件中，然后在页面进行引入即可。推荐使用此方式。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026lt;link href=\u0026quot;mystyle.css\u0026quot; rel=\u0026quot;stylesheet\u0026quot; type=\u0026quot;text/css\u0026quot;/\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e4. CSS选择器\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4.1 基本选择器\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e元素选择器\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e/*元素选择器*/\np {color: \u0026quot;red\u0026quot;;}\n\n/*ID选择器*/\n#i1 {\n  background-color: red;\n}\n\n/*类选择器*/\n.c1 {\n  font-size: 14px;\n}\np.c1 {\n  color: red;\n}\n/*注意：\n样式类名不要用数字开头（有的浏览器不认）。\n标签中的class属性如果有多个，要用空格分隔。*/\n\n/*通用选择器*/\n* {\n  color: white;\n}\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e4.2 组合选择器\u003c/strong\u003e\u003c/p\u003e","title":"【前端】CSS"},{"content":"\nHTML思维导图\nWeb服务本质\n1.浏览器发请求 \u0026ndash;\u0026gt; HTTP协议 \u0026ndash;\u0026gt;\n2.服务端接收请求 \u0026ndash;\u0026gt;\n3.服务端返回响应 \u0026ndash;\u0026gt; 服务端把HTML文件内容发给浏览器 \u0026ndash;\u0026gt;\n4.浏览器渲染页面\nimport socket sk = socket.socket() sk.bind((\u0026quot;127.0.0.1\u0026quot;, 8080)) sk.listen(5) while True: conn, addr = sk.accept() data = conn.recv(8096) conn.send(b\u0026quot;HTTP/1.1 200 OK\\r\\n\\r\\n\u0026quot;) conn.send(b\u0026quot;\u0026lt;h1\u0026gt;Hello world!\u0026lt;/h1\u0026gt;\u0026quot;) conn.close() HTTP协议\n超文本传输协议，规定了浏览器与服务端之间消息传输的数据格式\n四大特性：\n1.基于请求响应\n2.基于TCP/IP之上的作用于应用层的协议\n3.无状态(服务端无法保存用户的状态)\n4.无连接(请求一次响应一次，之后立刻断开连接)\nwebsocket相当于HTTP协议的一个补丁，它支持长连接\n请求数据格式\n请求首行(表示HTTP协议版本，当前请求方式)\n请求头(一大堆k,v键值对)\n请求体(携带的是一些敏感信息比如密码、身份证号\u0026hellip;)\n响应数据格式\n响应首行(标识HTTP协议版本,响应状态码)\n响应头(一大堆k,v键值对)\n响应体(返回给浏览器页面的数据 通常响应体都是html页面)\n响应状态码\n用一串简单的数字来表示一些复杂的状态或者提示信息\n1XX:服务端已经成功接收了你的数据正在处理 你可以继续提交额外的数据\n2XX:服务端成功响应 你想要的数据(请求成功200)\n3XX:重定向(当你在访问一个需要登录之后才能访问的页面 你会发现窗口会自动调到登录页面 301 302)\n4XX:请求错误(请求资源不存在404,请求不合法不符合内部规定会权限不够403)\n5XX:服务器内部错误(500)\n请求方式\nget请求(向服务端要资源）\npost请求(向服务端提交数据)\nURL：统一资源定位符(网址)\nURL地址由4部分组成\n第1部分：为协议：http://、ftp://等\n第2部分：为站点地址：可以是域名或IP地址\n第3部分：为页面在站点中的目录：stu\n第4部分：为页面名称，例如 index.html 各部分之间用“/”符号隔开。\nHTML是什么？\n超文本标记语言（Hypertext Markup Language, HTML）是一种用于创建网页的标记语言。\n本质上是浏览器可识别的规则，我们按照规则写网页，浏览器根据规则渲染我们的网页。对于不同的浏览器，对同一个标签可能会有不同的解释(兼容性问题)。浏览器只认识html/css/js\n网页文件的扩展名：.html或.htm\nHTML不是什么？\nHTML是一种标记语言（markup language），它不是一种编程语言。\nHTML使用标签来描述网页。\nHTML文档结构\n基本结构\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026quot;zh-CN\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;css样式优先级\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; **\u003c!DOCTYPE html\u003e**声明为HTML5文档。\n\u0026lt; html\u0026gt;、**\u0026lt; /html\u0026gt;**是文档的开始标记和结束的标记。是HTML页面的根元素，在它们之间是文档的头部（head）和主体（body）。\n\u0026lt; head\u0026gt;、**\u0026lt; /head\u0026gt;**定义了HTML文档的开头部分。它们之间的内容不会在浏览器的文档窗口显示。包含了文档的元（meta）数据。\n\u0026lt; title**\u0026gt;** 、**\u0026lt; /title\u0026gt;**定义了网页标题，在浏览器标题栏显示。\n\u0026lt; body\u0026gt;、**\u0026lt; /body\u0026gt;**之间的文本是可见的网页主体内容。\n注意：对于中文网页需要使用 \u0026lt; meta charset=\u0026ldquo;utf-8\u0026rdquo;\u0026gt; 声明编码，否则会出现乱码。有些浏览器会设置 GBK 为默认编码，则你需要设置为** **。\nHTML标签格式\nHTML标签是由尖括号包围的关键字，如**\u0026lt; html\u0026gt;, **等,标签第一种分类方式：按单双标签分类\n1.双标签：HTML标签通常是成对出现的，比如：\u0026lt; div\u0026gt;和\u0026lt; /div\u0026gt;，第一个标签是开始，第二个标签是结束。结束标签会有斜线。\n2.单标签(自闭和标签)：也有一部分标签是单独呈现的，比如：\u0026lt; br/\u0026gt;、\u0026lt; hr/\u0026gt;、**\u0026lt; img src=\u0026ldquo;1.jpg\u0026rdquo; /\u0026gt;**等。\n标签的语法\n\u0026lt;标签名 属性1=“属性值1” 属性2=“属性值2”……\u0026gt;内容部分\u0026lt;/标签名\u0026gt;\n\u0026lt;标签名 属性1=“属性值1” 属性2=“属性值2”…… /\u0026gt;\n几个很重要的属性：\nid ：定义标签的唯一ID，HTML文档树中唯一\nclass ：为html元素定义一个或多个类名（classname）(CSS样式类名)\nstyle ：规定元素的行内样式（CSS样式）\nHTML注释\n标签\n\u003c!DOCTYPE\u003e 声明必须是 HTML 文档的第一行，位于 标签之前。 \u003c!DOCTYPE\u003e 声明不是 HTML 标签；它是指示 web 浏览器关于页面使用哪个 HTML 版本进行编写的指令。 HTML常用标签\nhead内常用标签\n\u0026lt; title\u0026gt; 定义网页标题\n**\u0026lt; style\u0026gt; **定义内部样式表\n\u0026lt; script\u0026gt; 定义JS代码或引入外部JS文件\n\u0026lt; link/\u0026gt; 引入外部样式表文件或网站图标\n\u0026lt; meta/\u0026gt; 定义网页原信息\nMeta标签介绍：\n**\u0026lt; meta\u0026gt;**元素可提供有关页面的元信息（mata-information）,针对搜索引擎和更新频度的描述和关键词。\n**\u0026lt; meta\u0026gt;**标签位于文档的头部，不包含任何内容。\n**\u0026lt; meta\u0026gt;**提供的信息是用户不可见的。\nhttp-equiv属****性 ：相当于http的文件头作用，它可以向浏览器传回一些有用的信息，以帮助正确地显示网页内容，与之对应的属性值为content，content中的内容其实就是各个参数的变量值。\n\u0026lt;!--指定文档的编码类型(需要知道)--\u0026gt; \u0026lt;meta http-equiv=\u0026quot;content-Type\u0026quot; charset=UTF8\u0026quot;\u0026gt; \u0026lt;!--2秒后跳转到对应的网址，注意引号(了解)--\u0026gt; \u0026lt;meta http-equiv=\u0026quot;refresh\u0026quot; content=\u0026quot;2;URL=https://www.baidu.com\u0026quot;\u0026gt; \u0026lt;!--告诉IE以最高级模式渲染文档(了解)--\u0026gt; \u0026lt;meta http-equiv=\u0026quot;x-ua-compatible\u0026quot; content=\u0026quot;IE=edge\u0026quot;\u0026gt; name属性 : 主要用于描述网页，与之对应的属性值为content，content中的内容主要是便于搜索引擎机器人查找信息和分类信息用的。\ncontent，content中的内容主要是便于搜索引擎机器人查找信息和分类信息用的。\nbody内常用标签\n特殊字符\ndiv标签和span标签\n第二种分类方式\n1.块儿级标签(独占浏览器一行)\ndiv p h 1.块儿级标签可以修改长宽2.块儿级标签内部可以嵌套任意的块级标签 但是p标签虽然是块儿级标签 但是他不能够其他块儿级标签 包括自身 可以嵌套行内标签总结: 只要是块儿级标签 都可以嵌套行内标签 p标签只能嵌套行内 其他块儿级可以嵌套任意的块儿级标签\n2.行内标签(自身文本多大就占多大)\nspan b s i u\ndiv和span通常都是用来构建网页布局的\nimg标签\na标签\n超链接标签\n所谓的超链接是指从一个网页指向一个目标的连接关系，这个目标可以是另一个网页，也可以是相同网页上的不同位置，还可以是一个图片，一个电子邮件地址，一个文件，甚至是一个应用程序。\nhref属性指定目标网页地址。该地址可以有几种类型：\n绝对URL - 指向另一个站点（比如 href=\u0026quot;http://www.jd.com）\n相对URL - 指当前站点中确切的路径（href=\u0026ldquo;index.htm\u0026rdquo;）\n锚URL - 指向页面中的锚（href=\u0026quot;#top\u0026quot;）\ntarget：\n_blank表示在新标签页中打开目标网页\n_self表示在当前标签页中打开目标网页\n列表\n1.无序列表\n第一项 第二项 type属性：\ndisc（实心圆点，默认值）\nircle（空心圆圈）\nsquare（实心方块）\nnone（无样式）\n2.有序列表\n第一项 第二项 type属性：\n1 数字列表，默认值\nA 大写字母\na 小写字母\nⅠ大写罗马\nⅰ小写罗马\n3.标题列表\n标题1 内容1 标题2 内容1 内容2 表格\n表格的基本结构\n\u0026lt;table\u0026gt; \u0026lt;thead\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;th\u0026gt;序号\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;姓名\u0026lt;/th\u0026gt; \u0026lt;th\u0026gt;爱好\u0026lt;/th\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/thead\u0026gt; \u0026lt;tbody\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;1\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;Egon\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;杠娘\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td\u0026gt;2\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;Yuan\u0026lt;/td\u0026gt; \u0026lt;td\u0026gt;日天\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/tbody\u0026gt; \u0026lt;/table\u0026gt; 属性\nborder: 表格边框. cellpadding: 内边距 cellspacing: 外边距. width: 像素 百分比.（最好通过css来设置长宽） rowspan: 单元格竖跨多少行 colspan: 单元格横跨多少列（即合并单元格 表单form\n功能\n表单用于向服务器传输数据，从而实现用户与Web服务器的交互 表单能够包含input系列标签，比如文本字段、复选框、单选框、提交按钮等等。 表单还可以包含textarea、select、fieldset和 label标签。\n表单属性\n表单元素\n基本概念： HTML表单是HTML元素中较为复杂的部分，表单往往和脚本、动态页面、数据处理等功能相结合，因此它是制作动态网站很重要的内容。 表单一般用来收集用户的输入信息 表单工作原理： 访问者在浏览有表单的网页时，可填写必需的信息，然后按某个按钮提交。这些信息通过Internet传送到服务器上。 服务器上专门的程序对这些数据进行处理，如果有错误会返回错误信息，并要求纠正错误。当数据完整无误后，服务器反馈一个输入完成的信息。\nDjango接收上传文件代码\nfrom django.conf.urls import url from django.shortcuts import HttpResponse def upload(request): print(\u0026quot;request.GET:\u0026quot;, request.GET) print(\u0026quot;request.POST:\u0026quot;, request.POST) if request.FILES: filename = request.FILES[\u0026quot;file\u0026quot;].name with open(filename, 'wb') as f: for chunk in request.FILES['file'].chunks(): f.write(chunk) return HttpResponse('上传成功') return HttpResponse(\u0026quot;收到了！\u0026quot;) urlpatterns = [ url(r'^upload/', upload), ] input\n元素会根据不同的 type 属性，变化为多种形态。**\n**\n属性说明\nname：表单提交时的“键”，注意和id的区别 value：表单提交时对应项的值 type=\u0026ldquo;button\u0026rdquo;, \u0026ldquo;reset\u0026rdquo;, \u0026ldquo;submit\u0026quot;时，为按钮上显示的文本年内容 type=\u0026ldquo;text\u0026rdquo;,\u0026ldquo;password\u0026rdquo;,\u0026ldquo;hidden\u0026quot;时，为输入框的初始值 type=\u0026ldquo;checkbox\u0026rdquo;, \u0026ldquo;radio\u0026rdquo;, \u0026ldquo;file\u0026rdquo;，为输入相关联的值 checked：radio和checkbox默认被选中的项 readonly：text和password设置只读 disabled：所有input均适用\nselect标签\n北京 上海 广州 深圳 属性说明： multiple：布尔属性，设置后为多选，否则默认单选 disabled：禁用 selected：默认选中该项 value：定义提交时的选项值 label标签\n定义： 标签为 input 元素定义标注（标记）。 说明： label 元素不会向用户呈现任何特殊效果。 标签的 for 属性值应当与相关元素的 id 属性值相同。 用户名 textarea多行文本\n默认内容 属性说明： name：名称 rows：行数 cols：列数 disabled：禁用 ","permalink":"https://zhong-er.com/posts/%E5%89%8D%E7%AB%AFhtml/","summary":"\u003cp\u003e\u003cimg alt=\"HTML.png\" loading=\"lazy\" src=\"/assets/201909033609_3808_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eHTML\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/1xQPI57bf8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eWeb服务本质\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.浏览器发请求 \u0026ndash;\u0026gt; HTTP协议 \u0026ndash;\u0026gt;\u003c/p\u003e\n\u003cp\u003e2.服务端接收请求 \u0026ndash;\u0026gt;\u003c/p\u003e\n\u003cp\u003e3.服务端返回响应 \u0026ndash;\u0026gt; 服务端把HTML文件内容发给浏览器 \u0026ndash;\u0026gt;\u003c/p\u003e\n\u003cp\u003e4.浏览器渲染页面\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eimport socket\nsk = socket.socket()\nsk.bind((\u0026quot;127.0.0.1\u0026quot;, 8080))\nsk.listen(5)\nwhile True:\n    conn, addr = sk.accept()\n    data = conn.recv(8096)\n    conn.send(b\u0026quot;HTTP/1.1 200 OK\\r\\n\\r\\n\u0026quot;)\n    conn.send(b\u0026quot;\u0026lt;h1\u0026gt;Hello world!\u0026lt;/h1\u0026gt;\u0026quot;)\n    conn.close()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003eHTTP协议\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e超文本传输协议，规定了浏览器与服务端之间消息传输的数据格式\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e四大特性：\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.基于请求响应\u003c/p\u003e\n\u003cp\u003e2.基于TCP/IP之上的作用于应用层的协议\u003c/p\u003e\n\u003cp\u003e3.无状态(服务端无法保存用户的状态)\u003c/p\u003e\n\u003cp\u003e4.无连接(请求一次响应一次，之后立刻断开连接)\u003c/p\u003e\n\u003cp\u003ewebsocket相当于HTTP协议的一个补丁，它支持长连接\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e请求数据格式\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e请求首行(表示HTTP协议版本，当前请求方式)\u003c/p\u003e\n\u003cp\u003e请求头(一大堆k,v键值对)\u003c/p\u003e\n\u003cp\u003e请求体(携带的是一些敏感信息比如密码、身份证号\u0026hellip;)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e响应数据格式\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e响应首行(标识HTTP协议版本,响应状态码)\u003c/p\u003e\n\u003cp\u003e响应头(一大堆k,v键值对)\u003c/p\u003e\n\u003cp\u003e响应体(返回给浏览器页面的数据 通常响应体都是html页面)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e响应状态码\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e用一串简单的数字来表示一些复杂的状态或者提示信息\u003c/p\u003e\n\u003cp\u003e1XX:服务端已经成功接收了你的数据正在处理 你可以继续提交额外的数据\u003c/p\u003e\n\u003cp\u003e2XX:服务端成功响应 你想要的数据(请求成功200)\u003c/p\u003e\n\u003cp\u003e3XX:重定向(当你在访问一个需要登录之后才能访问的页面 你会发现窗口会自动调到登录页面 301 302)\u003c/p\u003e\n\u003cp\u003e4XX:请求错误(请求资源不存在404,请求不合法不符合内部规定会权限不够403)\u003c/p\u003e\n\u003cp\u003e5XX:服务器内部错误(500)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e请求方式\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eget请求(向服务端要资源）\u003c/p\u003e\n\u003cp\u003epost请求(向服务端提交数据)\u003c/p\u003e","title":"【前端】HTML"},{"content":"\n事务/索引与慢查询优化 \u0026raquo;\u0026gt;思维导图\u0026gt; \u0026raquo;博客园\n视图\n什么是视图？\n视图就是通过查询得到一张虚拟表，然后保存下来，下次直接使用即可\n为什么要用视图？\n如果要频繁使用一张虚拟表，可以不用重复查询\n如何用视图\ncreate view teacher2course as select * from teacher inner join course on teacher.tid = course.teacher_id;\n强调\n1、在硬盘中，视图只有表结构文件，没有表数据文件\n2、视图通常是用于查询，尽量不要修改视图中的数据\ndrop view teacher2course;\n思考:开发过程中会不会去使用视图？\n不会！视图是mysql的功能，如果你的项目里面大量的使用到了视图，那意味着你后期想要扩张某个功能的时候这个功能恰巧又需要对视图进行修改，意味着你需要先在mysql这边将视图先修改一下，然后再去应用程序中修改对应的sql语句，这就涉及到跨部门沟通的问题，所以通常不会使用视图，而是通过重新修改sql语句来扩展功能\n触发器\n什么是触发器？\n在满足对某张表数据的增、删、改的情况下，自动触发的功能称之为触发器\n为何要用触发器？\n触发器专门针对我们对某一张表数据增insert、删delete、改update的行为，这类行为一旦执行 就会触发触发器的执行，即自动运行另外一段sql代码\n创建触发器语法\n# 针对插入 create trigger tri_after_insert_t1 after insert on 表名 for each row begin sql代码。。。 end create trigger tri_after_insert_t2 before insert on 表名 for each row begin sql代码。。。 end # 针对删除 create trigger tri_after_delete_t1 after delete on 表名 for each row begin sql代码。。。 end create trigger tri_after_delete_t2 before delete on 表名 for each row begin sql代码。。。 end # 针对修改 create trigger tri_after_update_t1 after update on 表名 for each row begin sql代码。。。 end create trigger tri_after_update_t2 before update on 表名 for each row begin sql代码。。。 end # 案例 CREATE TABLE cmd ( id INT PRIMARY KEY auto_increment, USER CHAR (32), priv CHAR (10), cmd CHAR (64), sub_time datetime, # 提交时间 success enum ('yes', 'no') # 0代表执行失败 ); CREATE TABLE errlog ( id INT PRIMARY KEY auto_increment, err_cmd CHAR (64), err_time datetime ); delimiter $$ # 将mysql默认的结束符由;换成$$ create trigger tri_after_insert_cmd after insert on cmd for each row begin if NEW.success = 'no' then # 新记录都会被MySQL封装成NEW对象 insert into errlog(err_cmd,err_time) values(NEW.cmd,NEW.sub_time); end if; end $$ delimiter ; # 结束之后记得再改回来，不然后面结束符就都是$$了 # 往表cmd中插入记录，触发触发器，根据IF的条件决定是否插入错误日志 INSERT INTO cmd ( USER, priv, cmd, sub_time, success ) VALUES ('egon','0755','ls -l /etc',NOW(),'yes'), ('egon','0755','cat /etc/passwd',NOW(),'no'), ('egon','0755','useradd xxx',NOW(),'no'), ('egon','0755','ps aux',NOW(),'yes'); # 查询errlog表记录 select * from errlog; # 删除触发器 drop trigger tri_after_insert_cmd; 事务\n什么事务？\n开启一个事务可以包含一些sql语句，这些sql语句要么同时成功要么一个都别想成功，称之为事务的原子性\n事务的作用？\n保证了对数据操作的数据安全性案例:用交行的卡操作建行ATM机给工商的账户转钱\n事务具有4个属性，这四个属性通常称为ACID特性。\n原子性（atomicity）\n一个事务是一个不可分割的工作单位，事务中包括的诸操作要么都做，要么都不做。\n一致性（consistency）\n事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。\n隔离性（isolation）\n一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的，并发执行的各个事务之间不能互相干扰。\n持久性（durability）\n持久性也称永久性（permanence），指一个事务一旦提交，它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。\n如何用？\ncreate table user( id int primary key auto_increment, name char(32), balance int ); insert into user(name,balance) values ('wsb',1000), ('egon',1000), ('ysb',1000); # 修改数据之前先开启事务操作 start transaction; # 修改操作 update user set balance=900 where name='wsb'; # 买支付100元 update user set balance=1010 where name='egon'; # 中介拿走10元 update user set balance=1090 where name='ysb'; # 卖家拿到90元 # 回滚到上一个状态 rollback; # 开启事务之后，只要没有执行commit操作，数据其实都没有真正刷新到硬盘 commit; \u0026quot;\u0026quot;\u0026quot;开启事务检测操作是否完整，不完整主动回滚到上一个状态，如果完整就应该执行commit操作\u0026quot;\u0026quot;\u0026quot; # 站在python代码的角度，应该实现的伪代码逻辑， try: update user set balance=900 where name='wsb'; # 买支付100元 update user set balance=1010 where name='egon'; # 中介拿走10元 update user set balance=1090 where name='ysb'; # 卖家拿到90元 except 异常: rollback; else: commit; # 那如何检测异常？ 存储过程\n存储过程包含了一系列可执行的sql语句，存储过程存放于MySQL中，通过调用它的名字可以执行其内部的一堆sql\n三种开发模型\n第一种\n\u0026quot;\u0026quot;\u0026quot;\n应用程序：只需要开发应用程序的逻辑mysql:编写好存储过程，以供应用程序调用\n优点：开发效率，执行效率都高\n缺点：考虑到人为因素、跨部门沟通等问题，会导致扩展性差\n\u0026quot;\u0026quot;\u0026quot;\n第二种\n\u0026quot;\u0026quot;\u0026quot;\n应用程序：除了开发应用程序的逻辑，还需要编写原生sql\n优点：比方式1，扩展性高(非技术性的)\n缺点：\n1、开发效率，执行效率都不如方式1\n2、编写原生sql太过于复杂，而且需要考虑到sql语句的优化问题\n\u0026quot;\u0026quot;\u0026quot;\n第三种\n\u0026quot;\u0026quot;\u0026quot;\n应用程序：开发应用程序的逻辑，不需要编写原生sql，基于别人编写好的框架来处理数据，ORM\n优点：不用再编写纯生sql，这意味着开发效率比方式2高，同时兼容方式2扩展性高的好处\n缺点：执行效率连方式2都比不过\n\u0026quot;\u0026quot;\u0026quot;\n创建存储过程\ndelimiter $$ create procedure p1( in m int, # in表示这个参数必须只能是传入不能被返回出去 in n int, out res int # out表示这个参数可以被返回出去，还有一个inout表示即可以传入也可以被返回出去 ) begin select tname from teacher where tid \u0026gt; m and tid \u0026lt; n; set res=0; end $$ delimiter ; 如何用存储过程\n# 1、直接在mysql中调用 set @res=10 # res的值是用来判断存储过程是否被执行成功的依据，所以需要先定义一个变量@res存储10 call p1(2,4,10); # 报错 call p1(2,4,@res); # 查看结果 select @res; # 执行成功，@res变量值发生了变化 # 2、在python程序中调用 pymysql链接mysql 产生的游表cursor.callproc('p1',(2,4,10)) # 内部原理：@_p1_0=2,@_p1_1=4,@_p1_2=10; cursor.excute('select @_p1_2;') # 3、存储过程与事务使用举例(了解) delimiter // create PROCEDURE p5( OUT p_return_code tinyint ) BEGIN DECLARE exit handler for sqlexception BEGIN -- ERROR set p_return_code = 1; rollback; END; DECLARE exit handler for sqlwarning BEGIN -- WARNING set p_return_code = 2; rollback; END; START TRANSACTION; update user set balance=900 where id =1; update user123 set balance=1010 where id = 2; update user set balance=1090 where id =3; COMMIT; -- SUCCESS set p_return_code = 0; # 0代表执行成功 END // delimiter ; 内置函数\n注意与存储过程的区别，mysql内置的函数只能在sql语句中使用！\nCREATE TABLE blog ( id INT PRIMARY KEY auto_increment, NAME CHAR (32), sub_time datetime ); INSERT INTO blog (NAME, sub_time) VALUES ('第1篇','2015-03-01 11:31:21'), ('第2篇','2015-03-11 16:31:21'), ('第3篇','2016-07-01 10:21:31'), ('第4篇','2016-07-22 09:23:21'), ('第5篇','2016-07-23 10:11:11'), ('第6篇','2016-07-25 11:21:31'), ('第7篇','2017-03-01 15:33:21'), ('第8篇','2017-03-01 17:32:21'), ('第9篇','2017-03-01 18:31:21'); select date_format(sub_time,'%Y-%m'),count(id) from blog group by date_format(sub_time,'%Y-%m'); 流程控制\nif条件语句\n# if条件语句 delimiter // CREATE PROCEDURE proc_if () BEGIN declare i int default 0; if i = 1 THEN SELECT 1; ELSEIF i = 2 THEN SELECT 2; ELSE SELECT 7; END IF; END // delimiter ; while循环\n# while循环 delimiter // CREATE PROCEDURE proc_while () BEGIN DECLARE num INT ; SET num = 0 ; WHILE num \u0026lt; 10 DO SELECT num ; SET num = num + 1 ; END WHILE ; END // delimiter ; 索引与慢查询优化\n索引\n索引在MySQL中也叫做“键”，是存储引擎用于快速找到记录的一种数据结构。primary key unique key index key 注意foreign key不是用来加速查询用的，不在我们研究范围之内,上面三种key前两种除了有加速查询的效果之外还有额外的约束条件(primary key:非空且唯一，unique key:唯一)，而index key没有任何约束功能只会帮你加速查询 索引就是一种数据结构，类似于书的目录。意味着以后再查数据应该先找目录再找数据，而不是用翻页的方式查询数据 本质都是：通过不断地缩小想要获取数据的范围来筛选出最终想要的结果，同时把随机的事件变成顺序的事件，也就是说，有了这种索引机制，我们可以总是用同一种查找方式来锁定数据。 索引的影响:在表中有大量数据的前提下，创建索引速度会很慢 在索引创建完毕后，对表的查询性能会大幅度提升，但是写的性能会降低 b+树\n只有叶子结点存放真实数据，根和树枝节点存的仅仅是虚拟数据 查询次数由树的层级决定，层级越低次数越少 一个磁盘块儿的大小是一定的，那也就意味着能存的数据量是一定的。如何保证树的层级最低呢？一个磁盘块儿存放占用空间比较小的数据项 思考我们应该给我们一张表里面的什么字段字段建立索引能够降低树的层级高度\u0026raquo;\u0026gt; 主键id字段\n聚集索引(primary key)\n聚集索引其实指的就是表的主键，innodb引擎规定一张表中必须要有主键。先来回顾一下存储引擎。\nmyisam在建表的时候对应到硬盘有几个文件(三个)？\ninnodb在建表的时候对应到硬盘有几个文件(两个)？\nfrm文件只存放表结构，不可能放索引，也就意味着innodb的索引跟数据都放在idb表数据文件中。\n特点:叶子结点放的一条条完整的记录\n辅助索引(unique,index)\n辅助索引:查询数据的时候不可能都是用id作为筛选条件，也可能会用name，password等字段信息，那么这个时候就无法利用到聚集索引的加速查询效果。就需要给其他字段建立索引，这些索引就叫辅助索引\n特点：叶子结点存放的是辅助索引字段对应的那条记录的主键的值(比如:按照name字段创建索引，那么叶子节点存放的是:{name对应的值:name所在的那条记录的主键值})\nselect name from user where name=\u0026lsquo;jason\u0026rsquo;;\n上述语句叫覆盖索引:只在辅助索引的叶子节点中就已经找到了所有我们想要的数据\nselect age from user where name=\u0026lsquo;jason\u0026rsquo;;\n上述语句叫非覆盖索引,虽然查询的时候命中了索引字段name，但是要查的是age字段，所以还需要利用主键才去查找\n测试索引\n在没有索引的前提下测试查询速度很慢\n在表中已经存在大量数据的前提下，为某个字段段建立索引，建立速度会很慢\n在索引建立完毕后，以该字段为查询条件时，查询速度提升明显\n需要注意的是：innodb表的索引会存放于s1.ibd文件中，而myisam表的索引则会有单独的索引文件table1.MYIMySAM索引文件和数据文件是分离的，索引文件仅保存数据记录的地址。而在innodb中，表数据文件本身就是按照B+Tree（BTree即Balance True）组织的一个索引结构，这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键，因此innodb表数据文件本身就是主索引。因为inndob的数据文件要按照主键聚集，所以innodb要求表必须要有主键（Myisam可以没有），如果没有显式定义，则mysql系统会自动选择一个可以唯一标识数据记录的列作为主键，如果不存在这种列，则mysql会自动为innodb表生成一个隐含字段作为主键，这字段的长度为6个字节，类型为长整型.\n联合索引\n联合索引时指对表上的多个列合起来做一个索引。联合索引的创建方法与单个索引的创建方法一样，不同之处在仅在于有多个索引列\n从本质上来说，联合索引就是一棵B+树，不同的是联合索引的键值得数量不是1，而是\u0026gt;=2。\n联合索引的第二个好处是在第一个键相同的情况下，已经对第二个键进行了排序处理\n例如在很多情况下应用程序都需要查询某个用户的购物情况，并按照时间进行排序，最后取出最近三次的购买记录，这时使用联合索引可以帮我们避免多一次的排序操作，因为索引本身在叶子节点已经排序了\n覆盖索引\nInnoDB存储引擎支持覆盖索引（covering index，或称索引覆盖），即从辅助索引中就可以得到查询记录，而不需要查询聚集索引中的记录。\n使用覆盖索引的一个好处是：辅助索引不包含整行记录的所有信息，故其大小要远小于聚集索引，因此可以减少大量的IO操作\n注意：覆盖索引技术最早是在InnoDB Plugin中完成并实现，这意味着对于InnoDB版本小于1.0的，或者MySQL数据库版本为5.0以下的，InnoDB存储引擎不支持覆盖索引特性\n查询优化神器-explain\n关于explain命令相信大家并不陌生，具体用法和字段含义可以参考官网explain-output，这里需要强调rows是核心指标，绝大部分rows小的语句执行一定很快（有例外，下面会讲到）。所以优化语句基本上都是在优化rows。\n执行计划：让mysql预估执行操作(一般正确)\nall \u0026lt; index \u0026lt; range \u0026lt; index_merge \u0026lt; ref_or_null \u0026lt; ref \u0026lt; eq_ref \u0026lt; system/const\nid,email\n慢：\nselect * from userinfo3 where name=\u0026lsquo;alex\u0026rsquo;\nexplain select * from userinfo3 where name=\u0026lsquo;alex\u0026rsquo;\ntype: ALL(全表扫描)\nselect * from userinfo3 limit 1;\n快：\nselect * from userinfo3 where email=\u0026lsquo;alex\u0026rsquo;\ntype: const(走索引)\n慢查询优化的基本步骤\n0.先运行看看是否真的很慢，注意设置SQL_NO_CACHE\n1.where条件单表查，锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起，单表每个字段分别查询，看哪个字段的区分度最高\n2.explain查看执行计划，是否与1预期一致（从锁定记录较少的表开始查询）\n3.order by limit 形式的sql语句让排序的表优先查\n4.了解业务方使用场景\n5.加索引时参照建索引的几大原则\n6.观察结果，不符合预期继续从0分析\n","permalink":"https://zhong-er.com/posts/%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BA%8B%E5%8A%A1%E7%B4%A2%E5%BC%95%E4%B8%8E%E6%85%A2%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96/","summary":"\u003cp\u003e\u003cimg alt=\"事务_索引与慢查询优化.png\" loading=\"lazy\" src=\"/assets/201908238895_3436_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e事务/索引与慢查询优化 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/2qUg6bPFW8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003ca href=\"https://www.cnblogs.com/renvip/p/11400755.html\" title=\"博客园\"\u003e\u003c/a\u003e\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11400755.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e视图\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e什么是视图？\u003cbr\u003e\n视图就是通过查询得到一张虚拟表，然后保存下来，下次直接使用即可\u003c/p\u003e\n\u003cp\u003e为什么要用视图？\u003cbr\u003e\n如果要频繁使用一张虚拟表，可以不用重复查询\u003c/p\u003e\n\u003cp\u003e如何用视图\u003cbr\u003e\ncreate \u003cstrong\u003eview\u003c/strong\u003e teacher2course \u003cstrong\u003eas\u003c/strong\u003e select * from teacher inner join course on teacher.tid = course.teacher_id;\u003c/p\u003e\n\u003cp\u003e强调\u003c/p\u003e\n\u003cp\u003e1、在硬盘中，视图只有表结构文件，没有表数据文件\u003c/p\u003e\n\u003cp\u003e2、视图通常是用于查询，尽量不要修改视图中的数据\u003cbr\u003e\ndrop view teacher2course;\u003c/p\u003e\n\u003cp\u003e思考:开发过程中会不会去使用视图？\u003cbr\u003e\n不会！视图是mysql的功能，如果你的项目里面大量的使用到了视图，那意味着你后期想要扩张某个功能的时候这个功能恰巧又需要对视图进行修改，意味着你需要先在mysql这边将视图先修改一下，然后再去应用程序中修改对应的sql语句，这就涉及到跨部门沟通的问题，所以通常不会使用视图，而是通过重新修改sql语句来扩展功能\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e触发器\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e什么是触发器？\u003cbr\u003e\n在满足对某张表数据的增、删、改的情况下，自动触发的功能称之为触发器\u003c/p\u003e\n\u003cp\u003e为何要用触发器？\u003cbr\u003e\n触发器专门针对我们对某一张表数据增insert、删delete、改update的行为，这类行为一旦执行 就会触发触发器的执行，即自动运行另外一段sql代码\u003c/p\u003e\n\u003cp\u003e创建触发器语法\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 针对插入\ncreate trigger tri_after_insert_t1 after insert on 表名 for each row\nbegin\n    sql代码。。。\nend \ncreate trigger tri_after_insert_t2 before insert on 表名 for each row\nbegin\n    sql代码。。。\nend\n# 针对删除\ncreate trigger tri_after_delete_t1 after delete on 表名 for each row\nbegin\n    sql代码。。。\nend\ncreate trigger tri_after_delete_t2 before delete on 表名 for each row\nbegin\n    sql代码。。。\nend\n# 针对修改\ncreate trigger tri_after_update_t1 after update on 表名 for each row\nbegin\n    sql代码。。。\nend\ncreate trigger tri_after_update_t2 before update on 表名 for each row\nbegin\n    sql代码。。。\nend\n# 案例\nCREATE TABLE cmd (\n    id INT PRIMARY KEY auto_increment,\n    USER CHAR (32),\n    priv CHAR (10),\n    cmd CHAR (64),\n    sub_time datetime,  # 提交时间\n    success enum ('yes', 'no')  # 0代表执行失败\n);\nCREATE TABLE errlog (\n    id INT PRIMARY KEY auto_increment,\n    err_cmd CHAR (64),\n    err_time datetime\n);\ndelimiter $$  # 将mysql默认的结束符由;换成$$\ncreate trigger tri_after_insert_cmd after insert on cmd for each row\nbegin\n    if NEW.success = 'no' then  # 新记录都会被MySQL封装成NEW对象\n        insert into errlog(err_cmd,err_time) values(NEW.cmd,NEW.sub_time);\n    end if;\nend $$\ndelimiter ;  # 结束之后记得再改回来，不然后面结束符就都是$$了\n# 往表cmd中插入记录，触发触发器，根据IF的条件决定是否插入错误日志\nINSERT INTO cmd (\n    USER,\n    priv,\n    cmd,\n    sub_time,\n    success\n)\nVALUES\n    ('egon','0755','ls -l /etc',NOW(),'yes'),\n    ('egon','0755','cat /etc/passwd',NOW(),'no'),\n    ('egon','0755','useradd xxx',NOW(),'no'),\n    ('egon','0755','ps aux',NOW(),'yes');\n# 查询errlog表记录\nselect * from errlog;\n# 删除触发器\ndrop trigger tri_after_insert_cmd;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e事务\u003c/strong\u003e\u003c/p\u003e","title":"【数据库】事务/索引与慢查询优化"},{"content":"\nNavicat/Pytcharm连接MySQL**\u0026gt;\u0026gt; \u0026gt;思维导图\u0026gt; \u0026raquo;博客园**\nNavicat连接MySQL\n测试+链接数据库\n新建库\n新建表，新增字段+类型+约束\n设计表：外键\n新建查询\n建立表模型\n批量加注释：ctrl+？键\n批量去注释：ctrl+shift+？键\nPytcharm连接MySQL\n安装pymysql模块\n代码连接\nimport pymysql # 链接 conn=pymysql.connect( host=\u0026lsquo;localhost\u0026rsquo;, user=\u0026lsquo;root\u0026rsquo;, password=\u0026lsquo;123\u0026rsquo;, database=\u0026lsquo;dbname\u0026rsquo;, charset=\u0026lsquo;utf8\u0026rsquo;) # 编码千万不要加- 如果写成了utf-8会直接报错 autocommit = True # 这个参数配置完成后 增删改操作都不需要在手动加conn.commit了 # 游标 cursor=conn.cursor() # 执行完毕返回的结果集默认以元组显示 cursor=conn.cursor(cursor=pymysql.cursors.DictCursor) # 产生一个游标对象 以字典的形式返回查询出来的数据 键是表的字段 值是表的字段对应的信息\npymysql操作数据库\nuser = input(\u0026quot;\u0026raquo;\u0026gt;:\u0026quot;).strip() pwd = input(\u0026quot;\u0026raquo;\u0026gt;:\u0026quot;).strip() sql=\u0026lsquo;select * from userinfo where name=\u0026quot;%s\u0026quot; and password=\u0026quot;%s\u0026quot;\u0026rsquo; %(user,pwd) # 注意%s需要加引号 rows=cursor.execute(sql) # 执行sql语句，返回sql查询成功的记录数目 # 获取真实数据cursor.fetchone(),cursor.fetchall(),cursor.fetchmany(),类似管道取值，获取一条，所有，多条 print(cursor.fetchone()) # 只获取一条数据 print(cursor.fetchall()) # 获取所有的数据 返回的结果是一个列表 cursor.scroll(1,\u0026lsquo;relative\u0026rsquo;) # 相对移动 relative相对于当前位置 往后移动几位 cursor.scroll(3,\u0026lsquo;absolute\u0026rsquo;) # 绝对移动 absolute相对于起始位置 往后移动几位 cursor.close() conn.close()\nsql注入问题\n# 不要手动去拼接查询的sql语句 username = input(\u0026quot;\u0026raquo;\u0026gt;:\u0026quot;).strip() password = input(\u0026quot;\u0026raquo;\u0026gt;:\u0026quot;).strip() sql = \u0026ldquo;select * from user where username=\u0026rsquo;%s\u0026rsquo; and password=\u0026rsquo;%s\u0026rsquo;\u0026rdquo;%(username,password) # 用户名正确 username \u0026raquo;\u0026gt;: jason\u0026rsquo; \u0026ndash; jjsakfjjdkjjkjs # 用户名密码都不对的情况 username \u0026raquo;\u0026gt;: xxx\u0026rsquo; or 1=1 \u0026ndash;asdjkdklqwjdjkjasdljad password \u0026raquo;\u0026gt;: \u0026rsquo;\u0026rsquo; \u0026quot;\u0026quot;\u0026quot; sql注入 就是利用注释等具有特殊意义的符号 来完成一些骚操作 后续写sql语句 不要手动拼接关键性的数据 而是让excute帮你去做拼接 \u0026quot;\u0026quot;\u0026quot;\n增删改\n# 增 sql = \u0026ldquo;insert into user(username,password) values(%s,%s)\u0026rdquo; rows = cursor.excute(sql,(\u0026lsquo;jason\u0026rsquo;,\u0026lsquo;123\u0026rsquo;)) # 修改 sql = \u0026ldquo;update user set username=\u0026lsquo;jasonDSB\u0026rsquo; where id=1\u0026rdquo; rows = cursor.excute(sql) \u0026quot;\u0026quot;\u0026quot; 增和改单单执行excute并不会真正影响到数据，需要再执行conn.commit()才可以完成真正的增改 \u0026quot;\u0026quot;\u0026quot; # 一次插入多行记录 res = cursor,excutemany(sql,[(),(),()]）\n","permalink":"https://zhong-er.com/posts/%E6%95%B0%E6%8D%AE%E5%BA%93navicatpytcharm%E8%BF%9E%E6%8E%A5mysql/","summary":"\u003cp\u003e\u003cimg alt=\"Navicat_Pytcharm连接MySQL.png\" loading=\"lazy\" src=\"/assets/201908227713_7199.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eNavicat/Pytcharm连接MySQL\u003c/strong\u003e**\u0026gt;\u003cstrong\u003e\u003cstrong\u003e\u0026gt; \u0026gt;\u003cstrong\u003e\u003ca href=\"https://mubu.com/doc/12gY78M4W8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/a\u003e\u003c/strong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11396758.html\" title=\"博客园\"\u003e博客园\u003c/a\u003e**\u003c/p\u003e\n\u003cp\u003eNavicat连接MySQL\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e测试+链接数据库\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e新建库\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e新建表，新增字段+类型+约束\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e设计表：外键\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e新建查询\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e建立表模型\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e批量加注释：ctrl+？键\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e批量去注释：ctrl+shift+？键\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003ePytcharm连接MySQL\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e安装pymysql模块\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e代码连接\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eimport pymysql\n# 链接\nconn=pymysql.connect(\n  host=\u0026lsquo;localhost\u0026rsquo;,\n  user=\u0026lsquo;root\u0026rsquo;,\n  password=\u0026lsquo;123\u0026rsquo;,\n  database=\u0026lsquo;dbname\u0026rsquo;,\n  charset=\u0026lsquo;utf8\u0026rsquo;)  # 编码千万不要加- 如果写成了utf-8会直接报错\n  autocommit = True  # 这个参数配置完成后  增删改操作都不需要在手动加conn.commit了\n# 游标\ncursor=conn.cursor()  # 执行完毕返回的结果集默认以元组显示\ncursor=conn.cursor(cursor=pymysql.cursors.DictCursor)  \n# 产生一个游标对象  以字典的形式返回查询出来的数据 键是表的字段  值是表的字段对应的信息\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003epymysql操作数据库\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003euser = input(\u0026quot;\u0026raquo;\u0026gt;:\u0026quot;).strip()\npwd = input(\u0026quot;\u0026raquo;\u0026gt;:\u0026quot;).strip()\nsql=\u0026lsquo;select * from userinfo where name=\u0026quot;%s\u0026quot; and password=\u0026quot;%s\u0026quot;\u0026rsquo; %(user,pwd)  # 注意%s需要加引号\nrows=cursor.execute(sql)  # 执行sql语句，返回sql查询成功的记录数目\n# 获取真实数据cursor.fetchone(),cursor.fetchall(),cursor.fetchmany(),类似管道取值，获取一条，所有，多条\nprint(cursor.fetchone())  # 只获取一条数据\nprint(cursor.fetchall())  # 获取所有的数据  返回的结果是一个列表\ncursor.scroll(1,\u0026lsquo;relative\u0026rsquo;)  # 相对移动 relative相对于当前位置 往后移动几位\ncursor.scroll(3,\u0026lsquo;absolute\u0026rsquo;)  # 绝对移动 absolute相对于起始位置 往后移动几位\ncursor.close()\nconn.close()\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003esql注入问题\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e# 不要手动去拼接查询的sql语句\nusername = input(\u0026quot;\u0026raquo;\u0026gt;:\u0026quot;).strip()\npassword = input(\u0026quot;\u0026raquo;\u0026gt;:\u0026quot;).strip()\nsql = \u0026ldquo;select * from user where username=\u0026rsquo;%s\u0026rsquo; and password=\u0026rsquo;%s\u0026rsquo;\u0026rdquo;%(username,password)\n# 用户名正确\nusername \u0026raquo;\u0026gt;: jason\u0026rsquo; \u0026ndash; jjsakfjjdkjjkjs\n# 用户名密码都不对的情况\nusername \u0026raquo;\u0026gt;: xxx\u0026rsquo; or 1=1 \u0026ndash;asdjkdklqwjdjkjasdljad\npassword \u0026raquo;\u0026gt;: \u0026rsquo;\u0026rsquo;\n\u0026quot;\u0026quot;\u0026quot;\nsql注入 就是利用注释等具有特殊意义的符号 来完成一些骚操作\n后续写sql语句  不要手动拼接关键性的数据\n而是让excute帮你去做拼接\n\u0026quot;\u0026quot;\u0026quot;\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e增删改\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e# 增\nsql = \u0026ldquo;insert into user(username,password) values(%s,%s)\u0026rdquo;\nrows = cursor.excute(sql,(\u0026lsquo;jason\u0026rsquo;,\u0026lsquo;123\u0026rsquo;))\n# 修改\nsql = \u0026ldquo;update user set username=\u0026lsquo;jasonDSB\u0026rsquo; where id=1\u0026rdquo;\nrows = cursor.excute(sql)\n\u0026quot;\u0026quot;\u0026quot;\n增和改单单执行excute并不会真正影响到数据，需要再执行conn.commit()才可以完成真正的增改\n\u0026quot;\u0026quot;\u0026quot;\n# 一次插入多行记录\nres = cursor,excutemany(sql,[(),(),()]）\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e","title":"【数据库】Navicat/Pytcharm连接MySQL"},{"content":"\n表的查询 \u0026raquo;\u0026gt;思维导图\u0026gt; \u0026raquo;博客园\n单表查询\n前期表准备\ncreate table emp( id int not null unique auto_increment, name varchar(20) not null, sex enum('male','female') not null default 'male', # 大部分是男的 age int(3) unsigned not null default 28, hire_date date not null, post varchar(50), post_comment varchar(100), salary double(15,2), office int, # 一个部门一个屋子 depart_id int );# 插入记录# 三个部门：教学，销售，运营insert into emp(name,sex,age,hire_date,post,salary,office,depart_id) values ('jason','male',18,'20170301','张江第一帅形象代言',7300.33,401,1), # 以下是教学部 ('egon','male',78,'20150302','teacher',1000000.31,401,1), ('kevin','male',81,'20130305','teacher',8300,401,1), ('tank','male',73,'20140701','teacher',3500,401,1), ('owen','male',28,'20121101','teacher',2100,401,1), ('jerry','female',18,'20110211','teacher',9000,401,1), ('nick','male',18,'19000301','teacher',30000,401,1), ('sean','male',48,'20101111','teacher',10000,401,1), ('歪歪','female',48,'20150311','sale',3000.13,402,2), # 以下是销售部门 ('丫丫','female',38,'20101101','sale',2000.35,402,2), ('丁丁','female',18,'20110312','sale',1000.37,402,2), ('星星','female',18,'20160513','sale',3000.29,402,2), ('格格','female',28,'20170127','sale',4000.33,402,2), ('张野','male',28,'20160311','operation',10000.13,403,3), # 以下是运营部门 ('程咬金','male',18,'19970312','operation',20000,403,3), ('程咬银','female',18,'20130311','operation',19000,403,3), ('程咬铜','male',18,'20150411','operation',18000,403,3), ('程咬铁','female',18,'20140512','operation',17000,403,3); # ps：如果在windows系统中，插入中文字符，select的结果为空白，可以将所有字符编码统一设置成gbk +----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ | 1 | jason | male | 18 | 2017-03-01 | 张江第一帅形象代言 | NULL | 7300.33 | 401 | 1 | | 2 | egon | male | 78 | 2015-03-02 | teacher | NULL | 1000000.31 | 401 | 1 | | 3 | kevin | male | 81 | 2013-03-05 | teacher | NULL | 8300.00 | 401 | 1 | | 4 | tank | male | 73 | 2014-07-01 | teacher | NULL | 3500.00 | 401 | 1 | | 5 | owen | male | 28 | 2012-11-01 | teacher | NULL | 2100.00 | 401 | 1 | | 6 | jerry | female | 18 | 2011-02-11 | teacher | NULL | 9000.00 | 401 | 1 | | 7 | nick | male | 18 | 1900-03-01 | teacher | NULL | 30000.00 | 401 | 1 | | 8 | sean | male | 48 | 2010-11-11 | teacher | NULL | 10000.00 | 401 | 1 | | 9 | 歪歪 | female | 48 | 2015-03-11 | sale | NULL | 3000.13 | 402 | 2 | | 10 | 丫丫 | female | 38 | 2010-11-01 | sale | NULL | 2000.35 | 402 | 2 | | 11 | 丁丁 | female | 18 | 2011-03-12 | sale | NULL | 1000.37 | 402 | 2 | | 12 | 星星 | female | 18 | 2016-05-13 | sale | NULL | 3000.29 | 402 | 2 | | 13 | 格格 | female | 28 | 2017-01-27 | sale | NULL | 4000.33 | 402 | 2 | | 14 | 张野 | male | 28 | 2016-03-11 | operation | NULL | 10000.13 | 403 | 3 | | 15 | 程咬金 | male | 18 | 1997-03-12 | operation | NULL | 20000.00 | 403 | 3 | | 16 | 程咬银 | female | 18 | 2013-03-11 | operation | NULL | 19000.00 | 403 | 3 | | 17 | 程咬铜 | male | 18 | 2015-04-11 | operation | NULL | 18000.00 | 403 | 3 | | 18 | 程咬铁 | female | 18 | 2014-05-12 | operation | NULL | 17000.00 | 403 | 3 | +----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ 语法执行顺序\n书写顺序：select id,name from emp where id \u0026gt; 3 and id \u0026lt; 6;\n+----+-------+ | id | name | +----+-------+ | 3 | kevin | | 4 | tank | | 5 | owen | | 6 | jerry | +----+-------+ 执行顺序：from(确定到底是哪个表)，where(根据条件筛选数据)，select(拿出筛选出来的数据中的某些字段)\nselect * from emp \\G;当表地段特别多的时候，结果的排版可能会出现混乱，可以通过\\G规范查询结果\n*************************** 1. row *************************** id: 1 name: jason sex: male age: 18 hire_date: 2017-03-01 post: 张江第一帅形象代言 post_comment: NULL salary: 7300.33 office: 401 depart_id: 1 *************************** 2. row *************************** id: 2 name: egon sex: male age: 78 hire_date: 2015-03-02 post: teacher post_comment: NULL salary: 1000000.31 office: 401 depart_id: 1 ... where约束条件\n# 1.查询id大于等于3小于等于6的数据select id,name from emp where id\u0026gt;= 3 and id \u0026lt;= 6; select * from emp where id between 3 and 6; # 上述语句完全等价 +----+-------+ | id | name | +----+-------+ | 3 | kevin | | 4 | tank | | 5 | owen | | 6 | jerry | +----+-------+# 2.查询薪资是20000或者18000或者17000的数据select * from emp where salary = 20000 or salary = 18000 or salary = 17000; select * from emp where salary in (20000,18000,17000); # 简写 +----+-----------+--------+-----+------------+-----------+--------------+----------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+-----------+--------+-----+------------+-----------+--------------+----------+--------+-----------+ | 15 | 程咬金 | male | 18 | 1997-03-12 | operation | NULL | 20000.00 | 403 | 3 | | 17 | 程咬铜 | male | 18 | 2015-04-11 | operation | NULL | 18000.00 | 403 | 3 | | 18 | 程咬铁 | female | 18 | 2014-05-12 | operation | NULL | 17000.00 | 403 | 3 | +----+-----------+--------+-----+------------+-----------+--------------+----------+--------+-----------+ # 3.查询员工姓名中包含o字母的员工姓名和薪资# 在你刚开始接触mysql查询的时候，建议你按照查询的优先级顺序拼写出你的sql语句 # 先是查哪张表 from emp ，再是根据什么条件去查 where name like ‘%o%’ ，再是对查询出来的数据筛选展示部分 select name,salary\u0026quot;\u0026quot;\u0026quot; 模糊匹配 like %:匹配多个任意字符 _:匹配一个任意字符 \u0026quot;\u0026quot;\u0026quot;select name,salary from emp where name like '%o%'; +-------+------------+ | name | salary | +-------+------------+ | jason | 7300.33 | | egon | 1000000.31 | | owen | 2100.00 | +-------+------------+# 4.查询员工姓名是由四个字符组成的员工姓名与其薪资select name,salary from emp where name like '____'; select name,salary from emp where char_length(name) = 4; # 上述语句完全等价 +------+------------+ | name | salary | +------+------------+ | egon | 1000000.31 | | tank | 3500.00 | | owen | 2100.00 | | nick | 30000.00 | | sean | 10000.00 | +------+------------+# 5.查询id小于3或者大于6的数据select * from emp where id \u0026lt; 3 or id \u0026gt; 6;select * from emp where id not between 3 and 6; # 上述语句完全等价 +----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ | 1 | jason | male | 18 | 2017-03-01 | 张江第一帅形象代言 | NULL | 7300.33 | 401 | 1 | | 2 | egon | male | 78 | 2015-03-02 | teacher | NULL | 1000000.31 | 401 | 1 | | 7 | nick | male | 18 | 1900-03-01 | teacher | NULL | 30000.00 | 401 | 1 | | 8 | sean | male | 48 | 2010-11-11 | teacher | NULL | 10000.00 | 401 | 1 | | 9 | 歪歪 | female | 48 | 2015-03-11 | sale | NULL | 3000.13 | 402 | 2 | | 10 | 丫丫 | female | 38 | 2010-11-01 | sale | NULL | 2000.35 | 402 | 2 | | 11 | 丁丁 | female | 18 | 2011-03-12 | sale | NULL | 1000.37 | 402 | 2 | | 12 | 星星 | female | 18 | 2016-05-13 | sale | NULL | 3000.29 | 402 | 2 | | 13 | 格格 | female | 28 | 2017-01-27 | sale | NULL | 4000.33 | 402 | 2 | | 14 | 张野 | male | 28 | 2016-03-11 | operation | NULL | 10000.13 | 403 | 3 | | 15 | 程咬金 | male | 18 | 1997-03-12 | operation | NULL | 20000.00 | 403 | 3 | | 16 | 程咬银 | female | 18 | 2013-03-11 | operation | NULL | 19000.00 | 403 | 3 | | 17 | 程咬铜 | male | 18 | 2015-04-11 | operation | NULL | 18000.00 | 403 | 3 | | 18 | 程咬铁 | female | 18 | 2014-05-12 | operation | NULL | 17000.00 | 403 | 3 | +----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ # 6.查询薪资不在20000，18000，17000范围的数据select * from emp where salary not in (20000,18000,17000); +----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ | 1 | jason | male | 18 | 2017-03-01 | 张江第一帅形象代言 | NULL | 7300.33 | 401 | 1 | | 2 | egon | male | 78 | 2015-03-02 | teacher | NULL | 1000000.31 | 401 | 1 | | 3 | kevin | male | 81 | 2013-03-05 | teacher | NULL | 8300.00 | 401 | 1 | | 4 | tank | male | 73 | 2014-07-01 | teacher | NULL | 3500.00 | 401 | 1 | | 5 | owen | male | 28 | 2012-11-01 | teacher | NULL | 2100.00 | 401 | 1 | | 6 | jerry | female | 18 | 2011-02-11 | teacher | NULL | 9000.00 | 401 | 1 | | 7 | nick | male | 18 | 1900-03-01 | teacher | NULL | 30000.00 | 401 | 1 | | 8 | sean | male | 48 | 2010-11-11 | teacher | NULL | 10000.00 | 401 | 1 | | 9 | 歪歪 | female | 48 | 2015-03-11 | sale | NULL | 3000.13 | 402 | 2 | | 10 | 丫丫 | female | 38 | 2010-11-01 | sale | NULL | 2000.35 | 402 | 2 | | 11 | 丁丁 | female | 18 | 2011-03-12 | sale | NULL | 1000.37 | 402 | 2 | | 12 | 星星 | female | 18 | 2016-05-13 | sale | NULL | 3000.29 | 402 | 2 | | 13 | 格格 | female | 28 | 2017-01-27 | sale | NULL | 4000.33 | 402 | 2 | | 14 | 张野 | male | 28 | 2016-03-11 | operation | NULL | 10000.13 | 403 | 3 | | 16 | 程咬银 | female | 18 | 2013-03-11 | operation | NULL | 19000.00 | 403 | 3 | +----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+# 7.查询岗位描述为空的员工名与岗位名 针对null不能用等号，只能用isselect name,post from emp where post_comment = NULL; # 查询为空！ select name,post from emp where post_comment is NULL; +-----------+-----------------------------+ | name | post | +-----------+-----------------------------+ | jason | 张江第一帅形象代言 | | egon | teacher | | kevin | teacher | | tank | teacher | | owen | teacher | | jerry | teacher | | nick | teacher | | sean | teacher | | 歪歪 | sale | | 丫丫 | sale | | 丁丁 | sale | | 星星 | sale | | 格格 | sale | | 张野 | operation | | 程咬金 | operation | | 程咬银 | operation | | 程咬铜 | operation | | 程咬铁 | operation | +-----------+-----------------------------+ select name,post from emp where post_comment is not NULL; group by\n# 数据分组应用场景：每个部门的平均薪资，男女比例等 # 1.按部门分组select * from emp group by post; # 分组后取出的是每个组的第一条数据 +----+--------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ | id | name | sex | age | hire_date | post | post_comment | salary | office | depart_id | +----+--------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ | 14 | 张野 | male | 28 | 2016-03-11 | operation | NULL | 10000.13 | 403 | 3 | | 9 | 歪歪 | female | 48 | 2015-03-11 | sale | NULL | 3000.13 | 402 | 2 | | 2 | egon | male | 78 | 2015-03-02 | teacher | NULL | 1000000.31 | 401 | 1 | | 1 | jason | male | 18 | 2017-03-01 | 张江第一帅形象代言 | NULL | 7300.33 | 401 | 1 | +----+--------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+ select id,name,sex from emp group by post; # 验证 +----+--------+--------+ | id | name | sex | +----+--------+--------+ | 14 | 张野 | male | | 9 | 歪歪 | female | | 2 | egon | male | | 1 | jason | male | +----+--------+--------+\u0026quot;\u0026quot;\u0026quot; 分组之后应该做到最小单位是组,而不应该再展示组内的单个数据信息\tMySQL中分组之后 只能拿到分组的字段信息 无法直接获取其他字段信息 但是你可以通过其他方法(聚合函数)简介的获取 设置sql_mode为only_full_group_by，意味着以后但凡分组，只能取到分组的依据， 不应该在去取组里面的单个元素的值，那样的话分组就没有意义了，因为不分组就是对单个元素信息的随意获取\u0026quot;\u0026quot;\u0026quot;# 如果你的MySQL不报错 说明严格模式没有设置 show variables like '%mode%'; set session # 当前窗口有效 set global # 全局有效 set global sql_mode=\u0026quot;strict_trans_tables,only_full_group_by\u0026quot;; # 重新链接客户端 select * from emp group by post; # 报错 select id,name,sex from emp group by post; # 报错 select post from emp group by post; # 获取部门信息 +-----------------------------+ | post | +-----------------------------+ | operation | | sale | | teacher | | 张江第一帅形象代言 | +-----------------------------+# 强调:只要分组了，就不能够再“直接”查找到单个数据信息了，只能获取到组名 # 2.获取每个部门的最高工资 聚合函数 max min avg sum count# 以组为单位统计组内数据\u0026gt;\u0026gt;\u0026gt;聚合查询(聚集到一起合成为一个结果) # 每个部门的最高工资 select post,max(salary) from emp group by post; +-----------------------------+-------------+ | post | max(salary) | +-----------------------------+-------------+ | operation | 20000.00 | | sale | 4000.33 | | teacher | 1000000.31 | | 张江第一帅形象代言 | 7300.33 | +-----------------------------+-------------+ # 给字段取别名 select post as '部门',max(salary) as '最高工资' from emp group by post; select post '部门',max(salary) '最高工资' from emp group by post; # 每个部门的最低工资 select post,min(salary) from emp group by post; +-----------------------------+-------------+ | post | min(salary) | +-----------------------------+-------------+ | operation | 10000.13 | | sale | 1000.37 | | teacher | 2100.00 | | 张江第一帅形象代言 | 7300.33 | +-----------------------------+-------------+ # 每个部门的平均工资 select post,avg(salary) from emp group by post; # 每个部门的工资总和 select post,sum(salary) from emp group by post; # 每个部门的人数 select post,count(age) from emp group by post; select post,count(salary) from emp group by post; select post,count(id) from emp group by post; select post,count(post_comment) from emp group by post; # 在统计分组内个数的时候 填写任意非空字段都可以完成计数,推荐使用能够唯一标识数据的字段 # 比如id字段 \u0026quot;\u0026quot;\u0026quot; 聚合函数会自动将每一个分组内的单个数据做想要的计算,无需你考虑 \u0026quot;\u0026quot;\u0026quot;# 3.查询分组之后的部门名称和每个部门下所有的学生姓名# group_concat（分组之后用）不仅可以用来显示除分组外字段还有拼接字符串的作用select post,group_concat(name) from emp group by post; select post,group_concat(name,\u0026quot;_SB\u0026quot;) from emp group by post; select post,group_concat(name,\u0026quot;: \u0026quot;,salary) from emp group by post; select post,group_concat(salary) from emp group by post;# 4.补充concat（不分组时用）拼接字符串达到更好的显示效果 as语法使用select name as 姓名,salary as 薪资 from emp; select concat(\u0026quot;NAME: \u0026quot;,name) as 姓名,concat(\u0026quot;SAL: \u0026quot;,salary) as 薪资 from emp;# 补充as语法 即可以给字段起别名也可以给表起select emp.id,emp.name from emp as t1; # 报错 因为表名已经被你改成了t1 select t1.id,t1.name from emp as t1;# 查询四则运算 # 查询每个人的年薪select name,salary*12 as annual_salary from emp; select name,salary*12 annual_salary from emp; # as可以省略 having\n截止目前已经学习的语法\nselect 查询字段1,查询字段2,... from 表名 where 过滤条件 group by分组依据 # 语法这么写，但是执行顺序却不一样 from where group by select having的语法格式与where一致，只不过having是在分组之后进行的过滤，即where虽然不能用聚合函数，但是having可以！\n# 1、统计各部门年龄在30岁以上的员工平均工资，并且保留平均工资大于10000的部门select post,avg(salary) from emp where age \u0026gt;= 30 group by post having avg(salary) \u0026gt; 10000;# 如果不信你可以将having取掉，查看结果，对比即可验证having用法！ # 强调：having必须在group by后面使用select * from emp having avg(salary) \u0026gt; 10000; # 报错 distinct\n# 对有重复的展示数据进行去重操作 select distinct post from emp; order by\nselect * from emp order by salary asc; # 默认升序排 select * from emp order by salary desc; # 降序排 select * from emp order by age desc; # 降序排 # 先按照age降序排，在年轻相同的情况下再按照薪资升序排 select * from emp order by age desc,salary asc; # 统计各部门年龄在10岁以上的员工平均工资，并且保留平均工资大于1000的部门，然后对平均工资进行排序 select post,avg(salary) from emp where age \u0026gt; 10 group by post having avg(salary) \u0026gt; 1000 order by avg(salary) ; limit\n# 限制展示条数 select * from emp limit 3; # 查询工资最高的人的详细信息 select * from emp order by salary desc limit 1; # 分页显示 select * from emp limit 0,5; # 第一个参数表示起始位置，第二个参数表示的是条数，不是索引位置 select * from emp limit 5,5; 正则\nselect * from emp where name regexp '^j.*(n|y)$'; 多表查询\n创建表\n# 建表 create table dep( id int, name varchar(20) ); create table emp( id int primary key auto_increment, name varchar(20), sex enum('male','female') not null default 'male', age int, dep_id int ); # 插入数据 insert into dep values (200,'技术'), (201,'人力资源'), (202,'销售'), (203,'运营'); +------+--------------+ | id | name | +------+--------------+ | 200 | 技术 | | 201 | 人力资源 | | 202 | 销售 | | 203 | 运营 | +------+--------------+ insert into emp(name,sex,age,dep_id) values ('jason','male',18,200), ('egon','female',48,201), ('kevin','male',38,201), ('nick','female',28,202), ('owen','male',18,200), ('jerry','female',18,204); +----+-------+--------+------+--------+ | id | name | sex | age | dep_id | +----+-------+--------+------+--------+ | 1 | jason | male | 18 | 200 | | 2 | egon | female | 48 | 201 | | 3 | kevin | male | 38 | 201 | | 4 | nick | female | 28 | 202 | | 5 | owen | male | 18 | 200 | | 6 | jerry | female | 18 | 204 | +----+-------+--------+------+--------+ # 当初为什么我们要分表，就是为了方便管理，在硬盘上确实是多张表，但是到了内存中我们应该把他们再拼成一张表进行查询才合理 连表查询\nselect * from emp,dep; # 左表一条记录与右表所有记录都对应一遍\u0026gt;\u0026gt;\u0026gt;笛卡尔积 +----+-------+--------+------+--------+------+--------------+ | id | name | sex | age | dep_id | id | name | +----+-------+--------+------+--------+------+--------------+ | 1 | jason | male | 18 | 200 | 200 | 技术 | | 1 | jason | male | 18 | 200 | 201 | 人力资源 | | 1 | jason | male | 18 | 200 | 202 | 销售 | | 1 | jason | male | 18 | 200 | 203 | 运营 | | 2 | egon | female | 48 | 201 | 200 | 技术 | | 2 | egon | female | 48 | 201 | 201 | 人力资源 | | 2 | egon | female | 48 | 201 | 202 | 销售 | | 2 | egon | female | 48 | 201 | 203 | 运营 | | 3 | kevin | male | 38 | 201 | 200 | 技术 | | 3 | kevin | male | 38 | 201 | 201 | 人力资源 | | 3 | kevin | male | 38 | 201 | 202 | 销售 | | 3 | kevin | male | 38 | 201 | 203 | 运营 | | 4 | nick | female | 28 | 202 | 200 | 技术 | | 4 | nick | female | 28 | 202 | 201 | 人力资源 | | 4 | nick | female | 28 | 202 | 202 | 销售 | | 4 | nick | female | 28 | 202 | 203 | 运营 | | 5 | owen | male | 18 | 200 | 200 | 技术 | | 5 | owen | male | 18 | 200 | 201 | 人力资源 | | 5 | owen | male | 18 | 200 | 202 | 销售 | | 5 | owen | male | 18 | 200 | 203 | 运营 | | 6 | jerry | female | 18 | 204 | 200 | 技术 | | 6 | jerry | female | 18 | 204 | 201 | 人力资源 | | 6 | jerry | female | 18 | 204 | 202 | 销售 | | 6 | jerry | female | 18 | 204 | 203 | 运营 | +----+-------+--------+------+--------+------+--------------+ # 将所有的数据都对应了一遍，虽然不合理但是其中有合理的数据，现在我们需要做的就是找出合理的数据 # 查询员工及所在部门的信息 select * from emp,dep where emp.dep_id = dep.id; +----+-------+--------+------+--------+------+--------------+ | id | name | sex | age | dep_id | id | name | +----+-------+--------+------+--------+------+--------------+ | 1 | jason | male | 18 | 200 | 200 | 技术 | | 2 | egon | female | 48 | 201 | 201 | 人力资源 | | 3 | kevin | male | 38 | 201 | 201 | 人力资源 | | 4 | nick | female | 28 | 202 | 202 | 销售 | | 5 | owen | male | 18 | 200 | 200 | 技术 | +----+-------+--------+------+--------+------+--------------+ # 查询部门为技术部的员工及部门信息 select * from emp,dep where emp.dep_id = dep.id and dep.name = '技术'; +----+-------+------+------+--------+------+--------+ | id | name | sex | age | dep_id | id | name | +----+-------+------+------+--------+------+--------+ | 1 | jason | male | 18 | 200 | 200 | 技术 | | 5 | owen | male | 18 | 200 | 200 | 技术 | +----+-------+------+------+--------+------+--------+ # 将两张表关联到一起的操作，有专门对应的方法 # 1、内连接：只取两张表有对应关系的记录 select * from emp inner join dep on emp.dep_id = dep.id; +----+-------+--------+------+--------+------+--------------+ | id | name | sex | age | dep_id | id | name | +----+-------+--------+------+--------+------+--------------+ | 1 | jason | male | 18 | 200 | 200 | 技术 | | 2 | egon | female | 48 | 201 | 201 | 人力资源 | | 3 | kevin | male | 38 | 201 | 201 | 人力资源 | | 4 | nick | female | 28 | 202 | 202 | 销售 | | 5 | owen | male | 18 | 200 | 200 | 技术 | +----+-------+--------+------+--------+------+--------------+ select * from emp inner join dep on emp.dep_id = dep.id where dep.name = \u0026quot;技术\u0026quot;; +----+-------+------+------+--------+------+--------+ | id | name | sex | age | dep_id | id | name | +----+-------+------+------+--------+------+--------+ | 1 | jason | male | 18 | 200 | 200 | 技术 | | 5 | owen | male | 18 | 200 | 200 | 技术 | +----+-------+------+------+--------+------+--------+ # 2、左连接: 在内连接的基础上保留左表没有对应关系的记录 select * from emp left join dep on emp.dep_id = dep.id; +----+-------+--------+------+--------+------+--------------+ | id | name | sex | age | dep_id | id | name | +----+-------+--------+------+--------+------+--------------+ | 1 | jason | male | 18 | 200 | 200 | 技术 | | 5 | owen | male | 18 | 200 | 200 | 技术 | | 2 | egon | female | 48 | 201 | 201 | 人力资源 | | 3 | kevin | male | 38 | 201 | 201 | 人力资源 | | 4 | nick | female | 28 | 202 | 202 | 销售 | | 6 | jerry | female | 18 | 204 | NULL | NULL | +----+-------+--------+------+--------+------+--------------+ # 3、右连接: 在内连接的基础上保留右表没有对应关系的记录 select * from emp right join dep on emp.dep_id = dep.id; +------+-------+--------+------+--------+------+--------------+ | id | name | sex | age | dep_id | id | name | +------+-------+--------+------+--------+------+--------------+ | 1 | jason | male | 18 | 200 | 200 | 技术 | | 2 | egon | female | 48 | 201 | 201 | 人力资源 | | 3 | kevin | male | 38 | 201 | 201 | 人力资源 | | 4 | nick | female | 28 | 202 | 202 | 销售 | | 5 | owen | male | 18 | 200 | 200 | 技术 | | NULL | NULL | NULL | NULL | NULL | 203 | 运营 | +------+-------+--------+------+--------+------+--------------+ # 4、全连接：在内连接的基础上保留左、右面表没有对应关系的的记录 select * from emp left join dep on emp.dep_id = dep.id union select * from emp right join dep on emp.dep_id = dep.id; +------+-------+--------+------+--------+------+--------------+ | id | name | sex | age | dep_id | id | name | +------+-------+--------+------+--------+------+--------------+ | 1 | jason | male | 18 | 200 | 200 | 技术 | | 5 | owen | male | 18 | 200 | 200 | 技术 | | 2 | egon | female | 48 | 201 | 201 | 人力资源 | | 3 | kevin | male | 38 | 201 | 201 | 人力资源 | | 4 | nick | female | 28 | 202 | 202 | 销售 | | 6 | jerry | female | 18 | 204 | NULL | NULL | | NULL | NULL | NULL | NULL | NULL | 203 | 运营 | +------+-------+--------+------+--------+------+--------------+ 子查询\n# 就是将一个查询语句的结果用括号括起来当作另外一个查询语句的条件去用 # 1.查询部门是技术或者人力资源的员工信息 \u0026quot;\u0026quot;\u0026quot; 先获取技术部和人力资源部的id号，再去员工表里面根据前面的id筛选出符合要求的员工信息 \u0026quot;\u0026quot;\u0026quot; select * from emp where dep_id in (select id from dep where name = \u0026quot;技术\u0026quot; or name = \u0026quot;人力资源\u0026quot;); +----+-------+--------+------+--------+ | id | name | sex | age | dep_id | +----+-------+--------+------+--------+ | 1 | jason | male | 18 | 200 | | 2 | egon | female | 48 | 201 | | 3 | kevin | male | 38 | 201 | | 5 | owen | male | 18 | 200 | +----+-------+--------+------+--------+ # 2.每个部门最新入职的员工 思路：先查每个部门最新入职的员工，再按部门对应上联表查询 select t1.id,t1.name,t1.hire_date,t1.post,t2.* from emp as t1 inner join (select post,max(hire_date) as max_date from emp group by post) as t2 on t1.post = t2.post where t1.hire_date = t2.max_date ; \u0026quot;\u0026quot;\u0026quot; 记住一个规律，表的查询结果可以作为其他表的查询条件，也可以通过其别名的方式把它作为一张虚拟表去跟其他表做关联查询 \u0026quot;\u0026quot;\u0026quot; select * from emp inner join dep on emp.dep_id = dep.id; +----+-------+--------+------+--------+------+--------------+ | id | name | sex | age | dep_id | id | name | +----+-------+--------+------+--------+------+--------------+ | 1 | jason | male | 18 | 200 | 200 | 技术 | | 2 | egon | female | 48 | 201 | 201 | 人力资源 | | 3 | kevin | male | 38 | 201 | 201 | 人力资源 | | 4 | nick | female | 28 | 202 | 202 | 销售 | | 5 | owen | male | 18 | 200 | 200 | 技术 | +----+-------+--------+------+--------+------+--------------+ ","permalink":"https://zhong-er.com/posts/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%E7%9A%84%E6%9F%A5%E8%AF%A2/","summary":"\u003cp\u003e\u003cimg alt=\"表的查询.png\" loading=\"lazy\" src=\"/assets/201908214364_2376_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e表的查询 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/GmqlUoRwa\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003ca href=\"https://www.cnblogs.com/renvip/p/11391437.html\" title=\"博客园\"\u003e\u003c/a\u003e\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11391437.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e单表查询\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e前期表准备\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ecreate table emp(\n  id int not null unique auto_increment,\n  name varchar(20) not null,\n  sex enum('male','female') not null default 'male',  # 大部分是男的\n  age int(3) unsigned not null default 28,\n  hire_date date not null,\n  post varchar(50),\n  post_comment varchar(100),\n  salary double(15,2),\n  office int,  # 一个部门一个屋子\n  depart_id int\n);# 插入记录# 三个部门：教学，销售，运营insert into emp(name,sex,age,hire_date,post,salary,office,depart_id) values\n('jason','male',18,'20170301','张江第一帅形象代言',7300.33,401,1),  # 以下是教学部\n('egon','male',78,'20150302','teacher',1000000.31,401,1),\n('kevin','male',81,'20130305','teacher',8300,401,1),\n('tank','male',73,'20140701','teacher',3500,401,1),\n('owen','male',28,'20121101','teacher',2100,401,1),\n('jerry','female',18,'20110211','teacher',9000,401,1),\n('nick','male',18,'19000301','teacher',30000,401,1),\n('sean','male',48,'20101111','teacher',10000,401,1),\n('歪歪','female',48,'20150311','sale',3000.13,402,2),  # 以下是销售部门\n('丫丫','female',38,'20101101','sale',2000.35,402,2),\n('丁丁','female',18,'20110312','sale',1000.37,402,2),\n('星星','female',18,'20160513','sale',3000.29,402,2),\n('格格','female',28,'20170127','sale',4000.33,402,2),\n('张野','male',28,'20160311','operation',10000.13,403,3),  # 以下是运营部门\n('程咬金','male',18,'19970312','operation',20000,403,3),\n('程咬银','female',18,'20130311','operation',19000,403,3),\n('程咬铜','male',18,'20150411','operation',18000,403,3),\n('程咬铁','female',18,'20140512','operation',17000,403,3);\n# ps：如果在windows系统中，插入中文字符，select的结果为空白，可以将所有字符编码统一设置成gbk\n+----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+\n| id | name      | sex    | age | hire_date  | post                        | post_comment | salary     | office | depart_id |\n+----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+\n|  1 | jason     | male   |  18 | 2017-03-01 | 张江第一帅形象代言          | NULL         |    7300.33 |    401 |         1 |\n|  2 | egon      | male   |  78 | 2015-03-02 | teacher                     | NULL         | 1000000.31 |    401 |         1 |\n|  3 | kevin     | male   |  81 | 2013-03-05 | teacher                     | NULL         |    8300.00 |    401 |         1 |\n|  4 | tank      | male   |  73 | 2014-07-01 | teacher                     | NULL         |    3500.00 |    401 |         1 |\n|  5 | owen      | male   |  28 | 2012-11-01 | teacher                     | NULL         |    2100.00 |    401 |         1 |\n|  6 | jerry     | female |  18 | 2011-02-11 | teacher                     | NULL         |    9000.00 |    401 |         1 |\n|  7 | nick      | male   |  18 | 1900-03-01 | teacher                     | NULL         |   30000.00 |    401 |         1 |\n|  8 | sean      | male   |  48 | 2010-11-11 | teacher                     | NULL         |   10000.00 |    401 |         1 |\n|  9 | 歪歪      | female |  48 | 2015-03-11 | sale                        | NULL         |    3000.13 |    402 |         2 |\n| 10 | 丫丫      | female |  38 | 2010-11-01 | sale                        | NULL         |    2000.35 |    402 |         2 |\n| 11 | 丁丁      | female |  18 | 2011-03-12 | sale                        | NULL         |    1000.37 |    402 |         2 |\n| 12 | 星星      | female |  18 | 2016-05-13 | sale                        | NULL         |    3000.29 |    402 |         2 |\n| 13 | 格格      | female |  28 | 2017-01-27 | sale                        | NULL         |    4000.33 |    402 |         2 |\n| 14 | 张野      | male   |  28 | 2016-03-11 | operation                   | NULL         |   10000.13 |    403 |         3 |\n| 15 | 程咬金    | male   |  18 | 1997-03-12 | operation                   | NULL         |   20000.00 |    403 |         3 |\n| 16 | 程咬银    | female |  18 | 2013-03-11 | operation                   | NULL         |   19000.00 |    403 |         3 |\n| 17 | 程咬铜    | male   |  18 | 2015-04-11 | operation                   | NULL         |   18000.00 |    403 |         3 |\n| 18 | 程咬铁    | female |  18 | 2014-05-12 | operation                   | NULL         |   17000.00 |    403 |         3 |\n+----+-----------+--------+-----+------------+-----------------------------+--------------+------------+--------+-----------+\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e语法执行顺序\u003c/strong\u003e\u003c/p\u003e","title":"【数据库】表的查询"},{"content":"\n表与表的关系 \u0026raquo;\u0026gt;思维导图\u0026gt; \u0026raquo;博客园\n表与表之间的关系\n所有信息都记录在一张表中所带来的问题\n1.表的结构不清晰\n2.浪费硬盘空间\n3.表的扩展性极差(无法忽略的缺点)\n类似于我们将所有的代码都写在同一个py文件内\n确立表与表之间的关系一定要换位思考(必须两方都考虑周全之后才能得出结论)\n一对多关系\n以员工表和部门为例\n先站在员工表看，一个部门可以有多个员工\n再站在部门表看，一个员工不能属多个部门\n结论：员工表和部门表之间仅仅是单向的多对一，那么他们的表关系就是‘一对多’(表关系中没有多对一一说，只有一对多，无论是多对一还是一对多都叫‘一对多’)\n外键\n如何让两种表有代码层上真正的关联，就必须使用外键。\n什么是外键？外键就是让表与表有硬性层上的关系\nforeign key 外键约束\n1.在创建表的时候，必须先创建被关联表\n2.插入数据的时候，也必须先插入被关联表的数据\n建表\ncreate table dep( id int primary key auto_increment, dep_name varchar(32), dep_desc varchar(128) ); create table emp( id int primary key auto_increment, emp_name varchar(64), emp_gender enum('male','female','others') default 'male', dep_id int, foreign key(dep_id) references dep(id)\t); 插入数据\ninsert into dep(dep_name,dep_desc) values('外交部','搞外交'), ('教学部','教书育人'), ('技术部','技术能力有限部门'); insert into emp(emp_name,dep_id) values('jason',1), ('egon',2), ('tank',2), ('kevin',3); 修改表数据\nupdate dep set id=200 where id = 1; update emp set dep_id = 300 where id = 1; delete from dep where id = 3; 外键虽然能够帮你强制建立表关系 但是也会给表之间增加数据相关的约束\n1.删除数据的时候 先删员工表的数据 再删部门表的数据\ndelete from emp where id = 4; delete from dep where id = 3; 2.级联更新级联删除\ncreate table dep( id int primary key auto_increment, dep_name varchar(32), dep_desc varchar(128) ); create table emp( id int primary key auto_increment, emp_name varchar(64), emp_gender enum('male','female','others') default 'male', dep_id int, foreign key(dep_id) references dep(id) on update cascade on delete cascade ); insert into dep(dep_name,dep_desc) values('外交部','搞外交'), ('教学部','教书育人'), ('技术部','技术能力有限部门'); insert into emp(emp_name,dep_id) values('jason',1), ('egon',2), ('tank',2), ('kevin',3); update dep set id=200 where id = 3; delete from dep where id = 2; 多对多\n案例：图书表与作者表\n站在图书表来看，一个作者能写多本图书\n站在作者表来看，一本书可以有多个作者\n如果双方都可以，那么就是多对多\n强调foreign key只是用来帮你建表关系的，不是某个关系特有的方法\n注意：多对多关系的建立必须手动创建第三张表，用来专门记录两种表之间的关系\n建表\n# 先建两种普通的表 不需要设置外键 create table book( id int primary key auto_increment, title varchar(32), price int ); create table author( id int primary key auto_increment, name varchar(32), age int ); create table book2author( id int primary key auto_increment, book_id int, foreign key(book_id) references book(id) on update cascade on delete cascade, author_id int, foreign key(author_id) references author(id) on update cascade on delete cascade ); 插入数据\ninsert into book(title,price) values('金瓶梅',199),('聊斋',299),('jason教你删别人的库,让别人跑去吧',1); insert into author(name,age) values('jason',18),('tank',38); insert into book2author(book_id,author_id) values(4,3); # 报错 insert into book2author(book_id,author_id) values(1,1),(1,2),(2,1),(3,1),(3,2); 一对一\n一对一的场景，当你的表特别庞大的时候，你可以考虑拆分表\n案例：腾讯视频用户和会员\n当你没有交费之前，你是腾讯视频的用户\n当你交费之后，你就变成了腾讯视频的会员\n不是所有的客户都能变成会员\n建表\n# 左表的一条记录唯一对应右表的一条记录，反之也一样 create table user( id int primary key auto_increment, name char(20) not null, qq char(10) not null, phone char(16) not null ); create table vip( id int primary key auto_increment, vip_class int not null, user_id int unique, # 该字段一定要是唯一的 foreign key(user_id) references user(id) # 外键的字段一定要保证unique on delete cascade on update cascade ); # 三种外键关系都是用foreign key，区别在于如何使用以及其他条件限制即可做出三种关系 外键字段\n通常将关系字段称之为外键字段\n1.一对多的外键字段，建在多的一方\n2.多对多的外键字段，建在第三张表\n3.一对多的外键字段，建在任意一方都可以，但是推荐你建在查询频率较高的一方\ncreate table authordetail1( id int primary key auto_increment, phone int, addr char(255) ); create table author1( id int primary key auto_increment, name char(4), age int, authordetail_id int unique, foreign key(authordetail_id) references authordetail1(id) on update cascade on delete cascade ); 判断表关系的最简单的语法\n图书与出版社\n一本书可不可以有多个出版社 不可以!!! 一个出版社可不可以出版多本书 可以!!! 一对多的关系 图书与作者表\n一本书可不可以有多个作者 可以!!! 一个作者可不可以写多本书 可以!!! 多对多的关系 作者与作者详情\n一个作者可不可以有多个详情 不可以!!! 一个作者详情可不可以有多个作者 不可以!!! 要么两者是一对一 要么两者之间没任何关系 表的相关操作\n修改表\n# mysql对大小写不敏感！！！ 语法： 1. 修改表名 ALTER TABLE 表名 RENAME 新表名; 2. 增加字段 ALTER TABLE 表名 ADD 字段名 数据类型 [完整性约束条件…], ADD 字段名 数据类型 [完整性约束条件…]; ALTER TABLE 表名 ADD 字段名 数据类型 [完整性约束条件…] FIRST; ALTER TABLE 表名 ADD 字段名 数据类型 [完整性约束条件…] AFTER 字段名; 3. 删除字段 ALTER TABLE 表名 DROP 字段名; 4. 修改字段 # modify只能改字段数据类型完整约束，不能改字段名，但是change可以！ ALTER TABLE 表名 MODIFY 字段名 数据类型 [完整性约束条件…]; ALTER TABLE 表名 CHANGE 旧字段名 新字段名 旧数据类型 [完整性约束条件…]; ALTER TABLE 表名 CHANGE 旧字段名 新字段名 新数据类型 [完整性约束条件…]; 复制表\n# 查询语句执行的结果也是一张表，可以看成虚拟表 # 复制表结构＋记录 （key不会复制: 主键、外键和索引） create table new_service select * from service; # 只复制表结构 select * from service where 1=2; //条件为假，查不到任何记录 create table new1_service select * from service where 1=2; create table t4 like employees; ","permalink":"https://zhong-er.com/posts/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%E4%B8%8E%E8%A1%A8%E7%9A%84%E5%85%B3%E7%B3%BB/","summary":"\u003cp\u003e\u003cimg alt=\"表与表的关系.png\" loading=\"lazy\" src=\"/assets/201908208866_4264_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e表与表的关系 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/1SXlYCyZo8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11385551.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e表与表之间的关系\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e所有信息都记录在一张表中所带来的问题\u003c/p\u003e\n\u003cp\u003e1.表的结构不清晰\u003c/p\u003e\n\u003cp\u003e2.浪费硬盘空间\u003c/p\u003e\n\u003cp\u003e3.表的扩展性极差(无法忽略的缺点)\u003c/p\u003e\n\u003cp\u003e类似于我们将所有的代码都写在同一个py文件内\u003c/p\u003e\n\u003cp\u003e确立表与表之间的关系一定要换位思考(必须两方都考虑周全之后才能得出结论)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e一对多关系\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e以员工表和部门为例\u003c/p\u003e\n\u003cp\u003e先站在员工表看，一个部门可以有多个员工\u003c/p\u003e\n\u003cp\u003e再站在部门表看，一个员工不能属多个部门\u003c/p\u003e\n\u003cp\u003e结论：员工表和部门表之间仅仅是单向的多对一，那么他们的表关系就是‘一对多’(表关系中没有多对一一说，只有一对多，无论是多对一还是一对多都叫‘一对多’)\u003c/p\u003e\n\u003cp\u003e外键\u003c/p\u003e\n\u003cp\u003e如何让两种表有代码层上真正的关联，就必须使用外键。\u003c/p\u003e\n\u003cp\u003e什么是外键？外键就是让表与表有硬性层上的关系\u003c/p\u003e\n\u003cp\u003eforeign key 外键约束\u003c/p\u003e\n\u003cp\u003e1.在创建表的时候，必须先创建被关联表\u003c/p\u003e\n\u003cp\u003e2.插入数据的时候，也必须先插入被关联表的数据\u003c/p\u003e\n\u003cp\u003e建表\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ecreate table dep(\n        id int primary key auto_increment,\n        dep_name varchar(32),\n        dep_desc varchar(128)\n    );\n\ncreate table emp(\n        id int primary key auto_increment,\n        emp_name varchar(64),\n        emp_gender enum('male','female','others') default 'male',\n        dep_id int,\n        foreign key(dep_id) references dep(id)\t\n    );\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e插入数据\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003einsert into dep(dep_name,dep_desc) values('外交部','搞外交'),\n('教学部','教书育人'),\n('技术部','技术能力有限部门');\n\ninsert into emp(emp_name,dep_id) values('jason',1),\n('egon',2),\n('tank',2),\n('kevin',3);\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e修改表数据\u003c/p\u003e","title":"【数据库】表与表的关系"},{"content":"\n表操作 \u0026raquo;\u0026gt;思维导图\u0026gt;****\u0026gt; \u0026gt;博客园********\n数据类型\n创建表的完整语法\n# 语法： create table 表名( 字段名1 类型[(宽度)约束条件]， 字段名2 类型[(宽度)约束条件]， 字段名3类型[(宽度)约束条件] ) # 注意： 1.在同一张表中，字段名不嫩相同 2.宽度和约束条件可选，字段名和类型是必须的 3.最后一个字段后不能加逗号 # 补充： # 1.宽度指的是对存储数据的限制 create table userinfo(name char) insert into userinfo values('jsaon') \u0026quot;\u0026quot;\u0026quot; 1.没有安全模式的数据库版本，能够存放数据，但只会存进去一个j 2.最新版的数据库版本直接报错提示无法存储：Data too long for column 'name' at row 1 \u0026quot;\u0026quot;\u0026quot; # 2.约束条件初识\u0026gt;\u0026gt;\u0026gt; null 与 not null create table t1(id int,name char not null); insert into t1 values(1,'j'); # 正常存储 insert into t1 values(2,null); # 报错 # 总结 类型与约束条件的区别 # 类型：限制字段必须以什么样的数据类型存储 # 约束条件：约束条件是在类型之外添加的一种额外的限制 基本数据类型\n整型\n\u0026quot;\u0026quot;\u0026quot; 分类：TINYINT SMALLINT MEDIUMINT INT BIGINT 作用：存储年龄，等级，id，各种号码等 类型存储范围：参考上图 验证整型字段有无符号及范围 \u0026quot;\u0026quot;\u0026quot; create table t1(x tinyint); insert into t1 values(128),(-129); create table t2(x tinyint unsigned); insert into t2 values(-1),(256); create table t3(x int unsigned); insert into t3 values(4294967296); # 疑问:类型后面的宽度能否改变字段存储的大小限制create table t4(x int(8)); insert into t4 values(4294967296123); # 显示时，不够8位用0填充，如果超出8位则正常显示 create table t5(x int(8) unsigned zerofill); insert into t5 values(4294967296123); # create table t6(id int(10) unsigned); # create table t7(id int(11)); \u0026quot;\u0026quot;\u0026quot; 强调：对于整型来说，数据类型后的宽度并不是存储限制，而是显示限制，所以在创建表时，如果字段采用的是整型类型，完全无需指定显示宽度， 默认的显示宽度，足够显示完整当初存放的数据 严格模式补充：我们刚刚在上面设置了char,tinyint,存储数据时超过它们的最大存储长度，发现数据也能正常存储进去，只是mysql帮我们自动截取了最大长度。但在实际情况下，我们应该尽量减少数据库的操作，缓解数据库的压力，让它仅仅只管理数据即可，这样的情况下就需要设置安全模式 \u0026quot;\u0026quot;\u0026quot; show variables like \u0026quot;%mode%\u0026quot;; # 查看数据库配置中变量名包含mode的配置参数 # 修改安全模式 set session # 只在当前操作界面有效 set global # 全局有效 set global sql_mode ='STRICT_TRANS_TABLES' # 修改完之后退出当前客户端重新登陆即可 浮点型\n\u0026quot;\u0026quot;\u0026quot; 分类:FLOAT DOUBLE decimal 应用场景:身高，体重，薪资 字段限制特点(5,3)前一位表示所有的位数，后一位表示小数个数 三者最大整数位和小数位对比: \u0026quot;\u0026quot;\u0026quot; # 存储限制 float(255,30) double(255,30) decimal(255,30) # 精确度验证 create table t9(x float(255,30)); create table t10(x double(255,30)); create table t11(x decimal(65,30)); insert into t9 values(1.012345678901234567890123456789); insert into t10 values(1.012345678901234567890123456789); insert into t11 values(1.012345678901234567890123456789); 字符类型\n\u0026quot;\u0026quot;\u0026quot; 分类 char(定长) varchar(变长) 作用:姓名，地址，描述类信息 \u0026quot;\u0026quot;\u0026quot; create table t10(name char(4)) # 超出四个字符报错，不够四个字符空格补全 create table t11(name varchar(4)) # 超出四个字符报错，不够四个有几个就存几个 # 验证存储限制 insert into t12 values('hello'); insert into t13 values('hello'); # 验证存储长度 insert into t12 values('a'); #'a ' insert into t13 values('a'); #'a' select * from t12 select * from t13 # 无法查看真正的结果 select char_length(name) from t12 select char_length(name) from t13 # 仍然无法查看到真正的结果 \u0026quot;\u0026quot;\u0026quot;首先应该肯定的是在硬盘上存的绝对是真正的数据，但显示的时候mysql会自动将末尾的空格取掉\u0026quot;\u0026quot;\u0026quot; # 如果不想让mysql帮你做自动去除末尾空格的操作，需要再添加一个模式 set global sql_mode=\u0026quot;strict_trans_tables,PAD_CHAR_TO_FULL_LENGTH\u0026quot;; # 退出客户端重新登陆 select char_length(x) from t12; #4 select char_length(y) from t13; #1 # 针对char类型，mysql在存储时会将数据用空格补全存放到硬盘中。但是会在读出结果的时候自动取掉末尾的空格 \u0026quot;\u0026quot;\u0026quot; char与varchar的使用区别 \u0026quot;\u0026quot;\u0026quot; name char(5) # 缺点：浪费空间 # 优点：存取速度都快 egon alex lxx jxx txx name varchar(5) # 缺点：存取速度慢 # 优点：节省空间 1bytes+egon 1bytes+alex 1bytes+lxx 1bytes+jxx 1bytes+txx 时间类型\n# 分类： date time datetime year create table student( id int, name char(16), born_year year, birth date, study_time time, reg_time datetime ); insert into student values(1,'egon','2019','2019-05-09','11:11:00','2019-11-11 11:11:11'); 枚举与集合类型\n# 分类：枚举enum多选一 集合set多选多 create table user( id int, name char(16), gender enum('male','female','others') ); insert into user values(1,'jason','xxx') # 报错 insert into user values(2,'egon','female') # 正确！ create table teacher( id int, name char(16), gender enum('male','female','others'), hobby set('read','sleep','sanna','dbj') ); insert into teacher values(1,'egon','male','read,sleep,dbj') # 集合也可以只存一个 约束条件\n\u0026quot;\u0026quot;\u0026quot; PRIMARY KEY (PK) 标识该字段为该表的主键，可以唯一的标识记录 FOREIGN KEY (FK) 标识该字段为该表的外键 NOT NULL 标识该字段不能为空 UNIQUE KEY (UK) 标识该字段的值是唯一的 AUTO_INCREMENT 标识该字段的值自动增长（整数类型，而且为主键） DEFAULT 为该字段设置默认值 UNSIGNED 无符号 ZEROFILL 使用0填充 \u0026quot;\u0026quot;\u0026quot; not null + default\ncreate table user( id int, name char(16) ); insert into user values(1,null) # 可以修改 alter table user modify name char(16) not null; insert into user(name,id) values(null,2); # 报错 插入数据可以在表名后面指定插入数据对应的字段 create table student( id int, name char(16) not null, gender enum('male','female','others') default 'male' ) insert into student(id,name) values(1,'jason') # 成功 unique\n# 单列唯一 create table user1( id int unique, name char(16) ); insert into user1 values(1,'jason'),(1,'egon') # 报错 insert into user1 values(1,'jason'),(2,'egon') # 成功 # 联合唯一 create table server( id int, ip char(16), port int, unique(ip,port) ) insert into server values(1,'127.0.0.1',8080); insert into server values(2,'127.0.0.1',8080); # 报错 insert into server values(1,'127.0.0.1',8081); primary key + auto_increment\n# 单从约束角度来说primary key就等价于not null unique create table t11(id int primary key); desc t11; insert into t11 values(1),(1); # 报错 insert into t11 values(1),(2); # 除了约束之外，它还是innodb引擎组织数据的依据，提升查询效率 \u0026quot;\u0026quot;\u0026quot; 强调: 1.一张表中必须有且只有一个主键，如果你没有设置主键，那么会从上到下搜索直到遇到一个非空且唯一的字段自动将其设置为主键 \u0026quot;\u0026quot;\u0026quot; create table t12( id int, name char(16), age int not null unique, addr char(16) not null unique )engine=innodb; desc t12; \u0026quot;\u0026quot;\u0026quot; 2.如果表里面没有指定任何的可以设置为主键的字段，那么innodb会采用自己默认的一个隐藏字段作为主键，隐藏意味着你在查询的时候无法根据这个主键字段加速查询了 索引：类似于书的目录，没有主键就相当于一页一页翻着查 3.一张表中通常都应该有一个id字段，并且通常将改id字段作成主键 \u0026quot;\u0026quot;\u0026quot; # 联合主键：多个字段联合起来作为表的一个主键，本质还是一个主键 create table t18( ip char(16), port int, primary key(ip,port) ); desc t18; # 主键id作为数据的编号，每次最好能自动递增 create table t13( id int primary key auto_increment, name char(16) ); insert into t13('jason'),('jason'),('jason'); # id字段自动从1开始递增 # 注意：auto_increment通常都是加在主键上，并且只能给设置为key的字段加 补充\n\u0026quot;\u0026quot;\u0026quot; delete from tb1; 强调：上面的这条命令确实可以将表里的所有记录都删掉，但不会将id重置为0， 所以收该条命令根本不是用来清空表的，delete是用来删除表中某一些符合条件的记录 delete from tb1 where id \u0026gt; 10; 如果要清空表，使用truncate tb1； 作用：将整张表重置,id重新从0开始记录 \u0026quot;\u0026quot;\u0026quot; ","permalink":"https://zhong-er.com/posts/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%E6%93%8D%E4%BD%9C/","summary":"\u003cp\u003e\u003cimg alt=\"表操作.png\" loading=\"lazy\" src=\"/assets/201908198617_7065_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e表操作 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/3r9ztv_KD8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/a\u003e\u003cstrong\u003e\u0026gt;****\u0026gt; \u0026gt;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11379733.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e********\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e数据类型\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e创建表的完整语法\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 语法：\ncreate table 表名(\n字段名1 类型[(宽度)约束条件]，\n字段名2 类型[(宽度)约束条件]，\n字段名3类型[(宽度)约束条件]\n)\n# 注意：\n1.在同一张表中，字段名不嫩相同\n2.宽度和约束条件可选，字段名和类型是必须的\n3.最后一个字段后不能加逗号\n# 补充：\n# 1.宽度指的是对存储数据的限制\ncreate table userinfo(name char)\ninsert into userinfo values('jsaon')\n\u0026quot;\u0026quot;\u0026quot;\n1.没有安全模式的数据库版本，能够存放数据，但只会存进去一个j\n2.最新版的数据库版本直接报错提示无法存储：Data too long for column 'name' at row 1\n\u0026quot;\u0026quot;\u0026quot;\n# 2.约束条件初识\u0026gt;\u0026gt;\u0026gt; null 与 not null\ncreate table t1(id int,name char not null);\ninsert into t1 values(1,'j');  # 正常存储\ninsert into t1 values(2,null);  # 报错\n# 总结 类型与约束条件的区别\n# 类型：限制字段必须以什么样的数据类型存储\n# 约束条件：约束条件是在类型之外添加的一种额外的限制\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e基本数据类型\u003c/strong\u003e\u003c/p\u003e","title":"【数据库】表操作"},{"content":"\n初识MySQL数据库 \u0026raquo;\u0026gt;思维导图****************\u0026gt; \u0026raquo;博客园\n数据库概念\nSQL能干嘛呢？它就是一款软件，安装在任何一台计算机或者服务器上的时候，只要告诉它擦混关键一个文件，新增一个数据，删除一个数据，它就能帮我去做想要的操作。SQL就是用来帮我们操作文件的。\n软件目录开发规范规定了数据的存放位置，但是这样的存取操作都是在本地存取数据，无法基于互联网通信，所以我们需要保存数据到一个公共的地方，所有的用户涉及到数据相关都必须来这个公共地方查找。\nMySQL的本质就是一款基于网络通信的应用软件，任何基于网路通信的软件，底层都是socket\n服务端-基于socket通信-收发消息-SQL语句(是一个公共的标准) 客户端-基于socket通信-收发消息-SQL语句(是一个公共的标准) ps:MySQL不仅支持MySQL的客户端来操作，也支持其他编程语言直接操作，但不同编程语言操作的语法都不一样 MySQL其实可以把它看成一款支持远程操作文件的软件\n一些数据常用的名词例如\n库 \u0026gt;\u0026gt;\u0026gt; 文件夹 表 \u0026gt;\u0026gt;\u0026gt; 文件 记录 \u0026gt;\u0026gt;\u0026gt; 文件内一行行的数据叫做一条条的记录 表头 \u0026gt;\u0026gt;\u0026gt; 表格的第一行数据 字段 \u0026gt;\u0026gt;\u0026gt; 字段名+字段类型 DBMS：数据库管理系统\n数据库分为关系型数据库和非关系型数据库\n关系型数据库\n关系型数据库就是数据与数据之间可以有关联和限制\n关系型数据库通常都是表结构，也就意味着你在用关系型数据库的时候第一步就是确定表结构\n常见的关系型数据库：MySQL,oracle,sqlite,db2,sql server等\n非关系行数据库\n通常都是以k,v键值的形式 存储数据\n常见的非关系型数据库：redis,memcache,mongodb(文档型数据库，非常接近关系型的非关系型数据)\nMySQL安装\n软件目录初识\n官网下载MySQL社区版后解压查看文件目录，bin目录下含有：\nmysqld.exe服务端\nmysql.exe客户端\ncmd启动服务端与客户端\n首先切换到bin目录下再执行命令，终端建议使用管理员身份运行，通过win+r启动的是普通用户\n启动mysqld\n启动mysql\n如果不输入用户名和密码 默认是访客模式登陆 所能用到的功能很少 mysql -h 127.0.0.1 -P 3306 -uroot -p 可以简写：mysql -uroot -p mysql在初始登陆的时候 是没有密码的 直接回车即可 mysql中的sql语句 是以分号结束的 不敲分号 默认你没有输入完客户端还会让你继续输入，此时可输入；补全sql语句并执行 当命令输入错误的时候 可以用\\c取消前面的命令 cancel 客户端退出登录：exit 或 quit 查看某个进程：tasklist |findstr 名称 杀死进程：taskkill /F /PID 进程号 环境变量配置\n将启动文件所在的路径添加到系统的环境变量中\n注意:配置完之后一段要重新启动mysql服务端及cmd终端\n此时再使用cmd启动mysql就不需要切换到bin录下了\n系统服务制作\n制作系统服务 你的cmd终端一定要是管理员身份\nmysqld \u0026ndash;install\n制作完成即可通过services.msc启动关闭mysql服务了\n修改密码与破解管理员密码\n修改密码\n# 没有密码的情况下： mysqladmin -uroot -p password 123 # 有密码的情况下： mysqladmin -uroot -p123 password 123456 破解密码\n先将已经启动的服务端停掉\n1.跳过用户名和密码的验证功能 启动服务端\n# 跳过授权表启动服务端： mysqld --skip-grant-tables 2.修改管理员用户对应的密码\nupdate mysql.user set password=password(123) where user='root' and host='localhost'; 3.关闭当前服务端 重新以校验用户名密码的方式启动\n4.正常以用户名密码的方式 连接mysql服务端\n统一字符编码\n配置文件\n\\s查看 mysql服务端简单配置\n通常情况下配置文件的后缀都是ini结尾\nmysql自带的配置文件不要修改\n但是你可以新建一个配置文件 my.ini\nmysql服务端在启动就会自动加载你的my.ini配置文件内的配置\n修改字符编码通用配置**\n**\n[mysqld] character-set-server=utf8 collation-server=utf8_general_ci [client] default-character-set=utf8 [mysql] user='root' password=123 default-character-set=utf8 mac系统需finder中切换到默认安装路径下/usr/local/mysql\n配置文件是my.cnf，没有ini等windows上的配置文件\n基本sql语句\n对库的操作\n# 增 create database db1; # 查 show databases; # 查所有 show create database db1; # 查单个 # 改 alter database db1 charset='gbk'; # 修改编码 # 删 drop database db1; # 删库 对表的操作\n在创建表的时候，需要先指定库\nuse dbname # 指定库 select database() # 查看当前虽在的库 # 增 create table userinfo(id int,name char); # 查 show tables; # 查看某个库下面的所有的表 show create table userinfo; # 查看表的详细信息 desc userinfo; \u0026lt;==\u0026gt; describe userinfo; # 查看表结构 # 改 alter table userinfo modify name char(32); # 删 drop table userinfo; 对记录的操作\n# 增 insert into userinfo values(1,'jason',123); # 插入单条数据 insert into userinfo values(1,'jason',123),(2,'egon',123),(3,'tank',123); # 插入多条数据 # 查 select * from userinfo; # 查询所有的字段信息 select name from userinfo; # 查询指定字段信息 select id,name from userinfo where id=1 or name=tank; # 带有筛选条件的字段信息 # 改 update userinfo set name='kevin' where id=1; # 修改数据的一个字段信息 update userinfo set name='jason',password=666 where id=1; # 修改数据的多个字段 # 删 delete from userinfo where id =1; # 指定删符合条件的数据 delete from userinfo; # 将表中的数据全部删除 ","permalink":"https://zhong-er.com/posts/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%88%9D%E8%AF%86mysql%E6%95%B0%E6%8D%AE%E5%BA%93/","summary":"\u003cp\u003e\u003cimg alt=\"初识数据库.png\" loading=\"lazy\" src=\"/assets/201908165672_5043_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e初识MySQL数据库 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/1Tr5lwG5a8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****************\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;博客园\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e数据库概念\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eSQL能干嘛呢？它就是一款软件，安装在任何一台计算机或者服务器上的时候，只要告诉它擦混关键一个文件，新增一个数据，删除一个数据，它就能帮我去做想要的操作。SQL就是用来帮我们操作文件的。\u003c/p\u003e\n\u003cp\u003e软件目录开发规范规定了数据的存放位置，但是这样的存取操作都是在本地存取数据，无法基于互联网通信，所以我们需要保存数据到一个公共的地方，所有的用户涉及到数据相关都必须来这个公共地方查找。\u003c/p\u003e\n\u003cp\u003eMySQL的本质就是一款基于网络通信的应用软件，任何基于网路通信的软件，底层都是socket\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e服务端-基于socket通信-收发消息-SQL语句(是一个公共的标准)\n客户端-基于socket通信-收发消息-SQL语句(是一个公共的标准)\nps:MySQL不仅支持MySQL的客户端来操作，也支持其他编程语言直接操作，但不同编程语言操作的语法都不一样\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eMySQL其实可以把它看成一款支持远程操作文件的软件\u003c/p\u003e\n\u003cp\u003e一些数据常用的名词例如\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e库 \u0026gt;\u0026gt;\u0026gt; 文件夹\n表 \u0026gt;\u0026gt;\u0026gt; 文件\n记录 \u0026gt;\u0026gt;\u0026gt; 文件内一行行的数据叫做一条条的记录\n表头 \u0026gt;\u0026gt;\u0026gt; 表格的第一行数据\n字段 \u0026gt;\u0026gt;\u0026gt; 字段名+字段类型\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003eDBMS：数据库管理系统\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e数据库分为关系型数据库和非关系型数据库\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e关系型数据库\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e关系型数据库就是数据与数据之间可以有关联和限制\u003c/p\u003e\n\u003cp\u003e关系型数据库通常都是表结构，也就意味着你在用关系型数据库的时候第一步就是确定表结构\u003c/p\u003e\n\u003cp\u003e常见的关系型数据库：MySQL,oracle,sqlite,db2,sql server等\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e非关系行数据库\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e通常都是以k,v键值的形式 存储数据\u003c/p\u003e\n\u003cp\u003e常见的非关系型数据库：redis,memcache,mongodb(文档型数据库，非常接近关系型的非关系型数据)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eMySQL安装\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e软件目录初识\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e官网下载MySQL社区版后解压查看文件目录，bin目录下含有：\u003c/p\u003e\n\u003cp\u003emysqld.exe服务端\u003c/p\u003e\n\u003cp\u003emysql.exe客户端\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ecmd启动服务端与客户端\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e首先切换到bin目录下再执行命令，终端建议使用管理员身份运行，通过win+r启动的是普通用户\u003c/p\u003e\n\u003cp\u003e启动mysqld\u003c/p\u003e\n\u003cp\u003e启动mysql\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e如果不输入用户名和密码 默认是访客模式登陆  所能用到的功能很少\nmysql -h 127.0.0.1 -P 3306 -uroot -p\n可以简写：mysql -uroot -p\nmysql在初始登陆的时候 是没有密码的 直接回车即可\nmysql中的sql语句 是以分号结束的 不敲分号 默认你没有输入完客户端还会让你继续输入，此时可输入；补全sql语句并执行\n当命令输入错误的时候  可以用\\c取消前面的命令  cancel\n客户端退出登录：exit 或 quit\n查看某个进程：tasklist |findstr 名称\n杀死进程：taskkill /F /PID 进程号\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e环境变量配置\u003c/strong\u003e\u003c/p\u003e","title":"【数据库】初识MySQL数据库"},{"content":"\nIO模型 \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园****\nIO模型介绍\n为了更好地了解IO模型，我们需要事先回顾下：同步、异步、阻塞、非阻塞\n同步（synchronous） IO和异步（asynchronous） IO，阻塞（blocking） IO和非阻塞（non-blocking）IO分别是什么，到底有什么区别？这个问题其实不同的人给出的答案都可能不同，比如wiki，就认为asynchronous IO和non-blocking IO是一个东西。这其实是因为不同的人的知识背景不同，并且在讨论这个问题的时候上下文(context)也不相同。所以，为了更好的回答这个问题，我先限定一下本文的上下文。\n本文讨论的背景是Linux环境下的network IO。本文最重要的参考文献是Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”，6.2节“I/O Models ”，Stevens在这节中详细说明了各种IO的特点和区别，如果英文够好的话，推荐直接阅读。Stevens的文风是有名的深入浅出，所以不用担心看不懂。本文中的流程图也是截取自参考文献。\nStevens在文章中一共比较了五种IO Model：\nblocking IO 阻塞IO nonblocking IO 非阻塞IO IO multiplexing IO多路复用 signal driven IO 信号驱动IO asynchronous IO 异步IO\n由signal driven IO（信号驱动IO）在实际中并不常用，所以主要介绍其余四种IO Model。 再说一下IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例)，它会涉及到两个系统对象，一个是调用这个IO的process (or thread)，另一个就是系统内核(kernel)。当一个read操作发生时，该操作会经历两个阶段：\n#1）等待数据准备 (Waiting for the data to be ready)\n#2）将数据从内核拷贝到进程中(Copying the data from the kernel to the process)\n记住这两点很重要，因为这些IO模型的区别就是在两个阶段上各有不同的情况。\n阻塞IO(non-blocking IO)\n在linux中，默认情况下所有的socket都是blocking，一个典型的读操作流程大概是这样：\n当用户进程调用了recvfrom这个系统调用，kernel就开始了IO的第一个阶段：准备数据。对于network io来说，很多时候数据在一开始还没有到达（比如，还没有收到一个完整的UDP包），这个时候kernel就要等待足够的数据到来。\n而在用户进程这边，整个进程会被阻塞。当kernel一直等到数据准备好了，它就会将数据从kernel中拷贝到用户内存，然后kernel返回结果，用户进程才解除block的状态，重新运行起来。\n所以，blocking IO的特点就是在IO执行的两个阶段（等待数据和拷贝数据两个阶段）都被block了。\n几乎所有的程序员第一次接触到的网络编程都是从listen()、send()、recv() 等接口开始的，使用这些接口可以很方便的构建服务器/客户机的模型。然而大部分的socket接口都是阻塞型的。如下图\nps：所谓阻塞型接口是指系统调用（一般是IO接口）不返回调用结果并让当前线程一直阻塞，只有当该系统调用获得结果或者超时出错时才返回。\n实际上，除非特别指定，几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题，如在调用recv(1024)的同时，线程将被阻塞，在此期间，线程将无法执行任何运算或响应任何的网络请求。\n一个简单的解决方案：\n#在服务器端使用多线程（或多进程）。多线程（或多进程）的目的是让每个连接都拥有独立的线程（或进程），这样任何一个连接的阻塞都不会影响其他的连接。\n该方案的问题是：\n#开启多进程或都线程的方式，在遇到要同时响应成百上千路的连接请求，则无论多线程还是多进程都会严重占据系统资源，降低系统对外界响应效率，而且线程与进程本身也更容易进入假死状态。\n改进方案：\n#很多程序员可能会考虑使用“线程池”或“连接池”。“线程池”旨在减少创建和销毁线程的频率，其维持一定合理数量的线程，并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池，尽量重用已有的连接、减少创建和关闭连接的频率。这两种技术都可以很好的降低系统开销，都被广泛应用很多大型系统，如websphere、tomcat和各种数据库等。\n改进后方案其实也存在着问题：\n#“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。而且，所谓“池”始终有其上限，当请求大大超过上限时，“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模，并根据响应规模调整“池”的大小。\n对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求，“线程池”或“连接池”或许可以缓解部分压力，但是不能解决所有问题。总之，多线程模型可以方便高效的解决小规模的服务请求，但面对大规模的服务请求，多线程模型也会遇到瓶颈，可以用非阻塞接口来尝试解决这个问题。\n非阻塞IO(non-blocking IO)\nLinux下，可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时，流程是这个样子：\n从图中可以看出，当用户进程发出read操作时，如果kernel中的数据还没有准备好，那么它并不会block用户进程，而是立刻返回一个error。从用户进程角度讲 ，它发起一个read操作后，并不需要等待，而是马上就得到了一个结果。用户进程判断结果是一个error时，它就知道数据还没有准备好，于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情，或者直接再次发送read操作。一旦kernel中的数据准备好了，并且又再次收到了用户进程的system call，那么它马上就将数据拷贝到了用户内存（这一阶段仍然是阻塞的），然后返回。\n也就是说非阻塞的recvform系统调用调用之后，进程并没有被阻塞，内核马上返回给进程，如果数据还没准备好，此时会返回一个error。进程在返回之后，可以干点别的事情，然后再发起recvform系统调用。重复上面的过程，循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据，直到数据准备好，再拷贝数据到进程，进行数据处理。需要注意，拷贝数据整个过程，进程仍然是属于阻塞的状态。\n所以，在非阻塞式IO中，用户进程其实是需要不断的主动询问kernel数据准备好了没有。\n非阻塞IO实例\n# 服务端 from socket import * import time s=socket(AF_INET,SOCK_STREAM) s.bind(('127.0.0.1',8080)) s.listen(5) s.setblocking(False) # 设置socket的接口为非阻塞 conn_l=[] del_l=[] while True: try: conn,addr=s.accept() conn_l.append(conn) except BlockingIOError: print(conn_l) for conn in conn_l: try: data=conn.recv(1024) if not data: del_l.append(conn) continue conn.send(data.upper()) except BlockingIOError: pass except ConnectionResetError: del_l.append(conn) for conn in del_l: conn_l.remove(conn) conn.close() del_l=[] # 客户端 from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8080)) while True: msg=input('\u0026gt;\u0026gt;: ') if not msg:continue c.send(msg.encode('utf-8')) data=c.recv(1024) print(data.decode('utf-8')) 但是非阻塞IO模型绝不被推荐。\n我们不能否则其优点：能够在等待任务完成的时间里干其他活了（包括提交其他任务，也就是 “后台” 可以有多个任务在“”同时“”执行）。\n但是也难掩其缺点：\n#1. 循环调用recv()将大幅度推高CPU占用率；这也是我们在代码中留一句time.sleep(2)的原因,否则在低配主机下极容易出现卡机情况\n#2. 任务完成的响应延迟增大了，因为每过一段时间才去轮询一次read操作，而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。\n此外，在这个方案中recv()更多的是起到检测“操作是否完成”的作用，实际操作系统提供了更为高效的检测“操作是否完成“作用的接口，例如select()多路复用模式，可以一次检测多个连接是否活跃。\n多路复用IO(IO multiplexing)\nIO multiplexing这个词可能有点陌生，但是如果我说select/epoll，大概就都能明白了。有些地方也称这种IO方式为事件驱动IO(event driven IO)。我们都知道，select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket，当某个socket有数据到达了，就通知用户进程。它的流程如图：\n当用户进程调用了select，那么整个进程会被block，而同时，kernel会“监视”所有select负责的socket，当任何一个socket中的数据准备好了，select就会返回。这个时候用户进程再调用read操作，将数据从kernel拷贝到用户进程。\n这个图和blocking IO的图其实并没有太大的不同，事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom)，而blocking IO只调用了一个系统调用(recvfrom)。但是，用select的优势在于它可以同时处理多个connection。\n强调：\n1. 如果处理的连接数不是很高的话，使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好，可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快，而是在于能处理更多的连接。\n2. 在多路复用模型中，对于每一个socket，一般都设置成为non-blocking，但是，如上图所示，整个用户的process其实是一直被block的。只不过process是被select这个函数block，而不是被socket IO给block。\n结论: select的优势在于可以处理多个连接，不适用于单个连接\nselect网络IO模型\n# 服务端 from socket import * import select s=socket(AF_INET,SOCK_STREAM) s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('127.0.0.1',8081)) s.listen(5) s.setblocking(False) # 设置socket的接口为非阻塞 read_l=[s,] while True: r_l,w_l,x_l=select.select(read_l,[],[]) print(r_l) for ready_obj in r_l: if ready_obj == s: conn,addr=ready_obj.accept() # 此时的ready_obj等于s read_l.append(conn) else: try: data=ready_obj.recv(1024) # 此时的ready_obj等于conn if not data: ready_obj.close() read_l.remove(ready_obj) continue ready_obj.send(data.upper()) except ConnectionResetError: ready_obj.close() read_l.remove(ready_obj) # 客户端 from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8081)) while True: msg=input('\u0026gt;\u0026gt;: ') if not msg:continue c.send(msg.encode('utf-8')) data=c.recv(1024) print(data.decode('utf-8')) select监听fd变化的过程分析：\n#用户进程创建socket对象，拷贝监听的fd到内核空间，每一个fd会对应一张系统文件表，内核空间的fd响应到数据后，就会发送信号给用户进程数据已到；\n#用户进程再发送系统调用，比如（accept）将内核空间的数据copy到用户空间，同时作为接受数据端内核空间的数据清除，这样重新监听时fd再有新的数据又可以响应到了（发送端因为基于TCP协议所以需要收到应答后才会清除）。\n该模型的优点：\n#相比其他模型，使用select() 的事件驱动模型只用单线程（进程）执行，占用资源少，不消耗太多 CPU，同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序，这个模型有一定的参考价值。\n该模型的缺点：\n#首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时，select()接口本身需要消耗大量时间去轮询各个句柄。\n#很多操作系统提供了更为高效的接口，如linux提供了epoll，BSD提供了kqueue，Solaris提供了/dev/poll，…。\n#如果需要实现更高效的服务器程序，类似epoll这样的接口更被推荐。遗憾的是不同的操作系统特供的epoll接口有很大差异，\n#所以使用类似于epoll的接口实现具有较好跨平台能力的服务器会比较困难。 #其次，该模型将事件探测和事件响应夹杂在一起，一旦事件响应的执行体庞大，则对整个模型是灾难性的。\n异步IO(Asynchronous I/O)\nLinux下的asynchronous IO其实用得不多，从内核2.6版本才开始引入。先看一下它的流程：\n用户进程发起read操作之后，立刻就可以开始去做其它的事。而另一方面，从kernel的角度，当它受到一个asynchronous read之后，首先它会立刻返回，所以不会对用户进程产生任何block。然后，kernel会等待数据准备完成，然后将数据拷贝到用户内存，当这一切都完成之后，kernel会给用户进程发送一个signal，告诉它read操作完成了。\nIO模型比较分析\n到目前为止，已经将四个IO Model都介绍完了。现在回过头来回答最初的那几个问题：blocking和non-blocking的区别在哪，synchronous IO和asynchronous IO的区别在哪。\n先回答最简单的这个：blocking vs non-blocking。前面的介绍中其实已经很明确的说明了这两者的区别。调用blocking IO会一直block住对应的进程直到操作完成，而non-blocking IO在kernel还准备数据的情况下会立刻返回。\n再说明synchronous IO和asynchronous IO的区别之前，需要先给出两者的定义。Stevens给出的定义（其实是POSIX的定义）是这样子的：\nA synchronous I/O operation causes the requesting process to be blocked until that I/O operationcompletes;\nAn asynchronous I/O operation does not cause the requesting process to be blocked;\n两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义，四个IO模型可以分为两大类，之前所述的blocking IO，non-blocking IO，IO multiplexing都属于synchronous IO这一类，而 asynchronous I/O后一类 。\n有人可能会说，non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方，定义中所指的”IO operation”是指真实的IO操作，就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候，如果kernel的数据没有准备好，这时候不会block进程。但是，当kernel中数据准备好的时候，recvfrom会将数据从kernel拷贝到用户内存中，这个时候进程是被block了，在这段时间内，进程是被block的。而asynchronous IO则不一样，当进程发起IO 操作之后，就直接返回再也不理睬了，直到kernel发送一个信号，告诉进程说IO完成。在这整个过程中，进程完全没有被block。\n各个IO Model的比较如图所示：\n经过上面的介绍，会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中，虽然进程大部分时间都不会被block，但是它仍然要求进程去主动的check，并且当数据准备完成以后，也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人（kernel）完成，然后他人做完后发信号通知。在此期间，用户进程不需要去检查IO操作的状态，也不需要主动的去拷贝数据。\nselectors模块\nselect,poll,epoll\nIO复用：为了解释这个名词，首先来理解下复用这个概念，复用也就是共用的意思，这样理解还是有些抽象，为此，咱们来理解下复用在通信领域的使用，在通信领域中为了充分利用网络连接的物理介质，往往在同一条网络链路上采用时分复用或频分复用的技术使其在同一链路上传输多路信号，到这里我们就基本上理解了复用的含义，即公用某个“介质”来尽可能多的做同一类(性质)的事，那IO复用的“介质”是什么呢？为此我们首先来看看服务器编程的模型，客户端发来的请求服务端会产生一个进程来对其进行服务，每当来一个客户请求就产生一个进程来服务，然而进程不可能无限制的产生，因此为了解决大量客户端访问的问题，引入了IO复用技术，即：一个进程可以同时对多个客户请求进行服务。也就是说IO复用的“介质”是进程(准确的说复用的是select和poll，因为进程也是靠调用select和poll来实现的)，复用一个进程(select和poll)来对多个IO进行服务，虽然客户端发来的IO是并发的但是IO所需的读写数据多数情况下是没有准备好的，因此就可以利用一个函数(select和poll)来监听IO所需的这些数据的状态，一旦IO有数据可以进行读写了，进程就来对这样的IO进行服务。\n理解完IO复用后，我们在来看下实现IO复用中的三个API(select、poll和epoll)的区别和联系\nselect，poll，epoll都是IO多路复用的机制，I/O多路复用就是通过一种机制，可以监视多个描述符，一旦某个描述符就绪（一般是读就绪或者写就绪），能够通知应用程序进行相应的读写操作。但select，poll，epoll本质上都是同步I/O，因为他们都需要在读写事件就绪后自己负责进行读写，也就是说这个读写过程是阻塞的，而异步I/O则无需自己负责进行读写，异步I/O的实现会负责把数据从内核拷贝到用户空间。三者的原型如下所示：\nint select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);\nint poll(struct pollfd *fds, nfds_t nfds, int timeout);\nint epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 1.select的第一个参数nfds为fdset集合中最大描述符值加1，fdset是一个位数组，其大小限制为__FD_SETSIZE（1024），位数组的每一位代表其对应的描述符是否需要被检查。第二三四参数表示需要关注读、写、错误事件的文件描述符位数组，这些参数既是输入参数也是输出参数，可能会被内核修改用于标示哪些描述符上发生了关注的事件，所以每次调用select前都需要重新初始化fdset。timeout参数为超时时间，该结构会被内核修改，其值为超时剩余的时间。\nselect的调用步骤如下：\n（1）使用copy_from_user从用户空间拷贝fdset到内核空间\n（2）注册回调函数__pollwait\n（3）遍历所有fd，调用其对应的poll方法（对于socket，这个poll方法是sock_poll，sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll）\n（4）以tcp_poll为例，其核心实现就是__pollwait，也就是上面注册的回调函数。\n（5）__pollwait的主要工作就是把current（当前进程）挂到设备的等待队列中，不同的设备有不同的等待队列，对于tcp_poll 来说，其等待队列是sk-\u0026gt;sk_sleep（注意把进程挂到等待队列中并不代表进程已经睡眠了）。在设备收到一条消息（网络设备）或填写完文件数 据（磁盘设备）后，会唤醒设备等待队列上睡眠的进程，这时current便被唤醒了。\n（6）poll方法返回时会返回一个描述读写操作是否就绪的mask掩码，根据这个mask掩码给fd_set赋值。\n（7）如果遍历完所有的fd，还没有返回一个可读写的mask掩码，则会调用schedule_timeout是调用select的进程（也就是 current）进入睡眠。当设备驱动发生自身资源可读写后，会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间（schedule_timeout 指定），还是没人唤醒，则调用select的进程会重新被唤醒获得CPU，进而重新遍历fd，判断有没有就绪的fd。\n（8）把fd_set从内核空间拷贝到用户空间。\n总结下select的几大缺点：\n（1）每次调用select，都需要把fd集合从用户态拷贝到内核态，这个开销在fd很多时会很大\n（2）同时每次调用select都需要在内核遍历传递进来的所有fd，这个开销在fd很多时也很大\n（3）select支持的文件描述符数量太小了，默认是1024\n2． poll与select不同，通过一个pollfd数组向内核传递需要关注的事件，故没有描述符个数的限制，pollfd中的events字段和revents分别用于标示关注的事件和发生的事件，故pollfd数组只需要被初始化一次。\npoll的实现机制与select类似，其对应内核中的sys_poll，只不过poll向内核传递pollfd数组，然后对pollfd中的每个描述符进行poll，相比处理fdset来说，poll效率更高。poll返回后，需要对pollfd中的每个元素检查其revents值，来得指事件是否发生。\n3．直到Linux2.6才出现了由内核直接支持的实现方法，那就是epoll，被公认为Linux2.6下性能最好的多路I/O就绪通知方法。epoll可以同时支持水平触发和边缘触发（Edge Triggered，只告诉进程哪些文件描述符刚刚变为就绪状态，它只说一遍，如果我们没有采取行动，那么它将不会再次告知，这种方式称为边缘触发），理论上边缘触发的性能要更高一些，但是代码实现相当复杂。epoll同样只告知那些就绪的文件描述符，而且当我们调用epoll_wait()获得就绪文件描述符时，返回的不是实际的描述符，而是一个代表就绪描述符数量的值，你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可，这里也使用了内存映射（mmap）技术，这样便彻底省掉了这些文件描述符在系统调用时复制的开销。另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中，进程只有在调用一定的方法后，内核才对所有监视的文件描述符进行扫描，而epoll事先通过epoll_ctl()来注册一个文件描述符，一旦基于某个文件描述符就绪时，内核会采用类似callback的回调机制，迅速激活这个文件描述符，当进程调用epoll_wait()时便得到通知。\nepoll既然是对select和poll的改进，就应该能避免上述的三个缺点。那epoll都是怎么解决的呢？在此之前，我们先看一下epoll 和select和poll的调用接口上的不同，select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函 数，epoll_create,epoll_ctl和epoll_wait，epoll_create是创建一个epoll句柄；epoll_ctl是注 册要监听的事件类型；epoll_wait则是等待事件的产生。\n对于第一个缺点，epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时（在epoll_ctl中指定 EPOLL_CTL_ADD），会把所有的fd拷贝进内核，而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝 一次。\n对于第二个缺点，epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中，而只在 epoll_ctl时把current挂一遍（这一遍必不可少）并为每个fd指定一个回调函数，当设备就绪，唤醒等待队列上的等待者时，就会调用这个回调 函数，而这个回调函数会把就绪的fd加入一个就绪链表）。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd（利用 schedule_timeout()实现睡一会，判断一会的效果，和select实现中的第7步是类似的）。\n对于第三个缺点，epoll没有这个限制，它所支持的FD上限是最大可以打开文件的数目，这个数字一般远大于2048,举个例子, 在1GB内存的机器上大约是10万左右，具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。\n总结：\n（1）select，poll实现需要自己不断轮询所有fd集合，直到设备就绪，期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用 epoll_wait不断轮询就绪链表，期间也可能多次睡眠和唤醒交替，但是它是设备就绪时，调用回调函数，把就绪fd放入就绪链表中，并唤醒在 epoll_wait中进入睡眠的进程。虽然都要睡眠和交替，但是select和poll在“醒着”的时候要遍历整个fd集合，而epoll在“醒着”的 时候只要判断一下就绪链表是否为空就行了，这节省了大量的CPU时间，这就是回调机制带来的性能提升。\n（2）select，poll每次调用都要把fd集合从用户态往内核态拷贝一次，并且要把current往设备等待队列中挂一次，而epoll只要 一次拷贝，而且把current往等待队列上挂也只挂一次（在epoll_wait的开始，注意这里的等待队列并不是设备等待队列，只是一个epoll内 部定义的等待队列），这也能节省不少的开销。\n这三种IO多路复用模型在不同的平台有着不同的支持，而epoll在windows下就不支持，好在我们有selectors模块，帮我们默认选择当前平台下最合适的\n基于selectors模块实现聊天\n#服务端from socket import *import selectors sel=selectors.DefaultSelector()def accept(server_fileobj,mask): conn,addr=server_fileobj.accept() sel.register(conn,selectors.EVENT_READ,read)def read(conn,mask): try: data=conn.recv(1024) if not data: print('closing',conn) sel.unregister(conn) conn.close() return conn.send(data.upper()+b'_SB') except Exception: print('closing', conn) sel.unregister(conn) conn.close() server_fileobj=socket(AF_INET,SOCK_STREAM) server_fileobj.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) server_fileobj.bind(('127.0.0.1',8088)) server_fileobj.listen(5) server_fileobj.setblocking(False) #设置socket的接口为非阻塞sel.register(server_fileobj,selectors.EVENT_READ,accept) #相当于网select的读列表里append了一个文件句柄server_fileobj,并且绑定了一个回调函数acceptwhile True: events=sel.select() #检测所有的fileobj，是否有完成wait data的 for sel_obj,mask in events: callback=sel_obj.data #callback=accpet callback(sel_obj.fileobj,mask) #accpet(server_fileobj,1)#客户端from socket import *c=socket(AF_INET,SOCK_STREAM) c.connect(('127.0.0.1',8088))while True: msg=input('\u0026gt;\u0026gt;: ') if not msg:continue c.send(msg.encode('utf-8')) data=c.recv(1024) print(data.decode('utf-8')) ","permalink":"https://zhong-er.com/posts/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8Bio%E6%A8%A1%E5%9E%8B/","summary":"\u003cp\u003e\u003cimg alt=\"IO模型.png\" loading=\"lazy\" src=\"/assets/201908156909_500_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eIO模型 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/2BCYe2Z478\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11359300.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eIO模型介绍\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e为了更好地了解IO模型，我们需要事先回顾下：同步、异步、阻塞、非阻塞\u003c/p\u003e\n\u003cp\u003e同步（synchronous） IO和异步（asynchronous） IO，阻塞（blocking） IO和非阻塞（non-blocking）IO分别是什么，到底有什么区别？这个问题其实不同的人给出的答案都可能不同，比如wiki，就认为asynchronous IO和non-blocking IO是一个东西。这其实是因为不同的人的知识背景不同，并且在讨论这个问题的时候上下文(context)也不相同。所以，为了更好的回答这个问题，我先限定一下本文的上下文。\u003c/p\u003e\n\u003cp\u003e本文讨论的背景是Linux环境下的network IO。本文最重要的参考文献是Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”，6.2节“I/O Models ”，Stevens在这节中详细说明了各种IO的特点和区别，如果英文够好的话，推荐直接阅读。Stevens的文风是有名的深入浅出，所以不用担心看不懂。本文中的流程图也是截取自参考文献。\u003c/p\u003e\n\u003cp\u003eStevens在文章中一共比较了五种IO Model：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eblocking IO 阻塞IO\u003c/li\u003e\n\u003cli\u003enonblocking IO 非阻塞IO\u003c/li\u003e\n\u003cli\u003eIO multiplexing IO多路复用\u003c/li\u003e\n\u003cli\u003esignal driven IO 信号驱动IO\u003c/li\u003e\n\u003cli\u003easynchronous IO 异步IO\u003cbr\u003e\n由signal driven IO（信号驱动IO）在实际中并不常用，所以主要介绍其余四种IO Model。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e再说一下IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例)，它会涉及到两个系统对象，一个是调用这个IO的process (or thread)，另一个就是系统内核(kernel)。当一个read操作发生时，该操作会经历两个阶段：\u003c/p\u003e\n\u003cp\u003e#1）等待数据准备 (Waiting for the data to be ready)\u003c/p\u003e\n\u003cp\u003e#2）将数据从内核拷贝到进程中(Copying the data from the kernel to the process)\u003c/p\u003e","title":"【并发编程】IO模型"},{"content":"\n并发编程(四) \u0026raquo;\u0026gt;思维导图\u0026gt; \u0026raquo;博客园****\n进程池与线程池\n什么是池？在保证计算机硬件安全的情况下最大限度的利用计算机 池其实是降低了程序的运行效率 但是保证了计算机硬件的安全 (硬件的发展跟不上软件的速度)\np = ThreadPoolExecutor()创建线程池，括号内可以传参数指定线程池内的线程个数，也可以不传，不传默认是当前所在计算机的cpu个数乘5\np = ProcessPoolExecutor()创建进程池，默认是当前计算机cpu的个数\n池子中创建的进程/线程创建一次就不会再创建了，至始至终用的都是最初的那几个，这样的话节省了反复开辟进程/线程的资源\nconcurrent.futures 模块提供异步执行回调高层接口\n异步执行可以由 ThreadPoolExecutor 使用线程或由 ProcessPoolExecutor 使用单独的进程来实现。 两者都是实现抽像类 Executor 定义的接口。\np.submit(fn, *args, **kwargs)朝池子提交任务，异步提交\n调度可调用对象 fn，以 fn(*args **kwargs) 方式执行并返回 Future 对像代表可调用对象的执行。 p.shutdown(wait=True)关闭池子\n#等待池子中所有的任务执行完毕之后 才会往下运行代码\nThreadPoolExecutor 是 Executor 的子类，它使用线程池来异步执行调用。\nProcessPoolExecutor 是 Executor 的子类，它使用进程池来实现异步执行调用。\nProcessPoolExecutor 使用 multiprocessing 回避 Global Interpreter Lock 但也意味着只可以处理和返回可序列化的对象。 main 模块必须可以被工作者子进程导入。这意味着 ProcessPoolExecutor 不可以工作在交互式解释器中。 异步回调\n提交任务的方式\n同步：提交任务之后 原地等待任务的返回结果 期间不做任何事\n异步：提交任务之后 不等待任务的返回结果(异步的结果怎么拿？？？) 直接执行下一行代码\n异步回调机制:当异步提交的任务有返回结果之后，会自动触发回调函数的执行\nresult()原地等待任务的返回结果\np.submit(fn,*args, **kwargs).add_done_callback(fn)提交任务的时候 绑定一个回调函数 一旦该任务有结果 立刻执行对于的回调函数\n案例\n线程池\nfrom concurrent.futures import ThreadPoolExecutor import time import os pool = ThreadPoolExecutor(5) # 括号内可以传参数指定线程池内的线程个数 # 也可以不传，不传默认是当前所在计算机的cpu个数乘5 def task(n): print(n,os.getpid()) # 查看当前进程号 time.sleep(2) return n**2 def call_back(n): print('拿到了异步提交任务的返回结果:',n.result()) for i in range(20): res = pool.submit(task,i).add_done_callback(call_back) # 提交任务的时候 绑定一个回调函数 一旦该任务有结果 立刻执行对于的回调函数 pool.shutdown() # 关闭池子 等待池子中所有的任务执行完毕之后 才会往下运行代码 进程池\nfrom concurrent.futures import ProcessPoolExecutor import time import os pool = ProcessPoolExecutor() # 默认是当前计算机cpu的个数 def task(n): print(n,os.getpid()) # 查看当前进程号 time.sleep(2) return n**2 def call_back(n): print('拿到了异步提交任务的返回结果:',n.result()) if __name__ == '__main__': for i in range(20): res = pool.submit(task,i).add_done_callback(call_back) # 提交任务的时候 绑定一个回调函数 一旦该任务有结果 立刻执行对于的回调函数 pool.shutdown() # 关闭池子 等待池子中所有的任务执行完毕之后 才会往下运行代码 协程\n协程就是单线程实现并发\n并发的条件：多道技术\n空间上的复用\n时间上的复用\n切换+保存状态(并发)ps:看起来像同时执行的 就可以称之为并发\n所以要想实现协程就是需要识别到IO操作就切换并保存状态，也就是让cpu一直处于就绪与运行状态，然而不让程序使用线程与进程而实现阻塞只能通过生成器函数yield来实现阻塞。\n实现上诉情况的方法一种是任务阻塞，第二种是计算时间过长，显然第二种情况进行切换不能提升效率，如果任务都是计算密集型的，这种切换反而反而会降低效率；第一种情况的切换，在任务一遇到io情况下，切到任务二去执行，这样就可以利用任务一阻塞的时间完成任务二的计算，效率的提升就在于此。\n案例 ：单纯地切换反而会降低运行效率\n# 串行执行 import time def consumer(res): '''任务1:接收数据,处理数据''' pass def producer(): '''任务2:生产数据''' res=[] for i in range(10000000): res.append(i) return res start=time.time() # 串行执行 res=producer() consumer(res) # 写成consumer(producer())会降低执行效率 stop=time.time() print(stop-start) #1.5536692142486572 # 基于yield并发执行 import time def consumer(): '''任务1:接收数据,处理数据''' while True: x=yield def producer(): '''任务2:生产数据''' g=consumer() next(g) for i in range(10000000): g.send(i) start=time.time() # 基于yield保存状态,实现两个任务直接来回切换,即并发的效果 # PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的. producer() stop=time.time() print(stop-start) #2.0272178649902344 案例 ：yield无法做到识别io阻塞\nimport time def consumer(): '''任务1:接收数据,处理数据''' while True: x=yield def producer(): '''任务2:生产数据''' g=consumer() next(g) for i in range(10000000): g.send(i) time.sleep(2) start=time.time() producer() # 并发执行,但是任务producer遇到io就会阻塞住,并不会切到该线程内的其他任务去执行 stop=time.time() print(stop-start) 这个时候需要找到一个能够识别IO的工具：gevent模块\nGevent 是一个第三方库，可以轻松通过gevent实现并发同步或异步编程，在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部，但它们被协作式地调度。\n用法介绍\ng1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1，spawn括号内第一个参数是函数名，如eat，后面可以有多个参数，可以是位置实参或关键字实参，都是传给函数eat的 g2=gevent.spawn(func2) g1.join() # 等待g1结束 g2.join() # 等待g2结束 # 或者上述两步合作一步：gevent.joinall([g1,g2]) g1.value#拿到func1的返回值 案例 ：遇到io主动切换\nimport gevent def eat(name): print('%s eat 1' %name) gevent.sleep(2) print('%s eat 2' %name) def play(name): print('%s play 1' %name) gevent.sleep(1) print('%s play 2' %name) g1=gevent.spawn(eat,'egon') g2=gevent.spawn(play,name='egon') g1.join() g2.join() # 或者gevent.joinall([g1,g2]) print('主') 上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了\nfrom gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面，如time，socket模块之前\n或者我们干脆记忆成：要用gevent，需要将from gevent import monkey;monkey.patch_all()放到文件的开头\nfrom gevent import monkey;monkey.patch_all() import gevent import time def eat(): print('eat food 1') time.sleep(2) print('eat food 2') def play(): print('play 1') time.sleep(1) print('play 2') g1=gevent.spawn(eat) g2=gevent.spawn(play) gevent.joinall([g1,g2]) print('主') 我们可以用threading.current_thread().getName()来查看每个g1和g2，查看的结果为DummyThread-n，即假线程\nfrom gevent import monkey;monkey.patch_all() import threading import gevent import time def eat(): print(threading.current_thread().getName()) print('eat food 1') time.sleep(2) print('eat food 2') def play(): print(threading.current_thread().getName()) print('play 1') time.sleep(1) print('play 2') g1=gevent.spawn(eat) g2=gevent.spawn(play) gevent.joinall([g1,g2]) print('主') Gevent之同步与异步\nfrom gevent import spawn,joinall,monkey;monkey.patch_all() import time def task(pid): \u0026quot;\u0026quot;\u0026quot; Some non-deterministic task \u0026quot;\u0026quot;\u0026quot; time.sleep(0.5) print('Task %s done' % pid) def synchronous(): # 同步 for i in range(10): task(i) def asynchronous(): # 异步 g_l=[spawn(task,i) for i in range(10)] joinall(g_l) print('DONE') if __name__ == '__main__': print('Synchronous:') synchronous() print('Asynchronous:') asynchronous() # 上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 # 初始化的greenlet列表存放在数组threads中，此数组被传给gevent.joinall 函数， # 后者阻塞当前流程，并执行所有给定的greenlet任务。执行流程只会在 所有greenlet执行完后才会继续向下走。 协程应用一：爬虫\nfrom gevent import monkey;monkey.patch_all() import gevent import requests import time def get_page(url): print('GET: %s' %url) response=requests.get(url) if response.status_code == 200: print('%d bytes received from %s' %(len(response.text),url)) start_time=time.time() gevent.joinall([ gevent.spawn(get_page,'https://www.python.org/'), gevent.spawn(get_page,'https://www.yahoo.com/'), gevent.spawn(get_page,'https://github.com/'), ]) stop_time=time.time() print('run time is %s' %(stop_time-start_time)) 协程应用二：通过gevent单线程下socket并发\nserver**\n**\nfrom gevent import monkey;monkey.patch_all() from socket import * import gevent # 如果不想用money.patch_all()打补丁,可以用gevent自带的socket # from gevent import socket # s=socket.socket() def server(server_ip,port): s=socket(AF_INET,SOCK_STREAM) s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind((server_ip,port)) s.listen(5) while True: conn,addr=s.accept() gevent.spawn(talk,conn,addr) def talk(conn,addr): try: while True: res=conn.recv(1024) print('client %s:%s msg: %s' %(addr[0],addr[1],res)) conn.send(res.upper()) except Exception as e: print(e) finally: conn.close() if __name__ == '__main__': server('127.0.0.1',8080) client\nfrom socket import * client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: msg=input('\u0026gt;\u0026gt;: ').strip() if not msg:continue client.send(msg.encode('utf-8')) msg=client.recv(1024) print(msg.decode('utf-8')) 多线程并发多个客户端\nfrom threading import Thread from socket import * import threading def client(server_ip,port): c=socket(AF_INET,SOCK_STREAM) # 套接字对象一定要加到函数内，即局部名称空间内，放在函数外则被所有线程共享，则大家公用一个套接字对象，那么客户端端口永远一样了 c.connect((server_ip,port)) count=0 while True: c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8')) msg=c.recv(1024) print(msg.decode('utf-8')) count+=1 if __name__ == '__main__': for i in range(500): t=Thread(target=client,args=('127.0.0.1',8080)) t.start() ","permalink":"https://zhong-er.com/posts/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E8%BF%9B%E7%A8%8B%E6%B1%A0%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%8D%8F%E7%A8%8B/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cimg alt=\"并发编程(四).png\" loading=\"lazy\" src=\"/assets/201908152948_8824_1.png\"\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e并发编程(四) \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/3EDdlPqM78\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11359207.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e进程池与线程池\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e什么是池？在保证计算机硬件安全的情况下最大限度的利用计算机 池其实是降低了程序的运行效率 但是保证了计算机硬件的安全 (硬件的发展跟不上软件的速度)\u003c/p\u003e\n\u003cp\u003ep = ThreadPoolExecutor()创建线程池，括号内可以传参数指定线程池内的线程个数，也可以不传，不传默认是当前所在计算机的cpu个数乘5\u003c/p\u003e\n\u003cp\u003ep = ProcessPoolExecutor()创建进程池，默认是当前计算机cpu的个数\u003c/p\u003e\n\u003cp\u003e池子中创建的进程/线程创建一次就不会再创建了，至始至终用的都是最初的那几个，这样的话节省了反复开辟进程/线程的资源\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003econcurrent.futures 模块提供异步执行回调高层接口\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e异步执行可以由 ThreadPoolExecutor 使用线程或由 ProcessPoolExecutor 使用单独的进程来实现。 两者都是实现抽像类 Executor 定义的接口。\u003c/p\u003e\n\u003cp\u003ep.submit(fn, *args, **kwargs)朝池子提交任务，异步提交\u003c/p\u003e\n\u003ch1 id=\"调度可调用对象-fn以-fnargs-kwargs-方式执行并返回-future-对像代表可调用对象的执行\"\u003e调度可调用对象 fn，以 fn(*args **kwargs) 方式执行并返回 Future 对像代表可调用对象的执行。\u003c/h1\u003e\n\u003cp\u003ep.shutdown(wait=True)关闭池子\u003c/p\u003e\n\u003cp\u003e#等待池子中所有的任务执行完毕之后 才会往下运行代码\u003c/p\u003e\n\u003cp\u003eThreadPoolExecutor 是 Executor 的子类，它使用线程池来异步执行调用。\u003c/p\u003e\n\u003cp\u003eProcessPoolExecutor 是 Executor 的子类，它使用进程池来实现异步执行调用。\u003c/p\u003e\n\u003ch1 id=\"processpoolexecutor-使用-multiprocessing-回避-global-interpreter-lock-但也意味着只可以处理和返回可序列化的对象-main-模块必须可以被工作者子进程导入这意味着-processpoolexecutor-不可以工作在交互式解释器中\"\u003eProcessPoolExecutor 使用 multiprocessing 回避 Global Interpreter Lock 但也意味着只可以处理和返回可序列化的对象。 \u003cstrong\u003emain\u003c/strong\u003e 模块必须可以被工作者子进程导入。这意味着 ProcessPoolExecutor 不可以工作在交互式解释器中。\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003e异步回调\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e提交任务的方式\u003c/p\u003e\n\u003cp\u003e同步：提交任务之后 原地等待任务的返回结果 期间不做任何事\u003c/p\u003e\n\u003cp\u003e异步：提交任务之后 不等待任务的返回结果(异步的结果怎么拿？？？) 直接执行下一行代码\u003c/p\u003e\n\u003cp\u003e异步回调机制:当异步提交的任务有返回结果之后，会自动触发回调函数的执行\u003c/p\u003e\n\u003cp\u003eresult()原地等待任务的返回结果\u003c/p\u003e\n\u003cp\u003ep.submit(fn,*args, **kwargs).add_done_callback(fn)提交任务的时候 绑定一个回调函数 一旦该任务有结果 立刻执行对于的回调函数\u003c/p\u003e","title":"【并发编程】进程池/线程池/协程"},{"content":"\n并发编程(三) \u0026raquo;\u0026gt;思维导图\u0026gt; \u0026raquo;博客园****\nGIL全局解释器锁\nCpython解释器：python解释器有很多种，最常见的就是Cpython解释器\nGIL本质也是一把互斥锁\n将并发变成串行牺牲效率保证数据的安全\n用来阻止同一个进程下的多个线程的同时执行\n(同一个进程内多个线程无法实现并行但是可以实现并发)\nGIL的存在是因为CPython解释器的内存管理不是线程安全的\n因为进程内的垃圾回收机制也是一个线程，所以有可能造成其他线程例如赋值时，刚创建值，还没指向变量名就被回收，再赋值直接报错\npython的多线程没法利用多核优势 是不是就是没有用了?\n研究python的多线程是否有用需要分情况讨论\n四个任务 计算密集型的 10s 单核情况下 开线程更省资源 多核情况下 开进程 10s 开线程 40s 四个任务 IO密集型的 单核情况下 开线程更节省资源 多核情况下 开线程更节省资源 python的多线程到底有没有用 需要看情况而定 并且肯定是有用的 多进程+多线程配合使用 多线程不能使用多核是所有解释型语言的通病 GIL与普通互斥锁\nfrom threading import Thread import time n = 100 def task(): global n tmp = n # time.sleep(1) n = tmp -1 t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(n) 死锁与递归锁\n所谓死锁： 是指两个或两个以上的进程或线程在执行过程中，因争夺资源而造成的一种互相等待的现象，若无外力作用，它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁，这些永远在互相等待的进程称为死锁进程，如下就是死锁\nfrom threading import Lock as Lock import time mutexA=Lock() mutexA.acquire() mutexA.acquire() print(123) mutexA.release() mutexA.release() 解决方法：递归锁\n在Python中为了支持在同一线程中多次请求同一资源，python提供了可重入锁RLock\n这个RLock内部维护着一个Lock和一个counter变量，counter记录了acquire的次数，从而使得资源可以被多次require。直到一个线程所有的acquire都被release，其他的线程才能获得资源。上面的例子如果使用RLock代替Lock，则不会发生死锁：\n\u0026quot;\u0026quot;\u0026quot; Rlock可以被第一个抢到锁的人连续的acquire和release 每acquire一次锁身上的计数加1 每release一次锁身上的计数减1 只要锁的计数不为0 其他人都不能抢 \u0026quot;\u0026quot;\u0026quot; # mutexA = Lock() # mutexB = Lock() mutexA = mutexB = RLock() # A B现在是同一把锁 class MyThread(Thread): def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1() self.func2() def func1(self): mutexA.acquire() print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire() print('%s抢到了B锁'%self.name) mutexB.release() print('%s释放了B锁'%self.name) mutexA.release() print('%s释放了A锁'%self.name) def func2(self): mutexB.acquire() print('%s抢到了B锁'%self.name) time.sleep(1) mutexA.acquire() print('%s抢到了A锁' % self.name) mutexA.release() print('%s释放了A锁' % self.name) mutexB.release() print('%s释放了B锁' % self.name) for i in range(10): t = MyThread() t.start() 信号量\n信号量可能在不同的领域中 对应不同的知识点\n信号量通过threading模块的Senmaphore类创建一个含有多个锁孔的锁对象，抛出这个锁喉，可被多个线程抢到，每抢到一个就少一个位置，直到有钥匙释放后有多出的空位了，可再被其他线程抢。\n\u0026quot;\u0026quot;\u0026quot; 互斥锁:一个厕所(一个坑位) 信号量:公共厕所(多个坑位) \u0026quot;\u0026quot;\u0026quot; from threading import Semaphore,Thread import time import random sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 def task(name): sm.acquire() print('%s占了一个坑位'%name) time.sleep(random.randint(1,3)) sm.release() for i in range(40): t = Thread(target=task,args=(i,)) t.start() Event事件\n通过threading模块的Event类创建一个事件对象，set用来发信号，wait用来等待信号，如果没有信号发出，含有wait的线程都会等在那里\n案例：红绿灯\nfrom threading import Event,Thread import time # 先生成一个event对象 e = Event() def light(): print('红灯正亮着') time.sleep(3) e.set() # 发信号 print('绿灯亮了') def car(name): print('%s正在等红灯'%name) e.wait() # 等待信号 print('%s加油门飙车了'%name) def blackcar(name): print('%s不用等红灯'%name) print('%s加油门飙车了'%name) t = Thread(target=light) t.start() t1 = Thread(target=blackcar,args=('黑车',)) t1.start() for i in range(5): t = Thread(target=car,args=('赛车%s'%i,)) t.start() 线程队列\nqueue队列 ：使用import queue，用法与进程Queue一样\n同一个进程下的多个线程本来就是数据共享 为什么还要用队列\n因为队列是管道+锁 使用队列你就不需要自己手动操作锁的问题\n因为锁操作的不好极容易产生死锁现象\nclass queue.Queue(maxsize=0) # 先进先出\nimport queue q=queue.Queue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 结果(先进先出): first second third ''' class queue.LifoQueue(maxsize=0) # last in fisrt out 后进先出\nimport queue q=queue.LifoQueue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ''' 结果(后进先出): third second first ''' class queue.PriorityQueue(maxsize=0) # 存储数据时可设置优先级的队列\nimport queue q=queue.PriorityQueue() # put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高 q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get()) ''' 结果(数字越小优先级越高,优先级高的优先出队): (10, 'b') (20, 'a') (30, 'c') ''' ","permalink":"https://zhong-er.com/posts/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E9%94%81%E7%BA%BF%E7%A8%8B%E9%98%9F%E5%88%97%E4%BF%A1%E5%8F%B7%E9%87%8Fevent%E4%BA%8B%E4%BB%B6/","summary":"\u003cp\u003e\u003cimg alt=\"并发编程(三).png\" loading=\"lazy\" src=\"/assets/201908143610_2559_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e并发编程(三) \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/2TRm4OQzz8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11352646.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eGIL全局解释器锁\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eCpython解释器：python解释器有很多种，最常见的就是Cpython解释器\u003c/p\u003e\n\u003cp\u003eGIL本质也是一把互斥锁\u003c/p\u003e\n\u003cp\u003e将并发变成串行牺牲效率保证数据的安全\u003c/p\u003e\n\u003cp\u003e用来阻止同一个进程下的多个线程的同时执行\u003c/p\u003e\n\u003cp\u003e(同一个进程内多个线程无法实现并行但是可以实现并发)\u003c/p\u003e\n\u003cp\u003eGIL的存在是因为CPython解释器的内存管理不是线程安全的\u003c/p\u003e\n\u003cp\u003e因为进程内的垃圾回收机制也是一个线程，所以有可能造成其他线程例如赋值时，刚创建值，还没指向变量名就被回收，再赋值直接报错\u003c/p\u003e\n\u003cp\u003epython的多线程没法利用多核优势 是不是就是没有用了?\u003c/p\u003e\n\u003cp\u003e研究python的多线程是否有用需要分情况讨论\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e四个任务 计算密集型的  10s\n单核情况下\n    开线程更省资源\n多核情况下\n    开进程 10s\n    开线程 40s\n四个任务 IO密集型的  \n单核情况下\n    开线程更节省资源\n多核情况下\n    开线程更节省资源\npython的多线程到底有没有用\n需要看情况而定  并且肯定是有用的\n多进程+多线程配合使用\n多线程不能使用多核是所有解释型语言的通病\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eGIL与普通互斥锁\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom threading import Thread\nimport time\nn = 100\ndef task():\n    global n\n    tmp = n\n    # time.sleep(1)\n    n = tmp -1\nt_list = []\nfor i in range(100):\n    t = Thread(target=task)\n    t.start()\n    t_list.append(t)\nfor t in t_list:\n    t.join()\nprint(n)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e死锁与递归锁\u003c/strong\u003e\u003c/p\u003e","title":"【并发编程】锁/线程队列/信号量/Event事件"},{"content":"\n并发编程(二) \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园****\n队列\n队列：先进先出\n堆栈：先进后出\nq = Queue(5) 括号内可以传参数：表示的是这个队列的最大存储数\nq.put(1) 括号内可以传参数：表示往队列中添加数据，当对列满了，程序会阻塞，直到有人从队列中取走值\nq.get()：表示向队列取值，当取完后再次获取，程序会阻塞，直到有人往队列存入值\nq.full()：判断对列是否满了\nq.empty()：判断队列中的数据是否取完\nq.get_nowait()：取值，没有值不等待直接报错\nfull,get_nowait,empty都不适用与多进程的情况\n其他方法\nq.qsize() 返回队列中目前项目的正确数量。此函数的结果并不可靠，因为在返回结果和在稍后程序中使用结果之间，队列中可能添加或删除了项目。在某些系统上，此方法可能引发NotImplementedError异常。 q.close() 关闭队列，防止队列中加入更多数据。调用此方法时，后台线程将继续写入那些已入队列但尚未写入的数据，但将在此方法完成时马上关闭。如果q被垃圾收集，将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如，如果某个使用者正被阻塞在get（）操作上，关闭生产者中的队列不会导致get（）方法返回错误。 q.cancel_join_thread() 不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。 q.join_thread() 连接队列的后台线程。此方法用于在调用q.close()方法后，等待所有队列项被消耗。默认情况下，此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。 进程间的通信IPC机制\n子进程放数据，主进程获取数据\n两个子进程相互放，取数据\nfrom multiprocessing import Process,Queue def producer(q): q.put('hello GF~') def consumer(q): print(q.get()) if __name__ == '__main__': q = Queue() p = Process(target=producer,args=(q,)) c = Process(target=consumer, args=(q,)) p.start() c.start() 生产者消费者模型\n生产者：生产/制造数据的\n消费者：消费/处理数据的\n例子：做包子的，买包子的\n# 1.做包子远比买包子的多 # 2.做包子的远比包子的少 # 供需不平衡的问题 from multiprocessing import Process,Queue,JoinableQueue import random import time def producer(name,food,q): for i in range(10): data = '%s生产了%s%s'%(name,food,i) time.sleep(random.random()) q.put(data) print(data) def consumer(name,q): while True: data = q.get() if data == None:break print('%s吃了%s'%(name,data)) time.sleep(random.random()) q.task_done() # 告诉队列你已经从队列中取出了一个数据 并且处理完毕了 if __name__ == '__main__': q = JoinableQueue() p = Process(target=producer,args=('大厨egon','馒头',q)) p1 = Process(target=producer,args=('跟班tank','生蚝',q)) c = Process(target=consumer,args=('许兆龙',q)) c1 = Process(target=consumer,args=('吃货jerry',q)) p.start() p1.start() c.daemon = True c1.daemon = True c.start() c1.start() p.join() p1.join() q.join() # 等到队列中数据全部取出 # q.put(None) # q.put(None) 线程\n进程线程其实都是虚拟单位,都是用来帮助我们形象的描述某种事物\n进程:资源单位\n线程:执行单位\n将内存比如成工厂那么进程就相当于是工厂里面的车间而你的线程就相当于是车间里面的流水线ps:每个进程都自带一个线程,线程才是真正的执行单位,进程只是在线程运行过程中提供代码运行所需要的资源 开进程\n1.申请内存空间 耗资源2.\u0026ldquo;拷贝代码\u0026rdquo; 耗资源\n开线程\n一个进程内可以起多个线程,并且线程与线程之间数据是共享的ps:开启线程的开销要远远小于开启进程的开销\n创建进程的两种方式\n# 方式一 from threading import Thread import time def task(name): print('%s is running'%name) time.sleep(3) print('%s is over'%name) # 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内 t = Thread(target=task,args=('egon',)) t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程 # 小的代码执行完 线程就已经开启了 print('主') # 方式二 from threading import Thread import time class MyThread(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): print('%s is running'%self.name) time.sleep(3) print('%s is over'%self.name) t = MyThread('egon') t.start() print('主') 线程对象及其他方法\nfrom threading import Thread,current_thread,active_count import time import os def task(name,i): print('%s is running'%name) # print('子current_thread:',current_thread().name) # print('子',os.getpid()) time.sleep(i) print('%s is over'%name) # 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内 t = Thread(target=task,args=('egon',1)) t1 = Thread(target=task,args=('jason',2)) t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程 t1.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程 t1.join() # 主线程等待子线程运行完毕 print('当前正在活跃的线程数',active_count()) # 小的代码执行完 线程就已经开启了 print('主') # print('主current_thread:',current_thread().name) # print('主',os.getpid()) threading模块\nmultiprocess模块的完全模仿了threading模块的接口，二者在使用层面，有很大的相似性\nThread类的其他方法\nThread实例对象的方法 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法： # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前，不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量，与len(threading.enumerate())有相同的结果。 join方法\nfrom threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('egon',)) t.start() t.join() print('主线程') print(t.is_alive()) ''' egon say hello 主线程 False ''' 守护线程\n主线程的结束也就意味着进程的结束\n主线程必须等待其他非守护线程的结束才能结束\n(意味子线程在运行的时候需要使用进程中的资源,而主线程一旦结束了资源也就销毁了)\nfrom threading import Thread,current_thread import time def task(i): print(current_thread().name) time.sleep(i) print('GG') # for i in range(3): # t = Thread(target=task,args=(i,)) # t.start() t = Thread(target=task,args=(1,)) t.daemon = True t.start() print('主') 线程间通信\nfrom threading import Thread money = 666 def task(): global money money = 999 t = Thread(target=task) t.start() t.join() print(money) 互斥锁\nfrom threading import Thread,Lock import time n = 100 def task(mutex): global n mutex.acquire() tmp = n time.sleep(0.1) n = tmp - 1 mutex.release() t_list = [] mutex = Lock() for i in range(100): t = Thread(target=task,args=(mutex,)) t.start() t_list.append(t) for t in t_list: t.join() print(n) 小例子\nfrom threading import Thread from multiprocessing import Process import time def foo(): print(123) time.sleep(1) print(\u0026quot;end123\u0026quot;) def bar(): print(456) time.sleep(3) print(\u0026quot;end456\u0026quot;) if __name__ == '__main__': t1=Thread(target=foo) t2=Thread(target=bar) t1.daemon=True t1.start() t2.start() print(\u0026quot;main-------\u0026quot;) ","permalink":"https://zhong-er.com/posts/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E9%98%9F%E5%88%97%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1%E7%BA%BF%E7%A8%8B/","summary":"\u003cp\u003e\u003cimg alt=\"并发编程(二).png\" loading=\"lazy\" src=\"/assets/201908124424_9438_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e并发编程(二) \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/ZRlhGS9Ua\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11340425.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e队列\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e队列：先进先出\u003c/p\u003e\n\u003cp\u003e堆栈：先进后出\u003c/p\u003e\n\u003cp\u003eq = Queue(5) 括号内可以传参数：表示的是这个队列的最大存储数\u003c/p\u003e\n\u003cp\u003eq.put(1) 括号内可以传参数：表示往队列中添加数据，当对列满了，程序会阻塞，直到有人从队列中取走值\u003c/p\u003e\n\u003cp\u003eq.get()：表示向队列取值，当取完后再次获取，程序会阻塞，直到有人往队列存入值\u003c/p\u003e\n\u003cp\u003eq.full()：判断对列是否满了\u003c/p\u003e\n\u003cp\u003eq.empty()：判断队列中的数据是否取完\u003c/p\u003e\n\u003cp\u003eq.get_nowait()：取值，没有值不等待直接报错\u003c/p\u003e\n\u003cp\u003efull,get_nowait,empty都不适用与多进程的情况\u003c/p\u003e\n\u003cp\u003e其他方法\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eq.qsize() \n    返回队列中目前项目的正确数量。此函数的结果并不可靠，因为在返回结果和在稍后程序中使用结果之间，队列中可能添加或删除了项目。在某些系统上，此方法可能引发NotImplementedError异常。\nq.close() \n    关闭队列，防止队列中加入更多数据。调用此方法时，后台线程将继续写入那些已入队列但尚未写入的数据，但将在此方法完成时马上关闭。如果q被垃圾收集，将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如，如果某个使用者正被阻塞在get（）操作上，关闭生产者中的队列不会导致get（）方法返回错误。\nq.cancel_join_thread()\n    不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。\nq.join_thread() \n    连接队列的后台线程。此方法用于在调用q.close()方法后，等待所有队列项被消耗。默认情况下，此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e进程间的通信IPC机制\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e子进程放数据，主进程获取数据\u003c/p\u003e\n\u003cp\u003e两个子进程相互放，取数据\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom multiprocessing import Process,Queue\ndef producer(q):\n    q.put('hello GF~')\ndef consumer(q):\n    print(q.get())\nif __name__ == '__main__':\n    q = Queue()\n    p = Process(target=producer,args=(q,))\n    c = Process(target=consumer, args=(q,))\n    p.start()\n    c.start()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e生产者消费者模型\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e生产者：生产/制造数据的\u003c/p\u003e\n\u003cp\u003e消费者：消费/处理数据的\u003c/p\u003e\n\u003cp\u003e例子：做包子的，买包子的\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 1.做包子远比买包子的多\n# 2.做包子的远比包子的少\n# 供需不平衡的问题\nfrom multiprocessing import Process,Queue,JoinableQueue\nimport random\nimport time\ndef producer(name,food,q):\n    for i in range(10):\n        data = '%s生产了%s%s'%(name,food,i)\n        time.sleep(random.random())\n        q.put(data)\n        print(data)\ndef consumer(name,q):\n    while True:\n        data = q.get()\n        if data == None:break\n        print('%s吃了%s'%(name,data))\n        time.sleep(random.random())\n        q.task_done()  # 告诉队列你已经从队列中取出了一个数据 并且处理完毕了\nif __name__ == '__main__':\n    q = JoinableQueue()\n    p = Process(target=producer,args=('大厨egon','馒头',q))\n    p1 = Process(target=producer,args=('跟班tank','生蚝',q))\n    c = Process(target=consumer,args=('许兆龙',q))\n    c1 = Process(target=consumer,args=('吃货jerry',q))\n    p.start()\n    p1.start()\n    c.daemon = True\n    c1.daemon = True\n    c.start()\n    c1.start()\n    p.join()\n    p1.join()\n    q.join()  # 等到队列中数据全部取出\n    # q.put(None)\n    # q.put(None)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e线程\u003c/strong\u003e\u003c/p\u003e","title":"【并发编程】队列/进程间通信/线程"},{"content":"\n并发编程(一) \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园****\n操作系统的作用\n1：隐藏丑陋复杂的硬件接口，提供良好的抽象接口\n2：管理、调度进程，并且将多个进程对硬件的竞争变得有序\n多道技术\n1.空间上的复用：多个赓续公用一套计算机硬件\n2.时间上的复用：切换+保存状态\n\u0026gt;1.当一个程序遇到IO操作 操作系统会剥夺该程序的cpu执行权限(提高了cpu的利用率 并且也不影响程序的执行效率) \u0026gt;2.当一个程序长时间占用cpu 操作系统也会剥夺该程序的cpu执行权限(降低了程序的执行效率) 并发:看起来像同时运行的就可以 并行:真正意义上的同时执行 单核的计算机不能实现并行,但是可以实现并发 单道程序工作示例\n多道程序工作示例\n进程\n程序：一堆代码**\n**\n进程：正在运行的程序\n第一，进程是一个实体。每一个进程都有它自己的地址空间，一般情况下，包括文本区域（text region）、数据区域（data region）和堆栈（stack region）。文本区域存储处理器执行的代码；数据区域存储变量和进程执行期间使用的动态分配的内存；堆栈区域存储着活动过程调用的指令和本地变量。 第二，进程是一个“执行中的程序”。程序是一个没有生命的实体，只有处理器赋予程序生命时（操作系统执行之），它才能成为一个活动的实体，我们称其为进程。[3] 进程是操作系统中最基本、重要的概念。是多道程序系统出现后，为了刻画系统内部出现的动态情况，描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。 进程的特征\n动态性：进程的实质是程序在多道程序系统中的一次执行过程，进程是动态产生，动态消亡的。 并发性：任何进程都可以同其他进程一起并发执行 独立性：进程是一个能独立运行的基本单位，同时也是系统分配资源和调度的独立单位； 异步性：由于进程间的相互制约，使进程具有执行的间断性，即进程按各自独立的、不可预知的速度向前推进 结构特征：进程由程序、数据和进程控制块三部分组成。 进程的调度\n1.先来先服务调度算法 先来先服务（FCFS）调度算法是一种最简单的调度算法，该算法既可用于作业调度，也可用于进程调度。FCFS算法比较有利于长作业（进程），而不利于短作业（进程）。由此可知，本算法适合于CPU繁忙型作业，而不利于I/O繁忙型的作业（进程）。 2.短作业优先调度算法 短作业（进程）优先调度算法（SJ/PF）是指对短作业或短进程优先调度的算法，该算法既可用于作业调度，也可用于进程调度。但其对长作业不利；不能保证紧迫性作业（进程）被及时处理；作业的长短只是被估算出来的。 3.时间片轮转法 时间片轮转(Round Robin，RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中，需要将CPU的处理时间分成固定大小的时间片，例如，几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片，但又未完成要求的任务，则它自行释放自己所占有的CPU而排到就绪队列的末尾，等待下一次调度。同时，进程调度程序又去调度当前就绪队列中的第一个进程。 显然，轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺，而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配，其中包含有不可抢占资源，所以作业调度不使用轮转法。在轮转法中，时间片长度的选取非常重要。首先，时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短，则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加，从而加重系统开销。反过来，如果时间片长度选择过长，例如，一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕，则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。 在轮转法中，加入到就绪队列的进程有3种情况： 一种是分给它的时间片用完，但进程还未完成，回到就绪队列的末尾等待下次调度去继续执行。 另一种情况是分给该进程的时间片并未用完，只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。 第三种情况就是新创建进程进入就绪队列。 如果对这些进程区别对待，给予不同的优先级和时间片从直观上看，可以进一步改善系统服务质量和效率。例如，我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列，每个队列按FCFS原则排列，各队列之间的进程享有不同的优先级，但同一队列内优先级相同。这样，当一个进程在执行完它的时间片之后，或从睡眠中被唤醒以及被创建之后，将进入不同的就绪队列。 4.多级反馈队列 前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法，仅照顾了短进程而忽略了长进程，而且如果并未指明进程的长度，则短进程优先和基于进程长度的抢占式调度算法都将无法使用。而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间，而且还可以满足各种类型进程的需要，因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中，调度算法的实施过程如下所述。(1) 应设置多个就绪队列，并为各个队列赋予不同的优先级。第一个队列的优先级最高，第二个队列次之，其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同，在优先权愈高的队列中，为每个进程所规定的执行时间片就愈小。例如，第二个队列的时间片要比第一个队列的时间片长一倍，……，第i+1个队列的时间片要比第i个队列的时间片长一倍。(2) 当一个新进程进入内存后，首先将它放入第一队列的末尾，按FCFS原则排队等待调度。当轮到该进程执行时，如它能在该时间片内完成，便可准备撤离系统；如果它在一个时间片结束时尚未完成，调度程序便将该进程转入第二队列的末尾，再同样地按FCFS原则等待调度执行；如果它在第二队列中运行一个时间片后仍未完成，再依次将它放入第三队列，……，如此下去，当一个长作业(进程)从第一队列依次降到第n队列后，在第n 队列便采取按时间片轮转的方式运行。(3) 仅当第一队列空闲时，调度程序才调度第二队列中的进程运行；仅当第1～(i-1)队列均空时，才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时，又有新进程进入优先权较高的队列(第1～(i-1)中的任何一个队列)，则此时新进程将抢占正在运行进程的处理机，即由调度程序把正在运行的进程放回到第i队列的末尾，把处理机分配给新到的高优先权进程。 进程的并发与并行\n并行： 并行是指两者同时执行（资源够用，比如三个线程，四核的CPU ） 并发：并发是指资源有限的情况下，两者交替轮流使用资源 区别 并行是从微观上，也就是在一个精确的时间片刻，有不同的程序在执行，这就要求必须有多个处理器 并发是从宏观上，在一个时间段上可以看出是同时执行的，比如一个服务器同时处理多个session 同步异步阻塞非阻塞\n进程的三种状态\n(1)就绪(Ready)状态当进程已分配到除CPU以外的所有必要的资源，只要获得处理机便可立即执行，这时的进程状态称为就绪状态。 (2)执行/运行(Running)状态当进程已获得处理机，其程序正在处理机上执行，此时的进程状态称为执行状态。 (3)阻塞(Blocked)状态正在执行的进程，由于等待某个事件发生而无法执行时，便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种，例如，等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。 同步:任务提交之后 原地等待的任务的执行并拿到返回结果才走 期间不做任何事(程序层面的表现就是卡住了)\n异步:任务提交之后 不再原地等待 而是继续执行下一行代码(结果是要的 但是是用过其他方式获取)\n强调:同步异步 阻塞非阻塞是两对概念 不能混为一谈\n进程的创建与结束\n创建进程的两种方式**\n**\n第一种方式\nfrom multiprocessing import Process import time def test(name): print('%s is running'%name) time.sleep(3) print('%s is over'%name) if __name__ == '__main__': p = Process(target=test,args=('egon',)) # 创建一个进程对象 p.start() # 告诉操作系统帮你创建一个进程 print('主') 第二种方式\nfrom multiprocessing import Process import time class MyProcess(Process): def __init__(self,name): super().__init__() self.name = name def run(self): print('%s is running' % self.name) time.sleep(3) print('%s is over' % self.name) if __name__ == '__main__': p = MyProcess('egon') p.start() print('主') windows创建进程会将代码以模块的方式 从上往下执行一遍 linux会直接将代码完完整整的拷贝一份 windows创建进程一定要在if __name__ == '__main__':代码块内创建 否则报错 创建进程就是在内存中重新开辟一块内存空间，将允许产生的代码丢进去，一个进程对应在内存就是一块独立的内存空间 进程与进程之间数据是隔离的 无法直接交互，但是可以通过某些技术实现间接交互 进程的结束\n1. 正常退出（自愿，如用户点击交互式页面的叉号，或程序执行完毕调用发起系统调用正常退出，在linux中用exit，在windows中用ExitProcess） 2. 出错退出（自愿，python a.py中a.py不存在） 3. 严重错误（非自愿，执行非法指令，如引用不存在的内存，1/0等，可以捕捉异常，try...except...） 4. 被其他进程杀死（非自愿，如kill -9） 在python程序中的进程操作\nmultiprocess\nmultiprocess不是一个模块而是python中一个操作、管理进程的包\n仔细说来，multiprocess不是一个模块而是python中一个操作、管理进程的包。 之所以叫multi是取自multiple的多功能的意思,在这个包中几乎包含了和进程有关的所有子模块。由于提供的子模块非常多，为了方便大家归类记忆，我将这部分大致分为四个部分：创建进程部分，进程同步部分，进程池部分，进程之间数据共享。 process模块\nprocess模块是一个创建进程的模块，借助这个模块，就可以完成进程的创建。\nProcess([group [, target [, name [, args [, kwargs]]]]])，由该类实例化得到的对象，表示一个子进程中的任务（尚未启动） 强调： 1. 需要使用关键字的方式来指定参数 2. args指定的为传给target函数的位置参数，是一个元组形式，必须有逗号 参数介绍： 1 group参数未使用，值始终为None 2 target表示调用对象，即子进程要执行的任务 3 args表示调用对象的位置参数元组，args=(1,2,'egon',) 4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18} 5 name为子进程的名称 方法介绍\n1 p.start()：启动进程，并调用该子进程中的p.run() 2 p.run():进程启动时运行的方法，正是它去调用target指定的函数，我们自定义类的类中一定要实现该方法 3 p.terminate():强制终止进程p，不会进行任何清理操作，如果p创建了子进程，该子进程就成了僵尸进程，使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放，进而导致死锁 4 p.is_alive():如果p仍然运行，返回True 5 p.join([timeout]):主线程等待p终止（强调：是主线程处于等的状态，而p是处于运行的状态）。timeout是可选的超时时间，需要强调的是，p.join只能join住start开启的进程，而不能join住run开启的进程 在windows中使用process模块的注意事项\n在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制)，在创建子进程的时候会自动 import 启动它的这个文件，而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来，import 的时候 ，就不会递归运行了。 开启子进程\nimport time from multiprocessing import Process def f(name): print('hello', name) print('我是子进程') if __name__ == '__main__': p = Process(target=f, args=('bob',)) p.start() time.sleep(1) print('执行主进程的内容了') join方法\nimport time from multiprocessing import Process def f(name): print('hello', name) time.sleep(1) print('我是子进程') if __name__ == '__main__': p = Process(target=f, args=('bob',)) p.start() # p.join() print('我是父进程') 查看主进程和子进程的进程号\nimport os from multiprocessing import Process def f(x): print('子进程id ：',os.getpid(),'父进程id ：',os.getppid()) return x*x if __name__ == '__main__': print('主进程id ：', os.getpid()) p_lst = [] for i in range(5): p = Process(target=f, args=(i,)) p.start() 多个进程同时运行\nimport time from multiprocessing import Process def f(name): print('hello', name) time.sleep(1) if __name__ == '__main__': p_lst = [] for i in range(5): p = Process(target=f, args=('bob',)) p.start() p_lst.append(p) 多个进程同时运行，再谈join方法(1)\nimport time from multiprocessing import Process def f(name): print('hello', name) time.sleep(1) if __name__ == '__main__': p_lst = [] for i in range(5): p = Process(target=f, args=('bob',)) p.start() p_lst.append(p) p.join() # [p.join() for p in p_lst] print('父进程在执行') 多个进程同时运行，再谈join方法(2)\nimport time from multiprocessing import Process def f(name): print('hello', name) time.sleep(1) if __name__ == '__main__': p_lst = [] for i in range(5): p = Process(target=f, args=('bob',)) p.start() p_lst.append(p) # [p.join() for p in p_lst] print('父进程在执行') 以继承Process类的形式开启进程\nimport os from multiprocessing import Process class MyProcess(Process): def __init__(self,name): super().__init__() self.name=name def run(self): print(os.getpid()) print('%s 正在和女主播聊天' %self.name) p1=MyProcess('wupeiqi') p2=MyProcess('yuanhao') p3=MyProcess('nezha') p1.start() # start会自动调用run p2.start() # p2.run() p3.start() p1.join() p2.join() p3.join() print('主线程') 进程之间的数据隔离问题\nfrom multiprocessing import Process def work(): global n n=0 print('子进程内: ',n) if __name__ == '__main__': n = 100 p=Process(target=work) p.start() print('主进程内: ',n) 守护进程\n主进程创建守护进程\n会随着主进程的结束而结束。 主进程创建守护进程 其一：守护进程会在主进程代码执行结束后就终止 其二：守护进程内无法再开启子进程,否则抛出异常：AssertionError: daemonic processes are not allowed to have children 注意：进程之间是互相独立的，主进程代码运行结束，守护进程随即终止 守护进程的启动\nimport os import time from multiprocessing import Process class Myprocess(Process): def __init__(self,person): super().__init__() self.person = person def run(self): print(os.getpid(),self.name) print('%s正在和女主播聊天' %self.person) p=Myprocess('哪吒') p.daemon=True # 一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行 p.start() time.sleep(10) # 在sleep时查看进程id对应的进程ps -ef|grep id print('主') 主进程代码执行结束守护进程立即结束\nfrom multiprocessing import Process def foo(): print(123) time.sleep(1) print(\u0026quot;end123\u0026quot;) def bar(): print(456) time.sleep(3) print(\u0026quot;end456\u0026quot;) p1=Process(target=foo) p2=Process(target=bar) p1.daemon=True p1.start() p2.start() time.sleep(0.1) print(\u0026quot;main-------\u0026quot;)# 打印该行则主进程代码结束,则守护进程p1应该被终止.# 可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止. 案例\nfrom multiprocessing import Process import time def test(name): print('%s总管正常活着'%name) time.sleep(3) print('%s总管正常死亡'%name) if __name__ == '__main__': p = Process(target=test,args=('egon',)) p.daemon = True # 将该进程设置为守护进程 这一句话必须放在start语句之前 否则报错 p.start() time.sleep(0.1) print('皇帝jason寿正终寝') 僵尸进程与孤儿进程\n僵尸进程\n父进程回收子进程资源的两种方式 1.join方法 2.父进程正常死亡 所有的进程都会步入僵尸进程 孤儿进程\n子进程没死 父进程意外死亡 针对linux会有儿童福利院(init) 如果父进程意外死亡他所创建的子进程都会被福利院收养 互斥锁\n当多个进程操作同一份数据的时候 会造成数据的错乱 这个时候必须加锁处理 将并发变成串行 虽然降低了效率但是提高了数据的安全 注意: 1.锁不要轻易使用 容易造成死锁现象 2.只在处理数据的部分加锁 不要在全局加锁 锁必须在主进程中产生 交给子进程去使用 案例：抢票\nfrom multiprocessing import Process,Lock import time import json # 查票 def search(i): with open('data','r',encoding='utf-8') as f: data = f.read() t_d = json.loads(data) print('用户%s查询余票为:%s'%(i,t_d.get('ticket'))) # 买票 def buy(i): with open('data','r',encoding='utf-8') as f: data = f.read() t_d = json.loads(data) time.sleep(1) if t_d.get('ticket') \u0026gt; 0: # 票数减一 t_d['ticket'] -= 1 # 更新票数 with open('data','w',encoding='utf-8') as f: json.dump(t_d,f) print('用户%s抢票成功'%i) else: print('没票了') def run(i,mutex): search(i) mutex.acquire() # 抢锁 只要有人抢到了锁 其他人必须等待该人释放锁 buy(i) mutex.release() # 释放锁 if __name__ == '__main__': mutex = Lock() # 生成了一把锁 for i in range(10): p = Process(target=run,args=(i,mutex)) p.start() ","permalink":"https://zhong-er.com/posts/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E8%BF%9B%E7%A8%8B/","summary":"\u003cp\u003e\u003cimg alt=\"并发编程(一).png\" loading=\"lazy\" src=\"/assets/201908119816_8792_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e并发编程(一) \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/3SQHwb0bb8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11337291.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e操作系统的作用\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1：隐藏丑陋复杂的硬件接口，提供良好的抽象接口\u003c/p\u003e\n\u003cp\u003e2：管理、调度进程，并且将多个进程对硬件的竞争变得有序\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e多道技术\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.空间上的复用：多个赓续公用一套计算机硬件\u003c/p\u003e\n\u003cp\u003e2.时间上的复用：切换+保存状态\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026gt;1.当一个程序遇到IO操作\n操作系统会剥夺该程序的cpu执行权限(提高了cpu的利用率 并且也不影响程序的执行效率)\n\u0026gt;2.当一个程序长时间占用cpu\n操作系统也会剥夺该程序的cpu执行权限(降低了程序的执行效率)\n并发:看起来像同时运行的就可以\n并行:真正意义上的同时执行\n单核的计算机不能实现并行,但是可以实现并发\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e单道程序工作示例\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"单道程序工作示例.png\" loading=\"lazy\" src=\"/assets/201908114716_2734_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e多道程序工作示例\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"多道程序工作示例.png\" loading=\"lazy\" src=\"/assets/201908111904_1209.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e进程\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e程序：一堆代码**\u003cbr\u003e\n**\u003c/p\u003e\n\u003cp\u003e进程：正在运行的程序\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e第一，进程是一个实体。每一个进程都有它自己的地址空间，一般情况下，包括文本区域（text region）、数据区域（data region）和堆栈（stack region）。文本区域存储处理器执行的代码；数据区域存储变量和进程执行期间使用的动态分配的内存；堆栈区域存储着活动过程调用的指令和本地变量。\n第二，进程是一个“执行中的程序”。程序是一个没有生命的实体，只有处理器赋予程序生命时（操作系统执行之），它才能成为一个活动的实体，我们称其为进程。[3] \n进程是操作系统中最基本、重要的概念。是多道程序系统出现后，为了刻画系统内部出现的动态情况，描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e进程的特征\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e动态性：进程的实质是程序在多道程序系统中的一次执行过程，进程是动态产生，动态消亡的。\n并发性：任何进程都可以同其他进程一起并发执行\n独立性：进程是一个能独立运行的基本单位，同时也是系统分配资源和调度的独立单位；\n异步性：由于进程间的相互制约，使进程具有执行的间断性，即进程按各自独立的、不可预知的速度向前推进\n结构特征：进程由程序、数据和进程控制块三部分组成。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e进程的调度\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1.先来先服务调度算法\n先来先服务（FCFS）调度算法是一种最简单的调度算法，该算法既可用于作业调度，也可用于进程调度。FCFS算法比较有利于长作业（进程），而不利于短作业（进程）。由此可知，本算法适合于CPU繁忙型作业，而不利于I/O繁忙型的作业（进程）。\n2.短作业优先调度算法\n短作业（进程）优先调度算法（SJ/PF）是指对短作业或短进程优先调度的算法，该算法既可用于作业调度，也可用于进程调度。但其对长作业不利；不能保证紧迫性作业（进程）被及时处理；作业的长短只是被估算出来的。\n3.时间片轮转法\n时间片轮转(Round Robin，RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中，需要将CPU的处理时间分成固定大小的时间片，例如，几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片，但又未完成要求的任务，则它自行释放自己所占有的CPU而排到就绪队列的末尾，等待下一次调度。同时，进程调度程序又去调度当前就绪队列中的第一个进程。 显然，轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺，而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配，其中包含有不可抢占资源，所以作业调度不使用轮转法。在轮转法中，时间片长度的选取非常重要。首先，时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短，则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加，从而加重系统开销。反过来，如果时间片长度选择过长，例如，一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕，则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。 在轮转法中，加入到就绪队列的进程有3种情况： 一种是分给它的时间片用完，但进程还未完成，回到就绪队列的末尾等待下次调度去继续执行。 另一种情况是分给该进程的时间片并未用完，只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。 第三种情况就是新创建进程进入就绪队列。 如果对这些进程区别对待，给予不同的优先级和时间片从直观上看，可以进一步改善系统服务质量和效率。例如，我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列，每个队列按FCFS原则排列，各队列之间的进程享有不同的优先级，但同一队列内优先级相同。这样，当一个进程在执行完它的时间片之后，或从睡眠中被唤醒以及被创建之后，将进入不同的就绪队列。\n4.多级反馈队列\n前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法，仅照顾了短进程而忽略了长进程，而且如果并未指明进程的长度，则短进程优先和基于进程长度的抢占式调度算法都将无法使用。而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间，而且还可以满足各种类型进程的需要，因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中，调度算法的实施过程如下所述。(1) 应设置多个就绪队列，并为各个队列赋予不同的优先级。第一个队列的优先级最高，第二个队列次之，其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同，在优先权愈高的队列中，为每个进程所规定的执行时间片就愈小。例如，第二个队列的时间片要比第一个队列的时间片长一倍，……，第i+1个队列的时间片要比第i个队列的时间片长一倍。(2) 当一个新进程进入内存后，首先将它放入第一队列的末尾，按FCFS原则排队等待调度。当轮到该进程执行时，如它能在该时间片内完成，便可准备撤离系统；如果它在一个时间片结束时尚未完成，调度程序便将该进程转入第二队列的末尾，再同样地按FCFS原则等待调度执行；如果它在第二队列中运行一个时间片后仍未完成，再依次将它放入第三队列，……，如此下去，当一个长作业(进程)从第一队列依次降到第n队列后，在第n 队列便采取按时间片轮转的方式运行。(3) 仅当第一队列空闲时，调度程序才调度第二队列中的进程运行；仅当第1～(i-1)队列均空时，才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时，又有新进程进入优先权较高的队列(第1～(i-1)中的任何一个队列)，则此时新进程将抢占正在运行进程的处理机，即由调度程序把正在运行的进程放回到第i队列的末尾，把处理机分配给新到的高优先权进程。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cimg alt=\"时间片转轮发+多级反馈队列.jpg\" loading=\"lazy\" src=\"/assets/201908118541_248.jpg-zhonger.com\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e进程的并发与并行\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e并行： 并行是指两者同时执行（资源够用，比如三个线程，四核的CPU ）\n并发：并发是指资源有限的情况下，两者交替轮流使用资源\n区别\n并行是从微观上，也就是在一个精确的时间片刻，有不同的程序在执行，这就要求必须有多个处理器\n并发是从宏观上，在一个时间段上可以看出是同时执行的，比如一个服务器同时处理多个session\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e同步异步阻塞非阻塞\u003c/strong\u003e\u003c/p\u003e","title":"【并发编程】进程"},{"content":"\nPython异常处理 \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园\n什么是异常处理？\n程序在运行过程中出现了不可预知的错误\n并且该错误没有对应的处理机制，那么就会以异常的形式表现出来\n造成的影响就是整个程序无法正常运行\n异常的结构\n1.异常的类型：NAMEERROR\n2.异常的信息：name \u0026lsquo;abc\u0026rsquo; is not defined\n3.异常的位置：Traceback (most recent call last): File \u0026ldquo;路径\u0026rdquo;, line 1, in abc\n异常的种类：\n1.语法错误\n是你程序立刻就能解决的，这种错误是不能被容忍的，语法上的错误，发现之后应该立刻解决\n2.逻辑错误\n这种错误是可以被容忍的，因为一眼看不出来，针对逻辑上的错误，可以采用异常处理机制进行捕获\n常见的错误类型\nNameError 名字错误\nSyntaxError 语法错误\nKeyError 健不存在\nvalueError 值错误\nIndexError 索引错误\nTypeError 类型错误\n异常处理\n在你认为可能出现bug的代码块上方try一下\ntry: 可能出错的代码 except 出错的类型 as e: # 将报错信息赋值给变量e 出错之后的处理机制 注意try内部的代码块越少越好\ntry后可跟多种except异常类型的捕获，但是不会连续连续捕获，因为有一个错误发生之后，会立刻停止代码的运行，执行该类型错误的except语句\ntry: name l = [1,2,3] l[111] d = {'name':'jason'} d['password'] except NameError: print('NameError') except IndexError: print('indexerror') except KeyError: print('keyerror') Exception 所有异常的基类，所有异常类型都被捕获\ntry: # name l = [1,2,3] l[111] # d = {'name':'jason'} # d['password'] except Exception: # 万能异常 所有的异常类型都被捕获 print('老子天下无敌') else: print('被检测的代码没有任何的异常发生 才会走else') finally: print('无论被检测的代码有没有异常发生 都会在代码运行完毕之后执行我') except后可跟else 被检测代码没有任何异常才会走else\nexcept后可跟finally 被检测代码无论有没有异常都会走finally\nraise 主动抛出异常\nif 'egon' == 'DSB': pass else: raise TypeError('尽说大实话') assert 断言、语言，猜某个数据的状态，猜对了不影响代码执行，猜错了直接报错\nl = [1,2,3] assert len(l) \u0026lt; 0 # 断言 预言 自定义异常\nclass MyError(BaseException): def __init__(self,msg): super().__init__() self.msg=msg def __str__(self): return '\u0026lt;dfsdf%ssdfsdaf\u0026gt;' %self.msg raise MyError('我自己定义的异常') # 主动抛出异常其实就是将异常类的对象打印出来,会走__str__方法 ","permalink":"https://zhong-er.com/posts/python%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/","summary":"\u003cp\u003e\u003cstrong\u003e\u003cimg alt=\"Python异常处理.png\" loading=\"lazy\" src=\"/assets/201908112295_3450_1.png\"\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython异常处理 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/3HxHgXnqf8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11322367.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e什么是异常处理？\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e程序在运行过程中出现了不可预知的错误\u003c/p\u003e\n\u003cp\u003e并且该错误没有对应的处理机制，那么就会以异常的形式表现出来\u003c/p\u003e\n\u003cp\u003e造成的影响就是整个程序无法正常运行\u003c/p\u003e\n\u003cp\u003e异常的结构\u003c/p\u003e\n\u003cp\u003e1.异常的类型：NAMEERROR\u003c/p\u003e\n\u003cp\u003e2.异常的信息：name \u0026lsquo;abc\u0026rsquo; is not defined\u003c/p\u003e\n\u003cp\u003e3.异常的位置：Traceback (most recent call last): File \u0026ldquo;路径\u0026rdquo;, line 1, in \u003cmodule\u003e abc\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e异常的种类：\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.语法错误\u003cbr\u003e\n是你程序立刻就能解决的，这种错误是不能被容忍的，语法上的错误，发现之后应该立刻解决\u003c/p\u003e\n\u003cp\u003e2.逻辑错误\u003cbr\u003e\n这种错误是可以被容忍的，因为一眼看不出来，针对逻辑上的错误，可以采用异常处理机制进行捕获\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e常见的错误类型\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eNameError 名字错误\u003c/p\u003e\n\u003cp\u003eSyntaxError 语法错误\u003c/p\u003e\n\u003cp\u003eKeyError 健不存在\u003c/p\u003e\n\u003cp\u003evalueError 值错误\u003c/p\u003e\n\u003cp\u003eIndexError 索引错误\u003c/p\u003e\n\u003cp\u003eTypeError 类型错误\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e异常处理\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e在你认为可能出现bug的代码块上方try一下\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003etry:\n\t可能出错的代码\nexcept 出错的类型 as e:  # 将报错信息赋值给变量e\n\t出错之后的处理机制\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e注意try内部的代码块越少越好\u003c/p\u003e\n\u003cp\u003etry后可跟多种except异常类型的捕获，但是不会连续连续捕获，因为有一个错误发生之后，会立刻停止代码的运行，执行该类型错误的except语句\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003etry:\n    name\n    l = [1,2,3]\n    l[111]\n    d = {'name':'jason'}\n    d['password']\nexcept NameError:\n    print('NameError')\nexcept IndexError:\n    print('indexerror')\nexcept KeyError:\n    print('keyerror')\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eException 所有异常的基类，所有异常类型都被捕获\u003c/p\u003e","title":"【Python】异常处理"},{"content":"\n网络编程(三) \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园****\n基于TCP协议使用socket分发大文件\n案例：电影上传\n思路：客户端端循环一行一行读文件并一行一行传输，服务端循环接收并写入文件\n客户端代码\nimport socket,json,os,struct client = cocket.socket() client.connet(('127.0.0.1',8080)) while True: # 获取电影类表，循环展示 MOVIE_DIR = r'D:\\路径\\视频' movie_list = os.listdir(MOVIE_DIR) for i,movie in enumerate(movie_list,1): print(i,movie) # 用户选择 choice = input('please choice movie to upload\u0026gt;\u0026gt;\u0026gt;:') # 判断是否是数字 if choice.isdigit(): # 将字符串转为int choice = in(choice) - 1 # 判断用户选择在不在列表范围内 if choice in range(0,len(movie_list)) # 获取用户想上传的文件路径 path = movie_list[choice] # 拼接文件的绝对路径 file_path = os.path.join(MOVIE_DIR,path) # 获取文件的大小 file_size = os.path.getsize(file_path) # 定义一个字典 res_d = { 'file_name':'性感荷官在线发牌.,mp4', 'file_size':file_size, 'msg':'注意身体，多喝营养快线' } # 序列话字典 json_d = json.dunps(res_d) json_bytes = json_d.encode('utf-8') # 1.先制作字典格式的报头 header = struct.pack('i',len(json_bytes)) # 2.发送字典的报头 client.send(header) # 3.再发字典 client.send(json_bytes) # 4.再发文件数据(打开文件循环发送) with open(file_path,r'b') as f: for line in f: client.send(line) else: print('not in rang') else: print('must be a number') 服务端代码\nimport socket,os,json,struct server = socket,socket() sever.bind(('127.0.0.1',8080)) server.listen(5) while True: conn,addr = server.accept() while True: try: header_len = conn.recv(4) # 解析字典报头 header_len = struct.unpack('i',hendaer_len)[0] # 再接收字典数据 header_dic = conn.recv(header_len) real_dic = json.loads(header_dic.decode('utf-8')) # 获取数据长度 total_size = real_dic.get('file_size') # 循环接受并写入文件 recv_size = 0 with open(real_dic.get('file_size'),'wb;) as f: while recv_size \u0026lt; total_size: data = conn.recv(1024) f.write(data) recv_size += len(data) print('上传成功！') except ConnetionResetError as e: print(e) break conn.close() UDP协议\nUDP是一个简单的传输层协议。和TCP相比，UDP有下面几个显著特性：\n1.UDP缺乏可靠性\nUDP 本身不提供确认，序列号，超时重传等机制。UDP 数据报可能在网络中被复制，被重新排序。即 UDP 不保证数据报会到达其最终目的地，也不保证各个数据报的先后顺序，也不保证每个数据报只到达一次 2.UDP 数据报是有长度的\n每个 UDP 数据报都有长度，如果一个数据报正确地到达目的地，那么该数据报的长度将随数据一起传递给接收方。而 TCP 是一个字节流协议，没有任何（协议上的）记录边界。 3.UDP 是无连接的\nUDP 客户和服务器之前不必存在长期的关系。UDP 发送数据报之前也不需要经过握手创建连接的过程。 4.UDP 支持多播和广播\n基于UDP协议的socket\ndp是无链接的，启动服务之后可以直接接受消息，不需要提前建立链接\nserver端\nimport socket udp_sk = socket.socket(type=socket.SOCK_DGRAM) # 创建一个服务器的套接字 udp_sk.bind(('127.0.0.1',9000)) # 绑定服务器套接字 msg,addr = udp_sk.recvfrom(1024) print(msg) udp_sk.sendto(b'hi',addr) # 对话(接收与发送) udp_sk.close() # 关闭服务器套接字 client端\nimport socket ip_port=('127.0.0.1',9000) udp_sk=socket.socket(type=socket.SOCK_DGRAM) udp_sk.sendto(b'hello',ip_port) back_msg,addr=udp_sk.recvfrom(1024) print(back_msg.decode('utf-8'),addr) UDP不会发生黏包\nUDP（user datagram protocol，用户数据报协议）是无连接的，面向消息的，提供高效率服务。 不会使用块的合并优化算法，, 由于UDP支持的是一对多的模式，所以接收端的skbuff(套接字缓冲区）采用了链式结构来记录每一个到达的UDP包，在每个UDP包中就有了消息头（消息来源地址，端口等信息），这样，对于接收端来说，就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。 对于空消息：tcp是基于数据流的，于是收发的消息不能为空，这就需要在客户端和服务端都添加空消息的处理机制，防止程序卡住，而udp是基于数据报的，即便是你输入的是空内容（直接回车），也可以被发送，udp协议会帮你封装上消息头发送过去。 不可靠不黏包的udp协议：udp的recvfrom是阻塞的，一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y;x数据就丢失，这意味着udp根本不会粘包，但是会丢数据，不可靠。 粘包现象只发生在tcp协议中\n1.从表面上看，黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。 2.实际上，主要还是因为接收方不知道消息之间的界限，不知道一次性提取多少字节的数据所造成的 udp和tcp一次发送数据长度的限制\n用UDP协议发送时，用sendto函数最大能发送数据的长度为：65535- IP头(20) – UDP头(8)＝65507字节。用sendto函数发送数据时，如果发送数据长度大于该值，则函数会返回错误。（丢弃这个包，不进行发送） 用TCP协议发送时，由于TCP是数据流协议，因此不存在包大小的限制（暂不考虑缓冲区的大小），这是指在用send函数时，数据长度参数不受限制。而实际上，所指定的这段数据并不一定会一次性发送出去，如果这段数据比较长，会被分段发送，如果比较短，可能会等待和下一次数据一起发送。 socketserver\nserver端\nimport socketserver class Myserver(socketserver.BaseRequestHandler): def handle(self): self.data = self.request.recv(1024).strip() print(\u0026quot;{} wrote:\u0026quot;.format(self.client_address[0])) print(self.data) self.request.sendall(self.data.upper()) if __name__ == \u0026quot;__main__\u0026quot;: HOST, PORT = \u0026quot;127.0.0.1\u0026quot;, 9999 # 设置allow_reuse_address允许服务器重用地址 socketserver.TCPServer.allow_reuse_address = True # 创建一个server, 将服务地址绑定到127.0.0.1:9999 server = socketserver.TCPServer((HOST, PORT),Myserver) # 让server永远运行下去，除非强制停止程序 server.serve_forever() client端\nimport socket HOST, PORT = \u0026quot;127.0.0.1\u0026quot;, 9999 data = \u0026quot;hello\u0026quot; # 创建一个socket链接，SOCK_STREAM代表使用TCP协议 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.connect((HOST, PORT)) # 链接到客户端 sock.sendall(bytes(data + \u0026quot;\\n\u0026quot;, \u0026quot;utf-8\u0026quot;)) # 向服务端发送数据 received = str(sock.recv(1024), \u0026quot;utf-8\u0026quot;)# 从服务端接收数据 print(\u0026quot;Sent: {}\u0026quot;.format(data)) print(\u0026quot;Received: {}\u0026quot;.format(received)) ","permalink":"https://zhong-er.com/posts/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8Bsocket/","summary":"\u003cp\u003e\u003cimg alt=\"网络编程(三).png\" loading=\"lazy\" src=\"/assets/201908085255_559_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e网络编程(三) \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/FpZaknXLa\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11322341.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e基于TCP协议使用socket分发大文件\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e案例：电影上传\u003c/p\u003e\n\u003cp\u003e思路：客户端端循环一行一行读文件并一行一行传输，服务端循环接收并写入文件\u003c/p\u003e\n\u003cp\u003e客户端代码\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eimport socket,json,os,struct\nclient = cocket.socket()\nclient.connet(('127.0.0.1',8080))\nwhile True:\n    # 获取电影类表，循环展示\n    MOVIE_DIR = r'D:\\路径\\视频'\n    movie_list = os.listdir(MOVIE_DIR)\n    for i,movie in enumerate(movie_list,1):\n        print(i,movie)\n    # 用户选择\n    choice = input('please choice movie to upload\u0026gt;\u0026gt;\u0026gt;:')\n    # 判断是否是数字\n    if choice.isdigit():\n        # 将字符串转为int\n        choice = in(choice) - 1\n        # 判断用户选择在不在列表范围内\n        if choice in range(0,len(movie_list))\n           # 获取用户想上传的文件路径\n            path = movie_list[choice]\n           # 拼接文件的绝对路径\n           file_path = os.path.join(MOVIE_DIR,path)\n           # 获取文件的大小\n           file_size = os.path.getsize(file_path)\n           # 定义一个字典\n           res_d = {\n                      'file_name':'性感荷官在线发牌.,mp4',\n                      'file_size':file_size,\n                      'msg':'注意身体，多喝营养快线'\n           }\n           # 序列话字典\n           json_d = json.dunps(res_d)\n           json_bytes = json_d.encode('utf-8')\n           # 1.先制作字典格式的报头\n           header = struct.pack('i',len(json_bytes))\n           # 2.发送字典的报头\n           client.send(header)\n           # 3.再发字典\n           client.send(json_bytes)\n           # 4.再发文件数据(打开文件循环发送)\n           with open(file_path,r'b') as f:\n               for line in f:\n                   client.send(line)\n        else:\n           print('not in rang')\n    else:\n        print('must be a number')\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e服务端代码\u003c/p\u003e","title":"【网络编程】Socket"},{"content":"\n网络编程(二) \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园****\n基于TCP协议的Socket套接字\nserver端\nimport socket sk = socket.socket() sk.bind(('127.0.0.1',8898)) # 把地址绑定到套接字 sk.listen() # 监听链接 conn,addr = sk.accept() # 接受客户端链接 ret = conn.recv(1024) # 接收客户端信息 print(ret) # 打印客户端信息 conn.send(b'hi') # 向客户端发送信息 conn.close() # 关闭客户端套接字 sk.close() # 关闭服务器套接字(可选) client端\nimport socket sk = socket.socket() # 创建客户套接字 sk.connect(('127.0.0.1',8898)) # 尝试连接服务器 sk.send(b'hello!') ret = sk.recv(1024) # 对话(发送/接收) print(ret) sk.close() # 关闭客户套接字 127.0.0.1本机回还地址：只能自己识别自己 其他人无法访问\nsend与recv对应，不要出现两边都是相同的情况\nrecv是跟内存要数据，至于数据的来源 你无需考虑\nTCP特点\n会将数据量比较小的并且时间间隔比较短的数据一次性打包发送给对方\nTCP是基于链接的，必须先启动服务端，然后再启动客户端去连接服务端\n解决粘包问题\n服务端\n1.先制作一个发送给客户端的字典\n2.制作字典的报头\n3.发送字典的报头\n4.发送字典\n5.再发真实数据\n客户端\n1.先接受字典的报头\n2.解析拿到字典的数据长度\n3.接受字典\n4.从字典中获取真实数据的长度\n5.接受真实数据\n服务端代码\nimport socket import subprocess import struct import json server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True : conn,addr = server.accept() while True: try: cmd = conn.recv(1024) if len(cmd) == 0:break cmd = cmd.decode('utf-8') obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) res = obj.studout.read() + obj.stderr.read() d = {'name':'jason','file_size':len(res),'info':'aaaaaaazzzzzzz'} json_d = json.dumps(d) # 1.先制作一个字典的报头 header = struct.pack('i',len(json_d)) # 2.发送字典报头 conn.send(header) # 3.发送字典 conn.send(json_d.encode('utf-8)) # 4.再发送真实数据 conn.send(res) except ConnectionResetError: breakconn.close() 客户端代码\nimprot cocket import struct import json client = socket.socket client.connect(('127.0.0.1',8080)) while True: msg = input(\u0026gt;\u0026gt;\u0026gt;:).encode('utf-8') if len(msg) == 0:continue client.send(msg) # 1.先接收字典报头 header_dict = client.recv(4) # 2.解析报头 获取字典的长度 dict_size = struct.unpack('i',header_dict)[0] # 解包的时候一定要加上索引0 # 3.接收字典数据 dict_bytes = client.recv(dict_size) dict_json = json.loads(dict_bytes.decode('utf-8')) # 4.从字典中获取信息 print(dict_json) recv_size = 0 real_data = b'' while recv_size \u0026lt; dict_json.get('file_size'): data = client.recv(1024) real_data += data recv_size += len(data) print(real_data.decode('gbk')) ","permalink":"https://zhong-er.com/posts/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8Btcp%E5%8D%8F%E8%AE%AE%E4%B8%8E%E7%B2%98%E5%8C%85%E9%97%AE%E9%A2%98/","summary":"\u003cp\u003e\u003cimg alt=\"网络编程(二).png\" loading=\"lazy\" src=\"/assets/201908079748_6105_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e网络编程(二) \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/M0ZxVWd7a\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11317624.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e基于TCP协议的Socket套接字\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eserver端\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eimport socket\nsk = socket.socket()\nsk.bind(('127.0.0.1',8898))  # 把地址绑定到套接字\nsk.listen()          # 监听链接\nconn,addr = sk.accept() # 接受客户端链接\nret = conn.recv(1024)  # 接收客户端信息\nprint(ret)       # 打印客户端信息\nconn.send(b'hi')        # 向客户端发送信息\nconn.close()       # 关闭客户端套接字\nsk.close()        # 关闭服务器套接字(可选)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eclient端\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eimport socket\nsk = socket.socket()           # 创建客户套接字\nsk.connect(('127.0.0.1',8898))    # 尝试连接服务器\nsk.send(b'hello!')\nret = sk.recv(1024)         # 对话(发送/接收)\nprint(ret)\nsk.close()            # 关闭客户套接字\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e127.0.0.1本机回还地址：只能自己识别自己 其他人无法访问\u003c/p\u003e\n\u003cp\u003esend与recv对应，不要出现两边都是相同的情况\u003c/p\u003e\n\u003cp\u003erecv是跟内存要数据，至于数据的来源 你无需考虑\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eTCP特点\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e会将数据量比较小的并且时间间隔比较短的数据一次性打包发送给对方\u003c/p\u003e\n\u003cp\u003eTCP是基于链接的，必须先启动服务端，然后再启动客户端去连接服务端\u003c/p\u003e","title":"【网络编程】TCP协议与粘包问题"},{"content":"\n网络编程(一) \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园****\n软件开发架构\nC/S架构(clinet/server)：客户端/服务端\nB/S架构(browser/server)：浏览器/服务器\n网络编程发展史\n任何先进的技术最早都源于军事\n要想实现远程通信第一个需要具备的条件是：物理连接介质\n计算机之间要想实现远程通信除了有物理连接介质还需要一套公共标准/协议、\nOSI七层协议\n应用层\nHTTP协议FTP协议\n表示层\n会话层\n传输层\nTCP\\UDP都是基于端口(port)工作的协议 计算机与计算机之间其实是计算机上的应用程序与应用程序之间的通信 端口:用来唯一标识一台计算机上的某个应用程序 端口号的范围:0~65535 注意:0~1024这些都是操作系统默认使用的端口号 建议:使用8000之后的端口 MySQL默认端口:3306 Redis默认端口:6379 django默认端口:8000 flask默认端口:5000\t端口号是动态分配的 第一次起qq分配到的是8989将qq关闭再次启动端口号可能就变了 总结 ip地址:用来唯一标识接入互联网的一台计算机 port端口:用来唯一标识一台计算机上的某个应用程序 ip+port:唯一标识接入互联网一台计算机上的某个应用程序 网络层\nIP协议规定了只要是接入互联网的计算机都必须有一个IP地址 ip地址特点:点分十进制 ip地址最小:0.0.0.0 ip地址最大:255.255.255.255 ip地址目前有两个版本:IPV4 IPV6(由于IPV4已经不够表示目前存在的计算机了 所以推出了IPV6版本) 数据链路层\n1.规定的电信号的分组方式 2.规定了任何一台接入互联网的计算机都必须有一块网卡 每一块网卡上面都刻有世界上独一无二的编号 12位16进制数：前6位是厂商编号，后6位是流水线编号\t我们管这12数叫mac地址 ps:以上两点合称为\u0026quot;以太网协议\u0026quot; 交换机：基于以太网协议通信，不能跨局域网通信。 以太网协议通信的特点：通信基本靠吼！ 缺点：广播风暴 互联网是有N多个局域网彼此之间连接而成的。 物理连接层\n基于电信号传输010101001010二进制数据\nTCP协议(详见《TCP协议三次握手四次挥手》****）\n流式协议,可靠协议\n基于TCP协议通信 必先建立双向通道\nTCP协议传输数据可靠的原因在于它有反馈机制，发送消息之后对方必须回复确认收到消息才会将数据从内存中清空，否则在限定的时间时间内会每隔一段时间发送一次，如果超过限定时间还未收到，就会把对方计算机列为宕机或其他故障，停止向此计算机发送消息\n三次握手\n所谓三次握手(Three-way Handshake)，是指建立一个 TCP 连接时，需要客户端和服务器总共发送3个包。\n第一次客户端发送SYN包给服务端，客户端发送完毕进入SYN_SEND状态， 第二次服务端收到第一次客户端发送的SYN包后发回ACK确认包并同时也发送一个SYN包到客户端，服务端发送完毕进入SYN_RECV状态， 第三次客户端接收到服务端发送的ACK+SYN包后向服务端发回ACK确认包，发送完毕进入Established状态。 四次挥手\n第一次客户端发送一个FIN包到服务器端，发送完毕后客户端进入 FIN_WAIT_1 状态； 第二次服务器端收到客户端第一次发送的FIN包后返回一个ACK确认包，服务器端进入 CLOSE_WAIT 状态，客户端接收到这个确认包之后，进入 FIN_WAIT_2 状态，等待服务器端关闭连接； 第三次服务端准备好关闭连接后向客户端发送FIN包，发送完毕后，服务器端进入 LAST_ACK 状态，等待来自客户端的最后一个ACK确认包； 第四次客户端发回ACK确认包进入TIME_WAIT状态，服务器端接收到这个确认包之后关闭连接进入 CLOSED 状态，客户端被动关闭进入 CLOSED 状态。 END\n","permalink":"https://zhong-er.com/posts/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8Bosi%E4%B8%83%E5%B1%82%E5%8D%8F%E8%AE%AEtcp%E5%8D%8F%E8%AE%AE/","summary":"\u003cp\u003e\u003cimg alt=\"网络编程.png\" loading=\"lazy\" src=\"/assets/201908068133_5580_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e网络编程(一) \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/2o2v35H6G8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11312188.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e软件开发架构\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eC/S架构(clinet/server)：客户端/服务端\u003c/p\u003e\n\u003cp\u003eB/S架构(browser/server)：浏览器/服务器\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e网络编程发展史\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e任何先进的技术最早都源于军事\u003c/p\u003e\n\u003cp\u003e要想实现远程通信第一个需要具备的条件是：物理连接介质\u003c/p\u003e\n\u003cp\u003e计算机之间要想实现远程通信除了有物理连接介质还需要一套公共标准/协议、\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eOSI七层协议\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e应用层\u003c/strong\u003e\u003cbr\u003e\nHTTP协议FTP协议\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e表示层\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e会话层\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e传输层\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eTCP\\UDP都是基于端口(port)工作的协议\n计算机与计算机之间其实是计算机上的应用程序与应用程序之间的通信\n端口:用来唯一标识一台计算机上的某个应用程序\n端口号的范围:0~65535\n注意:0~1024这些都是操作系统默认使用的端口号\n建议:使用8000之后的端口\nMySQL默认端口:3306\nRedis默认端口:6379\ndjango默认端口:8000\nflask默认端口:5000\t\n\n端口号是动态分配的 第一次起qq分配到的是8989将qq关闭再次启动端口号可能就变了\n\n总结\nip地址:用来唯一标识接入互联网的一台计算机\nport端口:用来唯一标识一台计算机上的某个应用程序\nip+port:唯一标识接入互联网一台计算机上的某个应用程序\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e网络层\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eIP协议规定了只要是接入互联网的计算机都必须有一个IP地址\nip地址特点:点分十进制\nip地址最小:0.0.0.0\nip地址最大:255.255.255.255\nip地址目前有两个版本:IPV4  IPV6(由于IPV4已经不够表示目前存在的计算机了 所以推出了IPV6版本)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e数据链路层\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1.规定的电信号的分组方式\n2.规定了任何一台接入互联网的计算机都必须有一块网卡\n每一块网卡上面都刻有世界上独一无二的编号\n12位16进制数：前6位是厂商编号，后6位是流水线编号\t\n我们管这12数叫mac地址\nps:以上两点合称为\u0026quot;以太网协议\u0026quot;\n交换机：基于以太网协议通信，不能跨局域网通信。\n以太网协议通信的特点：通信基本靠吼！\n                缺点：广播风暴\n互联网是有N多个局域网彼此之间连接而成的。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e物理连接层\u003c/strong\u003e\u003cbr\u003e\n基于电信号传输010101001010二进制数据\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eTCP协议(详见\u003c/strong\u003e\u003ca href=\"%7B#ZC_BLOG_HOST#%7Dpost/56.html\" title=\"TCP协议三次握手四次挥手\"\u003e\u003cstrong\u003e《TCP协议三次握手四次挥手》\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e）\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e流式协议,可靠协议\u003c/p\u003e\n\u003cp\u003e基于TCP协议通信 必先建立双向通道\u003c/p\u003e\n\u003cp\u003eTCP协议传输数据可靠的原因在于它有反馈机制，发送消息之后对方必须回复确认收到消息才会将数据从内存中清空，否则在限定的时间时间内会每隔一段时间发送一次，如果超过限定时间还未收到，就会把对方计算机列为宕机或其他故障，停止向此计算机发送消息\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e三次握手\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e所谓三次握手(Three-way Handshake)，是指建立一个 TCP 连接时，需要客户端和服务器总共发送3个包。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e第一次客户端发送SYN包给服务端，客户端发送完毕进入SYN_SEND状态，\n第二次服务端收到第一次客户端发送的SYN包后发回ACK确认包并同时也发送一个SYN包到客户端，服务端发送完毕进入SYN_RECV状态，\n第三次客户端接收到服务端发送的ACK+SYN包后向服务端发回ACK确认包，发送完毕进入Established状态。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e四次挥手\u003c/strong\u003e\u003c/p\u003e","title":"【网络编程】OSI七层协议/TCP协议"},{"content":"TCP 的特性 TCP 提供一种面向连接的、可靠的字节流服务\n在一个 TCP 连接中，仅有两方进行彼此通信。广播和多播不能用于 TCP\nTCP 使用校验和，确认和重传机制来保证可靠传输\nTCP 给数据分节进行排序，并使用累积确认保证数据的顺序不变和非重复\nTCP 使用滑动窗口机制来实现流量控制，通过动态改变窗口的大小进行拥塞控制\n注意：TCP 并不能保证数据一定会被对方接收到，因为这是不可能的。TCP 能够做到的是，如果有可能，就把数据递送到接收方，否则就（通过放弃重传并且中断连接这一手段）通知用户。因此准确说 TCP 也不是 100% 可靠的协议，它所能提供的是数据的可靠递送或故障的可靠通知。\n三次握手与四次挥手 所谓三次握手(Three-way Handshake)，是指建立一个 TCP 连接时，需要客户端和服务器总共发送3个包。\n三次握手的目的是连接服务器指定端口，建立 TCP 连接，并同步连接双方的序列号和确认号，交换 TCP 窗口大小信息。在 socket 编程中，客户端执行 connect() 时。将触发三次握手。\n第一次握手(SYN=1, seq=x):\n客户端发送一个 TCP 的 SYN 标志位置1的包，指明客户端打算连接的服务器的端口，以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。\n发送完毕后，客户端进入 SYN_SEND 状态。\n第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):\n服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号，放到 Seq 域里，同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1，即X+1。 发送完毕后，服务器端进入 SYN_RCVD 状态。\n第三次握手(ACK=1，ACKnum=y+1)\n客户端再次发送确认包(ACK)，SYN 标志位为0，ACK 标志位为1，并且把服务器发来 ACK 的序号字段+1，放在确定字段中发送给对方，并且在数据段放写ISN的+1\n发送完毕后，客户端进入 ESTABLISHED 状态，当服务器端接收到这个包时，也进入 ESTABLISHED 状态，TCP 握手结束。\n三次握手的过程的示意图如下：\nTCP 的连接的拆除需要发送四个包，因此称为四次挥手(Four-way handshake)，也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作，在 socket 编程中，任何一方执行 close() 操作即可产生挥手操作。\n第一次挥手(FIN=1，seq=x)\n假设客户端想要关闭连接，客户端发送一个 FIN 标志位置为1的包，表示自己已经没有数据可以发送了，但是仍然可以接受数据。\n发送完毕后，客户端进入 FIN_WAIT_1 状态。\n第二次挥手(ACK=1，ACKnum=x+1)\n服务器端确认客户端的 FIN 包，发送一个确认包，表明自己接受到了客户端关闭连接的请求，但还没有准备好关闭连接。\n发送完毕后，服务器端进入 CLOSE_WAIT 状态，客户端接收到这个确认包之后，进入 FIN_WAIT_2 状态，等待服务器端关闭连接。\n第三次挥手(FIN=1，seq=y)\n服务器端准备好关闭连接时，向客户端发送结束连接请求，FIN 置为1。\n发送完毕后，服务器端进入 LAST_ACK 状态，等待来自客户端的最后一个ACK。\n第四次挥手(ACK=1，ACKnum=y+1)\n客户端接收到来自服务器端的关闭请求，发送一个确认包，并进入 TIME_WAIT状态，等待可能出现的要求重传的 ACK 包。\n服务器端接收到这个确认包之后，关闭连接，进入 CLOSED 状态。\n客户端等待了某个固定时间（两个最大段生命周期，2MSL，2 Maximum Segment Lifetime）之后，没有收到服务器端的 ACK ，认为服务器端已经正常关闭连接，于是自己也关闭连接，进入 CLOSED 状态。\n四次挥手的示意图如下：\nSYN攻击 什么是 SYN 攻击（SYN Flood）？\n在三次握手过程中，服务器发送 SYN-ACK 之后，收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后，服务器才能转入 ESTABLISHED 状态.\nSYN 攻击指的是，攻击客户端在短时间内伪造大量不存在的IP地址，向服务器不断地发送SYN包，服务器回复确认包，并等待客户的确认。由于源地址是不存在的，服务器需要不断的重发直至超时，这些伪造的SYN包将长时间占用未连接队列，正常的SYN请求被丢弃，导致目标系统运行缓慢，严重者会引起网络堵塞甚至系统瘫痪。\nSYN 攻击是一种典型的 DoS/DDoS 攻击。\n如何检测 SYN 攻击？\n检测 SYN 攻击非常的方便，当你在服务器上看到大量的半连接状态时，特别是源IP地址是随机的，基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。\n如何防御 SYN 攻击？\nSYN攻击不能完全被阻止，除非将TCP协议重新设计。我们所做的是尽可能的减轻SYN攻击的危害，常见的防御 SYN 攻击的方法有如下几种：\n缩短超时（SYN Timeout）时间\n增加最大半连接数\n过滤网关防护\nSYN cookies技术\nTCP KeepAlive TCP 的连接，实际上是一种纯软件层面的概念，在物理层面并没有“连接”这种概念。TCP 通信双方建立交互的连接，但是并不是一直存在数据交互，有些连接会在数据交互完毕后，主动释放连接，而有些不会。在长时间无数据交互的时间段内，交互双方都有可能出现掉电、死机、异常重启等各种意外，当这些意外发生之后，这些 TCP 连接并未来得及正常释放，在软件层面上，连接的另一方并不知道对端的情况，它会一直维护这个连接，长时间的积累会导致非常多的半打开连接，造成端系统资源的消耗和浪费，为了解决这个问题，在传输层可以利用 TCP 的 KeepAlive 机制实现来实现。主流的操作系统基本都在内核里支持了这个特性。\nTCP KeepAlive 的基本原理是，隔一段时间给连接对端发送一个探测包，如果收到对方回应的 ACK，则认为连接还是存活的，在超过一定重试次数之后还是没有收到对方的回应，则丢弃该 TCP 连接。\nTCP-Keepalive-HOWTO 有对 TCP KeepAlive 特性的详细介绍，有兴趣的同学可以参考。这里主要说一下，TCP KeepAlive 的局限。首先 TCP KeepAlive 监测的方式是发送一个 probe 包，会给网络带来额外的流量，另外 TCP KeepAlive 只能在内核层级监测连接的存活与否，而连接的存活不一定代表服务的可用。例如当一个服务器 CPU 进程服务器占用达到 100%，已经卡死不能响应请求了，此时 TCP KeepAlive 依然会认为连接是存活的。因此 TCP KeepAlive 对于应用层程序的价值是相对较小的。需要做连接保活的应用层程序，例如 QQ，往往会在应用层实现自己的心跳功能。\n参考资料 计算机网络：自顶向下方法\nTCP三次握手及四次挥手详细图解\nTCP协议三次握手过程分析\nTCP协议中的三次握手和四次挥手(图解)\n百度百科：SYN攻击\nTCP-Keepalive-HOWTO\n","permalink":"https://zhong-er.com/posts/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8Btcp%E5%8D%8F%E8%AE%AE%E7%9A%84%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B-%E8%AF%A6%E8%A7%A3/","summary":"\u003ch2 id=\"tcp-的特性\"\u003eTCP 的特性\u003c/h2\u003e\n\u003cp\u003eTCP 提供一种面向连接的、可靠的字节流服务\u003c/p\u003e\n\u003cp\u003e在一个 TCP 连接中，仅有两方进行彼此通信。广播和多播不能用于 TCP\u003c/p\u003e\n\u003cp\u003eTCP 使用校验和，确认和重传机制来保证可靠传输\u003c/p\u003e\n\u003cp\u003eTCP 给数据分节进行排序，并使用累积确认保证数据的顺序不变和非重复\u003c/p\u003e\n\u003cp\u003eTCP 使用滑动窗口机制来实现流量控制，通过动态改变窗口的大小进行拥塞控制\u003c/p\u003e\n\u003cp\u003e注意：TCP 并不能保证数据一定会被对方接收到，因为这是不可能的。TCP 能够做到的是，如果有可能，就把数据递送到接收方，否则就（通过放弃重传并且中断连接这一手段）通知用户。因此准确说 TCP 也不是 100% 可靠的协议，它所能提供的是数据的可靠递送或故障的可靠通知。\u003c/p\u003e\n\u003ch2 id=\"三次握手与四次挥手\"\u003e三次握手与四次挥手\u003c/h2\u003e\n\u003cp\u003e所谓三次握手(Three-way Handshake)，是指建立一个 TCP 连接时，需要客户端和服务器总共发送3个包。\u003c/p\u003e\n\u003cp\u003e三次握手的目的是连接服务器指定端口，建立 TCP 连接，并同步连接双方的序列号和确认号，交换 TCP 窗口大小信息。在 socket 编程中，客户端执行 \u003ccode\u003econnect()\u003c/code\u003e 时。将触发三次握手。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e第一次握手(SYN=1, seq=x):\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e客户端发送一个 TCP 的 SYN 标志位置1的包，指明客户端打算连接的服务器的端口，以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。\u003c/p\u003e\n\u003cp\u003e发送完毕后，客户端进入 \u003ccode\u003eSYN_SEND\u003c/code\u003e 状态。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号，放到 Seq 域里，同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1，即X+1。 发送完毕后，服务器端进入 \u003ccode\u003eSYN_RCVD\u003c/code\u003e 状态。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e第三次握手(ACK=1，ACKnum=y+1)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e客户端再次发送确认包(ACK)，SYN 标志位为0，ACK 标志位为1，并且把服务器发来 ACK 的序号字段+1，放在确定字段中发送给对方，并且在数据段放写ISN的+1\u003c/p\u003e","title":"【网络编程】TCP协议的三次握手四次挥手-详解"},{"content":"\nPython反射/元类) \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园****\n反射reflect\n什么是反射, 其实是反省,自省的意思\n反射指的是一个对象应该具备,可以检测,修改,增加自身属性的能力\n反射就是通过字符串操作属性\n涉及的四个函数,这四个函数就是普通的内置函数 没有双下划綫,与print等等没有区别\nhasattr getattr setattr delattr p = Person(\u0026quot;jack\u0026quot;,18,\u0026quot;man\u0026quot;) # if hasattr(p,\u0026quot;name\u0026quot;): # 1.判断某个对象是否存在某个属性 # print(getattr(p,\u0026quot;names\u0026quot;,None)) # 2.从对象中取出属性,第三个值位默认值 当属性不存在是返回默认值 # 3.为对象添加新的属性 setattr(p,\u0026quot;id\u0026quot;,\u0026quot;123\u0026quot;) print(p.id) # 4.从对象中删除属性 delattr(p,\u0026quot;id\u0026quot;) print(p.id) 使用场景:反射其实就是对属性的增删改查,但是如果直接使用内置的dict来操作,语法繁琐,不好理解\n另外一个最主要的问题是,如果对象不是我自己写的是另一方提供的,我就必须判断这个对象是否满足的要求,也就是是否我需要的属性和方法\n框架代码\n\u0026quot;\u0026quot;\u0026quot; 反射被称为框架的基石,为什么 因为框架的设计者,不可能提前知道你的对象到底是怎么设计的 所以你提供给框架的对象 必须通过判断验证之后才能正常使用 判断验证就是反射要做的事情, 当然通过__dict__也是可以实现的, 其实这些方法也就是对__dict__的操作进行了封装 需求:要实现一个用于处理用户的终端指令的小框架 框架就是已经实现了最基础的构架,就是所有项目都一样的部分 \u0026quot;\u0026quot;\u0026quot; import plugins # 框架已经实现的部分 def run(plugin): while True: cmd = input(\u0026quot;请输入指令:\u0026quot;) if cmd == \u0026quot;exit\u0026quot;: break # 因为无法确定框架使用者是否传入正确的对象所以需要使用反射来检测 # 判断对象是否具备处理这个指令的方法 if hasattr(plugin,cmd): # 取出对应方法方法 func = getattr(plugin,cmd) func() # 执行方法处理指令 else: print(\u0026quot;该指令不受支持...\u0026quot;) print(\u0026quot;see you la la!\u0026quot;) # 创建一个插件对象 调用框架来使用它 # wincmd = plugins.WinCMD() # 框架之外的部分就有自定义对象来完成 linux = plugins.LinuxCMD() run(linux) 插件部分\nclass WinCMD: def cd(self): print(\u0026quot;wincmd 切换目录....\u0026quot;) def delete(self): print(\u0026quot;wincmd 要不要删库跑路?\u0026quot;) def dir(self): print(\u0026quot;wincmd 列出所有文件....\u0026quot;) class LinuxCMD: def cd(self): print(\u0026quot;Linuxcmd 切换目录....\u0026quot;) def rm(self): print(\u0026quot;Linuxcmd 要不要删库跑路?\u0026quot;) def ls(self): print(\u0026quot;Linuxcmd 列出所有文件....\u0026quot;) 上述框架代码中 写死了必须使用某个类,这是不合理的,因为无法提前知道对方的类在什么地方 以及类叫什么\n所以我们应该为框架的使用者提供一个配置文件,要求对方将累的信息写入配置文件\n然后框架自己去加载需要的模块\n最后的框架代码:\nimport importlib import settings # 框架已经实现的部分 def run(plugin): while True: cmd = input(\u0026quot;请输入指令:\u0026quot;) if cmd == \u0026quot;exit\u0026quot;: break # 因为无法确定框架使用者是否传入正确的对象所以需要使用反射来检测 # 判断对象是否具备处理这个指令的方法 if hasattr(plugin,cmd): # 取出对应方法方法 func = getattr(plugin,cmd) func() # 执行方法处理指令 else: print(\u0026quot;该指令不受支持...\u0026quot;) print(\u0026quot;see you la la!\u0026quot;) # 创建一个插件对象 调用框架来使用它 # wincmd = plugins.WinCMD() # 框架之外的部分就有自定义对象来完成 # 框架 得根据配置文件拿到需要的类 path = settings.CLASS_PATH # 从配置中单独拿出来 模块路径和 类名称 module_path,class_name = path.rsplit(\u0026quot;.\u0026quot;,1) #拿到模块 mk = importlib.import_module(module_path) # 拿到类 cls = getattr(mk,class_name) # 实例化对象 obj = cls() #调用框架 run(obj) 如此一来,框架就与实现代码彻底解耦了,只剩下配置文件\n元类 metaclass\n元类是什么,用于创建类的类\n万物皆对象,类当然也是对象\n对象是通过类实例化产生的,如果类也是对象的话,必然类对象也是有另一个类实例化产生的\n默认情况下所有类的元类都是type\n验证:\nclass Person: pass p = Person() print(type(p)) print(type(Person)) # Person类是通过type类实例化产生的 学习元类的目的\n高度的自定义一个类,例如控制类的名字必须以大驼峰的方式来书写\n类也是对象,也有自己的类,\n我们的需求是创建类对象做一些限制\n想到了初始化方法 我们只要找到类对象的类(元类),覆盖其中 init方法就能实现需求\n当然我们不能修改源代码,所以应该继承type来编写自己的元类,同时覆盖init来完成需求\n代码:\n\u0026quot;\u0026quot;\u0026quot; 只要继承了type 那么这个类就变成了一个元类 \u0026quot;\u0026quot;\u0026quot; # 定义了一个元类 class MyType(type): def __init__(self,clss_name,bases,dict): super().__init__(clss_name,bases,dict) print(clss_name,bases,dict) if not clss_name.istitle(): raise Exception(\u0026quot;你丫的 类名不会写...\u0026quot;) # 为pig类指定了元类为MyType class Pig(metaclass=MyType): pass class Duck(metaclass=MyType): pass 元类中call方法\n当你调用类对象时会自动执行元类中的__call__方法 ,并将这个类本身作为第一个参数传入,以及后面的一堆参数 覆盖元类中的call之后,这个类就无法产生对象,必须调用super().__call__来完成对象的创建 并返回其返回值 使用场景:\n当你想要控制对象的创建过程时,就覆盖call方法\n当你想要控制类的创建过程时,就覆盖init方法\n案例:实现将对象的所有属性名称转为大写\nlass MyType(type): def __call__(self, *args, **kwargs): new_args = [] for a in args: new_args.append(a.upper()) print(new_args) print(kwargs) return super().__call__(*new_args,**kwargs) class Person(metaclass=MyType): def __init__(self,name,gender): self.name = name self.gender = gender p = Person(name=\u0026quot;jack\u0026quot;,gender=\u0026quot;woman\u0026quot;) print(p.name) print(p.gender) 注意:一旦覆盖了call必须调用父类的call方法来产生对象并返回这个对象\n补充new方法\n当你要创建类对象时,会首先执行元类中的__new__方法,拿到一个空对象,然后会自动调用__init__来对这个类进行初始化操作 注意:,如果你覆盖了该方法则必须保证,new方法必须有返回值且必须是,对应的类对象\n测试\nclass Meta(type): def __new__(cls, *args, **kwargs): print(cls) # 元类自己 print(args) # 创建类需要的几个参数 类名,基类,名称空间 print(kwargs) #空的 print(\u0026quot;new run\u0026quot;) # return super().__new__(cls,*args,**kwargs) obj = type.__new__(cls,*args,**kwargs) return obj def __init__(self,a,b,c): super().__init__(a,b,c) print(\u0026quot;init run\u0026quot;) class A(metaclass=Meta): pass print(A) 总结new方法和__init__ 都可以实现控制类的创建过程,__init__更简单\n单例设计模式\n设计模式?用于解决某种固定问题的套路 例如:MVCMTV等 单例:指的是一个类产生一个对象 为什么要使用单例:单例是为了节省 资源,当一个类的所有对象属性全部相同时,则没有必要创建多个对象 元类实现\n# 单例n元类 class Single(type): def __call__(self, *args, **kwargs): if hasattr(self,\u0026quot;obj\u0026quot;): #判断是否存在已经有的对象 return getattr(self,\u0026quot;obj\u0026quot;) # 有就返回 obj = super().__call__(*args,**kwargs) # 没有则创建 print(\u0026quot;new 了\u0026quot;) self.obj = obj # 并存入类中 return obj class Student(metaclass=Single): def __init__(self,name): self.name = name class Person(metaclass=Single): pass # 只会创建一个对象 Person() Person() ","permalink":"https://zhong-er.com/posts/python%E5%8F%8D%E5%B0%84%E5%85%83%E7%B1%BB/","summary":"\u003cp\u003e\u003cimg alt=\"Python第21课(反射_元类).png\" loading=\"lazy\" src=\"/assets/201907306609_2135_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython反射/元类) \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/3nvhkZgcw8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11271212.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e反射reflect\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e什么是反射, 其实是反省,自省的意思\u003c/p\u003e\n\u003cp\u003e反射指的是一个对象应该具备,可以检测,修改,增加自身属性的能力\u003c/p\u003e\n\u003cp\u003e反射就是通过字符串操作属性\u003c/p\u003e\n\u003cp\u003e涉及的四个函数,这四个函数就是普通的内置函数 没有双下划綫,与print等等没有区别\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ehasattr  getattr setattr delattr \n\np = Person(\u0026quot;jack\u0026quot;,18,\u0026quot;man\u0026quot;)\n\n# if hasattr(p,\u0026quot;name\u0026quot;): # 1.判断某个对象是否存在某个属性\n#     print(getattr(p,\u0026quot;names\u0026quot;,None)) # 2.从对象中取出属性,第三个值位默认值 当属性不存在是返回默认值\n\n# 3.为对象添加新的属性\nsetattr(p,\u0026quot;id\u0026quot;,\u0026quot;123\u0026quot;)\nprint(p.id)\n\n# 4.从对象中删除属性\ndelattr(p,\u0026quot;id\u0026quot;)\nprint(p.id)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e使用场景:反射其实就是对属性的增删改查,但是如果直接使用内置的dict来操作,语法繁琐,不好理解\u003c/p\u003e\n\u003cp\u003e另外一个最主要的问题是,如果对象不是我自己写的是另一方提供的,我就必须判断这个对象是否满足的要求,也就是是否我需要的属性和方法\u003c/p\u003e\n\u003cp\u003e框架代码\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n反射被称为框架的基石,为什么\n因为框架的设计者,不可能提前知道你的对象到底是怎么设计的\n所以你提供给框架的对象 必须通过判断验证之后才能正常使用\n判断验证就是反射要做的事情,\n当然通过__dict__也是可以实现的, 其实这些方法也就是对__dict__的操作进行了封装\n需求:要实现一个用于处理用户的终端指令的小框架\n框架就是已经实现了最基础的构架,就是所有项目都一样的部分\n\u0026quot;\u0026quot;\u0026quot;\n\nimport plugins\n\n# 框架已经实现的部分\ndef run(plugin):\n    while True:\n        cmd = input(\u0026quot;请输入指令:\u0026quot;)\n        if cmd == \u0026quot;exit\u0026quot;:\n            break\n        # 因为无法确定框架使用者是否传入正确的对象所以需要使用反射来检测\n        # 判断对象是否具备处理这个指令的方法\n        if hasattr(plugin,cmd):\n            # 取出对应方法方法\n            func = getattr(plugin,cmd)\n            func() # 执行方法处理指令\n        else:\n            print(\u0026quot;该指令不受支持...\u0026quot;)\n    print(\u0026quot;see you la la!\u0026quot;)\n\n\n# 创建一个插件对象 调用框架来使用它\n# wincmd = plugins.WinCMD()\n# 框架之外的部分就有自定义对象来完成\nlinux = plugins.LinuxCMD()\nrun(linux)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e插件部分\u003c/p\u003e","title":"【Python】反射/元类"},{"content":"\nPython多态 \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园********\n多态\n概念 ：一种事物具备多种 不同的形态\n例如 ：水“固态”、“液态”、“气态”\n官方解释 ：多个不同类的对象可以响应同一个方法，产生不同的结果\n首先强调多态不是一种特殊的语法，而是一种状态、特性(即多个不同对象可以响应同一个方法，产生不同结果)\n好处 ：对于使用者而言，大大的降低了使用难度\n实现多态 ：接口、抽象类、鸭子类型都可以写出具备多态的代码，最简单的就是鸭子类型\n案例\n\u0026quot;\u0026quot;\u0026quot; 要管理 鸡 鸭 鹅 如何能够最方便的 管理,就是我说同一句话,他们都能理解 既它们拥有相同的方法 \u0026quot;\u0026quot;\u0026quot; class JI: def bark(self): print(\u0026quot;哥哥哥\u0026quot;) def spawn(self): print(\u0026quot;下鸡蛋..\u0026quot;) class Duck: def bark(self): print(\u0026quot;嘎嘎嘎\u0026quot;) def spawn(self): print(\u0026quot;下鸭蛋\u0026quot;) class E: def bark(self): print(\u0026quot;饿饿饿....\u0026quot;) def spawn(self): print(\u0026quot;下鹅蛋..\u0026quot;) j = JI() y = Duck() e = E() def mange(obj): obj.spawn() mange(j) mange(y) mange(e) # python中到处都有多态 a = 10 b = \u0026quot;10\u0026quot; c = [10] print(type(a)) print(type(b)) print(type(c)) isinstance\n判断一个对象是否是某个类的实例\n参数1 要判断的对象\n参数2 要判断的类型\nissubclass\n判断一个类是否是另一个类的自雷\n参数1 子类\n参数2 父类\n魔法函数\nstr\nstr 会在对象被转换为字符串时,转换的结果就是这个函数的返回值\n使用场景:我们可以利用该函数来自定义,对象的是打印格式\ndel\n执行时机: 手动删除对象时立马执行,或是程序运行结束时也会自动执行\n使用场景:当你的对象在使用过程中,打开了不属于解释器的资源:例如文件,网络端口\n# del使用案例 class FileTool: \u0026quot;\u0026quot;\u0026quot;该类用于简化文件的读写操作 \u0026quot;\u0026quot;\u0026quot; def __init__(self,path): self.file = open(path,\u0026quot;rt\u0026quot;,encoding=\u0026quot;utf-8\u0026quot;) self.a = 100 def read(self): return self.file.read() # 在这里可以确定一个事,这个对象肯定不使用了 所以可以放心的关闭问文件了 def __del__(self): self.file.close() tool = FileTool(\u0026quot;a.txt\u0026quot;) print(tool.read()) call\n执行时机:在调用对象时自动执行,(既对象加括号)\n测试\n#call 的执行时机 class A: def __call__(self, *args, **kwargs): print(\u0026quot;call run\u0026quot;) print(args) print(kwargs) a = A() a(1,a=100) slots\n该属性是一个类属性,用于优化对象内存占用\n优化的原理\n将原本不固定的属性数量,变得固定了这样的解释器就不会为这个对象创建名称空间,所以__dict__也没了 从而达到减少内存开销的效果 另外当类中出现了slots时将导致这个类的对象无法在添加新的属性\n案例\n# slots的使用 class Person: __slots__ = [\u0026quot;name\u0026quot;] def __init__(self,name): self.name = name p = Person(\u0026quot;jck\u0026quot;) # 查看内存占用 # print(sys.getsizeof(p)) # p.age = 20 # 无法添加 # dict 没有了 print(p.__dict__) 点语法的实现\ngetattr setattr delattr\ngetattr 用点访问属性的时如果属性不存在时执行 setattr 用点设置属性时 delattr 用del 对象.属性 删除属性时 执行 这几个函数反映了 python解释器是如何实现 用点来访问属性 getattribute 该函数也是用来获取属性 在获取属性时如果存在getattribute则先执行该函数,如果没有拿到属性则继续调用 getattr函数,如果拿到了则直接返回 []取值的实现\ngetitem setitem delitem\n任何的符号 都会被解释器解释成特殊含义 ,例如 . [] () getitem 当你用中括号去获取属性时 执行 setitem 当你用中括号去设置属性时 执行 delitem 当你用中括号去删除属性时 执行 运算符重载\n当我们在使用某个符号时,python解释器都会为这个符号定义一个含义,同时调用对应的处理函数, 当我们需要自定义对象的比较规则时,就可在子类中覆盖 大于 等于 等一系列方法\u0026hellip;.\n案例:原本自定义对象无法直接使用大于小于来进行比较 ,我们可自定义运算符来实现,让自定义对象也支持比较运算符\nclass Student(object): def __init__(self,name,height,age): self.name = name self.height = height self.age = age def __gt__(self, other): # print(self) # print(other) # print(\u0026quot;__gt__\u0026quot;) return self.height \u0026gt; other.height def __lt__(self, other): return self.height \u0026lt; other.height def __eq__(self, other): if self.name == other.name and self.age == other.age and self.height == other.height: return True return False stu1 = Student(\u0026quot;jack\u0026quot;,180,28) stu2 = Student(\u0026quot;jack\u0026quot;,180,28) # print(stu1 \u0026lt; stu2) print(stu1 == stu2) 上述代码中,other指的是另一个参与比较的对象,\n大于和小于只要实现一个即可,符号如果不同 解释器会自动交换两个对象的位置\n迭代器协议\n迭代器是指具有__iter__和__next__的对象\n我们可以为对象增加这两个方法来让对象变成一个迭代器\n案例\nclass MyRange: def __init__(self,start,end,step): self.start = start self.end = end self.step = step def __iter__(self): return self def __next__(self): a = self.start self.start += self.step if a \u0026lt; self.end: return a else: raise StopIteration for i in MyRange(1,10,2): print(i) 上下文管理\n上下文 context\n这个概念属于语言学科,指的是一段话的意义,要参考当前的场景,既上下文\n在python中,上下文可以理解为是一个代码区间,一个范围 ,例如with open 打开的文件仅在这个上下文中有效\n涉及到的两个方法:\nenter：表示进入上下文,(进入某个场景 了) exit：表示退出上下文,(退出某个场景 了) 当执行with 语句时,会先执行enter , 当代码执行完毕后执行exit,或者代码遇到了异常会立即执行exit,并传入错误信息 包含错误的类型.错误的信息.错误的追踪信息 注意:\nenter 函数应该返回对象自己 exit函数 可以有返回值,是一个bool类型,用于表示异常是否被处理,仅在上下文中出现异常有用 如果为True 则意味着,异常以及被处理了 False,异常未被处理,程序将中断报错 ","permalink":"https://zhong-er.com/posts/python%E5%A4%9A%E6%80%81/","summary":"\u003cp\u003e\u003cimg alt=\"Python第20课(多态).png\" loading=\"lazy\" src=\"/assets/201907295353_7862_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython多态 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/2Z5T8az_x8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11264086.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e********\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e多态\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e概念\u003c/strong\u003e ：一种事物具备多种 不同的形态\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e例如\u003c/strong\u003e ：水“固态”、“液态”、“气态”\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e官方解释\u003c/strong\u003e ：多个不同类的对象可以响应同一个方法，产生不同的结果\u003c/p\u003e\n\u003cp\u003e首先强调多态不是一种特殊的语法，而是一种状态、特性(即多个不同对象可以响应同一个方法，产生不同结果)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e好处\u003c/strong\u003e ：对于使用者而言，大大的降低了使用难度\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e实现多态\u003c/strong\u003e ：接口、抽象类、鸭子类型都可以写出具备多态的代码，最简单的就是鸭子类型\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e案例\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n要管理 鸡 鸭 鹅\n如何能够最方便的 管理,就是我说同一句话,他们都能理解\n既它们拥有相同的方法\n\n\u0026quot;\u0026quot;\u0026quot;\nclass JI:\n    def bark(self):\n        print(\u0026quot;哥哥哥\u0026quot;)\n\n    def spawn(self):\n        print(\u0026quot;下鸡蛋..\u0026quot;)\n\nclass Duck:\n    def bark(self):\n        print(\u0026quot;嘎嘎嘎\u0026quot;)\n\n    def spawn(self):\n        print(\u0026quot;下鸭蛋\u0026quot;)\n\nclass E:\n    def bark(self):\n        print(\u0026quot;饿饿饿....\u0026quot;)\n\n    def spawn(self):\n        print(\u0026quot;下鹅蛋..\u0026quot;)\n\nj = JI()\ny = Duck()\ne = E()\n\ndef mange(obj):\n    obj.spawn()\n\n\nmange(j)\nmange(y)\nmange(e)\n\n\n# python中到处都有多态  \na = 10\nb = \u0026quot;10\u0026quot;\nc = [10]\n\nprint(type(a))\nprint(type(b))\nprint(type(c))\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003eisinstance\u003c/strong\u003e\u003c/p\u003e","title":"【Python】多态"},{"content":"\nPython封装 \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园****\n封装\n什么是封装？\n就是将复杂的丑陋的,隐私的细节隐藏到内部,对外提供简单的使用接口\n对外隐藏内部实现细节,并提供访问的接口\n什么时候应该封装？\n当有一些数据不希望外界可以直接修改时\n当有一些函数不希望给外界使用时\n为什么需要封装 ？\n1.为了保证 关键数据的安全性\n2.对外部隐藏实现细节,隔离复杂度\n语法\nclass Person: def __init__(self,id_number,name,age) self.__id_number = id_number self.name = name self.age = age p = Person(\u0026quot;1111111111111\u0026quot;,\u0026quot;jack\u0026quot;,29) p.id_number = \u0026quot;222\u0026quot; 被封装的内容的特点:\n1.外界不能直接访问\n2.内部依然可以使用\n权限\n学习了封装后就可以控制属性的权限\n在python只要两种权限\n1.公开的.默认就是公开的\n2.私有的,只能由当前类自己使用\n在外界访问私有的内容\n属性虽然被封装了,但是还是需要使用的,在外界如何访问\n通过定义方法类完成对私有属性的修改和访问\n\u0026quot;\u0026quot;\u0026quot; 这是一个下载器类,需要提供一个缓存大小这样的属性 缓存大小不能超过内存限制 \u0026quot;\u0026quot;\u0026quot; class Downloader: def __init__(self,filename,url,buffer_size): self.filename = filename self.url = url self.__buffer_size= buffer_size def start_download(self): if self.__buffer_size \u0026lt;= 1024*1024: print(\u0026quot;开始下载....\u0026quot;) print(\u0026quot;当前缓冲器大小\u0026quot;,self.__buffer_size) else: print(\u0026quot;内存炸了! \u0026quot;) def set_buffer_size(self,size): #可以在方法中添加额外的逻辑 if not type(size) == int: print(\u0026quot;大哥 缓冲器必须是整型\u0026quot;) else: print(\u0026quot;缓冲区大小修改成功!\u0026quot;) self.__buffer_size = size def get_buffer_size(self): return self.__buffer_size d = Downloader(\u0026quot;葫芦娃\u0026quot;,\u0026quot;http://www.baicu.com\u0026quot;,1024*1024) # 通过函数取修改内部封装的属性 d.set_buffer_size(1024*512) # 通过函数访问内部封装的属性 print(d.get_buffer_size()) d.start_download() 这样一来我们可以在外界修改这个关键数据时,做一些限制\npython实现封装的原理\n就是在加载类的时候,把__替换成了 类名_\npython一般不会强制要求程序必须怎么怎么的\n对外部隐藏内部的实现细节,并提供访问的接口\n好处\n1.提高安全性\n2.隔离复杂度\n语法 :将要封装的属性或方法名称前加上双下划线\n访问被隐藏的属性\n提供用于访问和修改的方法\n使用property装饰器可以将一个方法伪装成普通顺属性,报纸属性之间调用方法一致\n封装的实现原理 ,替换变量名称\nproperty装饰器\n通过方法来修改或访问属性,本身没什么问题,但是这给对象的使用者带来了麻烦.\n使用必须知道哪些是普通属性,哪些是私有属性,需要使用不同的方式来调用他们\nproperty装饰就是为了使得调用方式一致\n有三个相关的装饰器\n1.property 该装器用在获取属性的方法上 2.@key.setter 该装器用在修改属性的方法上 3.@key.deleter 该装器用在删除属性的方法上 注意:key是被property装饰的方法的名称 也就是属性的名称 内部会创建一个对象 变量名称就是函数名称 所以在使用setter和deleter时 必须保证使用对象的名称取调用方法 所以是 key.setter 案例\nclass A: def __init__(self,name,key): self.__name = name self.__key = key @property def key(self): return self.__key @key.setter def key(self,new_key): if new_key \u0026lt;= 100: self.__key = new_key else: print(\u0026quot;key 必须小于等于100\u0026quot;) @key.deleter def key(self): print(\u0026quot;不允许删除该属性\u0026quot;) del self.__key a = A(\u0026quot;jack\u0026quot;,123) print(a.key) a.key = 321 print(a.key) property 可以用来实现计算属性\n计算属性指的是:属性的值,不能直接获得,必须通过计算才能获取\n例如:正方形求面积\n接口\n接口是一组功能的集合,但是接口中仅包含功能的名字,不包含具体的实现代码\n接口本质是一套协议标准,遵循这个标准的对象就能被调用\n接口目的就是为了提高扩展性:\n例如电脑提前指定制定一套USB接口协议,只要你遵循该协议,你的设备就可以被电脑使用,不需要关心到底是鼠标还是键盘\n案例\nclass USB: def open(self): pass def close(self): pass def read(self): pass def write(self): pass class Mouse(USB): def open(self): print(\u0026quot;鼠标开机.....\u0026quot;) def close(self): print(\u0026quot;鼠标关机了...\u0026quot;) def read(self): print(\u0026quot;获取了光标位置....\u0026quot;) def write(self): print(\u0026quot;鼠标不支持写入....\u0026quot;) def pc(usb_device): usb_device.open() usb_device.read() usb_device.write() usb_device.close() m = Mouse() # 将鼠标传给电脑 pc(m) class KeyBoard(USB): def open(self): print(\u0026quot;键盘开机.....\u0026quot;) def close(self): print(\u0026quot;键盘关机了...\u0026quot;) def read(self): print(\u0026quot;获取了按键字符....\u0026quot;) def write(self): print(\u0026quot;可以写入灯光颜色....\u0026quot;) # 来了一个键盘对象 k = KeyBoard() pc(k) 在上述案例中,PC的代码一旦完成,后期无论什么样的设备 只要遵循了USB接口协议,都能够被电脑所调用\n接口主要是方便了对象的使用者,降低使用者的 学习难度,只要学习一套使用方法,就可以以不变应万变\n问题:\n如果子类没有按照你的协议来设计,也没办法限制他,将导致代码无法运行\n抽象类\n指的是包含抽象方法(没有函数体的方法)的类,\n作用:可以限制子类必须类中定义的抽象方法\n最后:python一般不会限制你必须怎么写,作为一个优秀的程序员,就应该自觉遵守相关协议\n所以有了鸭子类型这么一说:\n如果这个对象长得像鸭子,走路像鸭子,那就他是鸭子\n你只要保证你的类按照相关的协议类编写,也可以达到提高扩展性的目的\n案例:\nclass Mouse: def open(self): print(\u0026quot;鼠标开机.....\u0026quot;) def close(self): print(\u0026quot;鼠标关机了...\u0026quot;) def read(self): print(\u0026quot;获取了光标位置....\u0026quot;) def write(self): print(\u0026quot;鼠标不支持写入....\u0026quot;) def pc(usb_device): usb_device.open() usb_device.read() usb_device.write() usb_device.close() m = Mouse() # 将鼠标传给电脑 pc(m) class KeyBoard: def open(self): print(\u0026quot;键盘开机.....\u0026quot;) def close(self): print(\u0026quot;键盘关机了...\u0026quot;) def read(self): print(\u0026quot;获取了按键字符....\u0026quot;) def write(self): print(\u0026quot;可以写入灯光颜色....\u0026quot;) # 来了一个键盘对象 k = KeyBoard() pc(k) class UDisk: def open(self): print(\u0026quot;U盘启动了...\u0026quot;) def close(self): print(\u0026quot;U盘关闭了...\u0026quot;) def read(self): print(\u0026quot;读出数据\u0026quot;) def write(self): print(\u0026quot;写入数据\u0026quot;) u = UDisk() pc(u) 接口是一套协议规范,明确子类们应该具备哪些功能\n抽象类是用于强制要求子类必须按照协议中规定的来实现\n然而,python不推崇限制你的语法, 我们可以设计成鸭子类型,既让多个不同类对象具备相同的属性和方法\n对于使用者而言,就可以以不变应万变,轻松的使用各种对象\n","permalink":"https://zhong-er.com/posts/python%E5%B0%81%E8%A3%85/","summary":"\u003cp\u003e\u003cimg alt=\"Python第19课(封装).png\" loading=\"lazy\" src=\"/assets/201907279602_9365_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython封装 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/3bGhBW7908\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11257242.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e封装\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e什么是封装？\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e就是将复杂的丑陋的,隐私的细节隐藏到内部,对外提供简单的使用接口\u003c/p\u003e\n\u003cp\u003e对外隐藏内部实现细节,并提供访问的接口\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e什么时候应该封装？\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e当有一些数据不希望外界可以直接修改时\u003c/p\u003e\n\u003cp\u003e当有一些函数不希望给外界使用时\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e为什么需要封装 ？\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.为了保证 关键数据的安全性\u003c/p\u003e\n\u003cp\u003e2.对外部隐藏实现细节,隔离复杂度\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e语法\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass Person:\n    def __init__(self,id_number,name,age)\n    \n\tself.__id_number = id_number\n        self.name = name\n        self.age = age\n   \np = Person(\u0026quot;1111111111111\u0026quot;,\u0026quot;jack\u0026quot;,29)\np.id_number = \u0026quot;222\u0026quot;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e被封装的内容的特点:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.外界不能直接访问\u003c/p\u003e\n\u003cp\u003e2.内部依然可以使用\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e权限\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e学习了封装后就可以控制属性的权限\u003c/p\u003e\n\u003cp\u003e在python只要两种权限\u003c/p\u003e\n\u003cp\u003e1.公开的.默认就是公开的\u003c/p\u003e\n\u003cp\u003e2.私有的,只能由当前类自己使用\u003c/p\u003e\n\u003cp\u003e在外界访问私有的内容\u003c/p\u003e\n\u003cp\u003e属性虽然被封装了,但是还是需要使用的,在外界如何访问\u003c/p\u003e\n\u003cp\u003e通过定义方法类完成对私有属性的修改和访问\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n这是一个下载器类,需要提供一个缓存大小这样的属性\n缓存大小不能超过内存限制\n\u0026quot;\u0026quot;\u0026quot;\nclass Downloader:\n    def __init__(self,filename,url,buffer_size):\n        self.filename = filename\n        self.url = url\n        self.__buffer_size= buffer_size\n    def start_download(self):\n        if self.__buffer_size \u0026lt;= 1024*1024:\n            print(\u0026quot;开始下载....\u0026quot;)\n            print(\u0026quot;当前缓冲器大小\u0026quot;,self.__buffer_size)\n        else:\n            print(\u0026quot;内存炸了! \u0026quot;)\n    def set_buffer_size(self,size):\n        #可以在方法中添加额外的逻辑\n        if not type(size) == int:\n            print(\u0026quot;大哥 缓冲器必须是整型\u0026quot;)\n        else:\n            print(\u0026quot;缓冲区大小修改成功!\u0026quot;)\n            self.__buffer_size = size\n    def get_buffer_size(self):\n        return self.__buffer_size\nd = Downloader(\u0026quot;葫芦娃\u0026quot;,\u0026quot;http://www.baicu.com\u0026quot;,1024*1024)\n# 通过函数取修改内部封装的属性\nd.set_buffer_size(1024*512)\n# 通过函数访问内部封装的属性\nprint(d.get_buffer_size())\nd.start_download()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e这样一来我们可以在外界修改这个关键数据时,做一些限制\u003c/p\u003e","title":"【Python】封装"},{"content":"\nPython继承 ****\u0026gt; \u0026raquo;思维导图************************\u0026gt; \u0026raquo;博客园********\n什么是继承？\n继承是一种关系,描述两个对象之间,什么是什么的关系 例如麦兜,佩奇,猪刚鬣 都是猪啊, 在程序中,继承描述的是类和类之间的关系 例如a继承了b, a就能直接使用b已经存在的方法和属性 a称之为子类,b称之为父类,也称之为基类 为什么要使用继承\n继承的一方可以直接使用被继承一方已经有的东西 其目的是为了重用已经有的代码,提高重用性 如何使用继承\n语法\nclass 类名称(父类的名称): 类的内容 #在python中 一个子类可以同时继承多个父类 抽象\n不具体,不清晰,很模糊,看不懂 将多个子类中相同的部分,进行抽取,形成一个新的类,这个过程也称之为抽象的过程 正确的使用继承: 1.先抽象在继承 2.继承一个已经现存的类,扩展或是修改原始的功能 属性的查找顺序\nclass A: text = \u0026quot;haha\u0026quot; class B(A): text = \u0026quot;heihei\u0026quot; pass b = B() b.text = \u0026quot;xixi\u0026quot; print(b.text) 对象自己的 - \u0026gt; 所在类中 -\u0026gt; 找父类 - \u0026gt;父类的父类 -\u0026gt;Object\n派生\n当一个子类中出现了与父类中不同的内容时,这个子类就称之为派生类 通常子类都会写一些新的代码,不可能和父类完全一样 , 既通常都是派生类, 所以派生类指的就是子类 覆盖\n也称之为重写 overrides 当子类出现了与父类名称完全一致的属性或是方法 练习：实现一个可以限制元素类型的容器 (字典,列表,元组,集合,字符串)\nclass MyList(list): def __init__(self,element_type): super().__init__() self.element_type = element_type def append(self, object): if type(object) == self.element_type: # 我们需要在这里访问父类的append函数来完成真正的存储操作 super(MyList, self).append(object) else: print(\u0026quot;sorry sir, you element type not is %s\u0026quot; % self.element_type) # 创建是指定要存储的元素类型 m = MyList(int) # 当你有需求,是需要在创建对象时 干点什么事儿 那就该想到初始化方法 m.append(1) print(m[0]) m.append(\u0026quot;121212\u0026quot;) 子类中访问父类的内容\n语法\n方式1: super(当前类名称,self).你要调的父类的属性或方法 方式2: super().你要调的父类的属性或方法 方式3: 类名称.你要调的父类的属性或方法(self) #方式3与继承无关 强调再强调:\n当你继承一个现有的类,并且你覆盖了父类的init方法时,必须在初始化方法的第一行调用父类的初始化方法,并传入父类所需的参数 组合\n也是一种关系,描述两个对象之间 是什么有什么的关系 例如,学生有手机 ,游戏中角色拥有某些装备 将一个对象作为另一个对象的属性,(既什么有什么) 组合的目的\n也是为了重用现有代码 什么时候使用继承:分析两个类的关系,到底是不是:什么是什么的关系 什么时候使用组合:如果两个类之间 没有太大的关系,完全不属于同类 另外组合相比继承,耦合度更低了 菱形继承\n首先明确python支持多继承\n补充：新式类与经典类\npython3中任何类都是直接或间接继承了Object 新式类,任何显式或隐式地继承自object的类就称之为新式类, python3中全都是新式类 经典类,既不是Object的子类 ,仅在python2中出现 当出现了菱形继承时,新式类,先深度,当遇到了共同父类时就广度 经典类,就是深度优先 ","permalink":"https://zhong-er.com/posts/python%E7%BB%A7%E6%89%BF/","summary":"\u003cp\u003e\u003cimg alt=\"Python第十八课(继承).png\" loading=\"lazy\" src=\"/assets/201907256978_8464_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython继承 ****\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/1E2IpX7lH8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e************************\u003c/a\u003e\u003cstrong\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11247379.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e********\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e什么是继承？\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e继承是一种关系,描述两个对象之间,什么是什么的关系\n例如麦兜,佩奇,猪刚鬣 都是猪啊,\n在程序中,继承描述的是类和类之间的关系  \n例如a继承了b, a就能直接使用b已经存在的方法和属性\na称之为子类,b称之为父类,也称之为基类\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e为什么要使用继承\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e继承的一方可以直接使用被继承一方已经有的东西\n其目的是为了重用已经有的代码,提高重用性\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e如何使用继承\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e语法\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass 类名称(父类的名称):\n    类的内容 \n    \n#在python中 一个子类可以同时继承多个父类\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e抽象\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e不具体,不清晰,很模糊,看不懂  \n将多个子类中相同的部分,进行抽取,形成一个新的类,这个过程也称之为抽象的过程\n正确的使用继承:\n\t1.先抽象在继承\n\t2.继承一个已经现存的类,扩展或是修改原始的功能\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e属性的查找顺序\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eclass A:\n    text = \u0026quot;haha\u0026quot;\n\nclass B(A):\n    text = \u0026quot;heihei\u0026quot;\n    pass\n\nb = B()\nb.text = \u0026quot;xixi\u0026quot;\n\nprint(b.text)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e对象自己的 - \u0026gt; 所在类中 -\u0026gt; 找父类 - \u0026gt;父类的父类 -\u0026gt;Object\u003c/p\u003e\n\u003cp\u003e派生\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e当一个子类中出现了与父类中不同的内容时,这个子类就称之为派生类\n通常子类都会写一些新的代码,不可能和父类完全一样 , 既通常都是派生类,\n所以派生类指的就是子类\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e覆盖\u003c/strong\u003e\u003c/p\u003e","title":"【Python】继承"},{"content":"\nPython面向对象 \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园\n面向对象编程思想OOP\n什么是面向对象\n[os-copy]\n面向对象是一种编程思想，是前辈们总结出的经验，指导程序员如何编写出更好的程序，核心是对象，程序就是一系列对象的集合，程序员负责调度控制这些对象来交互着完成任务\n案例1 把大象装进冰箱？\n面向过程:\n1.打开冰箱\n2.装入大象\n3.关闭冰箱\n面向对象:\n找个具备装大象的技能的对象\n在面向对象中程序员的角度发生改变，从具体的操作者变成了指挥者\n强调：对象不是凭空产生的，需要我们自己设计\n案例2 西天取经\n如来有一堆破书要传出去,他没有自己干,\n而是找了五个对象帮他去干, 如来只要负责控制调度的对象即可,\n如果某个对象发生变化,也不会影响其他的对象 , 扩展性\n案例3 曹操吟诗\n喝酒吃肉,人生真爽\n喝酒吃肉,人生几何\n对酒当歌,人生几何\n[/os-copy]\n面向对象三大优点\n[os-copy]\n1.扩展性\n2.灵活性\n3.重用性\n[/os-copy]\n缺点\n[os-copy]\n1.程序的复杂度提高了\n2.无法准确预知结果\n[/os-copy]\n使用场景：\n对扩展性要求较高，通常是直接面向用户的，例如：QQ，微信\n面向过程编程思想\n[os-copy]\n关注的核心是过程,过程是就是一步一步的执行步骤,既先干啥在干啥\n优点:逻辑清晰 , 复杂问题简单化,流程化\n缺点: 扩展性差,可维护性差\n使用场景:对扩展性要求较低的程序例如:系统内核,git,计算器\n记住:不是所有程序都要面向对象,得分析具体需求\n[/os-copy]\n类和对象\n类 ：即类型，类别，是一种抽象概念，是一系列具备相同特征和相同行的对象的集合\n对象 ：就是具体存在的某个事物，具备自己的特征和行为，对象就是特征和技能的结合体\n类和对象的关系\n[os-copy]\n类包含一系列对象，对象属于某个类\n在生活中是先有对象再有类，而在程序中是先有类才能有对象,我们必须先告诉计算机这类的对象有什么特征有什么行为\n总结出一个结论:在使用面向对象编程时,第一步就是思考需要什么样的对象,对象具备什么样的特征和行为,从而根据 这些信息总结出需要的类型\n[/os-copy]\n创建类和对象\n定义类的语法\nclass 类的名称: # 类中的内容 描述属性和技能 #描述属性用变量 #描述行为用函数 #类名称 书写规范 首先是见名知意 名称是大驼峰命名法 #驼峰就是单词首字母大写 , 大驼峰是第一个字母大写,小驼峰是第一个字母小写 创建对象的语法\nclass Person: pass #创建对象 p = Person() 属性的写法\n[os-copy]\n属性可以写在类中\n类中的属性,是所有对象公共的\n也可以写在对象中\n对象中的属性,是每个对象独特的(不一样的)\n如果类中和对象中存在同样的属性,先访问对象 如果没有在访问类\n[/os-copy]\n练习: 描述一个老师类 需要包含 一个公共属性和 一个独特的属性\nclass Teacher: school = \u0026quot;oldboy\u0026quot; t1 = Teacher() t1.name = \u0026quot;jack\u0026quot; t1.age = 28 属性的增删改查\n[os-copy]\n增加属性\n对象变量名称.属性名称 = 属性值\n删除属性\ndel 对象的变量名称.属性名称\n修改\n对象.属性 = 新的值\n查看属性 访问的是对象的所有属性\nprint(对象.dict)\n访问对象的类信息\nprint(对象.class)\n[/os-copy]\ninit方法\n[os-copy]\n叫做初始化方法,本质上就是一个函数\n特点1: 当实例化 对象时,会自动执行init方法\n特点2:会自动将对象作为第一个参数传入,参数名称位self ,self可以是别的名字,但不建议改\n功能:用户给对象赋初始值\n[/os-copy]\n练习:创建一个类具备几个属性,通过初始化方法来给他设置属性**\n**\nclass Dog: def __init__(self,kind,color,age): self.kind = kind self.color = color self.age = age d1 = Dog(\u0026quot;二哈\u0026quot;,\u0026quot;黑白\u0026quot;,1) d1 = Dog(\u0026quot;泰迪\u0026quot;,\u0026quot;棕色\u0026quot;,2) # 注意:该函数不能有任何返回值/.... 只能是None 规定如此.. 对象的精髓就是讲数据和处理数据的函数整合到一起 ,这样一来拿到一个对象就同时拿到了需要处理的数据以及处理数据的函数\n对象的绑定方法\n[os-copy]\n默认情况下类中的方法都是对象绑定方法\n其特殊之处在于,\n当使用对象调用该函数时会自动传入对象本身,作为第一个参数\n当使用类名来调用时他就是一个普通函数,有几个参数就得传几个参数\n[/os-copy]\n练习:写一个学生类,具备一个打招呼的技能 要能输出自己的名字信息\nclass Student: def __init__(self,name): self.name = name def say_hi(self): print(\u0026quot;hello my name is %s\u0026quot; % self.name) 类的绑定方法\n[os-copy]\n类绑定方法用@classmethod来装饰\n特殊之处:不管用类还是对象调用,都会自动传入类本身,作为第一个参数\n什么时候绑定给对象:当函数逻辑需要访问对象中的数据时\n什么时候绑定给类:当函数逻辑需要访问类中的数据时\n[/os-copy]\n非绑定方法\n或叫做静态方法,就是即不需访问类的数据,.也不需要访问对象的数据，不常用\n语法:@staticmethod\n","permalink":"https://zhong-er.com/posts/python%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/","summary":"\u003cp\u003e\u003cimg alt=\"Python第十七课(面向对象基础).png\" loading=\"lazy\" src=\"/assets/201907245717_4819_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython面向对象 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/2IXliWSXW8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003ca href=\"https://www.cnblogs.com/renvip/p/11239449.html\" title=\"博客园\"\u003e\u003c/a\u003e\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11239449.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e面向对象编程思想OOP\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e什么是面向对象\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e[os-copy]\u003c/p\u003e\n\u003cp\u003e面向对象是一种编程思想，是前辈们总结出的经验，指导程序员如何编写出更好的程序，核心是对象，程序就是一系列对象的集合，程序员负责调度控制这些对象来交互着完成任务\u003c/p\u003e\n\u003cp\u003e案例1 把大象装进冰箱？\u003cbr\u003e\n面向过程:\u003cbr\u003e\n1.打开冰箱\u003cbr\u003e\n2.装入大象\u003cbr\u003e\n3.关闭冰箱\u003cbr\u003e\n面向对象:\u003cbr\u003e\n找个具备装大象的技能的对象\u003c/p\u003e\n\u003cp\u003e在面向对象中程序员的角度发生改变，从具体的操作者变成了指挥者\u003cbr\u003e\n强调：对象不是凭空产生的，需要我们自己设计\u003c/p\u003e\n\u003cp\u003e案例2 西天取经\u003cbr\u003e\n如来有一堆破书要传出去,他没有自己干,\u003cbr\u003e\n而是找了五个对象帮他去干, 如来只要负责控制调度的对象即可,\u003c/p\u003e\n\u003cp\u003e如果某个对象发生变化,也不会影响其他的对象 , 扩展性\u003c/p\u003e\n\u003cp\u003e案例3 曹操吟诗\u003cbr\u003e\n喝酒吃肉,人生真爽\u003cbr\u003e\n喝酒吃肉,人生几何\u003cbr\u003e\n对酒当歌,人生几何\u003c/p\u003e\n\u003cp\u003e[/os-copy]\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e面向对象三大优点\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e[os-copy]\u003c/p\u003e\n\u003cp\u003e1.扩展性\u003c/p\u003e\n\u003cp\u003e2.灵活性\u003c/p\u003e\n\u003cp\u003e3.重用性\u003c/p\u003e\n\u003cp\u003e[/os-copy]\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e缺点\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e[os-copy]\u003c/p\u003e\n\u003cp\u003e1.程序的复杂度提高了\u003c/p\u003e\n\u003cp\u003e2.无法准确预知结果\u003c/p\u003e\n\u003cp\u003e[/os-copy]\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e使用场景：\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e对扩展性要求较高，通常是直接面向用户的，例如：QQ，微信\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e面向过程编程思想\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e[os-copy]\u003c/p\u003e\n\u003cp\u003e关注的核心是过程,过程是就是一步一步的执行步骤,既先干啥在干啥\u003c/p\u003e\n\u003cp\u003e优点:逻辑清晰 , 复杂问题简单化,流程化\u003c/p\u003e\n\u003cp\u003e缺点: 扩展性差,可维护性差\u003c/p\u003e\n\u003cp\u003e使用场景:对扩展性要求较低的程序例如:系统内核,git,计算器\u003c/p\u003e\n\u003cp\u003e记住:不是所有程序都要面向对象,得分析具体需求\u003c/p\u003e\n\u003cp\u003e[/os-copy]\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e类和对象\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e类\u003c/strong\u003e ：即类型，类别，是一种抽象概念，是一系列具备相同特征和相同行的对象的集合\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e对象\u003c/strong\u003e ：就是具体存在的某个事物，具备自己的特征和行为，对象就是特征和技能的结合体\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e类和对象的关系\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e[os-copy]\u003c/p\u003e\n\u003cp\u003e类包含一系列对象，对象属于某个类\u003c/p\u003e\n\u003cp\u003e在生活中是先有对象再有类，而在程序中是先有类才能有对象,我们必须先告诉计算机这类的对象有什么特征有什么行为\u003c/p\u003e\n\u003cp\u003e总结出一个结论:在使用面向对象编程时,第一步就是思考需要什么样的对象,对象具备什么样的特征和行为,从而根据 这些信息总结出需要的类型\u003c/p\u003e\n\u003cp\u003e[/os-copy]\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e创建类和对象\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e定义类的语法\u003c/strong\u003e\u003c/p\u003e","title":"【Python】面向对象"},{"content":"\n深浅拷贝/hashlib/logging/openpyxl模块 \u0026raquo;\u0026gt;思维导图\u0026gt; \u0026raquo;博客园\n模块与包\n模块\n\u0026quot;\u0026quot;\u0026quot; 三种来源 1.内置的 2.第三方的 3.自定义的 四种表示形式 1.py文件(******) 2.共享库 3.文件夹(一系列模块的结合体)(******) 4.C++编译的连接到python内置的 \u0026quot;\u0026quot;\u0026quot; 导入模块\n\u0026quot;\u0026quot;\u0026quot; 先产生一个执行文件的名称空间 1.创建模块文件的名称空间 2.执行模块文件中的代码 将产生的名字放入模块的名称空间中 3.在执行文件中拿到一个指向模块名称空间的名字 \u0026quot;\u0026quot;\u0026quot; 什么是包\n\u0026quot;\u0026quot;\u0026quot; 什么是包? 它是一系列模块文件的结合体,表示形式就是一个文件夹 该文件夹内部通常会有一个__init__.py文件 包的本质还是一个模块 \u0026quot;\u0026quot;\u0026quot; 导入包\n先产生一个执行文件的名称空间 1.创建包下面的__init__.py文件的名称空间 2.执行包下面的__init__.py文件中的代码 将产生的名字放入包下面的__init__.py文件名称空间中 3.在执行文件中拿到一个指向包下面的__init__.py文件名称空间的名字 在导入语句中 .号的左边肯定是一个包(文件夹) 当你作为包的设计者来说\n1.当模块的功能特别多的情况下 应该分文件管理 2.每个模块之间为了避免后期模块改名的问题 你可以使用相对导入(包里面的文件都应该是被导入的模块) # 研究模块与包 还可以站另外两个角度分析不同的问题 # 1.模块的开发者 # 2.模块的使用者 研究模块与包还可以站另外两个角度分析不同的问题\n1.站在包的开发者 如果使用绝对路径来管理的自己的模块 那么它只需要永远以包的路径为基准依次导入模块 2.站在包的使用者 你必须得将包所在的那个文件夹路径添加到system 注意\npython2如果要导入包 包下面必须要有__init__.py文件 python3如果要导入包 包下面没有__init__.py文件也不会报错 当你在删程序不必要的文件的时候 千万不要随意删除__init__.py文件 相对导入与绝对导入\n# 绝对导入: 以执行文件的sys.path为起始点开始导入,称之为绝对导入 # 优点: 执行文件与被导入的模块中都可以使用 # 缺点: 所有导入都是以sys.path为起始点,导入麻烦 # 相对导入: 参照当前所在文件的文件夹为起始开始查找,称之为相对导入 # 符号: .代表当前所在文件的文件加,..代表上一级文件夹,...代表上一级的上一级文件夹 # 优点: 导入更加简单 # 缺点: 只能在导入包中的模块时才能使用 #注意: 1. 相对导入只能用于包内部模块之间的相互导入,导入者与被导入者都必须存在于一个包内 2. attempted relative import beyond top-level package # 试图在顶级包之外使用相对导入是错误的,言外之意,必须在顶级包内使用相对导入,每增加一个.代表跳到上一级文件夹,而上一级不应该超出顶级包 logging模块(日志模块：记录)\n日志级别\nCRITICAL = 50 #FATAL = CRITICAL ERROR = 40 WARNING = 30 #WARN = WARNING INFO = 20 DEBUG = 10 NOTSET = 0 #不设置 默认级别为warning，默认打印到终端\nimport logging logging.debug('调试debug') logging.info('消息info') logging.warning('警告warn') logging.error('错误error') logging.critical('严重critical') ''' WARNING:root:警告warn ERROR:root:错误error CRITICAL:root:严重critical ''' 为logging模块指定全局配置，针对所有logger有效，控制打印到文件中\n#======介绍 可在logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为，可用参数有 filename：用指定的文件名创建FiledHandler（后边会具体讲解handler的概念），这样日志会被存储在指定的文件中。 filemode：文件打开方式，在指定了filename时使用这个参数，默认值为“a”还可指定为“w”。 format：指定handler使用的日志显示格式。 datefmt：指定日期时间格式。 level：设置rootlogger（后边会讲解具体概念）的日志级别 stream：用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件，默认为sys.stderr。若同时列出了filename和stream两个参数，则stream参数会被忽略。 format参数中可能用到的格式化串： %(name)s Logger的名字 %(levelno)s 数字形式的日志级别 %(levelname)s 文本形式的日志级别 %(pathname)s 调用日志输出函数的模块的完整路径名，可能没有 %(filename)s 调用日志输出函数的模块的文件名 %(module)s 调用日志输出函数的模块名 %(funcName)s 调用日志输出函数的函数名 %(lineno)d 调用日志输出函数的语句所在的代码行 %(created)f 当前时间，用UNIX标准的表示时间的浮 点数表示 %(relativeCreated)d 输出日志信息时的，自Logger创建以 来的毫秒数 %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 %(thread)d 线程ID。可能没有 %(threadName)s 线程名。可能没有 %(process)d 进程ID。可能没有 %(message)s用户输出的消息 #========使用 import logging logging.basicConfig(filename='access.log', format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S %p', level=10) logging.debug('调试debug') logging.info('消息info') logging.warning('警告warn') logging.error('错误error') logging.critical('严重critical') #========结果 access.log内容: 2017-07-28 20:32:17 PM - root - DEBUG -test: 调试debug 2017-07-28 20:32:17 PM - root - INFO -test: 消息info 2017-07-28 20:32:17 PM - root - WARNING -test: 警告warn 2017-07-28 20:32:17 PM - root - ERROR -test: 错误error 2017-07-28 20:32:17 PM - root - CRITICAL -test: 严重critical part2: 可以为logging模块指定模块级的配置,即所有logger的配置 Formatter，Handler，Logger，Filter对象\n#logger：产生日志的对象 #Filter：过滤日志的对象 #Handler：接收日志然后控制打印到不同的地方，FileHandler用来打印到文件中，StreamHandler用来打印到终端 #Formatter对象：可以定制不同的日志格式对象，然后绑定给不同的Handler对象使用，以此来控制不同的Handler的日志格式 ''' critical=50 error =40 warning =30 info = 20 debug =10 ''' import logging # 1.logger对象:负责产生日志 logger = logging.getLogger('转账记录') # 2.filter对象:过滤日志(了解) # 3.handler对象:控制日志输出的位置(文件/终端) hd1 = logging.FileHandler('a1.log',encoding='utf-8') # 输出到文件中 hd2 = logging.FileHandler('a2.log',encoding='utf-8') # 输出到文件中 hd3 = logging.StreamHandler() # 输出到终端 # 4.formmater对象:规定日志内容的格式 fm1 = logging.Formatter( fmt='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S %p', ) fm2 = logging.Formatter( fmt='%(asctime)s - %(name)s: %(message)s', datefmt='%Y-%m-%d', ) # 5.给logger对象绑定handler对象 logger.addHandler(hd1) logger.addHandler(hd2) logger.addHandler(hd3) # 6.给handler绑定formmate对象 hd1.setFormatter(fm1) hd2.setFormatter(fm2) hd3.setFormatter(fm1) # 7.设置日志等级 logger.setLevel(20) # 8.记录日志 logger.debug('写了半天 好累啊 好热啊 好想释放') logging配置字典\nimport os import logging.config # 定义三种日志输出格式 开始 standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \\ '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' # 定义日志输出格式 结束 \u0026quot;\u0026quot;\u0026quot; 下面的两个变量对应的值 需要你手动修改 \u0026quot;\u0026quot;\u0026quot; logfile_dir = os.path.dirname(__file__) # log文件的目录 logfile_name = 'a3.log' # log文件名 # 如果不存在定义的日志目录就创建一个 if not os.path.isdir(logfile_dir): os.mkdir(logfile_dir) # log文件的全路径 logfile_path = os.path.join(logfile_dir, logfile_name) # log配置字典 LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, }, 'filters': {}, # 过滤日志 'handlers': { #打印到终端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': logfile_path, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, 'encoding': 'utf-8', # 日志文件的编码，再也不用担心中文log乱码了 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 '': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上，即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上（更高level的logger）传递 }, # 当键不存在的情况下 默认都会使用该k:v配置 }, } # 使用日志字典配置 logging.config.dictConfig(LOGGING_DIC) # 自动加载字典中的配置 logger1 = logging.getLogger('asajdjdskaj') logger1.debug('好好的 不要浮躁 努力就有收获') hashlib模块(加密模块)\nhash是什么？ hash是一种算法（3.x里代替了md5模块和sha模块，主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ，MD5 算法），该算法接受传入的内容，经过运算得到一串hash值 hash值的特点是什么？ 1 只要传入的内容一样，得到的hash值必然一样=====\u0026gt;要用明文传输密码文件完整性校验 2 不能由hash值返解成内容=======》把密码做成hash值，不应该在网络传输明文密码 3 只要使用的hash算法不变，无论校验的内容有多大，得到的hash值长度是固定的 调用\n基本调用 import hashlib new_md5 = hashlib.md5() # 创建hashlib的md5对象 new_md5.update('字符串') # 将字符串载入到md5对象中，获得md5算法加密。 print(new_md5.hexdigest()) # 通过hexdigest()方法，获得new_md5对象的16进制md5显示。 简单调用 new_md5 = hashlib.new('md5',b'字符串').hexdigest() # 或者 new_md5 = hashlib.md5(str(time.time().encode('utf-8')).hexdigest() 注意：向对象中传入字符串时，必须为编码类型。可以使用字符串前b' '的方法或使用.encode('UTF-8')的方法，使字符串变为bytes类型。 加盐\nimport hashlib yan = '!任#意%字^符@' # 定义加盐字符串 pwd = input('\u0026gt;\u0026gt;\u0026gt;') md5_pwd = hashlib.md5() md5_pwd.update((pwd+yan).encode('UTF-8')) # 加盐 pwd = md5_pwd.hexdigest() # pwd = hashlib.new('md5',(pwd+yan).encode('UTF-8')).hexdigest() # 也可以这样简写 openpyxl模块\n安装\npip install openpyxl 打开文件\n# 创建 from openpyxl import Workbook # 实例化 wb = Workbook() # 激活 worksheet ws = wb.active # 打开已有 \u0026gt;\u0026gt;\u0026gt; from openpyxl import load_workbook \u0026gt;\u0026gt;\u0026gt; wb2 = load_workbook('文件名称.xlsx') 存储数据\n# 方式一：数据可以直接分配到单元格中(可以输入公式) ws['A1'] = 42 # 方式二：可以附加行，从第一列开始附加(从最下方空白处，最左开始)(可以输入多行) ws.append([1, 2, 3]) # 方式三：Python 类型会被自动转换 ws['A3'] = datetime.datetime.now().strftime(\u0026quot;%Y-%m-%d\u0026quot;) 创建表\n# 方式一：插入到最后(default) \u0026gt;\u0026gt;\u0026gt; ws1 = wb.create_sheet(\u0026quot;Mysheet\u0026quot;) # 方式二：插入到最开始的位置 \u0026gt;\u0026gt;\u0026gt; ws2 = wb.create_sheet(\u0026quot;Mysheet\u0026quot;, 0) 选择表\n# sheet 名称可以作为 key 进行索引 \u0026gt;\u0026gt;\u0026gt; ws3 = wb[\u0026quot;New Title\u0026quot;] \u0026gt;\u0026gt;\u0026gt; ws4 = wb.get_sheet_by_name(\u0026quot;New Title\u0026quot;) \u0026gt;\u0026gt;\u0026gt; ws is ws3 is ws4 True 查看表名\n# 显示所有表名 \u0026gt;\u0026gt;\u0026gt; print(wb.sheetnames) ['Sheet2', 'New Title', 'Sheet1'] # 遍历所有表 \u0026gt;\u0026gt;\u0026gt; for sheet in wb: ... print(sheet.title) 访问单元格cell\n# 单一单元格访问 # 方法一 \u0026gt;\u0026gt;\u0026gt; c = ws['A4'] # 方法二：row 行；column 列 \u0026gt;\u0026gt;\u0026gt; d = ws.cell(row=4, column=2, value=10) # 方法三：只要访问就创建 \u0026gt;\u0026gt;\u0026gt; for i in range(1,101): ... for j in range(1,101): ... ws.cell(row=i, column=j) # 多单元格访问 # 通过切片 \u0026gt;\u0026gt;\u0026gt; cell_range = ws['A1':'C2'] # 通过行(列) \u0026gt;\u0026gt;\u0026gt; colC = ws['C'] \u0026gt;\u0026gt;\u0026gt; col_range = ws['C:D'] \u0026gt;\u0026gt;\u0026gt; row10 = ws[10] \u0026gt;\u0026gt;\u0026gt; row_range = ws[5:10] # 通过指定范围(行 → 行) \u0026gt;\u0026gt;\u0026gt; for row in ws.iter_rows(min_row=1, max_col=3, max_row=2): ... for cell in row: ... print(cell) \u0026lt;Cell Sheet1.A1\u0026gt; \u0026lt;Cell Sheet1.B1\u0026gt; \u0026lt;Cell Sheet1.C1\u0026gt; \u0026lt;Cell Sheet1.A2\u0026gt; \u0026lt;Cell Sheet1.B2\u0026gt; \u0026lt;Cell Sheet1.C2\u0026gt;　# 通过指定范围(列 → 列) \u0026gt;\u0026gt;\u0026gt; for row in ws.iter_rows(min_row=1, max_col=3, max_row=2): ... for cell in row: ... print(cell) \u0026lt;Cell Sheet1.A1\u0026gt; \u0026lt;Cell Sheet1.B1\u0026gt; \u0026lt;Cell Sheet1.C1\u0026gt; \u0026lt;Cell Sheet1.A2\u0026gt; \u0026lt;Cell Sheet1.B2\u0026gt; \u0026lt;Cell Sheet1.C2\u0026gt; # 遍历所有 方法一 \u0026gt;\u0026gt;\u0026gt; ws = wb.active \u0026gt;\u0026gt;\u0026gt; ws['C9'] = 'hello world' \u0026gt;\u0026gt;\u0026gt; tuple(ws.rows) ((\u0026lt;Cell Sheet.A1\u0026gt;, \u0026lt;Cell Sheet.B1\u0026gt;, \u0026lt;Cell Sheet.C1\u0026gt;), (\u0026lt;Cell Sheet.A2\u0026gt;, \u0026lt;Cell Sheet.B2\u0026gt;, \u0026lt;Cell Sheet.C2\u0026gt;), ... (\u0026lt;Cell Sheet.A8\u0026gt;, \u0026lt;Cell Sheet.B8\u0026gt;, \u0026lt;Cell Sheet.C8\u0026gt;), (\u0026lt;Cell Sheet.A9\u0026gt;, \u0026lt;Cell Sheet.B9\u0026gt;, \u0026lt;Cell Sheet.C9\u0026gt;)) # 遍历所有 方法二 \u0026gt;\u0026gt;\u0026gt; tuple(ws.columns) ((\u0026lt;Cell Sheet.A1\u0026gt;, \u0026lt;Cell Sheet.A2\u0026gt;, \u0026lt;Cell Sheet.A3\u0026gt;, ... \u0026lt;Cell Sheet.B7\u0026gt;, \u0026lt;Cell Sheet.B8\u0026gt;, \u0026lt;Cell Sheet.B9\u0026gt;), (\u0026lt;Cell Sheet.C1\u0026gt;, ... \u0026lt;Cell Sheet.C8\u0026gt;, \u0026lt;Cell Sheet.C9\u0026gt;)) 保存数据\n\u0026gt;\u0026gt;\u0026gt; wb.save('文件名称.xlsx') 深浅拷贝\n浅拷贝\n深拷贝\nimport copy l = [1,2,[1,2]] l1 = l print(id(l),id(l1)) l1 = copy.copy(l) # 浅拷贝 print(id(l),id(l1)) l[0] = 222 print(l,l1) l[2].append(666) print(l,l1) l1 = copy.deepcopy(l) # 深拷贝 l[2].append(666) print(l,l1) END\n","permalink":"https://zhong-er.com/posts/python%E6%B7%B1%E6%B5%85%E6%8B%B7%E8%B4%9Dhashlibloggingopenpyxl%E6%A8%A1%E5%9D%97/","summary":"\u003cp\u003e\u003cimg alt=\"Python第十六课(模块3).png\" loading=\"lazy\" src=\"/assets/201907194511_4004_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e深浅拷贝/hashlib/logging/openpyxl模块 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/1XBfSvM5X8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;博客园\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e模块与包\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e模块\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n三种来源\n    1.内置的\n    2.第三方的\n    3.自定义的\n四种表示形式\n    1.py文件(******)\n    2.共享库\n    3.文件夹(一系列模块的结合体)(******)\n    4.C++编译的连接到python内置的\n\u0026quot;\u0026quot;\u0026quot;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e导入模块\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n先产生一个执行文件的名称空间\n    1.创建模块文件的名称空间\n    2.执行模块文件中的代码 将产生的名字放入模块的名称空间中\n    3.在执行文件中拿到一个指向模块名称空间的名字\n\u0026quot;\u0026quot;\u0026quot;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e什么是包\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;\u0026quot;\u0026quot;\n什么是包?\n    它是一系列模块文件的结合体,表示形式就是一个文件夹\n    该文件夹内部通常会有一个__init__.py文件\n    包的本质还是一个模块\n\u0026quot;\u0026quot;\u0026quot;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e导入包\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e先产生一个执行文件的名称空间\n    1.创建包下面的__init__.py文件的名称空间\n    2.执行包下面的__init__.py文件中的代码 将产生的名字放入包下面的__init__.py文件名称空间中\n    3.在执行文件中拿到一个指向包下面的__init__.py文件名称空间的名字\n在导入语句中 .号的左边肯定是一个包(文件夹)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e当你作为包的设计者来说\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1.当模块的功能特别多的情况下 应该分文件管理\n2.每个模块之间为了避免后期模块改名的问题 你可以使用相对导入(包里面的文件都应该是被导入的模块)\n# 研究模块与包 还可以站另外两个角度分析不同的问题\n# 1.模块的开发者\n# 2.模块的使用者\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e研究模块与包还可以站另外两个角度分析不同的问题\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1.站在包的开发者\n如果使用绝对路径来管理的自己的模块 那么它只需要永远以包的路径为基准依次导入模块\n2.站在包的使用者\n你必须得将包所在的那个文件夹路径添加到system\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e注意\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003epython2如果要导入包 包下面必须要有__init__.py文件\npython3如果要导入包 包下面没有__init__.py文件也不会报错\n当你在删程序不必要的文件的时候 千万不要随意删除__init__.py文件\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e相对导入与绝对导入\u003c/p\u003e","title":"【Python】深浅拷贝/hashlib/logging/openpyxl模块"},{"content":"\nPython****时间/序列化/sys/os模块 \u0026raquo;\u0026gt;思维导图****\u0026gt; \u0026raquo;博客园****************\ncollections模块 collections模块在内置的数据类型，比如：int、str、list、dict等基础之上额外提供了几种数据类型。 1.namedtuple(): 生成可以使用名字来访问元素内容的tuple子类 2.deque: 双端队列，可以快速的从另外一侧追加和推出对象 3.Counter: 计数器，主要用来计数 4.OrderedDict: 有序字典 5.defaultdict: 带有默认值的字典 ****具名元组(namedtuple)\ntuple是一个不可变的集合，namedtuple是一个函数，她用来构建一个自定义的tuple对象， 并且规定了tuple元素的个数，可通过属性而不是索引来引用tuple的某个元素。 我们可以通过namedtuple很方便的自定义一个数据类型，它具有tuple 的不变属性又可以根据属性来引用。 ****想表示空间坐标点x为1， y为2，z为5的坐标\nfrom collections import namedtuple point = namedtuple('坐标',['x','y','z']) # 第二个参数既可以传可迭代对象 point1 = namedtuple('坐标','x y z') # 也可以传字符串 但是字符串之间以空格隔开 p = point(1,2,5) p1 = point1(2,3,6)print(p,p.x,p.y,p.z) # 坐标(x=1, y=2, z=5) 1 2 5print(p1,p1.x,p1.y,p1.z) # 坐标(x=2, y=3, z=6) 2 3 6 想表示一张黑桃A的扑克牌\ncard = namedtuple('扑克牌','color number') card1 = namedtuple('扑克牌',['color','number']) A = card('♠','A') B = card1('♠','A')print(A.color,A.number,A) # ♠ A 扑克牌(color='♠', number='A')print(B.color,B.number,B) # ♠ A 扑克牌(color='♠', number='A') 想表示一个城市\ncity = namedtuple('日本','name person size') c = city('东京','R老师','L')print(c,c.name,c.person,c.size) # 日本(name='东京', person='R老师', size='L') 东京 R老师 L 队列(queue)\nimport queue q = queue.Queue() # 生成队列对象 q.put('first') # 往队列中添加值 q.put('second') q.put('third')print(q.get()) # first 朝队列要值print(q.get()) # secondprint(q.get()) # thirdprint(q.get()) # 如果队列中的值取完了 程序会在原地等待 直到从队列中拿到值才停止 先进先出(FIFO first in first out)\nput()向队列添加值\nget()向队列要值 # 值不够 程序会在原地等待 直到队列中给get方法返回一个值 才会继续往下执行\n队列不应该支持任意位置插值，只能在首尾插值(不能插队)\n双端队列(deque)\nappend()在末尾添加元素\nappendleft()在开头添加元素\npop()弹出末尾的元素\npopleft()弹出开头的元素\ninsert(index,obj)双端队列可以根据索引在任意位置插值\nfrom collections import deque q = deque(['a','b','c']) q.append(1) # 在末尾添加值 q.appendleft(2) # 在开头添加值 \u0026quot;\u0026quot;\u0026quot; 队列不应该支持任意位置插值 只能在首尾插值(不能插队) \u0026quot;\u0026quot;\u0026quot; q.insert(0,'哈哈哈') # 特殊点:双端队列可以根据索引在任意位置插值print(q) # deque(['哈哈哈', 2, 'a', 'b', 'c', 1])print(q.pop()) # 1print(q.popleft()) # 哈哈哈print(q.popleft()) # 2print(q) # deque(['a', 'b', 'c']) 有序字典(OrderedDict)\nnormal_d = dict([('a',1),('b',2),('c',3)])print(normal_d) # {'a': 1, 'b': 2, 'c': 3} from collections import OrderedDict order_d = OrderedDict([('a',1),('b',2),('c',3)]) # // 有序字典顺序是插入进去的顺序排列的print(order_d) # OrderedDict([('a', 1), ('b', 2), ('c', 3)]) order_d1 = OrderedDict() order_d1['z'] = 1 order_d1['y'] = 2 order_d1['x'] = 3print(order_d1) for i in order_d1: print(i) # z y xprint(order_d1) # OrderedDict([('z', 1), ('y', 2), ('x', 3)]) dict中key是无序的，在做迭代时，无法确认key的顺序\norderdDict是对字典类型的补充，他记住了字典元素添加的顺序\n默认字典(defaultdict)\nfrom collections import defaultdict values = [11, 22, 33,44,55,66,77,88,99,90] my_dict = defaultdict(list) # 后续该字典中新建的key对应的value默认就是列表print(my_dict['aaa']) # [] for value in values: if value\u0026gt;66: my_dict['k1'].append(value) else: my_dict['k2'].append(value)print(my_dict) # defaultdict(\u0026lt;class 'list'\u0026gt;, {'aaa': [], 'k2': [11, 22, 33, 44, 55, 66], 'k1': [77, 88, 99, 90]}) my_dict1 = defaultdict(int)print(my_dict1['xxx']) # 0print(my_dict1['yyy']) # 0 my_dict2 = defaultdict(bool)print(my_dict2['kkk']) # False my_dict3 = defaultdict(tuple)print(my_dict3['mmm']) # () defaultdict(list)字典内所有新增的键对应的值都是一个空列表 并且不是共用一个\n在使用 d[key] 取值的时候， 如果指定的key不存在，就会抛出KeyError异常\n使用defaultdict，只要传入一个函数，那么请求一个不存在的key时， 便会调用这个函数使用其结果来作为这个key的默认值\n\u0026gt;\u0026gt;\u0026gt; from collections import defaultdict \u0026gt;\u0026gt;\u0026gt; dd = defaultdict(lambda: 'N/A') \u0026gt;\u0026gt;\u0026gt; dd['key1'] = 'abc' \u0026gt;\u0026gt;\u0026gt; dd['key1'] # key1存在 'abc' \u0026gt;\u0026gt;\u0026gt; dd['key2'] # key2不存在，返回默认值 'N/A' 计数器(Counter)\nc = Counter('abcdeabcdabcaba')print c 输出：Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1}) Counter是对字典类型的补充，用于追踪值的出现次数\n它是一个无序的容器类型，以字典的键值对形式存储，其中元素作为key，其计数作为value\n计数值可以是任意的Interger（包括0和负数）\ntime时间模块\n三种表现形式\n# 导入时间模块 \u0026gt;\u0026gt;\u0026gt;import time# 时间戳 \u0026gt;\u0026gt;\u0026gt;time.time() 1500875844.800804 # 时间字符串 \u0026gt;\u0026gt;\u0026gt;time.strftime(\u0026quot;%Y-%m-%d %X\u0026quot;) '2017-07-24 13:54:37' \u0026gt;\u0026gt;\u0026gt;time.strftime(\u0026quot;%Y-%m-%d %H-%M-%S\u0026quot;) '2017-07-24 13-55-04' # 时间元组:localtime将一个时间戳转换为当前时区的struct_timetime.localtime()time.struct_time(tm_year=2017, tm_mon=7, tm_mday=24, tm_hour=13, tm_min=59, tm_sec=37, tm_wday=0, tm_yday=205, tm_isdst=0) 1.时间戳\n时间戳(timestamp) ：通常来说，时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。我们运行“type(time.time())”，返回的是float类型。\n2.格式化时间\npython中时间日期的格式化符号 %y 两位数的年份表示（00-99） %Y 四位数的年份表示（000-9999） %m 月份（01-12） %d 月内中的一天（0-31） %H 24小时制小时数（0-23） %I 12小时制小时数（01-12） %M 分钟数（00=59） %S 秒（00-59） %a 本地简化星期名称 %A 本地完整星期名称 %b 本地简化的月份名称 %B 本地完整的月份名称 %c 本地相应的日期表示和时间表示 %j 年内的一天（001-366） %p 本地A.M.或P.M.的等价符 %U 一年中的星期数（00-53）星期天为星期的开始 %w 星期（0-6），星期天为星期的开始 %W 一年中的星期数（00-53）星期一为星期的开始 %x 本地相应的日期表示 %X 本地相应的时间表示 %Z 当前时区的名称 %% %号本身 3.结构化时间(元组struct_time)\nstruct_time元组共有9个元素共九个元素:(年，月，日，时，分，秒，一年中第几周，一年中第几天等）\ntime.localtime() #time.struct_time(tm_year=2019, tm_mon=7, tm_mday=19, tm_hour=16, tm_min=27, tm_sec=29, tm_wday=4, tm_yday=200, tm_isdst=0) 几种格式之间的转换\ndatetime时间模块\n打印时间\nimport datetimeprint(datetime.date.today()) # date\u0026gt;\u0026gt;\u0026gt;:年月日print(datetime.datetime.today()) # datetime\u0026gt;\u0026gt;\u0026gt;:年月日 时分秒 res = datetime.date.today() res1 = datetime.datetime.today()print(res.year)print(res.month)print(res.day)print(res.weekday()) # 0-6表示星期 0表示周一print(res.isoweekday()) # 1-7表示星期 7就是周日 日期+/-\nimport datetime \u0026quot;\u0026quot;\u0026quot; 日期对象 = 日期对象 +/- timedelta对象 timedelta对象 = 日期对象 +/- 日期对象 \u0026quot;\u0026quot;\u0026quot; current_time = datetime.date.today() # 日期对象 timetel_t = datetime.timedelta(days=7) # timedelta对象 res1 = current_time+timetel_t # 日期对象print(current_time - timetel_t)print(res1-current_time) # 小练习 计算今天距离今年过生日还有多少天 birth = datetime.datetime(2019,12,21,8,8,8) current_time = datetime.datetime.today()print(birth-current_time) UTC时间\ndt_today = datetime.datetime.today() dt_now = datetime.datetime.now() dt_utcnow = datetime.datetime.utcnow()print(dt_utcnow,dt_now,dt_today) random随机模块\n取随机数\n\u0026gt;\u0026gt;\u0026gt; import random #随机小数 \u0026gt;\u0026gt;\u0026gt; random.random() # 大于0且小于1之间的小数 0.7664338663654585 \u0026gt;\u0026gt;\u0026gt; random.uniform(1,3) #大于1小于3的小数 1.6270147180533838 #恒富：发红包 #随机整数 \u0026gt;\u0026gt;\u0026gt; random.randint(1,5) # 大于等于1且小于等于5之间的整数 \u0026gt;\u0026gt;\u0026gt; random.randrange(1,10,2) # 大于等于1且小于10之间的奇数 #随机选择一个返回 \u0026gt;\u0026gt;\u0026gt; random.choice([1,'23',[4,5]]) # #1或者23或者[4,5] #随机选择多个返回，返回的个数为函数的第二个参数 \u0026gt;\u0026gt;\u0026gt; random.sample([1,'23',[4,5]],2) # #列表元素任意2个组合 [[4, 5], '23'] 打乱列表顺序(洗牌)\n\u0026gt;\u0026gt;\u0026gt; item=[1,3,5,7,9] \u0026gt;\u0026gt;\u0026gt; random.shuffle(item) # 打乱次序 \u0026gt;\u0026gt;\u0026gt; item [5, 1, 3, 7, 9] \u0026gt;\u0026gt;\u0026gt; random.shuffle(item) \u0026gt;\u0026gt;\u0026gt; item [5, 9, 7, 1, 3] 生成验证码\ndef get_code(n): code = '' for i in range(n): # 先生成随机的大写字母 小写字母 数字 upper_str = chr(random.randint(65,90)) lower_str = chr(random.randint(97,122)) random_int = str(random.randint(0,9)) # 从上面三个中随机选择一个作为随机验证码的某一位 code += random.choice([upper_str,lower_str,random_int]) return code res = get_code(4)print(res) OS模块\nos模块是与操作系统交互的一个接口\nos.makedirs('dirname1/dirname2') 可生成多层递归目录 os.removedirs('dirname1') 若目录为空，则删除，并递归到上一级目录，如若也为空，则删除，依此类推 os.mkdir('dirname') 生成单级目录；相当于shell中mkdir dirname os.rmdir('dirname') 删除单级空目录，若目录不为空则无法删除，报错；相当于shell中rmdir dirname os.listdir('dirname') 列出指定目录下的所有文件和子目录，包括隐藏文件，并以列表方式打印 os.remove() 删除一个文件 os.rename(\u0026quot;oldname\u0026quot;,\u0026quot;newname\u0026quot;) 重命名文件/目录 os.stat('path/filename') 获取文件/目录信息 os.system(\u0026quot;bash command\u0026quot;) 运行shell命令，直接显示 os.popen(\u0026quot;bash command).read() 运行shell命令，获取执行结果 os.getcwd() 获取当前工作目录，即当前python脚本工作的目录路径 os.chdir(\u0026quot;dirname\u0026quot;) 改变当前脚本工作目录；相当于shell下cd os.pathos.path.abspath(path) 返回path规范化的绝对路径 os.path.split(path) 将path分割成目录和文件名二元组返回 os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素 os.path.basename(path) 返回path最后的文件名。如何path以／或\\结尾，那么就会返回空值。即os.path.split(path)的第二个元素 os.path.exists(path) 如果path存在，返回True；如果path不存在，返回False os.path.isabs(path) 如果path是绝对路径，返回True os.path.isfile(path) 如果path是一个存在的文件，返回True。否则返回False os.path.isdir(path) 如果path是一个存在的目录，则返回True。否则返回False os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回，第一个绝对路径之前的参数将被忽略 os.path.getatime(path) 返回path所指向的文件或者目录的最后访问时间 os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间 os.path.getsize(path) 返回path的大小 stat结构\nst_mode: inode 保护模式 st_ino: inode 节点号。 st_dev: inode 驻留的设备。 st_nlink: inode 的链接数。 st_uid: 所有者的用户ID。 st_gid: 所有者的组ID。 st_size: 普通文件以字节为单位的大小；包含等待某些特殊文件的数据。 st_atime: 上次访问的时间。 st_mtime: 最后一次修改的时间。 st_ctime: 由操作系统报告的\u0026quot;ctime\u0026quot;。在某些系统上（如Unix）是最新的元数据更改的时间，在其它系统上（如Windows）是创建时间（详细信息参见平台的文档）。 os模块属性\nos.sep 输出操作系统特定的路径分隔符，win下为\u0026quot;\\\\\u0026quot;,Linux下为\u0026quot;/\u0026quot; os.linesep 输出当前平台使用的行终止符，win下为\u0026quot;\\r\\n\u0026quot;,Linux下为\u0026quot;\\n\u0026quot; os.pathsep 输出用于分割文件路径的字符串 win下为;,Linux下为:os.name 输出字符串指示当前使用平台。win-\u0026gt;'nt'; Linux-\u0026gt;'posix' sys模块\nsys模块是与python解释器交互的一个接口\nsys.argv 命令行参数List，第一个元素是程序本身路径 sys.exit(n) 退出程序，正常退出时exit(0),错误退出sys.exit(1) sys.version 获取Python解释程序的版本信息 sys.path 返回模块的搜索路径，初始化时使用PYTHONPATH环境变量的值 sys.platform 返回操作系统平台名称 异常处理与status\nimport sys try: sys.exit(1) except SystemExit as e: print(e) 序列化模块\n序列:字符串\n序列化:其他数据类型转换成字符串的过程\n反序列化:字符串转成其他数据类型\njson模块\njson，用于字符串 和 python数据类型间进行转换\n优点：所有的语言都支持json格式\n缺点：支持的数据类型很少(字符串,列表,字典,整型,元组(转成列表) ,布尔值)\nloads和dumps\nimport json dic = {'k1':'v1','k2':'v2','k3':'v3'} str_dic = json.dumps(dic) #序列化：将一个字典转换成一个字符串print(type(str_dic),str_dic) #\u0026lt;class 'str'\u0026gt; {\u0026quot;k3\u0026quot;: \u0026quot;v3\u0026quot;, \u0026quot;k1\u0026quot;: \u0026quot;v1\u0026quot;, \u0026quot;k2\u0026quot;: \u0026quot;v2\u0026quot;} #注意，json转换完的字符串类型的字典中的字符串是由\u0026quot;\u0026quot;表示的 dic2 = json.loads(str_dic) #反序列化：将一个字符串格式的字典转换成一个字典 #注意，要用json的loads功能处理的字符串类型的字典中的字符串必须由\u0026quot;\u0026quot;表示print(type(dic2),dic2) #\u0026lt;class 'dict'\u0026gt; {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} list_dic = [1,['a','b','c'],3,{'k1':'v1','k2':'v2'}] str_dic = json.dumps(list_dic) #也可以处理嵌套的数据类型print(type(str_dic),str_dic) #\u0026lt;class 'str'\u0026gt; [1, [\u0026quot;a\u0026quot;, \u0026quot;b\u0026quot;, \u0026quot;c\u0026quot;], 3, {\u0026quot;k1\u0026quot;: \u0026quot;v1\u0026quot;, \u0026quot;k2\u0026quot;: \u0026quot;v2\u0026quot;}] list_dic2 = json.loads(str_dic)print(type(list_dic2),list_dic2) #\u0026lt;class 'list'\u0026gt; [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}] load和dump\nimport json f = open('json_file','w') dic = {'k1':'v1','k2':'v2','k3':'v3'} json.dump(dic,f) #dump方法接收一个文件句柄，直接将字典转换成json字符串写入文件 f.close() f = open('json_file') dic2 = json.load(f) #load方法接收一个文件句柄，直接将文件中的json字符串转换成数据结构返回 f.close()print(type(dic2),dic2) pickle模块\npickle，用于python特有的类型 和 python的数据类型间进行转换\n缺点：只支持python\n优点：python所有的数据类型都支持\npickle模块提供了四个功能：dumps(序列化)、loads(反序列化)、dump(存)、load(读)\npickleimport pickled = {'name':'jason'} res = pickle.dumps(d) # 将对象直接转成二进制print(pickle.dumps(d)) res1 = pickle.loads(res)print(res1,type(res1)) \u0026quot;\u0026quot;\u0026quot; 用pickle操作文件的时候 文件的打开模式必须是b模式 \u0026quot;\u0026quot;\u0026quot; # with open('userinfo_1','wb') as f: # pickle.dump(d,f) # with open('userinfo_1','rb') as f: # res = pickle.load(f) # print(res,type(res)) subprocess模块\n# subprocess \u0026quot;\u0026quot;\u0026quot;sub :子 process:进程 \u0026quot;\u0026quot;\u0026quot; \u0026quot;\u0026quot;\u0026quot; 1.用户通过网络连接上了你的这台电脑 2.用户输入相应的命令 基于网络发送给了你这台电脑上某个程序 3.获取用户命令 里面subprocess执行该用户命令 4.将执行结果再基于网络发送给用户 这样就实现 用户远程操作你这台电脑的操作 \u0026quot;\u0026quot;\u0026quot; while True: cmd = input('cmd\u0026gt;\u0026gt;\u0026gt;:').strip() import subprocess obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) print(obj) print('正确命令返回的结果stdout',obj.stdout.read().decode('gbk')) print('错误命令返回的提示信息stderr',obj.stderr.read().decode('gbk')) END\n","permalink":"https://zhong-er.com/posts/python%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97%E5%8C%96sysos%E6%A8%A1%E5%9D%97/","summary":"\u003cp\u003e\u003cimg alt=\"Python第十五课(模块2).png\" loading=\"lazy\" src=\"/assets/201907191143_207_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython****时间/序列化/sys/os模块 \u0026raquo;\u0026gt;\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/24AoWYUXS8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11210464.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****************\u003c/a\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"collections模块\"\u003ecollections模块\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003ecollections模块在内置的数据类型，比如：int、str、list、dict等基础之上额外提供了几种数据类型。\n1.namedtuple(): 生成可以使用名字来访问元素内容的tuple子类\n2.deque: 双端队列，可以快速的从另外一侧追加和推出对象\n3.Counter: 计数器，主要用来计数\n4.OrderedDict: 有序字典\n5.defaultdict: 带有默认值的字典\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e****\u003cstrong\u003e具名元组(namedtuple)\u003c/strong\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003cpre\u003e\u003ccode\u003etuple是一个不可变的集合，namedtuple是一个函数，她用来构建一个自定义的tuple对象，\n并且规定了tuple元素的个数，可通过属性而不是索引来引用tuple的某个元素。\n我们可以通过namedtuple很方便的自定义一个数据类型，它具有tuple 的不变属性又可以根据属性来引用。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e****想表示空间坐标点x为1， y为2，z为5的坐标\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efrom collections import namedtuple\npoint = namedtuple('坐标',['x','y','z'])  # 第二个参数既可以传可迭代对象\npoint1 = namedtuple('坐标','x y z')  # 也可以传字符串 但是字符串之间以空格隔开\np = point(1,2,5)\np1 = point1(2,3,6)print(p,p.x,p.y,p.z)  # 坐标(x=1, y=2, z=5) 1 2 5print(p1,p1.x,p1.y,p1.z)  # 坐标(x=2, y=3, z=6) 2 3 6\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e想表示一张黑桃A的扑克牌\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ecard = namedtuple('扑克牌','color number')\ncard1 = namedtuple('扑克牌',['color','number'])\nA = card('♠','A')\nB = card1('♠','A')print(A.color,A.number,A)  # ♠ A 扑克牌(color='♠', number='A')print(B.color,B.number,B)  # ♠ A 扑克牌(color='♠', number='A')\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e想表示一个城市\u003c/p\u003e","title":"【Python】时间/序列化/sys/os模块"},{"content":"\nPython正则表达式与re模块************\u0026gt; \u0026raquo;转到****思维导图************************************\u0026gt; \u0026raquo;转到********博客园****\n正则表达式与re模块的关系\n1.正则表达式是一门独立的技术，任何语言均可使用\n2.python中要想使用正则表达式需要通过re模块\n正则表达式应用场景\n推荐书籍：正则指引\n1.爬虫\n2.数据分析\n字符组概念\n在同一个位置可能出现的各种字符组成了一个字符组，在正则表达式中用[]表示(一个字符组每次只能匹配一个字符)\n小例子\n匹配0-9数字([0123456789]也支持简写[0-9])(如果想匹配横杠,转义即可) 匹配A-Z字母(依据上面的简写规则[A-Z]) 匹配a-z字母(依据上面的简写规则[a-z]) ps:这种上面到上面的范围必须是从小到大[a-Z]不行，[A-z]可以(但是内部有几个特殊符号)，因为内部对应的asicc码A在所有字母里面最小，z在所有字母里面最大 正则 待匹配字符 匹配结果 说明 [0123456789] 8 True 在一个字符组里枚举合法的所有字符，字符组里的任意一个字符 和\u0026quot;待匹配字符\u0026quot;相同都视为可以匹配 [0123456789] a False 由于字符组中没有\u0026quot;a\u0026quot;字符，所以不能匹配 [0-9]| 7| True| 也可以用-表示范围,[0-9]就和[0123456789]是一个意思\n[a-z]| s| True| 同样的如果要匹配所有的小写字母，直接用[a-z]就可以表示\n[A-Z]| B| True| [A-Z]就表示所有的大写字母\n[0-9a-fA-f]| e| True| 可以匹配数字，大小写形式的a～f，用来验证十六进制字符\n字符\n元字符\n| 匹配内容\n\u0026mdash;|\u0026mdash;\n. | 匹配除换行符以外的任意字符\n\\w| 匹配字母或数字或下划线\n\\s| 匹配任意的空白符\n\\d| 匹配数字\n\\n| 匹配一个换行符\n\\t| 匹配一个制表符\n\\b| 匹配一个单词的结尾\n^| 匹配字符串的开始\n$| 匹配字符串的结尾\n\\W| 匹配非字母或数字或下划线\n\\D| 匹配非数字\n\\S| 匹配非空白符\na|b| 匹配字符a或字符b\n()| 匹配括号内的表达式，也表示一个组\n[\u0026hellip;]| 匹配字符组中的字符\n[^\u0026hellip;]| 匹配除了字符组中字符的所有字符\n^:以什么什么开头 $:以什么什么结尾 ps:^与$连用能够精准匹配固定长度的目标字符,^只能出现在开头()，$只能出现结尾 |:或 ab|abc优先匹配前面的正则表达式ab，匹配上就不再用后面的abc，可以通过调换顺序修改优先级 ^:出现在[]中的^表示除了^后面的其他都匹配 [^a]除了a其他都匹配[^a-z]除了小写字母a-z其他都匹配 量词\n量词\n| 用法说明\n\u0026mdash;|\u0026mdash;\n*| 重复零次或更多次\n+| 重复一次或更多次\n?| 重复零次或一次\n{n}| 重复n次\n{n,}| 重复n次或更多次\n{n,m}| 重复n到m次\n.^$\n正则 待匹配字符 匹配结果 说明 海. 海燕海娇海东 海燕海娇海东 匹配所有\u0026quot;海.\u0026ldquo;的字符 ^海. 海燕海娇海东 海燕 只从开头匹配\u0026quot;海.\u0026rdquo; 海.$ 海燕海娇海东 海东 只匹配结尾的\u0026quot;海.$\u0026quot; .+?{}\n正则 待匹配字符 匹配结果 说明 李.? 李杰和李莲英和李二棍子 李杰 李莲 李二 ?表示重复零次或一次，即只匹配\u0026quot;李\u0026quot;后面一个任意字符 李.*| 李杰和李莲英和李二棍子| 李杰和李莲英和李二棍子| *表示重复零次或多次，即匹配\u0026quot;李\u0026quot;后面0或多个任意字符\n李.+| 李杰和李莲英和李二棍子| 李杰和李莲英和李二棍子| +表示重复一次或多次，即只匹配\u0026quot;李\u0026quot;后面1个或多个任意字\n李.{1,2}| 李杰和李莲英和李二棍子| 李杰和\n李莲英\n李二棍| {1,2}匹配1到2次任意字符\n字符集[] [^]\n正则 待匹配字符 匹配结果 说明 李[杰莲英二棍子]* 李杰和李莲英和李二棍子 李杰 李莲英 李二棍子 表示匹配\u0026quot;李\u0026quot;字后面[杰莲英二棍子]的字符任意次 李[^和]*| 李杰和李莲英和李二棍子| 李杰\n李莲英\n李二棍子| 表示匹配一个不是\u0026quot;和\u0026quot;的字符任意次\n[\\d]| 456bdha3| 4\n5\n6\n3| 表示匹配任意一个数字，匹配到4个结果\n[\\d]+| 456bdha3| 456\n3| 表示匹配任意个数字，匹配到2个结果\n分组 ()与 或 | [^]\n身份证号码是一个长度为15或18个字符的字符串，如果是15位则全部?️数字组成，首位不能为0；如果是18位，则前17位全部是数字，末位可能是数字或x，下面我们尝试用正则来表示：\n正则 待匹配字符 匹配结果 说明 ^[1-9]\\d{13,16}[0-9x]$ 110101198001017032 110101198001017032 表示可以匹配一个正确的身份证号 ^[1-9]\\d{13,16}[0-9x]$ 1101011980010170 1101011980010170 表示也可以匹配这串数字，但这并不是一个正确的身份证号码，它是一个16位的数 ^[1-9]\\d{14}(\\d{2}[0-9x])?$| 1101011980010170| False| 现在不会匹配错误的身份证号了\n()表示分组，将\\d{2}[0-9x]分成一组，就可以整体约束他们出现的次数为0-1次\n^([1-9]\\d{16}[0-9x]|[1-9]\\d{14})$| 110105199812067023| 110105199812067023| 表示先匹配[1-9]\\d{16}[0-9x]如果没有匹配上就匹配[1-9]\\d{14}\n*转义符 *\n在正则表达式中，有很多有特殊意义的是元字符，比如\\n和\\s等，如果要在正则中匹配正常的\u0026quot;\\n\u0026quot;而不是\u0026quot;换行符\u0026quot;就需要对\u0026quot;\u0026quot;进行转义，变成\u0026rsquo;\\'。\n在python中，无论是正则表达式，还是待匹配的内容，都是以字符串的形式出现的，在字符串中\\也有特殊的含义，本身还需要转义。所以如果匹配一次\u0026quot;\\n\u0026quot;,字符串中要写成\u0026rsquo;\\\\n\u0026rsquo;，那么正则里就要写成\u0026quot;\\\\\\\\n\u0026quot;,这样就太麻烦了。这个时候我们就用到了r\u0026rsquo;\\n\u0026rsquo;这个概念，此时的正则是r\u0026rsquo;\\\\n\u0026rsquo;就可以了。\n正则 待匹配字符 匹配结果 说明 \\n \\n False 因为在正则表达式中\\是有特殊意义的字符，所以要匹配\\n本身，用表达式\\n无法匹 \\\\n| \\n| True| 转义\\之后变成\\\\，即可匹配\n\u0026ldquo;\\\\\\\\n\u0026rdquo;| \u0026lsquo;\\\\n\u0026rsquo;| True| 如果在python中，字符串中的\u0026rsquo;'也需要转义，所以每一个字符串\u0026rsquo;'又需要转义一\nr\u0026rsquo;\\\\n\u0026rsquo;| r\u0026rsquo;\\n\u0026rsquo;| True| 在字符串之前加r，让整个字符串不转义\n贪婪匹配与非贪婪匹配(惰性匹配)\n贪婪匹配：在满足匹配时，匹配尽可能长的字符串，默认情况下，采用贪婪匹配\n正则 待匹配字符 匹配结果 说明 \u0026lt;.*\u0026gt; ","permalink":"https://zhong-er.com/posts/python%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E4%B8%8Ere%E6%A8%A1%E5%9D%97/","summary":"\u003cp\u003e\u003cimg alt=\"Python第十三课(模块1).png\" loading=\"lazy\" src=\"/assets/201907172541_862_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython正则表达式与re模块\u003c/strong\u003e************\u0026gt; \u0026raquo;转到****\u003ca href=\"https://mubu.com/doc/1cDg8PhNk8\" title=\"思维导图\"\u003e\u003cstrong\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/strong\u003e************************************\u003c/a\u003e\u003cstrong\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003c/strong\u003e********\u003ca href=\"https://www.cnblogs.com/renvip/p/11196211.html\" title=\"博客园\"\u003e博客园\u003c/a\u003e****\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e正则表达式与re模块的关系\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.正则表达式是一门独立的技术，任何语言均可使用\u003c/p\u003e\n\u003cp\u003e2.python中要想使用正则表达式需要通过re模块\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e正则表达式应用场景\u003c/strong\u003e\u003cbr\u003e\n推荐书籍：正则指引\u003c/p\u003e\n\u003cp\u003e1.爬虫\u003c/p\u003e\n\u003cp\u003e2.数据分析\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e字符组概念\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e在同一个位置可能出现的各种字符组成了一个字符组，在正则表达式中用[]表示(一个字符组每次只能匹配一个字符)\u003c/p\u003e\n\u003cp\u003e小例子\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e匹配0-9数字([0123456789]也支持简写[0-9])(如果想匹配横杠,转义即可)\n匹配A-Z字母(依据上面的简写规则[A-Z])\n匹配a-z字母(依据上面的简写规则[a-z])\nps:这种上面到上面的范围必须是从小到大[a-Z]不行，[A-z]可以(但是内部有几个特殊符号)，因为内部对应的asicc码A在所有字母里面最小，z在所有字母里面最大\n\u003c/code\u003e\u003c/pre\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003e正则\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e待匹配字符\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e匹配结果\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003e说明\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e[0123456789]\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e8\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eTrue\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e在一个字符组里枚举合法的所有字符，字符组里的任意一个字符\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e和\u0026quot;待匹配字符\u0026quot;相同都视为可以匹配\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e[0123456789]\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ea\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eFalse\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e由于字符组中没有\u0026quot;a\u0026quot;字符，所以不能匹配\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e[0-9]| 7| True| 也可以用-表示范围,[0-9]就和[0123456789]是一个意思\u003c/p\u003e\n\u003cp\u003e[a-z]| s| True| 同样的如果要匹配所有的小写字母，直接用[a-z]就可以表示\u003c/p\u003e\n\u003cp\u003e[A-Z]| B| True| [A-Z]就表示所有的大写字母\u003c/p\u003e\n\u003cp\u003e[0-9a-fA-f]| e| True| 可以匹配数字，大小写形式的a～f，用来验证十六进制字符\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e字符\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e元字符\u003cbr\u003e\n| 匹配内容\u003c/p\u003e\n\u003cp\u003e\u0026mdash;|\u0026mdash;\u003cbr\u003e\n. | 匹配除换行符以外的任意字符\u003cbr\u003e\n\\w| 匹配字母或数字或下划线\u003cbr\u003e\n\\s| 匹配任意的空白符\u003cbr\u003e\n\\d| 匹配数字\u003cbr\u003e\n\\n| 匹配一个换行符\u003cbr\u003e\n\\t| 匹配一个制表符\u003cbr\u003e\n\\b| 匹配一个单词的结尾\u003cbr\u003e\n^| 匹配字符串的开始\u003cbr\u003e\n$| 匹配字符串的结尾\u003cbr\u003e\n\\W| 匹配非字母或数字或下划线\u003c/p\u003e","title":"【Python】正则表达式与re模块"},{"content":"\nPython模块******\u0026gt; \u0026raquo;转到**思维导图********************\u0026gt; \u0026raquo;转到博客园************\n1.什么是模块？\n1模块就是一系列工能的结合体 2模块的三种来源： 1.内置的(python解释器自带) 2.第三方的(别人写的) 3.自定义的(你自己写的) 3模块的四种表现形式 1.使用python编写的py文件(也就意味着py文件也可以称之为模块:一个py文件也可以称之为一个模块) 2.已被编译为共享库或DLL的C或C++扩展(了解) 3.包好一组模块的包：把一系列模块组织到一起的文件夹(文件夹下有一个__init__.py文件，该文件夹称之为包) 包:一系列py文件的结合体 4.使用C编写并链接到python解释器的内置模块 2.为什么要用模块**？**\n1.从文件级别组织程序，更方便管理。 当多个文件中都需要使用相同的方法的时候 可以将该公共的方法写到一个py文件中，其他的文件以模块的形式导过去直接调用即可 2.拿来主义，提升开发效率 3如何使用模块？\n1.import的使用 模块可以包含可执行的语句和函数的定义，这些语句的目的是初始化模块，它们只在模块名第一次遇到导入import语句时才执行 2.在第一次导入模块时会做三件事，重复导入会直接引用内存中已经加载好的结果 1.首先创建一个执行文件(run.py)的名称空间，执行模块文件 在模块中定义的函数和方法若是使用到了global时访问的就是这个名称空间 2.运行模块文件(md.py)中的代码将产生的名字与值存放到执行文件(run.py)的名称空间中 在新创建的命名空间中执行模块中包含的代码 3.在执行文件中产生一个执行指向名称空间的名字(md) 创建名字来引用该命名空间 3from...import...句式缺点: 1.访问模块中的名字不需要加模块名前缀 2.在访问模块中的名字可能会与当前执行文件中的名字冲突 4补充知识点： __name__ 当文件被当做执行文件执行的时候__name__打印的结果是__main__ 当文件被当做模块导入的时候__name__打印的结果是模块名(没有后缀) __all__ __all__可以指定当所在py文件被当做模块导入的时候,可以限制导入者能够拿到的名字个数 from md1 import * 一次性将md1模块中的名字全部加载过来 不推荐使用 并且你根本不知道到底有哪些名字可以用 异常有两大类 1.语法结构错误:需要你当场修改 异常捕获没法完成 2.逻辑错误:异常捕获可以处理 4.循环导入\n1.如果出现循环导入问题 那么一定是你的程序设计的不合理。循环导入问题应该在程序设计阶段就应该避免。 2.解决循环导入问题的方式 1.方式1：将循环导入的句式写在文件最下方() 2.方式2：函数内导入模块 5.模块的查找顺序\n1.先从内存中找 2.再从内置中找 3.最后环境变量(sys.path)中找 一定要分清楚谁是执行文件谁是被导入文件sys.path返回的是一个大列表,里面放了一对文件路径,第一个路径永远是执行文件所在的文件夹注意:py文件名不应该与模块名(内置的,第三方)冲突 6.绝对导入与相对导入\n1绝对导入 绝对导入必须依据执行文件所在的文件夹路径为准 绝对导入无论在执行文件中还是被导入文件都适用 2相对导入 .代表的当前路径 ..代表的上一级路径 ...代表的是上上一级路径 注意相对导入不能再执行文件中使用 相对导入只能在被导入的模块中使用,使用相对导入就不需要考虑执行文件到底是谁，只需要知道模块与模块之间路径关系 7.软件开发规范\n项目名bin文件夹 start.py项目启动文件 conf文件夹 settings.py项目配置文件 core文件 src.py项目核心逻辑文件 db文件夹 数据库文件 lib文件夹 common.py项目所用到的公共的功能 log文件夹 log.log项目的日志文件 readme文本文件 介绍项目 start.py格式**\n**\nimport sys import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) sys.path.append(BASE_DIR) \u0026quot;\u0026quot;\u0026quot; pycharm会自动将你新建的最顶层的目录自动添加到环境变量中 上面这两句话 不是针对你的 是针对下载你这个软件的用户 \u0026quot;\u0026quot;\u0026quot; from core import src if __name__ == '__main__': src.run() # 1.将core文件路径添加到system path中 ...太low # 2.将ATM文件夹添加到system path中 END\n","permalink":"https://zhong-er.com/posts/python%E6%A8%A1%E5%9D%97/","summary":"\u003cp\u003e\u003cimg alt=\"Python第十三课(模块1).png\" loading=\"lazy\" src=\"/assets/201907164750_6602_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython模块\u003c/strong\u003e******\u0026gt; \u0026raquo;转到**\u003ca href=\"https://mubu.com/doc/2SBPS0hBq8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e********************\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11203235.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e************\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e1.什么是模块？\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1模块就是一系列工能的结合体\n2模块的三种来源：\n    1.内置的(python解释器自带)\n    2.第三方的(别人写的)\n    3.自定义的(你自己写的)\n3模块的四种表现形式\n    1.使用python编写的py文件(也就意味着py文件也可以称之为模块:一个py文件也可以称之为一个模块)\n    2.已被编译为共享库或DLL的C或C++扩展(了解)\n    3.包好一组模块的包：把一系列模块组织到一起的文件夹(文件夹下有一个__init__.py文件，该文件夹称之为包) 包:一系列py文件的结合体\n    4.使用C编写并链接到python解释器的内置模块\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e2.为什么要用模块\u003c/strong\u003e**？**\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1.从文件级别组织程序，更方便管理。\n当多个文件中都需要使用相同的方法的时候 可以将该公共的方法写到一个py文件中，其他的文件以模块的形式导过去直接调用即可\n2.拿来主义，提升开发效率\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e3如何使用模块？\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1.import的使用\n    模块可以包含可执行的语句和函数的定义，这些语句的目的是初始化模块，它们只在模块名第一次遇到导入import语句时才执行\n2.在第一次导入模块时会做三件事，重复导入会直接引用内存中已经加载好的结果\n    1.首先创建一个执行文件(run.py)的名称空间，执行模块文件\n在模块中定义的函数和方法若是使用到了global时访问的就是这个名称空间\n    2.运行模块文件(md.py)中的代码将产生的名字与值存放到执行文件(run.py)的名称空间中\n        在新创建的命名空间中执行模块中包含的代码\n    3.在执行文件中产生一个执行指向名称空间的名字(md)\n        创建名字来引用该命名空间\n3from...import...句式缺点:\n    1.访问模块中的名字不需要加模块名前缀\n    2.在访问模块中的名字可能会与当前执行文件中的名字冲突\n4补充知识点：\n    __name__\n        当文件被当做执行文件执行的时候__name__打印的结果是__main__\n        当文件被当做模块导入的时候__name__打印的结果是模块名(没有后缀)\n    __all__\n        __all__可以指定当所在py文件被当做模块导入的时候,可以限制导入者能够拿到的名字个数\n    from md1 import *\n        一次性将md1模块中的名字全部加载过来 不推荐使用 并且你根本不知道到底有哪些名字可以用\n    异常有两大类\n        1.语法结构错误:需要你当场修改 异常捕获没法完成\n        2.逻辑错误:异常捕获可以处理\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e4.循环导入\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1.如果出现循环导入问题 那么一定是你的程序设计的不合理。循环导入问题应该在程序设计阶段就应该避免。\n2.解决循环导入问题的方式\n    1.方式1：将循环导入的句式写在文件最下方()\n    2.方式2：函数内导入模块\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e5.模块的查找顺序\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1.先从内存中找\n2.再从内置中找\n3.最后环境变量(sys.path)中找\n    一定要分清楚谁是执行文件谁是被导入文件sys.path返回的是一个大列表,里面放了一对文件路径,第一个路径永远是执行文件所在的文件夹注意:py文件名不应该与模块名(内置的,第三方)冲突\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e6.绝对导入与相对导入\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1绝对导入\n    绝对导入必须依据执行文件所在的文件夹路径为准\n    绝对导入无论在执行文件中还是被导入文件都适用\n2相对导入\n    .代表的当前路径\n    ..代表的上一级路径\n    ...代表的是上上一级路径\n    注意相对导入不能再执行文件中使用\n    相对导入只能在被导入的模块中使用,使用相对导入就不需要考虑执行文件到底是谁，只需要知道模块与模块之间路径关系\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e7.软件开发规范\u003c/strong\u003e\u003c/p\u003e","title":"【Python】模块"},{"content":"\nPython迭代器/生成器******\u0026gt; \u0026raquo;转到**思维导图****\u0026gt; \u0026raquo;转到博客园****\n1.迭代\n1.1什么是迭代：\n更新换代(重复)的过程，每次迭代都必须基于上一次的结果\n1.2什么是迭代器：\n迭代取值的工具\n1.3为什么要有迭代器：\n迭代器提供了一种不依赖于索引取值的方式\n对于序列类型：字符串、列表、元组，我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的，若还想取出其内部包含的元素，则必须找出一种不依赖于索引的迭代方式，这就是迭代器\n1.4什么是可迭代对象：\n可迭代对象指的是内置有__iter__方法的对象，即obj.iter\n1.5什么是迭代器对象：\n可迭代对象执行obj.iter()得到的结果就是迭代器对象。而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象。\n1.6注意：\n迭代器对象一定是可迭代对象，而可迭代对象不一定是迭代器对象\n需要迭代取值的数据类型：字符串str、列表list、元组tuple、字典dict、集合set、文件对象\n文件对象执行内置的__iter__之后还是本身 没有任何变化，文件对象本身就是迭代器对象\n针对双下线开头双下划线结尾的方法，推荐读:双下+方法名\n2.迭代器取值：\n迭代器取值 调用__next__\n迭代器对象无论执行多少次__iter__方法得到的还是迭代器对象本身\n2.1异常报错StopIteration：取完了\n2.2异常处理：\nwhile True: try: print(iter_d.__next__()) except StopIteration: print('老母猪生不动了') break 2.3迭代器取值的特点：只能往后依次取，不能后退\n2.4迭代器取值的优点：\n1.不依赖于索引取值\n2.内存中永远只占一份空间,不会导致内存溢出\n2.5迭代器取值的缺点：\n1.不能够获取指定的元素\n2.取完之后会报StopIteration错\n3.for循环的本质\n1.将in后面的对象调用__iter__转换成迭代器对象\n2.调用__next__迭代取值\n3.内部有异常捕获StopIteration,当__next__报这个错 自动结束循环\n4.生成器\n只要函数内部包含有yield关键字，那么函数名()的到的结果就是生成器，并且不会执行函数内部代码\n生成器就是迭代器，可以用__next__方法取值\nyield后面跟的值就是调用迭代器__next__方法你能得到的值\nyield既可以返回一个值也可以返回多个值，并且多个值也是按照元组的形式返回\n可以用.send给yield左边的变量传参，但必须先将代码运行至yield才能为期传值\nyeild\n1.帮你提供了一种自定义生成器方式\n2.会帮你将函数的运行状态暂停住\n3.可以返回值\nyeid与return异同:\n相同点：都可以返回值,并且都可以返回多个\n不同点：yield可以返回多次值,而return只能返回一次函数立即结束，yield还可以接受外部传入的值\n5.生成器表达式\n例如：res = (i for i in range(1,10) if i != 4)\n一道面试题\ndef add(n,i): return n+i def test(): for i in range(4): yield i g=test() for n in [1,10]: g=(add(n,i) for i in g) # 第一次for循环g=(add(n,i) for i in test()) # 第二次for循环g=(add(n,i) for i in (add(n,i) for i in test())) print(n) res=list(g) \u0026quot;\u0026quot;\u0026quot; for i in (add(10,i) for i in test()): 会执行所有的生成器内部的代码 add(n,i) \u0026quot;\u0026quot;\u0026quot; #A. res=[10,11,12,13] #B. res=[11,12,13,14] #C. res=[20,21,22,23] 答案 #D. res=[21,22,23,24] 6.常用内置方法\nabs()取绝对值。 all()传入一个可迭代对象只要对象中一个元素为False就返回False any()传入一个可迭代对象只要对象中一个元素为True就返回True bin()十进制转二进制 返回一个整数 int的二进制表示。 oct()十进制转八进制 返回一个整数 int的二进制表示。 hex()十进制转16进制 返回一个整数 int的二进制表示。 int()用于将一个字符串或数字转换为整型,如果传入两个参数，第一个是要转的进制数，第二个是表示当前数是多少进制，最终将进制数转为十进制数。 bool()将给定参数转换为布尔类型，如果没有参数，返回 False callable()用于检查一个对象是否是可调用的。 char()将数字转换成ascii码表对应的字符。 unichr() 函数 和 chr()函数功能基本一样， 只不过是返回 unicode 的字符。 ord()将字符按照ascii表转成对应的数字。 dict()用于创建一个字典。 dir()获取当前对象名称空间里面的名字。 divmod() 函数把除数和余数运算结果结合起来，返回一个包含商和余数的元组(a // b, a % b) enumerate()函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列，同时列出数据和数据下标，一般用在 for 循环当中。 eval()用来执行一个字符串表达式，并返回表达式的值。 exec()执行储存在字符串或文件中的 Python 语句，相比于 eval，exec可以执行更复杂的 Python 代码。其返回值为none format()格式化输出 globals()会以字典类型返回当前位置的全部全局变量。 locals()会以字典类型返回当前位置的全部局部变量。 help()查看函数注释说明 isinstance()用来判断一个对象是否是一个已知的类型，类似 type()。 next() 返回迭代器的下一个项目。 open() 函数用于打开一个文件，创建一个 file 对象，相关的方法才可以调用它进行读写。 pow() 方法返回 x^y（x的y次方） 的值。 reverse() 函数用于反向列表中元素。 round() 方法返回浮点数x的四舍五入值。 END\n","permalink":"https://zhong-er.com/posts/python%E8%BF%AD%E4%BB%A3%E5%99%A8%E7%94%9F%E6%88%90%E5%99%A8/","summary":"\u003cp\u003e\u003cimg alt=\"Python第十二课(迭代器_生成器).png\" loading=\"lazy\" src=\"/assets/201907152595_5668_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython迭代器/生成器\u003c/strong\u003e******\u0026gt; \u0026raquo;转到**\u003ca href=\"https://mubu.com/doc/3Hk6DuxYi8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11191167.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e1.迭代\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.1什么是迭代：\u003c/p\u003e\n\u003cp\u003e更新换代(重复)的过程，每次迭代都必须基于上一次的结果\u003c/p\u003e\n\u003cp\u003e1.2什么是迭代器：\u003c/p\u003e\n\u003cp\u003e迭代取值的工具\u003c/p\u003e\n\u003cp\u003e1.3为什么要有迭代器：\u003c/p\u003e\n\u003cp\u003e迭代器提供了一种不依赖于索引取值的方式\u003cbr\u003e\n对于序列类型：字符串、列表、元组，我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的，若还想取出其内部包含的元素，则必须找出一种不依赖于索引的迭代方式，这就是迭代器\u003c/p\u003e\n\u003cp\u003e1.4什么是可迭代对象：\u003c/p\u003e\n\u003cp\u003e可迭代对象指的是内置有__iter__方法的对象，即obj.\u003cstrong\u003eiter\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.5什么是迭代器对象：\u003c/p\u003e\n\u003cp\u003e可迭代对象执行obj.\u003cstrong\u003eiter\u003c/strong\u003e()得到的结果就是迭代器对象。而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象。\u003c/p\u003e\n\u003cp\u003e1.6注意：\u003c/p\u003e\n\u003cp\u003e迭代器对象一定是可迭代对象，而可迭代对象不一定是迭代器对象\u003c/p\u003e\n\u003cp\u003e需要迭代取值的数据类型：字符串str、列表list、元组tuple、字典dict、集合set、文件对象\u003c/p\u003e\n\u003cp\u003e文件对象执行内置的__iter__之后还是本身 没有任何变化，文件对象本身就是迭代器对象\u003c/p\u003e\n\u003cp\u003e针对双下线开头双下划线结尾的方法，推荐读:双下+方法名\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2.迭代器取值：\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e迭代器取值 调用__next__\u003c/p\u003e\n\u003cp\u003e迭代器对象无论执行多少次__iter__方法得到的还是迭代器对象本身\u003c/p\u003e\n\u003cp\u003e2.1异常报错StopIteration：取完了\u003c/p\u003e\n\u003cp\u003e2.2异常处理：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewhile True: \n    try: \n        print(iter_d.__next__()) \n    except StopIteration: \n        print('老母猪生不动了')\n    break\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2.3迭代器取值的特点：只能往后依次取，不能后退\u003c/p\u003e\n\u003cp\u003e2.4迭代器取值的优点：\u003c/p\u003e\n\u003cp\u003e1.不依赖于索引取值\u003c/p\u003e\n\u003cp\u003e2.内存中永远只占一份空间,不会导致内存溢出\u003c/p\u003e\n\u003cp\u003e2.5迭代器取值的缺点：\u003c/p\u003e\n\u003cp\u003e1.不能够获取指定的元素\u003c/p\u003e\n\u003cp\u003e2.取完之后会报StopIteration错\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3.for循环的本质\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.将in后面的对象调用__iter__转换成迭代器对象\u003c/p\u003e\n\u003cp\u003e2.调用__next__迭代取值\u003c/p\u003e\n\u003cp\u003e3.内部有异常捕获StopIteration,当__next__报这个错 自动结束循环\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4.生成器\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e只要函数内部包含有yield关键字，那么函数名()的到的结果就是生成器，并且不会执行函数内部代码\u003c/p\u003e\n\u003cp\u003e生成器就是迭代器，可以用__next__方法取值\u003c/p\u003e\n\u003cp\u003eyield后面跟的值就是调用迭代器__next__方法你能得到的值\u003c/p\u003e\n\u003cp\u003eyield既可以返回一个值也可以返回多个值，并且多个值也是按照元组的形式返回\u003c/p\u003e\n\u003cp\u003e可以用.send给yield左边的变量传参，但必须先将代码运行至yield才能为期传值\u003c/p\u003e\n\u003cp\u003eyeild\u003c/p\u003e\n\u003cp\u003e1.帮你提供了一种自定义生成器方式\u003c/p\u003e\n\u003cp\u003e2.会帮你将函数的运行状态暂停住\u003c/p\u003e\n\u003cp\u003e3.可以返回值\u003c/p\u003e\n\u003cp\u003eyeid与return异同:\u003c/p\u003e\n\u003cp\u003e相同点：都可以返回值,并且都可以返回多个\u003c/p\u003e\n\u003cp\u003e不同点：yield可以返回多次值,而return只能返回一次函数立即结束，yield还可以接受外部传入的值\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e5.生成器表达式\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e例如：res = (i for i in range(1,10) if i != 4)\u003c/p\u003e","title":"【Python】迭代器/生成器"},{"content":"\nPython函数 \u0026raquo;\u0026gt;转到思维导图****\u0026gt; \u0026raquo;转到博客园\n递归函数\n函数在调用阶段直接或间接的又调用自身\n如果一个函数在内部调用自身本身，这个函数就是递归函数。\n举例\n# 举个例子，我们来计算阶乘n! = 1 x 2 x 3 x ... x n，用函数fact(n)表示，可以看出： # fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n # 所以，fact(n)可以表示为n x fact(n-1)，只有n=1时需要特殊处理。 # 于是，fact(n)用递归的方式写出来就是： def fact(n): if n==1: return 1 return n * fact(n - 1) # 如果我们计算fact(5)，可以根据函数定义看到计算过程如下： ===\u0026gt; fact(5) ===\u0026gt; 5 * fact(4) ===\u0026gt; 5 * (4 * fact(3)) ===\u0026gt; 5 * (4 * (3 * fact(2))) ===\u0026gt; 5 * (4 * (3 * (2 * fact(1)))) ===\u0026gt; 5 * (4 * (3 * (2 * 1))) ===\u0026gt; 5 * (4 * (3 * 2)) ===\u0026gt; 5 * (4 * 6) ===\u0026gt; 5 * 24 ===\u0026gt; 120 扩展 import sys\n# import sys # print(sys.getrecursionlimit()) # 不是很精确 # sys.setrecursionlimit(2000) # 也可以手动设定最大地柜次数 算法之二分法\n算法：解决问题的高效率的方法\n二分法：容器类型里面的数字必须有大小顺序\n# 快速查找一个值是否存在一个有序列表里 l = [1,2,3,4,5,6,7,8,9,10] num = 9 def get_num(l,num): if not l: # 如果列表为空还没找到就返回给用户不存在 print('不存在') return middle_num = len(l)//2 if num \u0026gt; l[middle_num]: num_right = l[middle_num+1:] get _num(num_right,num) elif num \u0026lt; l[middle_num]: num_left = l[0:middle_num] get_num(num_left,num) else: print('find it',num) get_num(l,num) 三元表达式\n三元表达式固定表达式\n值1 if 条件 else 值2\n条件成立 值1\n条件不成立 值2\n三元表达式的应用场景只推荐只有两种的情况的可能下\ndef my_max(x,y): if x \u0026gt; y: return x else: return y \u0026quot;\u0026quot;\u0026quot; 当x大的时候返回x当y大的时候返回y 当某个条件成立做一件事,不成立做另外一件事 \u0026quot;\u0026quot;\u0026quot; x = 99999 y = 9898898 res = x if x \u0026gt; y else y # 如果if后面的条件成立返回if前面的值 否则返回else后面的值 print(res) 列表生成式\n要生成list[1,2,3,4,5]可以用list(range(1,6)) 要生成[1X1,2X2,3X3,4X4,5X5]一般方法是循环: l = [] for x in rang(1,6): l.append(x*x) 但是for循环太繁琐，列表生成式则可以一行语句生成: [x*x for x in range(1,6)] for循环后可以加上if判断 [x*x for x in range(1,6) if x%2 == 0] 后面不支持再加else的情况 # 先for循环依次取出列表里面的每一个元素 # 然后交由if判断 条件成立才会交给for前面的代码 # 如果条件不成立 当前的元素 直接舍弃 字典生成式\n通过列表生成式，我们把[]换成{},推演出字典生成式 l1 = ['name','password','hobby'] l2 = ['jason','123','DBJ','egon'] d = {} for i,j in enumerate(l1): d[j] = l2[i] print(d) l1 = ['jason','123','read'] d = {i:j for i,j in enumerate(l1) if j != '123'} print(d) 集合生成式\nres = {i for i in range(10) if i != 4} print(res) 生成器\n我们继续根据上面的列生成式表和字典生成式推演 把[]换成(),这个时候并不是元组生成式，而是生成器 返回的是个生成器 \u0026gt;\u0026gt;\u0026gt; g = (x * x for x in range(10)) \u0026gt;\u0026gt;\u0026gt; g \u0026lt;generator object \u0026lt;genexpr\u0026gt; at 0x1022ef630\u0026gt; 而我们要想取生成器的元素，需要用for循环 \u0026gt;\u0026gt;\u0026gt; g = (x * x for x in range(10)) \u0026gt;\u0026gt;\u0026gt; for n in g: ... print(n) 匿名函数\n没有名字的函数，临时存在，用完就没了 (lambda x: x * x) # :左边的相当于函数的形参 # :右边的相当于函数的返回值 # 匿名函数通常不会单独使用,是配合内置函数一起使用 # 匿名函数有个限制，就是只能有一个表达式，不用写return，返回值就是该表达式的结果。 \u0026gt;\u0026gt;\u0026gt; f = lambda x: x * x \u0026gt;\u0026gt;\u0026gt; f \u0026lt;function \u0026lt;lambda\u0026gt; at 0x101c6ef28\u0026gt; \u0026gt;\u0026gt;\u0026gt; f(5) 25 常用内置函数\nmap/reduce\nmap()函数接收两个参数，一个是函数，一个是序列 map将传入的函数一次作用到序列的每个元素，并把结果作为新的序列返回。 例如我们把一个匿名函数lambda x: x * x作用[1,2,3,4,5]上 res = map(lambda x:x*x,[1,2,3,4,5]) reduce()把一个函数作用在一个序列上，这个函数必须接收两个参数， reduce把结果继续和序列的下一个元素做累计计算。 例如我们对一个序列求和 res = reduce(lambda x,y:x+y,[1,2,3,4,5]) filter\nfilter()函数用于过滤序列，和map()类似，filter()也接收一个函数和一个序列。 和map()不同的是，filter()把传入的函数依次作用于每个元素， 然后根据返回值是True还是False决定保留还是丢弃该元素。 例如在一个列表中删掉偶数只保留奇数 list(filter(lambda x:x%2==1,[1,2,3,4,5,6])) sorted\nsorted()函数就可以对list进行排序： sorted([36, 5, -12, 9, -21]) sorted()函数还可以reverse来实现自定义的排序， \u0026gt;\u0026gt;\u0026gt; sorted([2,4,3,1,5], reverse=True) [5,4,3,2,1] zip\nzip()函数用于将序列作为参数，将序列中对应的元素打包成一个个元组，然后返回由这些元组组成的列表 如果各个序列的元素个数不一致，则返回列表长度与最短的对象相同，利用 * 号操作符，可以将元组解压为列表。 例如 \u0026gt;\u0026gt;\u0026gt;a = [1,2,3] \u0026gt;\u0026gt;\u0026gt;b = [4,5,6] \u0026gt;\u0026gt;\u0026gt;c = [4,5,6,7,8] \u0026gt;\u0026gt;\u0026gt;zipped = zip(a,b) # 打包为元组的列表 [(1, 4), (2, 5), (3, 6)] \u0026gt;\u0026gt;\u0026gt; zip(a,c) # 元素个数与最短的列表一致 [(1, 4), (2, 5), (3, 6)] \u0026gt;\u0026gt;\u0026gt; zip(*zipped) # 与 zip 相反，*zipped 可理解为解压，返回二维矩阵式 [(1, 2, 3), (4, 5, 6)] END\n","permalink":"https://zhong-er.com/posts/46-python%E5%87%BD%E6%95%B0/","summary":"\u003cp\u003e\u003cimg alt=\"Python第十一课(函数4).png\" loading=\"lazy\" src=\"/assets/201907129002_6648_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython函数 \u0026raquo;\u0026gt;转到\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/1IPnO44N88\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11178851.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e递归函数\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e函数在调用阶段直接或间接的又调用自身\u003c/p\u003e\n\u003cp\u003e如果一个函数在内部调用自身本身，这个函数就是递归函数。\u003c/p\u003e\n\u003cp\u003e举例\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 举个例子，我们来计算阶乘n! = 1 x 2 x 3 x ... x n，用函数fact(n)表示，可以看出：\n# fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n\n# 所以，fact(n)可以表示为n x fact(n-1)，只有n=1时需要特殊处理。\n# 于是，fact(n)用递归的方式写出来就是：\ndef fact(n):\n    if n==1:\n        return 1\n    return n * fact(n - 1)\n# 如果我们计算fact(5)，可以根据函数定义看到计算过程如下：\n===\u0026gt; fact(5)\n===\u0026gt; 5 * fact(4)\n===\u0026gt; 5 * (4 * fact(3))\n===\u0026gt; 5 * (4 * (3 * fact(2)))\n===\u0026gt; 5 * (4 * (3 * (2 * fact(1))))\n===\u0026gt; 5 * (4 * (3 * (2 * 1)))\n===\u0026gt; 5 * (4 * (3 * 2))\n===\u0026gt; 5 * (4 * 6)\n===\u0026gt; 5 * 24\n===\u0026gt; 120\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e扩展 import sys\u003c/p\u003e","title":"【Python】函数"},{"content":"\nPython装饰器\u0026gt; \u0026raquo;转到****思维导图********\u0026gt; \u0026raquo;转到博客园\n闭包函数\n1.定义在函数内部的函数\n2.内部函数内部函数引用了外部函数名称空间作用域的名字\n只要满足以上两点都叫做闭包函数\ndef func(): name = jason' # 先在内部找name没有再往上层找 有了就停止查找 def inner(): print(name) # 现在内部找name没有再往上层找 return inner f = func() # f获取到的是inner的内存地址 f() # 相当于执行 inner() # 函数在定义阶段名字的查找孙旭就已经固定死了，不会因为函数调用位置的变化而变化 给函数传参的两种方式\n1.直接传参\ndef index(name): print(name) 2.闭包\ndef outter(name): # name = 'jason' def index(): print(name) return index 装饰器\n器：就是一个工具\n装饰：就是给被装饰对象添加新的功能\n开放封闭原则：\n开放：对扩展开放\n封闭：对修改封闭\n装饰器(可调用对象)必须遵循的两个原则：\n1.不改变被装饰对象的源代码\n2.不改变被装饰对象(可调用对象)调用方法\n可调用：callable\ndef new(): print('这是原始函数') def outter(func): def inner(): print('这是内部函数，这里可以加新功能') func() # 这里的func是你想调用的最原始的函数的内存地址,加上()直接调用 print('这是内部函数，这里可以加新功能') return inner # 这里不加括号 返回的是inner函数的内存地址 new = outter(new) # 括号内的new指向的是最原始的new函数的内存地址，前面的new获取的是outter函数的返回值，返回值我们自定义了返回 inner函数的内存地址 # 总结：前面的new获取的是inner的内存地址，后面括号内的new指向的就是最原始的new的内存地址 new() 扩展：import time\nimport time time.time() 获取时间戳(秒) 当前时间距离1970-1-1 00:00:00 所经过的秒数 time.sleep(seconds) 让程序暂定几秒钟 装饰器语法糖\n把装饰器赋值给最近的可调用对象\n将最近的可调用对象的名字当做参数自动传入装饰器外层函数\n书写时两者之间不要空行，应紧紧挨着\n# 先写装饰器 def outter(func): def inner(): print('这里可以加新功能') func() # 这里的func是你想调用的最原始的函数的内存地址,加上()直接调用 print('这里可以加新功能') return inner # 这里不加括号 返回的是inner函数的内存地址 @outter # new = outter(new) 把装饰器赋值给最近的可调用对象，并将对象名传入。 def new(): print('这是原始函数') new() 装饰器修复技术\n1.返回原来的函数的函数名\n2.返回原来的函数的注释\nfrom functools import wraps def outter(func): @ wraps(func) # 装饰器修复加在最内层函数正上方 def inner(*args,**kwargs): # * **在形参中使用 # 执行被装饰函数之前你可以做的操作 res = func(*args,**kwargs) # * # 执行被装饰函数之后你可以做到操作 return res return inner @outter def index(username,*args,**kwargs): \u0026quot;\u0026quot;\u0026quot;index注释\u0026quot;\u0026quot;\u0026quot; pass print(index) 无参装饰器\nfrom functools import wraps def outter(func): @wraps(func) # 加在最内层函数正上方 def inner(*args,**kwargs): # *和**在形参中使用 print('这里可以加新功能') # 执行被装饰函数之前你可以做的操作 func(*args,**kwargs) # 这里的func是你想调用的最原始的函数的内存地址,加上()直接调用， *和**在实参中使用 print('这里可以加新功能') # 执行被装饰函数之后你可以做到操作 return inner # 这里不加括号 返回的是inner函数的内存地址 @outter # new = outter(new) 把装饰器赋值给最近的可调用对象，并将对象名传入。 def new(): print('这是原始函数') new() 有参装饰器\nfrom functools import wraps choice = input('\u0026gt;\u0026gt;\u0026gt;:') def wrappers(data): def outter(func): @wraps(func) # 加在最内层函数正上方 def inner(*args,**kwargs): if data == '1': # 如果用户输入‘1’那么执行index() # 执行被装饰函数之前你可以做的操作 res = func(*args,**kwargs) # * **在实参中使用 res接收原始函数的返回值 # 执行被装饰函数之后你可以做到操作 return res return 999 return inner return outter @wrappers(choice) # index = wrappers(index) def index() print('123') return 666 # 原始函数的返回值 index() 多个装饰器\n1. 装饰的时候 从下往上执行\n2. 执行的时候 从上往下执行\ndef outter1(func1): #func1=wrapper2的内存地址 print('加载了outter1') def wrapper1(*args,**kwargs): print('执行了wrapper1') res1=func1(*args,**kwargs) return res1 return wrapper1 def outter2(func2): #func2=wrapper3的内存地址 print('加载了outter2') def wrapper2(*args,**kwargs): print('执行了wrapper2') res2=func2(*args,**kwargs) return res2 return wrapper2 def outter3(func3): # func3=最原始的那个index的内存地址 print('加载了outter3') def wrapper3(*args,**kwargs): print('执行了wrapper3') res3=func3(*args,**kwargs) return res3 return wrapper3 @outter1 # outter1(wrapper2的内存地址)======\u0026gt;index=wrapper1的内存地址 @outter2 # outter2(wrapper3的内存地址)======\u0026gt;wrapper2的内存地址 @outter3 # outter3(最原始的那个index的内存地址)===\u0026gt;wrapper3的内存地址 def index(): print('from index') print('======================================================') index() # 1. 加载顺序(outter函数的调用顺序):自下而上 # 2. 执行顺序(wrapper函数的执行顺序):自上而下 END\n","permalink":"https://zhong-er.com/posts/python%E8%A3%85%E9%A5%B0%E5%99%A8/","summary":"\u003cp\u003e\u003cimg alt=\"Python第十课(函数3).png\" loading=\"lazy\" src=\"/assets/201907122677_6873_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython装饰器\u003c/strong\u003e\u0026gt; \u0026raquo;转到****\u003ca href=\"https://mubu.com/doc/IAt9WRlRa\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e********\u003c/a\u003e\u003cstrong\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11178817.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e闭包函数\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.定义在函数内部的函数\u003c/p\u003e\n\u003cp\u003e2.内部函数内部函数引用了外部函数名称空间作用域的名字\u003c/p\u003e\n\u003cp\u003e只要满足以上两点都叫做闭包函数\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef func():\n    name = jason'  # 先在内部找name没有再往上层找 有了就停止查找\n    def inner():\n        print(name)  # 现在内部找name没有再往上层找\n    return inner\nf = func()  # f获取到的是inner的内存地址\nf()  # 相当于执行 inner()\n# 函数在定义阶段名字的查找孙旭就已经固定死了，不会因为函数调用位置的变化而变化\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e给函数传参的两种方式\u003c/p\u003e\n\u003cp\u003e1.直接传参\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef index(name):\n    print(name)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2.闭包\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef outter(name):\n    # name = 'jason'\n    def index():\n        print(name)\n    return index\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e装饰器\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e器：就是一个工具\u003c/p\u003e\n\u003cp\u003e装饰：就是给被装饰对象添加新的功能\u003c/p\u003e\n\u003cp\u003e开放封闭原则：\u003c/p\u003e\n\u003cp\u003e开放：对扩展开放\u003c/p\u003e\n\u003cp\u003e封闭：对修改封闭\u003c/p\u003e\n\u003cp\u003e装饰器(可调用对象)必须遵循的两个原则：\u003c/p\u003e\n\u003cp\u003e1.不改变被装饰对象的源代码\u003c/p\u003e\n\u003cp\u003e2.不改变被装饰对象(可调用对象)调用方法\u003c/p\u003e\n\u003cp\u003e可调用：callable\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef new():\n    print('这是原始函数')\n\ndef outter(func):\n    def inner():\n        print('这是内部函数，这里可以加新功能')\n        func()  # 这里的func是你想调用的最原始的函数的内存地址,加上()直接调用\n        print('这是内部函数，这里可以加新功能')\n    return inner  # 这里不加括号 返回的是inner函数的内存地址\nnew = outter(new)  # 括号内的new指向的是最原始的new函数的内存地址，前面的new获取的是outter函数的返回值，返回值我们自定义了返回 inner函数的内存地址\n# 总结：前面的new获取的是inner的内存地址，后面括号内的new指向的就是最原始的new的内存地址\nnew()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e扩展：import time\u003c/p\u003e","title":"【Python】装饰器"},{"content":"\nPython名称空间与作用域/函数的嵌套** \u0026raquo;\u0026gt;转到**思维导图********\u0026gt; \u0026raquo;转到****博客园\n命名关键字参数\n在函数定义阶段，写在*和**可变长参数之间的形参\n在给命名关键字参数传值的时候，只能用关键字为期传值\n在定义阶段 给你感觉好像是z是默认值参数，放在了m这个位置参数的前面了，应该报错\n其实z和m都是命名关键字参数，不是默认值参数和位置参数\ndef func(x,y=1,*args,z=3,m,**kwargs): print(x,y) print(args) print(z,m) print(kwargs) func(1,2,1,2,3,4,5,6,7,78,8,9,0,z=69,m=999,o=666999,l = 999666) 函数对象\n函数是第一类对象：函数名指向的值可以被当中的参数传递\n1.函数名可以被传递\nname = 'jason' x = name print(x) print(id(x)) def func(): print('from func') # print(func) print(id(func)) f = func func() print(f) f() # 其实指向的也是函数func指向函数体代码的内存地址 2.函数名可以被党组参数传递给其他函数\ndef func(): print('from func') def index(args): print(args) args() print('from index') # index(1) index(func) 3.函数名可以被当做函数的返回值\ndef index(): print('index') def func(): print('func') return index res = func() print(res) res() 4.函数名可以被当做容器类型的参数\n# def func(): # print('func') # print(func()) # l = [1,2,func,func()] # [1,2,\u0026lt;function func at 0x000001F7D79899D8\u0026gt;,None] # print(l) # 循环打印项目功能提示信息 供用户选择 用户选择谁就执行谁 def register(): username = input('username\u0026gt;\u0026gt;\u0026gt;:').strip() pwd = input('password\u0026gt;\u0026gt;\u0026gt;:').strip() print(username,pwd) print('register ...') def login(): print('login ...') def transfer(): print('transfer ...') def shopping(): print('shopping...') def pay(): print('pay ...') msg = \u0026quot;\u0026quot;\u0026quot; 1 注册 2 登陆 3 转账 4 购物 5 支付 \u0026quot;\u0026quot;\u0026quot; func_dict = { '1':register, '2':login, '3':transfer, '4':shopping, '5':pay, } while True: print(msg) choice = input('请现在你想要执行的功能\u0026gt;\u0026gt;\u0026gt;:').strip() if choice in func_dict: func_dict.get(choice)() # 函数名() # if choice == '1': # register() # elif choice == '2': # login() # elif choice == '3': # transfer() # elif choice == '4': # shopping() # elif choice == '5': # pay() else: print('你输入的功能暂时没有') 函数的嵌套调用\n在函数内部调用其他函数可以将复杂的逻辑简单化\ndef my_max(x,y): if x \u0026gt; y: return x return y def my_max4(a,b,c,d): res1 = my_max(a,b) res2 = my_max(res1,c) res3 = my_max(res2,d) return res3 print(my_max4(1,2,10,4)) 函数的嵌套定义\n写一个函数，该函数用户可以通过传参的不同控制函数执行不同的功能\ndef outer(): x = 1 print('outer') def inner(): print('inner') # print(inner) return inner res = outer() # print(res) res() 名称空间\n名称空间是存放的变量名与变量值的内存地址的绑定关系的地方\n要想访问一个变量的值，必须先去名称空间中拿到对应的名字，才能够访问变量的值\n名称空间分类\n1.内置名称空间：Python解释器提前给你定义好的名字(已经存放到内置名称空间中了)\nlen max min 2.全局名称空间：文件级别的代码\nx = 1 if 1 ==1 : y = 2 print(y) while True: z = 3 #x,y,z都会放到全局名称空间 #if for while 无论嵌套多少层 它们内部所创建的名字都是全局名称空间的 3.局部名称空间：函数体内创建的名字都属于局部名称空间\n生命周期\n内置名称空间：只要python解释器已启动立马创建，关闭python解释器的时候内置名称空间自动销毁\n全局名称空间:只要你右键运行py文件会自动创建 ，py文件程序运行结束自动销毁\n局部名称空间:函数被调用的时候自动创建 函数指向结束立即销毁(动态创建动态销毁)\n名称空间查找顺序\n需要先确定你当前在哪**\n**\n1.站在全局：全局 \u0026raquo;\u0026gt;内置\n2.站在局部：局部\u0026raquo;\u0026gt;全局\u0026raquo;\u0026gt;内置\n函数在定义阶段查找名字的顺序就已经固定了，不会因为函数的调用位置变化而改变\nx=111 def outer(): def inner(): print('from inner',x) return inner f=outer() x = 222 f() 作用域\n全局作用域：全局有效\n# global 在局部修改全局的不可变数据类型 # x = [] # 因为列表是可变类型 x = 1 # 不可变类型 username = 'jason' def func(): # x.append('嘿嘿嘿') global x,username # 修改全局变量 而不是创建局部名称空间 x = 999 username = 'egon' func() print(x) print(username) 局部作用域：局部有效\ndef func(): x = 1 def index(): nonlocal x x = 2 index() print(x) func() global:局部修改全局 如果想修改多个 逗号隔开\nnonlocal:局部修局部 如果想修改多个 逗号隔开\nEND\n","permalink":"https://zhong-er.com/posts/python%E5%90%8D%E7%A7%B0%E7%A9%BA%E9%97%B4%E4%B8%8E%E4%BD%9C%E7%94%A8%E5%9F%9F%E5%87%BD%E6%95%B0%E7%9A%84%E5%B5%8C%E5%A5%97/","summary":"\u003cp\u003e\u003cimg alt=\"Python第九课(函数2).png\" loading=\"lazy\" src=\"/assets/201907105483_5533_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython名称空间与作用域/函数的嵌套\u003c/strong\u003e** \u0026raquo;\u0026gt;转到**\u003ca href=\"https://mubu.com/doc/25A0qDQkG8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e********\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到****\u003ca href=\"https://www.cnblogs.com/renvip/p/11158059.html\"\u003e博客园\u003c/a\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e命名关键字参数\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e在函数定义阶段，写在*和**可变长参数之间的形参\u003c/p\u003e\n\u003cp\u003e在给命名关键字参数传值的时候，只能用关键字为期传值\u003c/p\u003e\n\u003cp\u003e在定义阶段 给你感觉好像是z是默认值参数，放在了m这个位置参数的前面了，应该报错\u003c/p\u003e\n\u003cp\u003e其实z和m都是命名关键字参数，不是默认值参数和位置参数\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef func(x,y=1,*args,z=3,m,**kwargs):\n    print(x,y)\n    print(args)\n    print(z,m)\n    print(kwargs)\nfunc(1,2,1,2,3,4,5,6,7,78,8,9,0,z=69,m=999,o=666999,l = 999666)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e函数对象\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e函数是第一类对象：函数名指向的值可以被当中的参数传递\u003c/p\u003e\n\u003cp\u003e1.函数名可以被传递\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ename = 'jason'\nx = name\nprint(x)\nprint(id(x))\ndef func():\n    print('from func')\n# print(func)\nprint(id(func))\nf = func\n\nfunc()\nprint(f)\nf()  # 其实指向的也是函数func指向函数体代码的内存地址\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2.函数名可以被党组参数传递给其他函数\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef func():\n    print('from func')\n\ndef index(args):\n    print(args)\n    args()\n    print('from index')\n# index(1)\nindex(func)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e3.函数名可以被当做函数的返回值\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef index():\n    print('index')\n\n\ndef func():\n    print('func')\n    return index\nres = func()\nprint(res)\nres()\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e4.函数名可以被当做容器类型的参数\u003c/p\u003e","title":"【Python】名称空间与作用域/函数的嵌套"},{"content":"\nPython函数 \u0026gt; \u0026raquo;转到思维导图********\u0026gt; \u0026raquo;转到博客园****************\n函数的返回值\n函数内要想返回给调用者值 必须用关键字return\n不写return:函数默认返回None\ndef func(): print('hahaha') res = func() print(res) 只写return:return除了可以返回值之外，还可以直接结束整个函数的运行\n只写return 返回的也是None(None就表示什么都没有)\n只写return 返回的也是None(None就表示什么都没有) def func(): l = ['jason','egon','tank'] while True: for i in l: if i == 'egon': # 当i为egon的时候 直接结束函数运行 # break return # print('asdasdkljlsjadl') # 这一行代码拥有都不会运行 print(i) res = func() print(res) 写return None:跟上面的只写return是一样的\ndef func(): return None res = func() print(res) 写return返回一个值:这个值可以是python任意数据类型\ndef func(): return '123' def func1(): return [1,2,3] def func2(): return {'name':'jason'} def func3(): return (1,) def func4(): return {1,2,3,4,5} def func5(): return True print(func(),func1(),func2(),func3(),func4(),func5()) 写return返回多个值:return会自动将多个值以元组的形式返回给调用者\n\u0026quot;\u0026quot;\u0026quot; 1.为什么组织成元祖返回 函数不希望自己处理的结果被修改 \u0026quot;\u0026quot;\u0026quot; def func(): return 1,2,3,4 # 返回的是(1, 2, 3, 4) res = func() print(res) def func1(): return 'a','b','c' # ('a', 'b', 'c') res = func1() print(res) def func2(): return [1,2,3],[1,2,3],[1,2,3] # ([1, 2, 3], [1, 2, 3], [1, 2, 3]) res1 = func2() print(res1) def func3(): return {'name':'jason'},{'username':'tank'},{'user_name':'egon'} # ({'name': 'jason'}, {'username': 'tank'}, {'user_name': 'egon'}) res2 = func3() print(res2) \u0026quot;\u0026quot;\u0026quot; 2.如何不返回元祖 返回多个值 并且不想让return帮你做处理 自己手动加上你想返回的数据类型符号 \u0026quot;\u0026quot;\u0026quot; def func4(): return [[1,2,3,4],[1,2,3,4],[1,2,34]] res = func4() print(res) \u0026quot;\u0026quot;\u0026quot; 1.所有的函数都有返回值,无论你写不写return python中所有的函数都有返回值 不写的情况下默认返回None 2.光写return 或者return None并不是为了考虑返回值 而是为了结束函数的运行 \u0026quot;\u0026quot;\u0026quot; 函数的参数概要\ns = 'hello' l = [1.2,3,4] print(len(s)) print(len(l)) def my_len(args): # 需要0个参数 print(args) n = 0 for i in args: n += 1 return n res = my_len() print(res) my_len(l) # 给my_len传了一个参数 my_len('asda') print(my_len('hello')) print(my_len('hello world')) print(my_len([1,2,3,4,5])) \u0026quot;\u0026quot;\u0026quot; 函数参数的两大类型 形参:在函数的定义阶段 括号内写的变量名 叫做该函数的形式参数 简称 形参 实参:在函数的调用阶段 括号内实际传入的值 叫做实际参数 简称 实参 形参与实参的关系 形参就相当于变量名,而实参就相当于变量的值 函数调用传参的过程 就是给形参变量名赋值的过程 注意:形参和实参的绑定关系只在函数的调用阶段有效,函数运行结束关系自动解除 只在函数内部有效 函数外部无任何影响 函数的简易结构 def 函数名(形参1,形参2...): '''函数的注释 用来描述该函数的作用以及各个形参的类型''' 函数体代码1 函数体代码2 ... return 函数的返回值 \u0026quot;\u0026quot;\u0026quot; def func(x,y): \u0026quot;\u0026quot;\u0026quot; 该函数的作用 :param x: 对形参x的解释 :param y: 对形参y的解释 :return: 对函数返回值的解释 \u0026quot;\u0026quot;\u0026quot; print('hahaha') return 'heihei' print(help(func)) print(help(len)) 位置参数\n# 求两个数的大小 返回大的那个 # 位置参数:在函数定义阶段按照位置从左往右依次书写的变量名 叫做函数位置形参 # 位置形参在调用的时候 必须为其传值 def my_max(x,y): print(x,y) if x \u0026gt; y: return x else: return y res = my_max(1) # 在调用函数的时候 少一个实参不行 res = my_max(1,2,3) # 在调用函数的时候 多一个实参也不行 res = my_max(20,10) # 位置实参:在函数的调用阶段 传入的参数会按照位置一一对应给形参 # print(res) # 第一种直接按照位置传 一一对应 # 第二种指名道姓的传 \u0026gt;\u0026gt;\u0026gt;:关键字传参 my_max(y=20,x=10) my_max(10,y=20) # 位置和关键字混合使用 my_max(20,y=40) my_max(20,y=40,x=30) \u0026quot;\u0026quot;\u0026quot; 注意:在函数的调用阶段 位置参数和关键字参数可以混合使用 但是必须保证 1.位置参数必须在关键字参数的前面(越短的越靠前,越长的越复杂的越靠后) 2.同一个形参不能被多次赋值 \u0026quot;\u0026quot;\u0026quot; 默认值参数\n# 默认值参数:在函数的定义阶段,形参(变量名)就已经被赋值了 # 在调用的时候可以不为默认值形参传值,默认使用定义阶段就已经绑定的值 # 在调用的时候如果可以给默认值形参传值 传了那么就使用你传的值 # 在定义阶段 默认值形参必须放在位置形参的后面 def my_max(x,y=100): if x \u0026gt; y: return x return y # res = my_max(200) res1 = my_max(200,1000) res2 = my_max(y=200,x=1000) print(res2) def register(username,age,gender='male'): print(username,age,gender) register('jason',18) register('tank',28) register('egon',84) register('kevin',58) register('xiaohou',17,'female') # 默认值参数的应用场景 # 当形参接收的到值比较单一的情况下 通常可以考虑用默认值形参 def info(username,hobby,l=None): if l == None: l = [] l.append(hobby) print('%s 的爱好是 %s'%(username,l)) info('jason','study') info('tank','生蚝') info('kevin','喝腰子汤') info('egon','女教练') 解决方法1 info('jason','study',[]) info('tank','生蚝',[]) info('kevin','喝腰子汤',[]) info('egon','女教练',[]) 解决方法2 info('jason','study') info('tank','生蚝') info('kevin','喝腰子汤') info('egon','女教练') m = 100 def my_max(x,y=m): print(x,y) m = 222 my_max(111) def func(x,y=100): print(x,y) \u0026quot;\u0026quot;\u0026quot; 函数在定义阶段 内部所使用的变量都已经初始化完毕了 不会因为调用的位置的变化 而影响到内部的值(暂时可忽略) 函数无论在什么地方被调用 都会跑到函数定义阶段去执行代码 形参中用到的值都是往函数定义阶段代码往上找 \u0026quot;\u0026quot;\u0026quot; 可变长参数\n# 可变长参数 # 站在调用函数传递实参的角度 实参的个数不固定的情况 # 也就意味形参也不固定 # 站在形参的角度 可以用*和**来接收多余的(溢出的)位置参数和关键字参数 # 站在形参的角度 看 * # 形参中的*会将多余的(溢出的)位置实参 统一用元组的形式处理 传递给*后面的形参名 def func(x,y,*z): print(x,y,z) # z = (3, 4, 5, 6, 7, 8, 54, 43, 4, 5, 6, 6, 7, 8) func(1,2,3,4,5,6,7,8,54,43,4,5,6,6,7,8,) # 站在实参的角度 看 * def func(x,y,z): print(x,y,z) l = [1,2,3] a,b,c = l func(a,b,c) func(*[1,2,3,4,5,6]) # *会将列表打散成位置实参一一传入等价于func(1,2,3,4,5,6) func(*(1,2,3)) # 等价于func(1,2,3) def func(x,*z): print(x,z) func(1,*{1,2,3}) # *在形参中只能接收多余的位置实参 不能接收关键字实参 # *只能将列表 元组 集合 字符串 # *的内部你可以看成是for循环 # 站在形参的角度看 ** def func(x,y,**z): print(x,y,z) # z = {'z': 1, 'a': 1, 'b': 2, 'c': 3} func(x=1,y=2,z=1,a=1,b=2,c=3) # **会接收所有多余的关键字参数 并将关键字参数 转换成字典的形式 字典的key就是关键字的名字 # 字典的value就是关键字的名字指向的值 将字典交给**后面的变量名 # 站在实参的角度看 ** def func(x,y,z): print(x,y,z) func(12,3,4) func(x=1,y=2,z=3) d = {'x':1,'y':2,'z':333} func(x=1,y=2,z=3) func(**d) # 等价于func(x=1,y=2,z=333) # **会将字典拆封成 key = value的形式 \u0026quot;\u0026quot;\u0026quot; 总结 * 与 ** *在形参中能够接受多余的位置参数 组织成一个元祖赋值给*后面的变量名 **在形参中能够接受多余的关键字参数 组织成一个字典赋值给**后面的变量名 *:在实参中 *能够将列表 元祖 集合 字符串 打散成位置实参的形式传递给函数 (*就看成是for循环取值) **:在实参中 能将字典打散成key = value的形式 按照关键字参数传递给函数 \u0026quot;\u0026quot;\u0026quot; # 需求 你写的函数 无论调用者按照正确传参的方式无论怎么传 你的函数都能够正常执行 def func1(*x,**y): print(x,y) func1(1,2,3,4,5,6,x=1,y=2,z = 3) \u0026quot;\u0026quot;\u0026quot; 注意python推荐形参*和**通用的写法 \u0026quot;\u0026quot;\u0026quot; def func2(*args,**kwargs): print(args,kwargs) func2(1,2,3,4,5,6,x=1,y=2,z = 3) END\n","permalink":"https://zhong-er.com/posts/43-python%E5%87%BD%E6%95%B0/","summary":"\u003cp\u003e\u003cimg alt=\"Python第八课(函数).png\" loading=\"lazy\" src=\"/assets/201907099354_6068_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython函数 \u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/15Mo0f_bV8\" title=\"思维导图\"\u003e\u003cstrong\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/strong\u003e********\u003c/a\u003e\u003cstrong\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11158059.html\"\u003e\u003cstrong\u003e\u003cstrong\u003e博客园\u003c/strong\u003e\u003c/strong\u003e****************\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e函数的返回值\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e函数内要想返回给调用者值 必须用关键字return\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e不写return:函数默认返回None\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef func():\n    print('hahaha')\nres = func()\nprint(res)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e只写return:return除了可以返回值之外，还可以直接结束整个函数的运行\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e只写return 返回的也是None(None就表示什么都没有)\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e只写return 返回的也是None(None就表示什么都没有)\ndef func():\n    l = ['jason','egon','tank']\n    while True:\n        for i in l:\n            if i == 'egon':  # 当i为egon的时候 直接结束函数运行\n                # break\n                return\n                # print('asdasdkljlsjadl')  # 这一行代码拥有都不会运行\n            print(i)\nres = func()\nprint(res)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e写return None:跟上面的只写return是一样的\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef func():\n    return None\nres = func()\nprint(res)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e写return返回一个值:这个值可以是python任意数据类型\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edef func():\n    return '123'\ndef func1():\n    return [1,2,3]\ndef func2():\n    return {'name':'jason'}\ndef func3():\n    return (1,)\ndef func4():\n    return {1,2,3,4,5}\ndef func5():\n    return True\nprint(func(),func1(),func2(),func3(),func4(),func5())\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e写return返回多个值:return会自动将多个值以元组的形式返回给调用者\u003c/strong\u003e\u003c/p\u003e","title":"【Python】函数"},{"content":"\nPython文件操作/函数前戏 \u0026raquo;\u0026gt;转到思维导图********\u0026gt; \u0026raquo;转到博客园****\n文件操作模式补充\n\u0026quot;+\u0026quot; 表示可以同时读写某个文件 r+， 读写【可读，可写】 w+，写读【可读，可写】 a+， 写读【可读，可写】 文件内光标移动\n一: read(3)： 1. 文件打开方式为文本模式时，代表读取3个字符 2. 文件打开方式为b模式时，代表读取3个字节 二: 其余的文件内光标移动都是以字节为单位如seek，tell，truncate 注意： 1. seek有三种移动方式0，1，2，其中1和2必须在b模式下进行，但无论哪种模式，都是以bytes为单位移动的 2. truncate是截断文件，所以文件的打开方式必须可写，但是不能用w或w+等方式打开，因为那样直接清空文件了，所以truncate要在r+或a或a+等模式下测试效果 检测文件内容\nwith open(r'test01.txt','rb') as f: # 先将光标移动到文件末尾 f.seek(0,2) while True: res = f.readline() # 查看光标移动了多少位 bytes # print(f.tell()) if res: print(\u0026quot;新增的文件内容:%s\u0026quot;%res.decode('utf-8')) # 说明有人操作当前文件 # else: # # 说明文件没有被任何人操作 # print('暂无其他人操作该文件') 截断文件\nwith open(r'test','a',encoding='utf-8') as f: f.truncate(6) # 接收的字节的长度 整型 # 保留0~6字节数 后面的全部删除(截断) 修改文件\n文件的数据是存放于硬盘上的，因而只存在覆盖、不存在修改这么一说，我们平时看到的修改文件，都是模拟出来的效果，具体的说有两种实现方式：\n方式一：将硬盘存放的该文件的内容全部加载到内存，在内存中是可以修改的，修改完毕后，再由内存覆盖到硬盘（word，vim，nodpad++等编辑器）\nwith open(r'test02.txt','r',encoding='utf-8') as f: data = f.read() print(data) print(type(data)) with open(r'test02.txt','w',encoding='utf-8') as f: res = data.replace('egon','jason') print(data) f.write(res) \u0026quot;\u0026quot;\u0026quot; 优点:任意时间硬盘上只有一个文件 不会占用过多硬盘空间 缺点:当文件过大的情况下,可能会造成内存溢出 \u0026quot;\u0026quot;\u0026quot; 方式二：将硬盘存放的该文件的内容一行一行地读入内存，修改完毕就写入新文件，最后用新文件覆盖源文件\nimport os with open(r'test02.txt','r',encoding='utf-8') as read_f,\\ open(r'test02.swap','a',encoding='utf-8') as write_f: for line in read_f: new_line = line.replace('jason','egon') write_f.write(new_line) os.remove('test02.txt') os.rename('test02.swap','test02.txt') \u0026quot;\u0026quot;\u0026quot; 优点:内存中始终只有一行内容 不占内存 缺点:再某一时刻硬盘上会同时存在两个文件 \u0026quot;\u0026quot;\u0026quot; 函数前戏\ndef用来定义函数，必须先定义后使用\n函数名的命名规则跟变量名一模一样\n定义的时候，只检测语法，不执行代码\n#语法 def 函数名(参数1,参数2,参数3,...): '''注释''' 函数体 return 返回的值 #函数名要能见名知意 END\n","permalink":"https://zhong-er.com/posts/python%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E5%87%BD%E6%95%B0%E5%89%8D%E6%88%8F/","summary":"\u003cp\u003e\u003cimg alt=\"Python第07课(文件操作_函数前戏).png\" loading=\"lazy\" src=\"/assets/201908067592_1286_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython文件操作/函数前戏 \u0026raquo;\u0026gt;转到\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/2qvfaKltq8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e********\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11151888.html\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e文件操作模式补充\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026quot;+\u0026quot; 表示可以同时读写某个文件\nr+， 读写【可读，可写】\nw+，写读【可读，可写】\na+， 写读【可读，可写】\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e文件内光标移动\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e一: read(3)：\n1. 文件打开方式为文本模式时，代表读取3个字符\n2. 文件打开方式为b模式时，代表读取3个字节\n二: 其余的文件内光标移动都是以字节为单位如seek，tell，truncate\n注意：\n1. seek有三种移动方式0，1，2，其中1和2必须在b模式下进行，但无论哪种模式，都是以bytes为单位移动的\n2. truncate是截断文件，所以文件的打开方式必须可写，但是不能用w或w+等方式打开，因为那样直接清空文件了，所以truncate要在r+或a或a+等模式下测试效果\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e检测文件内容\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewith open(r'test01.txt','rb') as f:\n    # 先将光标移动到文件末尾\n    f.seek(0,2)\n    while True:\n        res = f.readline()\n        # 查看光标移动了多少位 bytes\n        # print(f.tell())\n        if res:\n            print(\u0026quot;新增的文件内容:%s\u0026quot;%res.decode('utf-8'))\n            # 说明有人操作当前文件\n        # else:\n        #     # 说明文件没有被任何人操作\n        #     print('暂无其他人操作该文件')\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e截断文件\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewith open(r'test','a',encoding='utf-8') as f:\n    f.truncate(6)  # 接收的字节的长度 整型\n    # 保留0~6字节数 后面的全部删除(截断)\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e修改文件\u003c/p\u003e","title":"【Python】文件操作/函数前戏"},{"content":" Python字符编码/文件操作 \u0026raquo;\u0026gt;转到思维导图****\u0026gt; \u0026raquo;转到博客园****\n字符编码\n# 字符编码针对的是文字。 # 字符编码只跟文本文件有关。 # 文本编辑器的输入和输出是两个过程。 # 字符编码表就是字符与数字的对应关系 ASCII码表 用八位二进制表示一个英文字符 GBK 用2Bytes表示一个中文字符，还是用1Bytes表示一个英文字母。最多能表示65535个字符 Unicode万国码 统一用2Bytes表示所有的字符 1.浪费存储空间 2.IO次数增加，程序运行效率降低(致命) # 当内存中的unicode编码格式数据存到硬盘的时候，会按照utf-8编码 # 会将unicode的英文字符由原来的2Bytes变成1Bytes # 会将unicode中文字符由原来的2Bytes变成3Bytes # 现在的计算机内存都是unicode，硬盘都是utf-8 unicode的两个特点 1.用户在输入的时候，无论输什么字符都能够兼容万国字符 2.其他国家编码的数据由硬盘读到内存的时候unicode与其他各个国家的编码都有对应关系 # 数据由内存保存到硬盘 内存中的unicode格式二进制数字 \u0026gt;\u0026gt;\u0026gt;编码(encode)\u0026gt;\u0026gt;\u0026gt;utf-8格式的二进制数据 # 硬盘中的数据由硬盘读到内存 硬盘中的utf-8格式的二进制数据\u0026gt;\u0026gt;\u0026gt;解码(decode)\u0026gt;\u0026gt;\u0026gt;内存中unicode格式的二进制数据 # 保证不乱码在于文本文件以什么编码编的就以什么编码解 # python2将py文件按照文本文件读入解释器中默认使用ASCII码 # python3将py文件按照文本文件读入解释器中默认使用utf-8 # 文件头 # coding:utf-8 因为所有的编码都支持英文字符,所以文件头才能够正常生效 # python3中字符串默认就是unicode编码格式的二进制数 # pycharm终端用的是utf-8格式，windows终端采用的是gbk格式。 encode() # 将unicode编码成可以存储和传输的utf-8的二进制数据 # bytes类型 字节串类型 你就把它当成二进制数据即可 decode() # 将硬盘中的utf-8格式的二进制数据解码成unicode格式的二进制数据 x = '上' res1 = x.encode('gbk') # 将unicode编码成可以存储和传输的utf-8的二进制数据 print(res1) # b'\\xe4\\xb8\\x8a' # bytes类型 字节串类型 你就把它当成二进制数据即可 res2 = res1.decode('gbk') # 将硬盘中的utf-8格式的二进制数据解码成unicode格式的二进制数据 print(res2) # 补充: # 你a他 # 1Bytes|1Bytes|1Bytes|1Bytes|1Bytes|1Bytes|1Bytes # 1+7bit|1+7bit|1+7bit|1+7bit|1+7bit|1+7bit|1+7bit 文件处理\n# 什么是文件？ # 操作系统提供给用户操作复杂硬件(硬盘)的简易的接口。 # 为什么操作文件？ # 人或者应用程序需要永久的保存数据。 # 打开文件的模式 f = open() 文件句柄 = open('文件路径', '模式') # 打开文件的模式有(默认为文本模式)： #文件读写模式 r ，只读模式【默认模式，文件必须存在，不存在则抛出异常】 w，只写模式【不可读；不存在则创建；存在则清空内容】 a， 只追加写模式【不可读；不存在则创建；存在则只追加内容】 #文件操作单位 t 文本文件 t在使用的时候需要指定encoding参数(如果不知道默认是操作系统的默认编码) 只能和r/w/a连用 默认就是t b 二进制 一定不能指定encoding参数 mode参数 可以不写 不写的话默认是rt 只能和r/w/a连用 该模式通常用来处理非文本文件 # encoding参数只在mode位文本模式的情况下才加 # 操作文件的方法 f.read() # 读取所有内容,光标移动到文件末尾 f.readline() # 读取一行内容,光标移动到第二行首部 f.readlines() # 读取每一行内容,存放于列表中 f.write('1111\\n222\\n') # 针对文本模式的写,需要自己写换行符 f.write('1111\\n222\\n'.encode('utf-8')) # 针对b模式的写,需要自己写换行符 f.writelines(['333\\n','444\\n']) # 文件模式 f.writelines([bytes('333\\n',encoding='utf-8'),'444\\n'.encode('utf-8')]) # b模式 f.readable() # 文件是否可读 f.writable() # 文件是否可读 f.closed # 文件是否关闭 为防止忘记f.close()，推荐使用with关键字来帮我们管理上下文 with open('a.txt','w') as f: f.encoding # 如果文件打开模式为b,则没有该属性 END\n","permalink":"https://zhong-er.com/posts/python%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C/","summary":"\u003ch2\u003e\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"Python第06课(字符编码_文件操作).png\" loading=\"lazy\" src=\"/assets/201908063789_220_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython字符编码/文件操作 \u0026raquo;\u0026gt;转到\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/2mwGPbqdZ8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11140402.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e字符编码\u003c/strong\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 字符编码针对的是文字。\n# 字符编码只跟文本文件有关。\n# 文本编辑器的输入和输出是两个过程。\n# 字符编码表就是字符与数字的对应关系\n\nASCII码表 用八位二进制表示一个英文字符\n\nGBK 用2Bytes表示一个中文字符，还是用1Bytes表示一个英文字母。最多能表示65535个字符\n\nUnicode万国码 统一用2Bytes表示所有的字符\n    1.浪费存储空间\n    2.IO次数增加，程序运行效率降低(致命)\n    \n# 当内存中的unicode编码格式数据存到硬盘的时候，会按照utf-8编码\n\n# 会将unicode的英文字符由原来的2Bytes变成1Bytes\n# 会将unicode中文字符由原来的2Bytes变成3Bytes\n# 现在的计算机内存都是unicode，硬盘都是utf-8\n\nunicode的两个特点\n    1.用户在输入的时候，无论输什么字符都能够兼容万国字符\n    2.其他国家编码的数据由硬盘读到内存的时候unicode与其他各个国家的编码都有对应关系\n    \n# 数据由内存保存到硬盘\n    内存中的unicode格式二进制数字 \u0026gt;\u0026gt;\u0026gt;编码(encode)\u0026gt;\u0026gt;\u0026gt;utf-8格式的二进制数据\n# 硬盘中的数据由硬盘读到内存\n    硬盘中的utf-8格式的二进制数据\u0026gt;\u0026gt;\u0026gt;解码(decode)\u0026gt;\u0026gt;\u0026gt;内存中unicode格式的二进制数据\n    \n# 保证不乱码在于文本文件以什么编码编的就以什么编码解\n\n# python2将py文件按照文本文件读入解释器中默认使用ASCII码\n# python3将py文件按照文本文件读入解释器中默认使用utf-8\n\n# 文件头 # coding:utf-8 因为所有的编码都支持英文字符,所以文件头才能够正常生效\n# python3中字符串默认就是unicode编码格式的二进制数\n# pycharm终端用的是utf-8格式，windows终端采用的是gbk格式。\n\nencode() # 将unicode编码成可以存储和传输的utf-8的二进制数据\n# bytes类型  字节串类型  你就把它当成二进制数据即可\ndecode()  # 将硬盘中的utf-8格式的二进制数据解码成unicode格式的二进制数据\n\nx = '上'\nres1 = x.encode('gbk')  # 将unicode编码成可以存储和传输的utf-8的二进制数据\nprint(res1)  # b'\\xe4\\xb8\\x8a'\n# bytes类型  字节串类型  你就把它当成二进制数据即可\nres2 = res1.decode('gbk')  # 将硬盘中的utf-8格式的二进制数据解码成unicode格式的二进制数据\nprint(res2)\n\n\n# 补充:\n# 你a他\n# 1Bytes|1Bytes|1Bytes|1Bytes|1Bytes|1Bytes|1Bytes\n# 1+7bit|1+7bit|1+7bit|1+7bit|1+7bit|1+7bit|1+7bit\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e文件处理\u003c/strong\u003e\u003c/p\u003e","title":"【Python】字符编码/文件操作"},{"content":"\nwin+r打开运行\n输入msconfig\n回车\n点击引导选项卡\n进一步设置引导\n可以选择不需要的系统进行删除，然后格式化磁盘，最终实现多系统的删除。\n","permalink":"https://zhong-er.com/posts/%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95%E5%A6%82%E4%BD%95%E5%88%A0%E9%99%A4%E5%A4%9A%E7%B3%BB%E7%BB%9F%E5%BC%95%E5%AF%BC%E8%8F%9C%E5%8D%95/","summary":"\u003cp\u003e\u003cimg alt=\"错误记录.png\" loading=\"lazy\" src=\"/assets/202009035154_4969_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"1.jpg\" loading=\"lazy\" src=\"/assets/201907059811_1071_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003ewin+r打开运行\u003c/p\u003e\n\u003cp\u003e输入msconfig\u003c/p\u003e\n\u003cp\u003e回车\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"2.jpg\" loading=\"lazy\" src=\"/assets/201907055973_1361_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e点击引导选项卡\u003c/p\u003e\n\u003cp\u003e进一步设置引导\u003c/p\u003e\n\u003cp\u003e可以选择不需要的系统进行删除，然后格式化磁盘，最终实现多系统的删除。\u003c/p\u003e","title":"【问题记录】如何删除多系统引导菜单"},{"content":"\nPython列表/字典/元组/集合 \u0026raquo;\u0026gt;转到思维导图********\u0026gt; \u0026raquo;转到博客园****\n列表list\ncount()用来统计列表某个元素的个数\n\u0026gt;\u0026gt;\u0026gt; l = [1,2,3,4,5,6,7] \u0026gt;\u0026gt;\u0026gt; res = l.count(8) \u0026gt;\u0026gt;\u0026gt; print(res) 0 clear()用来清除列表元素\n\u0026gt;\u0026gt;\u0026gt; res = l.clear() \u0026gt;\u0026gt;\u0026gt; print(res) None \u0026gt;\u0026gt;\u0026gt; print(l) [] reverse()用来将列表反转\n\u0026gt;\u0026gt;\u0026gt; l = [1,2,3,4,5,6,7] \u0026gt;\u0026gt;\u0026gt; print(id(l)) 2271674158920 \u0026gt;\u0026gt;\u0026gt; l.reverse() \u0026gt;\u0026gt;\u0026gt; print(l) [7, 6, 5, 4, 3, 2, 1] \u0026gt;\u0026gt;\u0026gt; print(id(l)) 2271674158920 sort()按升序将列表元素排序\n\u0026gt;\u0026gt;\u0026gt; l.sort() \u0026gt;\u0026gt;\u0026gt; print(l) [1, 2, 3, 4, 5, 6, 7] \u0026gt;\u0026gt;\u0026gt; print(id(l)) 2271674158920 队列：先进先出\n堆栈：先进后出\n能存多个值，有序，可变\n元组tuple\n作用：能够存储多个元素，元素与元素之间逗号隔开，元素可以是任意类型，元组不能被修改\n定义：在()内用逗号分隔开多个任意类型的值。与列表类型比，只不过吧[ ]换成了( )\n在定义容器类型的时候，哪怕内部只有一个元素，也要用逗号隔开区分一下\nlist[0]按索引取值\n\u0026gt;\u0026gt;\u0026gt; t = (1, 2, 3, 'a', 'b', [1, 2, 3]) \u0026gt;\u0026gt;\u0026gt; print(t[0]) 1 \u0026gt;\u0026gt;\u0026gt; print(t[1]) 2 \u0026gt;\u0026gt;\u0026gt; print(t[-1]) [1, 2, 3] \u0026gt;\u0026gt;\u0026gt; print(t[-1][0]) 1 \u0026gt;\u0026gt;\u0026gt; t[-1][0] = '我改了' \u0026gt;\u0026gt;\u0026gt; print(t) (1, 2, 3, 'a', 'b', ['我改了', 2, 3]) list[::2]切片(顾头不顾尾，步长)\n\u0026gt;\u0026gt;\u0026gt; print(t[::2]) (1, 3, 'b') len()长度\n\u0026gt;\u0026gt;\u0026gt; print(len(t)) 6 in和not in成员运算\n\u0026gt;\u0026gt;\u0026gt; print('a' in t) True 循环\nfor i in t: print(i) 能存多个值，有序，不可变\n字典dict\n作用：记录多个值，每一个值都对应的key用来描述value的作用。\n定义：在{}内用逗号分隔开多 个key:value，其中value可以是任意类型，而key必须是不可变的类型，通常情况下应该str类型\nd = {'name':'jason','password':123} # d = dict( {'name':'jason','password':123}) 字典的key是不能重复的。要唯一表示一条数据。如果重复了，只会按照最后一组城府的键值对存储\ndict[\u0026lsquo;key\u0026rsquo;]=\u0026lsquo;value\u0026rsquo;按key存取值：可存可取。当key不存在的情况下，会自动新增一个键值对\n\u0026gt;\u0026gt;\u0026gt; d3 = {'name':'jason','password':'123'} \u0026gt;\u0026gt;\u0026gt; print(id(d3)) 2052650650072 \u0026gt;\u0026gt;\u0026gt; print(d3['name']) jason \u0026gt;\u0026gt;\u0026gt; d3['age'] = 18 \u0026gt;\u0026gt;\u0026gt; print(d3,id(d3)) {'name': 'jason', 'password': '123', 'age': 18} 2052650650072 len()统计键值对的个数\n\u0026gt;\u0026gt;\u0026gt; print(len(d3)) 3 in和not in成员运算，对于字典来说只能判断key值\n\u0026gt;\u0026gt;\u0026gt; d3 = {'name':'jason','password':'123'} \u0026gt;\u0026gt;\u0026gt; print('123' in d3) False \u0026gt;\u0026gt;\u0026gt; print('name' in d3) True del dict[\u0026lsquo;key\u0026rsquo;]删除键值对\n\u0026gt;\u0026gt;\u0026gt; d3 = {'name':'jason','password':'123'} \u0026gt;\u0026gt;\u0026gt; del d3['name'] \u0026gt;\u0026gt;\u0026gt; print(d3) {'password': '123'} dict.pop(\u0026lsquo;key\u0026rsquo;)弹出的仅仅是value。当键不存在的时候直接报错\n\u0026gt;\u0026gt;\u0026gt; d3 = {'name':'jason','password':'123'} \u0026gt;\u0026gt;\u0026gt; res = d3.pop('name') \u0026gt;\u0026gt;\u0026gt; res = d3.pop('age') Traceback (most recent call last): File \u0026quot;\u0026lt;stdin\u0026gt;\u0026quot;, line 1, in \u0026lt;module\u0026gt; KeyError: 'age' \u0026gt;\u0026gt;\u0026gt; print(res) jason \u0026gt;\u0026gt;\u0026gt; print(d3) {'password': '123'} \u0026gt;\u0026gt;\u0026gt; d3.clear() \u0026gt;\u0026gt;\u0026gt; print(d3) {} print(dict.keys()) # dict_keys([\u0026lsquo;key1\u0026rsquo;, \u0026lsquo;key2\u0026rsquo;])\n\u0026gt;\u0026gt;\u0026gt; d1 = {'name':'jason','password':123} \u0026gt;\u0026gt;\u0026gt; print(d1.keys()) dict_keys(['name', 'password']) \u0026gt;\u0026gt;\u0026gt; for k in d1.keys(): ... print(k) ... name password print(dict.values()) # dict_values([\u0026lsquo;value1\u0026rsquo;, \u0026lsquo;value2\u0026rsquo;])现在看做一个列表即可\n\u0026gt;\u0026gt;\u0026gt; print(d1.values()) dict_values(['jason', 123]) print(dict.items()) # dict_items([(\u0026lsquo;key1\u0026rsquo;, \u0026lsquo;key2\u0026rsquo;), (\u0026lsquo;value1\u0026rsquo;, \u0026lsquo;value2\u0026rsquo;)])把他看做是列表套元组，元组的第一个元素是字典的key，第二个元素是字典的value\n\u0026gt;\u0026gt;\u0026gt; print(d1.items()) dict_items([('name', 'jason'), ('password', 123)]) dict.get(\u0026lsquo;key\u0026rsquo;,\u0026lsquo;自定义返回值\u0026rsquo;)根据key获取value。当key在字典中时返回对应的value；当key不在字典中时，不报错，默认返回none，也可自定义返回值。\n\u0026gt;\u0026gt;\u0026gt; d1 = {'name':'jason','pwd':123} \u0026gt;\u0026gt;\u0026gt; print(d1['name']) jason \u0026gt;\u0026gt;\u0026gt; print(d1['age']) Traceback (most recent call last): File \u0026quot;\u0026lt;stdin\u0026gt;\u0026quot;, line 1, in \u0026lt;module\u0026gt; KeyError: 'age' \u0026gt;\u0026gt;\u0026gt; print(d1.get('name','你给我的name在字典的key中')) jason \u0026gt;\u0026gt;\u0026gt; res = d1.get('age') \u0026gt;\u0026gt;\u0026gt; res1 = d1.get('xxx','你给我的age在字典的key中') \u0026gt;\u0026gt;\u0026gt; print(res1) 你给我的age在字典的key中 \u0026gt;\u0026gt;\u0026gt; print(d1.get('xxx','asdsad')) asdsad list=[\u0026lsquo;key1\u0026rsquo;,\u0026lsquo;key2\u0026rsquo;] # print(dict.fromkeys(list,\u0026lsquo;value\u0026rsquo;)) # {\u0026lsquo;key1\u0026rsquo;:\u0026lsquo;value\u0026rsquo;,\u0026lsquo;key2\u0026rsquo;:\u0026lsquo;value\u0026rsquo;} # 快速创建一个字典\n\u0026gt;\u0026gt;\u0026gt; l1 = ['name','password','age','hobby'] \u0026gt;\u0026gt;\u0026gt; print(dict.fromkeys(l1,123)) {'name': 123, 'password': 123, 'age': 123, 'hobby': 123} dict.popitem()尾部以元组的形式弹出键值对\n\u0026gt;\u0026gt;\u0026gt; print(d1.popitem()) ('age', 18) dict.setdefault(\u0026lsquo;key1\u0026rsquo;,\u0026lsquo;value2\u0026rsquo;) # 当键存在的情况下，返回对应值\u0026rsquo;value1\u0026rsquo;；当键不存在的情况下，新增键值对\u0026rsquo;key1\u0026rsquo;,\u0026lsquo;value2\u0026rsquo;,并且将新增的键值对的值\u0026rsquo;value2\u0026rsquo;返回给你。\n\u0026gt;\u0026gt;\u0026gt; d1 = {'name':'jason','pwd':123} \u0026gt;\u0026gt;\u0026gt; res1 = d1.setdefault('name','xxoo') \u0026gt;\u0026gt;\u0026gt; print(d1,res1) {'name': 'jason', 'pwd': 123} jason \u0026gt;\u0026gt;\u0026gt; res2 = d1.setdefault('age',18) \u0026gt;\u0026gt;\u0026gt; print(d1,res2) {'name': 'jason', 'pwd': 123, 'age': 18} 18 dict.update()\n\u0026gt;\u0026gt;\u0026gt; d1 = {'name':'jason','pwd':123} \u0026gt;\u0026gt;\u0026gt; d2 = {\u0026quot;age\u0026quot;:18} \u0026gt;\u0026gt;\u0026gt; d1.update(d2) \u0026gt;\u0026gt;\u0026gt; print(d1) {'name': 'jason', 'pwd': 123, 'age': 18} \u0026gt;\u0026gt;\u0026gt; d1['age'] = 19 \u0026gt;\u0026gt;\u0026gt; print(d1) {'name': 'jason', 'pwd': 123, 'age': 19} \u0026gt;\u0026gt;\u0026gt; d1.update(age=666) \u0026gt;\u0026gt;\u0026gt; print(d1) {'name': 'jason', 'pwd': 123, 'age': 666} 存多个值，无序，可变\n集合set\n作用：去重，关系运算\n定义：在{ }内用逗号分开的多个值，在定义空集合的时候只能用关键字set(),如果只写了一个大括号，python默认将他当做字典类型\n\u0026gt;\u0026gt;\u0026gt; s = {1,2,3,4,5,6} \u0026gt;\u0026gt;\u0026gt; print(type(s)) \u0026lt;class 'set'\u0026gt; \u0026gt;\u0026gt;\u0026gt; s1 = set() \u0026gt;\u0026gt;\u0026gt; print(type(s1)) \u0026lt;class 'set'\u0026gt; \u0026gt;\u0026gt;\u0026gt; x = {} \u0026gt;\u0026gt;\u0026gt; print(type(x)) \u0026lt;class 'dict'\u0026gt; \u0026gt;\u0026gt;\u0026gt; t = () \u0026gt;\u0026gt;\u0026gt; print(type(t)) \u0026lt;class 'tuple'\u0026gt; \u0026gt;\u0026gt;\u0026gt; d = {[1,2]:'name'} Traceback (most recent call last): File \u0026quot;\u0026lt;stdin\u0026gt;\u0026quot;, line 1, in \u0026lt;module\u0026gt; TypeError: unhashable type: 'list' 集合三大特性：1.每个值都必须是不可变类型；2.元素不能重复；3.集合内元素无序\n# s = {1,2,3,4,5,6,'a',(1,2),[1,2]} # 报错 # s = {1,2,3,4,5,6,'a',(1,2),{\u0026quot;name\u0026quot;:'jason'}} # 报错 \u0026gt;\u0026gt;\u0026gt; s = {1,2,2,2,2,2,2,2,2,2,3,4,4,4,4,4,5} \u0026gt;\u0026gt;\u0026gt; print(s) # 自动将重复的元素 去除 {1, 2, 3, 4, 5} \u0026gt;\u0026gt;\u0026gt; s = {1, 2, 3, 4, 5, 6} \u0026gt;\u0026gt;\u0026gt; print(s[1]) # 注意集合的目的是将不同的值存放到一起，不同的集合间用来做关系运算，无需纠结于集合中单个值 |合集\n\u0026amp;交集\n-差集\n^对称差集\n==是否相等\n=父集：一个集合是包含另外一个集合\n\u0026lt;=子集\npythons = {'jason', 'nick', 'tank', 'egon', 'kevin', 'owen', 'alex'} linux = {'frank', 'jerry', 'tank', 'egon', 'alex'} # 既报名python也报名linux的学生姓名 print(pythons \u0026amp; linux) # 交集 # 只报名python的学生姓名 print(pythons - linux) # 只报名linux的学生姓名 print(linux - pythons) # 没有同时报名两门课程的学生姓名 print(pythons ^ linux) # 对称差集 # 取出所有报了名的学生姓名 print(pythons | linux) set.update({1,2,3})将集合并入得到合集\n\u0026gt;\u0026gt;\u0026gt; s1={1,2,3} \u0026gt;\u0026gt;\u0026gt; s1.update({3,4,5}) \u0026gt;\u0026gt;\u0026gt; print(s1) {1, 2, 3, 4, 5} set.pop()随机弹出一个元素\n\u0026gt;\u0026gt;\u0026gt; s1={1,2,3} \u0026gt;\u0026gt;\u0026gt; res=s1.pop() \u0026gt;\u0026gt;\u0026gt; print(res) 1 \u0026gt;\u0026gt;\u0026gt; print(s1) {2, 3} set.remove(3) # 单纯的删除,返回值为None\n\u0026gt;\u0026gt;\u0026gt; s1={1,2,3} \u0026gt;\u0026gt;\u0026gt; res=s1.remove(3) \u0026gt;\u0026gt;\u0026gt; print(res) None \u0026gt;\u0026gt;\u0026gt; print(s1) {1, 2} set.discard(4) # 删除的元素不存在会报错\n\u0026gt;\u0026gt;\u0026gt; s1={1,2,3} \u0026gt;\u0026gt;\u0026gt; res=s1.discard(3) \u0026gt;\u0026gt;\u0026gt; print(s1) {1, 2} \u0026gt;\u0026gt;\u0026gt; print(res) ##单纯的删除,返回值为None None \u0026gt;\u0026gt;\u0026gt; s1.remove(444444) #删除的元素不存在则报错 Traceback (most recent call last): File \u0026quot;\u0026lt;stdin\u0026gt;\u0026quot;, line 1, in \u0026lt;module\u0026gt; KeyError: 444444 \u0026gt;\u0026gt;\u0026gt; s1.discard(444444) #删除的元素不存在不会报错 set.add((\u0026rsquo;tuple\u0026rsquo;,))将容器类型也当成一个元素传入\n\u0026gt;\u0026gt;\u0026gt; s1={1,2,3} \u0026gt;\u0026gt;\u0026gt; s1.add(4) \u0026gt;\u0026gt;\u0026gt; print(s1) {1, 2, 3, 4} END\n","permalink":"https://zhong-er.com/posts/python%E5%88%97%E8%A1%A8%E5%AD%97%E5%85%B8%E5%85%83%E7%BB%84%E9%9B%86%E5%90%88/","summary":"\u003cp\u003e\u003cimg alt=\"Python第05课(数据类型5).png\" loading=\"lazy\" src=\"/assets/201908067586_2449_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython列表/字典/元组/集合 \u0026raquo;\u0026gt;转到\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/3wTzttZOc8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e********\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11133594.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e列表list\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003ecount()用来统计列表某个元素的个数\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026gt;\u0026gt;\u0026gt; l = [1,2,3,4,5,6,7]\n\u0026gt;\u0026gt;\u0026gt; res = l.count(8)\n\u0026gt;\u0026gt;\u0026gt; print(res)\n0\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eclear()用来清除列表元素\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026gt;\u0026gt;\u0026gt; res = l.clear()\n\u0026gt;\u0026gt;\u0026gt; print(res)\nNone\n\u0026gt;\u0026gt;\u0026gt; print(l)\n[]\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003ereverse()用来将列表反转\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026gt;\u0026gt;\u0026gt; l = [1,2,3,4,5,6,7]\n\u0026gt;\u0026gt;\u0026gt; print(id(l))\n2271674158920\n\u0026gt;\u0026gt;\u0026gt; l.reverse()\n\u0026gt;\u0026gt;\u0026gt; print(l)\n[7, 6, 5, 4, 3, 2, 1]\n\u0026gt;\u0026gt;\u0026gt; print(id(l))\n2271674158920\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003esort()按升序将列表元素排序\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026gt;\u0026gt;\u0026gt; l.sort()\n\u0026gt;\u0026gt;\u0026gt; print(l)\n[1, 2, 3, 4, 5, 6, 7]\n\u0026gt;\u0026gt;\u0026gt; print(id(l))\n2271674158920\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e队列：先进先出\u003c/p\u003e","title":"【Python】列表/字典/元组/集合"},{"content":"\nPython数据类型 \u0026raquo;\u0026gt;转到思维导图\u0026gt; \u0026raquo;转到博客园****\n整型(int)\n用途：记录QQ号、手机号、身份证号等\n定义：age = 18\n常用操作+内置的方法\nint只能转存数字的字符串，小数点都不行\n进制转换\n\u0026gt;\u0026gt;\u0026gt; print(int('1100',2)) # 二进制转十进制 0,1 12 \u0026gt;\u0026gt;\u0026gt; print(int('14',8)) # 八进制转十进制 0-7 12 \u0026gt;\u0026gt;\u0026gt; print(int('c',16)) # 十六进制转十进制 0-9 A-F 12 # int可以传第一个参数，表示的是第一个参数到底是什么进制 然后转成十进制 \u0026gt;\u0026gt;\u0026gt; print(bin(12)) # 10进制转二进制 0b1100 # 0b1100 0b表示后面的数字是二进制数 \u0026gt;\u0026gt;\u0026gt; print(oct(12)) # 10进制转八进制 0o14 # 0o14 0o表示后面的数字是八进制数 \u0026gt;\u0026gt;\u0026gt; print(hex(12)) # 10进制转16进制 0xc # 0xc 0x表示后面的数字是十六进制数 有序：但凡有索引的数据都是有序的\n不可变：值改变的情况下，id一定变\n浮点型(float)\n用途：记录薪资、身高、体重等\n定义：\nsalary = 1.1 # salary = float(1.1) \u0026gt;\u0026gt;\u0026gt; res = float('1.11') \u0026gt;\u0026gt;\u0026gt; print(type(res)) \u0026lt;class 'float'\u0026gt; 有序：但凡有索引的数据都是有序的\n不可变：值改变的情况下，id一定变\n字符串(str)\n用途：描述性\n定义：\u0026rsquo; \u0026lsquo;,\u0026quot; \u0026ldquo;,\u0026rdquo;\u0026quot;\u0026quot; \u0026quot;\u0026quot;\u0026quot;\n常用操作+内置的方法\n1.按索引取值(正向取+反向取):只能取\n\u0026gt;\u0026gt;\u0026gt; s = 'hello big baby~' \u0026gt;\u0026gt;\u0026gt; print(s[0]) h 2.切片(顾头不顾尾，步长)：从一个大的字符串中截取一段新的小字符串\n# 左边的固定就是头，右边固定就是位 没有说按数字来分头尾 \u0026gt;\u0026gt;\u0026gt; s = 'hello big baby~' \u0026gt;\u0026gt;\u0026gt; print(s[0:5]) hello \u0026gt;\u0026gt;\u0026gt; print(s[0:10:1]) hello big # 步长不写默认是1 \u0026gt;\u0026gt;\u0026gt; print(s[0:10:2]) hlobg # 步长表示隔几个取一个 了解负数取值 \u0026gt;\u0026gt;\u0026gt; print(s[-1]) ~ \u0026gt;\u0026gt;\u0026gt; print(s[0:5:-2]) # 切片取值默认是从左往右的 \u0026gt;\u0026gt;\u0026gt; print(s[5:0:-1]) # 切片取值默认是从左往右的 olle \u0026gt;\u0026gt;\u0026gt; print(s[-1:-10:-1]) ~ybab gib 3.长度len：统计的是字符串中字符的个数\n\u0026gt;\u0026gt;\u0026gt; s1 = ' ' \u0026gt;\u0026gt;\u0026gt; print(len(s1)) 1 4.成员运算in和not in ：判断一个子字符串是否存在于一个大的字符串中\n\u0026gt;\u0026gt;\u0026gt; print('egon' in 'egon is dsb and egon is sb') True \u0026gt;\u0026gt;\u0026gt; print('g' in 'egon is dsb and egon is sb') True \u0026gt;\u0026gt;\u0026gt; print('jason' not in 'egon is dsb and egon is sb') True 5.去掉字符串左右两边的字符strip，不管中间的\n\u0026gt;\u0026gt;\u0026gt; username = input('\u0026gt;\u0026gt;\u0026gt;:').strip() \u0026gt;\u0026gt;\u0026gt;: jason \u0026gt;\u0026gt;\u0026gt; if username == 'jason': ... print('good job') ... good job # strip()默认去除字符串首尾的空格 # 使用内置的方法统一采用 句点符(.) \u0026gt;\u0026gt;\u0026gt; name1 = 'jason' \u0026gt;\u0026gt;\u0026gt; name2 = ' jason '.strip() \u0026gt;\u0026gt;\u0026gt; print(name1 == name2) True \u0026gt;\u0026gt;\u0026gt; name3 = '$$$$$eg$on$$$$' \u0026gt;\u0026gt;\u0026gt; print(name3.strip('$')) eg$on # 了解即可 \u0026gt;\u0026gt;\u0026gt; name4 = '% ￥#jason\u0026amp;*)' \u0026gt;\u0026gt;\u0026gt; print(name4.strip('% ￥#)')) jason\u0026amp;* # rstrip() lstrip() \u0026gt;\u0026gt;\u0026gt; name1 = 'jason' \u0026gt;\u0026gt;\u0026gt; name2 = ' jason '.strip() \u0026gt;\u0026gt;\u0026gt; print(name1 == name2) True \u0026gt;\u0026gt;\u0026gt; name3 = '$$$$$eg$on$$$$' \u0026gt;\u0026gt;\u0026gt; print(name3.strip('$')) eg$on \u0026gt;\u0026gt;\u0026gt; name4 = '% ￥#jason\u0026amp;*)' \u0026gt;\u0026gt;\u0026gt; print(name4.strip('% ￥#)')) jason\u0026amp;* \u0026gt;\u0026gt;\u0026gt; name5 = '$$$$jason$$$$' \u0026gt;\u0026gt;\u0026gt; print(name5.lstrip('$')) # left左边 jason$$$$ \u0026gt;\u0026gt;\u0026gt; print(name5.rstrip('$')) # right右边 $$$$jason 6.切分split：针对按照某种分隔符组织的字符串，可以用split将其切分成列表，进而进行取值\n\u0026gt;\u0026gt;\u0026gt; data = 'jaso n|123| handsome' \u0026gt;\u0026gt;\u0026gt; print(data.split('|')) ['jaso n', '123', ' handsome'] \u0026gt;\u0026gt;\u0026gt; username,password,info = data.split('|') \u0026gt;\u0026gt;\u0026gt; print(username,password,info) jaso n 123 handsome # 强调:split切分出来的数据类型是一个列表 \u0026gt;\u0026gt;\u0026gt; print(data.split('o')) # 切割的顺序其实从左往右的 ['jas', ' n|123| hands', 'me'] \u0026gt;\u0026gt;\u0026gt; print(data.split('o',1)) # 切割的顺序其实从左往右的 ['jas', ' n|123| handsome'] \u0026gt;\u0026gt;\u0026gt; print(data.rsplit('o',1)) # 切割的顺序其实从左往右的 ['jaso n|123| hands', 'me'] # 如果不指定那么split和rsplit效果是一样 7.循环\n\u0026gt;\u0026gt;\u0026gt; for i in data: ... print(i) ... 重点掌握的内置方法\n1.strip,lstrip,rstrip\n2.lower,upper\n\u0026gt;\u0026gt;\u0026gt; s = 'JaS1oN' \u0026gt;\u0026gt;\u0026gt; res = s.lower() \u0026gt;\u0026gt;\u0026gt; print(res) jas1on \u0026gt;\u0026gt;\u0026gt; print(s) JaS1oN \u0026gt;\u0026gt;\u0026gt; print(s.upper()) JAS1ON \u0026gt;\u0026gt;\u0026gt; print(s) JaS1oN # 调用字符串的方法并没有改变字符串本身 3.startswith,endswith\n\u0026gt;\u0026gt;\u0026gt; s1 = 'egon is dsb' \u0026gt;\u0026gt;\u0026gt; print(s1.startswith('e')) # 判断字符串是否以什么什么开头 True \u0026gt;\u0026gt;\u0026gt; print(s1.endswith('n')) # 判断字符串是否以什么什么结尾 False 4.format的三种玩法(python推荐使用format做格式化输出)\n第一种 按位置占位 跟%s原理一致\n\u0026gt;\u0026gt;\u0026gt; str1 = 'my name is {} my age is {}'.format(18,'jason',) \u0026gt;\u0026gt;\u0026gt; print(str1) my name is 18 my age is jason \u0026gt;\u0026gt;\u0026gt; str2 = 'my name is {} my age is {}'.format('jason',18) \u0026gt;\u0026gt;\u0026gt; print(str2) my name is jason my age is 18 第二种 按索引占位\n\u0026gt;\u0026gt;\u0026gt; str1 = 'my {1} name is {0} my {0} age is {0}'.format('egon',18) \u0026gt;\u0026gt;\u0026gt; print(str1) my 18 name is egon my egon age is egon 第三种 指名道姓占位(关键字传参)\n\u0026gt;\u0026gt;\u0026gt; str1 = 'my {name} name is {age} my {name} age is {name}'.format(name='jason',age=18) \u0026gt;\u0026gt;\u0026gt; print(str1) my jason name is 18 my jason age is jason 5.split,rsplit\n6.join\n\u0026gt;\u0026gt;\u0026gt; data = 'jason|123|handsome' \u0026gt;\u0026gt;\u0026gt; res_list = data.split('|') \u0026gt;\u0026gt;\u0026gt; print(res_list) ['jason', '123', 'handsome'] \u0026gt;\u0026gt;\u0026gt; res_str = '$'.join(res_list) # 将容器类型中的多个元素通过指定字符拼接成一个字符串 \u0026gt;\u0026gt;\u0026gt; print(res_str) jason$123$handsome \u0026gt;\u0026gt;\u0026gt; l = ['1','a','b'] \u0026gt;\u0026gt;\u0026gt; res = '|'.join(l) \u0026gt;\u0026gt;\u0026gt; print(res) 1|a|b 7.replace\n\u0026gt;\u0026gt;\u0026gt; str = 'egon is dsb and egon is sb he has a BENZ' \u0026gt;\u0026gt;\u0026gt; res = str.replace('egon','kevin',1) \u0026gt;\u0026gt;\u0026gt; print(res) kevin is dsb and egon is sb he has a BENZ \u0026gt;\u0026gt;\u0026gt; print(str) egon is dsb and egon is sb he has a BENZ 8.isdigit # 判断字符串中包含的是否是纯数字\n\u0026gt;\u0026gt;\u0026gt; while True: ... age = input('\u0026gt;\u0026gt;\u0026gt;:') ... if age.isdigit(): ... age = int(age) ... if age \u0026gt; 28: ... print('阿姨好') ... else: ... print('加个微信') ... else: ... print('你TM能不能好好输') ... \u0026gt;\u0026gt;\u0026gt;:30 阿姨好 \u0026gt;\u0026gt;\u0026gt;:14 加个微信 \u0026gt;\u0026gt;\u0026gt;:aaa 你TM能不能好好输 需要了解的内置方法\n1.find,rfind,index,rindex,count\n\u0026gt;\u0026gt;\u0026gt; s = 'kevin is dsb o and kevin is sb' \u0026gt;\u0026gt;\u0026gt; print(s.find('dsb')) # 返回的是d字符所在的索引值 9 \u0026gt;\u0026gt;\u0026gt; print(s.find('xxx')) # 找不到的时候不报错返回的是-1 -1 \u0026gt;\u0026gt;\u0026gt; print(s.find('i',0,3)) # 还可以通过索引来限制查找范围 -1 \u0026gt;\u0026gt;\u0026gt; print(s.index('o')) # 返回所传字符所在的索引值 13 \u0026gt;\u0026gt;\u0026gt; print(s.index('i',0,3)) # 返回所传字符所在的索引值 Traceback (most recent call last): File \u0026quot;\u0026lt;stdin\u0026gt;\u0026quot;, line 1, in \u0026lt;module\u0026gt; ValueError: substring not found \u0026gt;\u0026gt;\u0026gt; print(s.count('n')) # 统计字符出现的次数 3 2.center,ljust,rjust,zfill\n\u0026gt;\u0026gt;\u0026gt; s9 = 'jason' \u0026gt;\u0026gt;\u0026gt; print(s9.center(12,'*')) ***jason**** \u0026gt;\u0026gt;\u0026gt; print(s9.ljust(40,'$')) jason$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ \u0026gt;\u0026gt;\u0026gt; print(s9.rjust(40,'$')) $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$jason \u0026gt;\u0026gt;\u0026gt; print(s9.rjust(40,' ')) jason \u0026gt;\u0026gt;\u0026gt; print(s9.zfill(40)) 00000000000000000000000000000000000jason 3.expandtabs\n\u0026gt;\u0026gt;\u0026gt; s10 = 'a\\tbc' \u0026gt;\u0026gt;\u0026gt; print(s10.expandtabs(100)) a bc 4.captalize,swapcase,title\n\u0026gt;\u0026gt;\u0026gt; s12 = 'hElLo WoRLD sH10' \u0026gt;\u0026gt;\u0026gt; print(s12.capitalize()) # Hello world 首字母大写 Hello world sh10 \u0026gt;\u0026gt;\u0026gt; print(s12.swapcase()) # 大小写互换 HeLlO wOrld Sh10 \u0026gt;\u0026gt;\u0026gt; print(s12.title()) # 每个单词的首字母大小 Hello World Sh10 5.is数字系列\n\u0026gt;\u0026gt;\u0026gt; num1=b'4' #bytes \u0026gt;\u0026gt;\u0026gt; num2=u'4' #unicode,python3中无需加u就是unicode \u0026gt;\u0026gt;\u0026gt; num3='壹' #中文数字 \u0026gt;\u0026gt;\u0026gt; num4='Ⅳ' #罗马数字 # ''.isnumeric(): unicode,中文数字，罗马数字 只要是表示数字都识别 \u0026gt;\u0026gt;\u0026gt; print(num2.isnumeric()) True \u0026gt;\u0026gt;\u0026gt; print(num3.isnumeric()) True \u0026gt;\u0026gt;\u0026gt; print(num4.isnumeric()) True # ''.isdecimal(): unicode 只识别普通的阿拉伯数字 \u0026gt;\u0026gt;\u0026gt; print(num2.isdecimal()) True \u0026gt;\u0026gt;\u0026gt; print(num3.isdecimal()) False \u0026gt;\u0026gt;\u0026gt; print(num4.isdecimal()) False # ''.isdigit() :bytes,unicode 通常情况下使用isdigit就已经满足需求了 \u0026gt;\u0026gt;\u0026gt; print(num1.isdigit()) True \u0026gt;\u0026gt;\u0026gt; print(num2.isdigit()) True \u0026gt;\u0026gt;\u0026gt; print(num3.isdigit()) False \u0026gt;\u0026gt;\u0026gt; print(num4.isdigit()) False 列表类型\n用途：多个装备，多个爱好，多门课程等\n定义：[ ]内可以有多个任意类型的值，逗号分隔\n\u0026gt;\u0026gt;\u0026gt; l1=list({'name':'jason','password':'123'}) \u0026gt;\u0026gt;\u0026gt; print(l1) ['name', 'password'] # list内部原理就是for循环取值 然后一个个塞到列表中去 优先掌握的操作\n#1、按索引存取值(正向存取+反向存取)：即可存也可以取 \u0026gt;\u0026gt;\u0026gt; l = [1,2,3,4] \u0026gt;\u0026gt;\u0026gt; print(l[0:4:1]) [1, 2, 3, 4] \u0026gt;\u0026gt;\u0026gt; print(l[0::]) [1, 2, 3, 4] \u0026gt;\u0026gt;\u0026gt; print(l[5::-1]) [4, 3, 2, 1] \u0026gt;\u0026gt;\u0026gt; print(id(l)) 1931767893768 \u0026gt;\u0026gt;\u0026gt; l[0] = 69 \u0026gt;\u0026gt;\u0026gt; print(id(l)) 1931767893768 \u0026gt;\u0026gt;\u0026gt; print(l) [69, 2, 3, 4] #2、切片(顾头不顾尾，步长) \u0026gt;\u0026gt;\u0026gt; l = [11,22,33,44,55] \u0026gt;\u0026gt;\u0026gt; l1 = [99,88,77,66] # 1.尾部添加一个66 \u0026gt;\u0026gt;\u0026gt; l.append(66) # 注意append值能将被添加的数据当作列表的一个元素 \u0026gt;\u0026gt;\u0026gt; print(l) [11, 22, 33, 44, 55, 66] # 2.任意位置添加元素 \u0026gt;\u0026gt;\u0026gt; l.insert(2,96) # 通过索引在任意位置添加元素 \u0026gt;\u0026gt;\u0026gt; print(l) # 注意insert值能将被添加的数据当作列表的一个元素 [11, 22, 96, 33, 44, 55, 66] # 3.添加容器类型数据 \u0026gt;\u0026gt;\u0026gt; l.append(l1) \u0026gt;\u0026gt;\u0026gt; l.insert(-1,l1) \u0026gt;\u0026gt;\u0026gt; l.extend(l1) # 内部原理for循环l1一个个追加到列表的尾部 \u0026gt;\u0026gt;\u0026gt; l.extend([1,]) \u0026gt;\u0026gt;\u0026gt; print(l) [11, 22, 96, 33, 44, 55, 66, [99, 88, 77, 66], [99, 88, 77, 66], 99, 88, 77, 66, 1] #3、长度 \u0026gt;\u0026gt;\u0026gt; print(len(l)) 14 #4、成员运算in和not in \u0026gt;\u0026gt;\u0026gt; print( 444 in l) False #5、往列表中添加元素（******） # append # insert # extend #6、删除 \u0026gt;\u0026gt;\u0026gt; print(l) [11, 22, 96, 33, 44, 55, 66, [99, 88, 77, 66], [99, 88, 77, 66], 99, 88, 77, 66, 1] \u0026gt;\u0026gt;\u0026gt; del l[2] # del适用于所有的删除操作 \u0026gt;\u0026gt;\u0026gt; print(l) [11, 22, 33, 44, 55, 66, [99, 88, 77, 66], [99, 88, 77, 66], 99, 88, 77, 66, 1] \u0026gt;\u0026gt;\u0026gt; res1 = l.pop() # 尾部弹出 \u0026gt;\u0026gt;\u0026gt; res2 = l.pop() \u0026gt;\u0026gt;\u0026gt; res3 = l.pop() \u0026gt;\u0026gt;\u0026gt; print(res1,res2,res3) 1 66 77 \u0026gt;\u0026gt;\u0026gt; res1 = l.pop(0) # 可以指定索引 按照索引弹出元素 \u0026gt;\u0026gt;\u0026gt; print(res1) 11 \u0026gt;\u0026gt;\u0026gt; res = l.remove(33) # 指定要删除的元素的值 \u0026gt;\u0026gt;\u0026gt; print(l) [22, 44, 55, 66, [99, 88, 77, 66], [99, 88, 77, 66], 99, 88] \u0026gt;\u0026gt;\u0026gt; print(res) None #7、循环 \u0026gt;\u0026gt;\u0026gt; for i in l: ... print(i) ... 22 44 55 66 [99, 88, 77, 66] [99, 88, 77, 66] 99 88 END\n","permalink":"https://zhong-er.com/posts/python%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/","summary":"\u003cp\u003e\u003cimg alt=\"Python第04课(数据类型).png\" loading=\"lazy\" src=\"/assets/201908064143_4756_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython数据类型 \u0026raquo;\u0026gt;转到\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/3BB-YIvKm8\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11128314.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e整型(int)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e用途：记录QQ号、手机号、身份证号等\u003c/p\u003e\n\u003cp\u003e定义：age = 18\u003c/p\u003e\n\u003cp\u003e常用操作+内置的方法\u003c/p\u003e\n\u003cp\u003eint只能转存数字的字符串，小数点都不行\u003c/p\u003e\n\u003cp\u003e进制转换\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026gt;\u0026gt;\u0026gt; print(int('1100',2))  # 二进制转十进制  0,1\n12\n\u0026gt;\u0026gt;\u0026gt; print(int('14',8))  # 八进制转十进制  0-7\n12\n\u0026gt;\u0026gt;\u0026gt; print(int('c',16))  # 十六进制转十进制 0-9 A-F\n12\n# int可以传第一个参数，表示的是第一个参数到底是什么进制  然后转成十进制\n\n\u0026gt;\u0026gt;\u0026gt; print(bin(12))  # 10进制转二进制\n0b1100  # 0b1100  0b表示后面的数字是二进制数\n\u0026gt;\u0026gt;\u0026gt; print(oct(12))  # 10进制转八进制\n0o14  # 0o14  0o表示后面的数字是八进制数\n\u0026gt;\u0026gt;\u0026gt; print(hex(12))  # 10进制转16进制\n0xc  # 0xc  0x表示后面的数字是十六进制数\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e有序：但凡有索引的数据都是有序的\u003c/p\u003e\n\u003cp\u003e不可变：值改变的情况下，id一定变\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e浮点型(float)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e用途：记录薪资、身高、体重等\u003c/p\u003e\n\u003cp\u003e定义：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003esalary = 1.1 # salary = float(1.1)\n\n\u0026gt;\u0026gt;\u0026gt; res = float('1.11')\n\u0026gt;\u0026gt;\u0026gt; print(type(res))\n\u0026lt;class 'float'\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e有序：但凡有索引的数据都是有序的\u003c/p\u003e","title":"【Python】数据类型"},{"content":"\nPython流程控制 \u0026raquo;\u0026gt;转到思维导图****\u0026gt; \u0026raquo;转到博客园********\nif判断\n判断失误的对错，真假，是否可行\n固定语法\nif 条件: 代码1 代码2 代码3 代码4 ... python的缩进表示代码的归属\n同一缩进的代码，称之为代码块\n补充：布尔值位False数据由哪些：0,None,\u0026rsquo; \u0026lsquo;,[ ],{ }\n布尔值也可以直接用来判断\n变量名也可以用来判断，因为变量名指向的值对应的就是True或False\nelse 语法 # 不能单独使用，必须和if，while，for配合使用\nif 条件: 代码1 代码2 代码3 代码4 ... else: # 不能单独使用，必须和if，while，for配合使用 代码1 代码2 代码3 代码4 ... if elif else 语法 ：同一级别配合使用的时候，只会指向一个代码块\nif 条件: 代码1 代码2 代码3 代码4 ... elif 条件: 代码1 代码2 代码3 代码4 ... else: # 不能单独使用，必须和if，while，for配合使用 代码1 代码2 代码3 代码4 ... 走了if就不会走elif else，走了elif就不会走else，if看到没走，走了else说明if elif都没走，都不满足if elif的条件\nif可以嵌套\nwhile 语法\nwhile 条件: 代码1 代码2 代码3 代码4 代码5 ... break ：立即结束本层循环（只针对它所属于的那一个while有效）\ncontinue ：跳出本次循环，直接开始下一次循环\nwhile+else\n只用当while循环依据条件正常结束才会走else代码\n如果是主动结束的break，那么不会走else\nfor循环\n不依赖与索引取值\nfor循环语法\nfor 变量名 in 容器类型: 代码1， 代码2， 代码3， 代码4， len() # 获取数据类型(容器类型)的个数，字符串是特例 获取的是字符串中字符的个数\nrange 在python2中与python3的区别\npython2中\n1.range其实就是一个列表\n2.xrange其实就是你python3中的range\npython3中的range是个老母猪，你需要值的时候我才给你\n","permalink":"https://zhong-er.com/posts/python%E6%B5%81%E7%A8%8B%E6%8E%A7%E5%88%B6/","summary":"\u003cp\u003e\u003cimg alt=\"Python第03课(流程控制).png\" loading=\"lazy\" src=\"/assets/201908064421_9543_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython流程控制 \u0026raquo;\u0026gt;转到\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/3DKlGWgq58\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11120996.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e********\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eif判断\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e判断失误的对错，真假，是否可行\u003c/p\u003e\n\u003cp\u003e固定语法\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eif 条件:\n    代码1\n    代码2\n    代码3\n    代码4\n    ...\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003epython的缩进表示代码的归属\u003c/p\u003e\n\u003cp\u003e同一缩进的代码，称之为代码块\u003c/p\u003e\n\u003cp\u003e补充：布尔值位False数据由哪些：0,None,\u0026rsquo; \u0026lsquo;,[ ],{ }\u003c/p\u003e\n\u003cp\u003e布尔值也可以直接用来判断\u003c/p\u003e\n\u003cp\u003e变量名也可以用来判断，因为变量名指向的值对应的就是True或False\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eelse 语法\u003c/strong\u003e # 不能单独使用，必须和if，while，for配合使用\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eif 条件:\n    代码1\n    代码2\n    代码3\n    代码4\n    ...\nelse:  # 不能单独使用，必须和if，while，for配合使用\n    代码1\n    代码2\n    代码3\n    代码4\n    ...\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003eif elif else 语法\u003c/strong\u003e ：同一级别配合使用的时候，只会指向一个代码块\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eif 条件:\n    代码1\n    代码2\n    代码3\n    代码4\n    ...\nelif 条件:\n    代码1\n    代码2\n    代码3\n    代码4\n    ...\nelse:  # 不能单独使用，必须和if，while，for配合使用\n    代码1\n    代码2\n    代码3\n    代码4\n    ...\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e走了if就不会走elif else，走了elif就不会走else，if看到没走，走了else说明if elif都没走，都不满足if elif的条件\u003c/p\u003e","title":"【Python】流程控制"},{"content":"\nPython输入输出/数据类型/运算符 \u0026raquo;\u0026gt;转到思维导图********\u0026gt; \u0026raquo;转到博客园********\nPython的注释\n单行注释# 和多行注释\u0026rsquo;\u0026rsquo;\u0026rsquo; \u0026rsquo;\u0026rsquo;'' 输入\npython3中的input全部存成字符串类型\npython2中需要认为告诉input输入的什么数据类型\nname = input(\u0026lsquo;please input\u0026raquo;\u0026gt;:\u0026rsquo;) please input\u0026raquo;\u0026gt;:jason Traceback (most recent call last): File \u0026ldquo;\u0026quot;, line 1, in File \u0026ldquo;\u0026quot;, line 1, in NameError: name \u0026lsquo;jason\u0026rsquo; is not defined name = input(\u0026lsquo;please input\u0026raquo;\u0026gt;:\u0026rsquo;) please input\u0026raquo;\u0026gt;:\u0026rsquo;egon\u0026rsquo; name \u0026rsquo;egon\u0026rsquo; type(name) \u0026lt;type \u0026lsquo;str\u0026rsquo;\u0026gt; name = input(\u0026lsquo;please input\u0026raquo;\u0026gt;:\u0026rsquo;) please input\u0026raquo;\u0026gt;:[1,2,3,4] name [1, 2, 3, 4] type(name) \u0026lt;type \u0026rsquo;list\u0026rsquo;\u0026gt;\nraw_input()会统一存为字符串\nname1 = raw_input(\u0026rsquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;:\u0026rsquo;)\n:jason name1 \u0026lsquo;jason\u0026rsquo; type(name1) \u0026lt;type \u0026lsquo;str\u0026rsquo;\u0026gt; name1 = raw_input(\u0026rsquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;:\u0026rsquo;) :[1,2,3,4,5] name1 \u0026lsquo;[1,2,3,4,5]\u0026rsquo; type(name1) \u0026lt;type \u0026lsquo;str\u0026rsquo;\u0026gt;\n格式化输出\ninput自带换行，一个print独占一行\nmy name is 用户输入的用户名 my age is 用户输入的年龄 name = input('username\u0026gt;\u0026gt;\u0026gt;:') age = input('age\u0026gt;\u0026gt;\u0026gt;:') age1 = 18 print(name,age) print(name) print(age) # input自带换行 一个print独占一行 print('my name is',name,'my age is',age) 占位符%s,%d 谁先来谁先做个数必须一致，%08d，不够8位0补齐\nprint('my name is %s my age is %s'%(name,age)) print('my name is %s my age is %s'%(age,name)) # 谁先来谁先坐 个数必须一致不能多也不能少 print('my name is %s my age is %d'%(name,age1)) # %d只能给数字占位 print('my name is %s my age is %s'%('jason',[1,2,3,4,5])) # %d只能给数字占位 %s可以给任意数据类型占位 print('%08d'%123) # 整数是8位，传入的数不够八位的话默认用0补全，超出8位，是多少就是多少 基本数据类型\nint(整型)，float(浮点型)，str(字符串)，list(列表)，dict(字典)，bool(布尔值)\n复数\na = 1-2j type(a) \u0026lt;type \u0026lsquo;complex\u0026rsquo;\u0026gt; a.real 查看实部 1.0 a.imag 查看虚部 -2.0\npython2中才有长整型概念，python3中只有整形\n字符串\n单引号、双引号、三引号(可写多行，也可用作注释)\n支持加和乘操作\n\u0026rsquo;\u0026rsquo;\u0026rsquo; 这是注释 这也是注释 \u0026rsquo;\u0026rsquo;\u0026rsquo; # 第一种 单引号 s1 = \u0026lsquo;我是字符串,jason说:\u0026ldquo;天下无敌\u0026rdquo;\u0026rsquo; # s1 = str(\u0026lsquo;我是字符串\u0026rsquo;) print(type(s1))\n# 第二种 双引号 s2 = \u0026ldquo;我是字符串\u0026rdquo; print(type(s2))\n# 第三种 三引号(可以是单 也可以是双 但是不能混合使用) 它还可以用来做注释 s3 = \u0026rsquo;\u0026rsquo;\u0026rsquo; 我是字符串 并且可以写多行 \u0026rsquo;\u0026rsquo;\u0026rsquo; s4 = \u0026quot;\u0026rdquo;\u0026rdquo; 我是字符串 并且可以写多行 \u0026quot;\u0026quot;\u0026quot; print(type(s3)) print(type(s4))\n# 之所以有三种定义方式 是考虑到用户在输入的文本中也需要用到引号\ns5 = \u0026lsquo;hello\u0026rsquo; s6 = \u0026lsquo;world\u0026rsquo; # print(s5+s6)\nprint(s5*10)\n# python中的字符串支持加和乘操作\n列表\n作用:能够存储多个数据 并且可以方便的取出一条或多条\n定义:[]内部通过逗号分隔开多个数据(元素) 并且这多个可以是任意的数据类型\n字典\n作用:能够存储多组数据，存取的数据有对应的描述\n定义:大括号内存多个元素,元素的格式key:value形式存储，键值对与键值对之间逗号隔开\nkey通常是描述value的描述性信息，key通常存的就是字符串\nvalue就是描述性信息对应的真实的数据，value可以是任意类型\n强调 字典是无序的\n字典暴露给用户可以操作的部分只有key 无法直接获取到value\n布尔值\n作用:标识事物的对错(可行性) 通过True False来判断事物的真假 对错 是否可行\ntag = True # tag = bool(True) tag1 = False\n# print( 1 \u0026gt; 0)\nx = 10 y = 20 print(x == y) # 单个等号是赋值 两个等号是比较 比较的仅仅是值是否相等 print(x is y) # 比较的是两个变量对应的id是否相等\n\u0026quot;\u0026quot;\u0026quot; id相等的情况下 值一定相等 id不相等的情况下，值有可能相等 \u0026quot;\u0026quot;\u0026quot;\n运算符\n基本运算符\n# print(10 / 3) #结果保留小数部分 # print(10 // 3) # 只保留整数部分 # print(10 % 3) # 取余数 # print(2**3) # 2的三次方 # python对数字的敏感度不是很高(存储的数字不精确)\n# 比较运算:== != \u0026gt; \u0026lt; \u0026gt;= \u0026lt;= # 了解知识: # 数字之间可以互相比较大小 # print(10 \u0026gt; 3) # print(10 \u0026gt; 3.1) # print(10 != 9) # 不等于\n# 了解 # a = \u0026lsquo;hello\u0026rsquo; # b = \u0026lsquo;z\u0026rsquo; # print(b \u0026gt; a) # A-Z a-z z对应的数字最大 A对应的数字最小\n# 赋值运算 xxx = 18 # 增量运算\n# xxx += 1 # xxx = xxx + 1 # xxx = 2 # xxx = xxx2 # xxx -= 10 # xxx = xxx - 10 # print(xxx)\n# 链式赋值 # x = 100 # y = 100 # z = 100\n# a = 100 # b = a # c = b\n# x = y = z = 8000\nm = 80 n = 70 # 第一种 # o = m # m = n # n = o # print(m,n)\n# 交叉赋值 # m,n = n,m # print(m,n)\n# 解压赋值 l = [1,2,3,2,3,4,5,6,7,6,4,56,7,6,5,3,3,4,4] # a = l[0] # b = l[1] # c = l[2] # d = l[3] # a,b,c,d, = l # 前面的变量个数与后面的元素个数必须相等 # a,,,d = l # a,*,d = l # print(a,d) # print()\n逻辑运算符\n# 与 或 非 # and or not # print( 1 \u0026gt; 0 and 3 \u0026gt; 4) # and两边必须同时成立 结果才成立 # print( 1 \u0026gt; 0 or 1 \u0026gt; 0 and 3 \u0026gt; 4) # or只要有一边成立 立即成立 后面的无需再看 # print(not 1 \u0026gt; 3) # 结果取反 age=20 sex=\u0026lsquo;female\u0026rsquo; print(age \u0026gt; 18 and age \u0026lt; 26 and sex == \u0026lsquo;female\u0026rsquo; and 1 \u0026gt; 3)\nprint(1 \u0026gt; 3 or 2 \u0026gt; 4 or \u0026lsquo;x\u0026rsquo; == \u0026lsquo;y\u0026rsquo; or 1==1)\n","permalink":"https://zhong-er.com/posts/python%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E8%BF%90%E7%AE%97%E7%AC%A6/","summary":"\u003cp\u003e\u003cimg alt=\"Python第02课(输入输出_数据类型_运算符).png\" loading=\"lazy\" src=\"/assets/201908066557_7614_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython输入输出/数据类型/运算符 \u0026raquo;\u0026gt;转到\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/SDSzaoBba\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e********\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11116171.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e********\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003ePython的注释\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e单行注释# 和多行注释\u0026rsquo;\u0026rsquo;\u0026rsquo; \u0026rsquo;\u0026rsquo;''\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e输入\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003epython3中的input全部存成字符串类型\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003epython2中需要认为告诉input输入的什么数据类型\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e name = input(\u0026lsquo;please input\u0026raquo;\u0026gt;:\u0026rsquo;)\nplease input\u0026raquo;\u0026gt;:jason\nTraceback (most recent call last):\nFile \u0026ldquo;\u003cstdin\u003e\u0026quot;, line 1, in \u003cmodule\u003e\nFile \u0026ldquo;\u003cstring\u003e\u0026quot;, line 1, in \u003cmodule\u003e\nNameError: name \u0026lsquo;jason\u0026rsquo; is not defined\n name = input(\u0026lsquo;please input\u0026raquo;\u0026gt;:\u0026rsquo;)\nplease input\u0026raquo;\u0026gt;:\u0026rsquo;egon\u0026rsquo;\n name\n\u0026rsquo;egon\u0026rsquo;\n type(name)\n\u0026lt;type \u0026lsquo;str\u0026rsquo;\u0026gt;\n name = input(\u0026lsquo;please input\u0026raquo;\u0026gt;:\u0026rsquo;)\nplease input\u0026raquo;\u0026gt;:[1,2,3,4]\n name\n[1, 2, 3, 4]\n type(name)\n\u0026lt;type \u0026rsquo;list\u0026rsquo;\u0026gt;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eraw_input()会统一存为字符串\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e name1 = raw_input(\u0026rsquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;:\u0026rsquo;)\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e:jason\n name1\n\u0026lsquo;jason\u0026rsquo;\n type(name1)\n\u0026lt;type \u0026lsquo;str\u0026rsquo;\u0026gt;\n name1 = raw_input(\u0026rsquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;\u0026raquo;:\u0026rsquo;)\n:[1,2,3,4,5]\n name1\n\u0026lsquo;[1,2,3,4,5]\u0026rsquo;\n type(name1)\n\u0026lt;type \u0026lsquo;str\u0026rsquo;\u0026gt;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003c/ul\u003e","title":"【Python】输入输出/数据类型/运算符"},{"content":"\nPython计算机发展史/变量/垃圾回收机制 \u0026raquo;\u0026gt;转到思维导图********\u0026gt; \u0026raquo;转到博客园********\n****编程语言发展史\n机器语言\n站在奴隶主的角度说奴隶主的话\n1.编译型(类似于谷歌翻译)\n2.解释型(类似于同声传译)\nPS:一次翻译之后，就可以拿着翻译之后的结果多次运行\n汇编语言\n用简单的英文标签来表示二进制数，直接操作硬件\n优点：开发效率高于机器语言\n缺点：执行效率较机器语言低\n高级语言\n站在奴隶主的角度说奴隶主的话\n1.编译型(类似于谷歌翻译)\n一次翻译之后，就可以拿着翻译之后的结果多次运行\n编译的过程需要用到编译器\n优点：执行效率高\n缺点：开发效率低\n2.解释型(类似于同声传译)\n一行一行的翻译(读一行翻译一行)\n解释的过程需要用到解释器\n优点：开发效率高\n缺点：执行效率低\nPS:\n学习难度：高级语言\u0026raquo;\u0026gt;汇编语言\u0026raquo;\u0026gt;机器语言\n执行效率：机器语言\u0026raquo;\u0026gt;汇编语言\u0026raquo;\u0026gt;高级语言\n开发效率：机器语言\u0026raquo;\u0026gt;汇编语言\u0026raquo;\u0026gt;高级语言\nPython\npython是解释型语言\n1.胶水语言\n2.调包侠\npython解释器，环境变量配置，多版本共存\n在IT行业不要贸然尝试用最新版本的软件！！！\n第一个Python程序\n1.交互式\n优点：输入内容立刻就有对应的返回结果\n缺点：无法永久保存数据\n2.命令行(文件形式)\n优点：可以永久保存数据\n缺点：暂时来看运行该文件有点麻烦\nPS：文件后缀名\n仅仅是给人看的\n你所认为的不同的文件后缀有不同的功能那是程序员自己写的\npython文件默认的后缀名就是.py结尾\n运行一个py文件需要走的步骤(*)\n1.将python解释器代码从硬盘督导内存(就等价于双击了word图标)\n2.将你写好的py文件由应哦按读到内存(就等价于你双击了一个word文档)\n3.解释器解释读取py文件中的内容，解释成计算机能够识别的语句(如果是一个普通文本文件，仅仅只会将文件内容展示到屏幕上给用户查看，不会检测翻译文件内容)\nPS:Pyhon解释器与普通文本编辑器前面两步是一毛一样的，仅仅第三步不一样(一个是解释语法，一个是文本展示)\n变量\n1.什么是变量\n量：衡量/记录事物的状态/特征\n变：状态/特征是可以变化的\n2.为什么要有变量？\n帮助你记录保存事物的某种状态或特征\n3.变量的定义\n1.只能包含数字，字母，下划线\n2.数字不能开头\n3.关键字不能作为变量名\n变量必须先定义后调用(使用)，变量名不要加引号\n4.变量额三要素\n1. id():返回的是一串数字\n2. type():返回的是该变量对应的主句的类型\n3. value:该变量指向的内存当中数据的值\n5.通常变量名的命名有两个流派\n驼峰体(前端语言js推荐的命名方式)\nuserName 下划线(python推荐的命名方式)\nuser_name 注意:变量名一定要起的有意义(见名知意)，千万不要用中文\n6.小整数池()\nPython实现int的时候有个小整数池。为了避免因创建相同的值而重复申请内存空间所带来的效率问题， Python解释器会在启动时创建出小整数池，范围是[-5,256]，该范围内的小整数对象是全局解释器范围内被重复使用，永远不会被GC回收\n\u0026gt;\u0026gt;\u0026gt; a = 257 \u0026gt;\u0026gt;\u0026gt; b = 257 \u0026gt;\u0026gt;\u0026gt; id(a) 2919979319120 \u0026gt;\u0026gt;\u0026gt; id(b) 2919979576208 \u0026gt;\u0026gt;\u0026gt; c = 256 \u0026gt;\u0026gt;\u0026gt; d = 256 \u0026gt;\u0026gt;\u0026gt; id(c) 1642892736 \u0026gt;\u0026gt;\u0026gt; id(d) 1642892736 垃圾回收机制\n1.引用计数：\n内存中的数据如果没有任何变量名与其有绑定关系，那么会被自动回收\n2.标记清除：\n当内存快要被某个应用程序占满时，会自动触发\n3.分代回收：\n根据值得存活时间的不同，化为不同的等级，等级越高垃圾回收机制扫描的频率越低\n常亮(不可变的量)\npython里面压根没有常量\n通常将全大写的变量名看做常量(python程序员约定俗成的)\n","permalink":"https://zhong-er.com/posts/python%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%8F%91%E5%B1%95%E5%8F%B2%E5%8F%98%E9%87%8F%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6/","summary":"\u003cp\u003e\u003cimg alt=\"Python第01课(计算机发展史_变量_垃圾回收.png\" loading=\"lazy\" src=\"/assets/201908062863_1504_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython计算机发展史/变量/垃圾回收机制 \u0026raquo;\u0026gt;转到\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/2rsFXAwli8\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e********\u003c/a\u003e\u003cstrong\u003e\u0026gt; \u0026raquo;转到\u003c/strong\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11103673.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e********\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11103673.html\" title=\"博客园\"\u003e****\u003c/a\u003e\u003ca href=\"https://www.cnblogs.com/renvip/p/11103673.html\" title=\"博客园\"\u003e\u003c/a\u003e\u003cstrong\u003e编程语言发展史\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e机器语言\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e站在奴隶主的角度说奴隶主的话\u003c/p\u003e\n\u003cp\u003e1.编译型(类似于谷歌翻译)\u003c/p\u003e\n\u003cp\u003e2.解释型(类似于同声传译)\u003c/p\u003e\n\u003cp\u003ePS:一次翻译之后，就可以拿着翻译之后的结果多次运行\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e汇编语言\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e用简单的英文标签来表示二进制数，直接操作硬件\u003c/p\u003e\n\u003cp\u003e优点：开发效率高于机器语言\u003c/p\u003e\n\u003cp\u003e缺点：执行效率较机器语言低\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e高级语言\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e站在奴隶主的角度说奴隶主的话\u003c/p\u003e\n\u003cp\u003e1.编译型(类似于谷歌翻译)\u003c/p\u003e\n\u003cp\u003e一次翻译之后，就可以拿着翻译之后的结果多次运行\u003c/p\u003e\n\u003cp\u003e编译的过程需要用到编译器\u003c/p\u003e\n\u003cp\u003e优点：执行效率高\u003c/p\u003e\n\u003cp\u003e缺点：开发效率低\u003c/p\u003e\n\u003cp\u003e2.解释型(类似于同声传译)\u003c/p\u003e\n\u003cp\u003e一行一行的翻译(读一行翻译一行)\u003c/p\u003e\n\u003cp\u003e解释的过程需要用到解释器\u003c/p\u003e\n\u003cp\u003e优点：开发效率高\u003c/p\u003e\n\u003cp\u003e缺点：执行效率低\u003c/p\u003e\n\u003cp\u003ePS:\u003c/p\u003e\n\u003cp\u003e学习难度：高级语言\u0026raquo;\u0026gt;汇编语言\u0026raquo;\u0026gt;机器语言\u003c/p\u003e\n\u003cp\u003e执行效率：机器语言\u0026raquo;\u0026gt;汇编语言\u0026raquo;\u0026gt;高级语言\u003c/p\u003e\n\u003cp\u003e开发效率：机器语言\u0026raquo;\u0026gt;汇编语言\u0026raquo;\u0026gt;高级语言\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003epython是解释型语言\u003c/p\u003e\n\u003cp\u003e1.胶水语言\u003c/p\u003e\n\u003cp\u003e2.调包侠\u003c/p\u003e\n\u003cp\u003epython解释器，环境变量配置，多版本共存\u003c/p\u003e\n\u003cp\u003e在IT行业不要贸然尝试用最新版本的软件！！！\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e第一个Python程序\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e1.交互式\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e优点：输入内容立刻就有对应的返回结果\u003c/p\u003e\n\u003cp\u003e缺点：无法永久保存数据\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2.命令行(文件形式)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e优点：可以永久保存数据\u003c/p\u003e\n\u003cp\u003e缺点：暂时来看运行该文件有点麻烦\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePS：文件后缀名\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e仅仅是给人看的\u003c/p\u003e\n\u003cp\u003e你所认为的不同的文件后缀有不同的功能那是程序员自己写的\u003c/p\u003e\n\u003cp\u003epython文件默认的后缀名就是.py结尾\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e运行一个py文件需要走的步骤(\u003c/strong\u003e*\u003cstrong\u003e)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.将python解释器代码从硬盘督导内存(就等价于双击了word图标)\u003c/p\u003e\n\u003cp\u003e2.将你写好的py文件由应哦按读到内存(就等价于你双击了一个word文档)\u003c/p\u003e\n\u003cp\u003e3.解释器解释读取py文件中的内容，解释成计算机能够识别的语句(如果是一个普通文本文件，仅仅只会将文件内容展示到屏幕上给用户查看，不会检测翻译文件内容)\u003c/p\u003e\n\u003cp\u003ePS:Pyhon解释器与普通文本编辑器前面两步是一毛一样的，仅仅第三步不一样(一个是解释语法，一个是文本展示)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e变量\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e1.什么是变量\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e量：衡量/记录事物的状态/特征\u003c/p\u003e\n\u003cp\u003e变：状态/特征是可以变化的\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2.为什么要有变量？\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e帮助你记录保存事物的某种状态或特征\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3.变量的定义\u003c/strong\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e1.只能包含数字，字母，下划线\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e2.数字不能开头\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e3.关键字不能作为变量名\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e变量必须先定义后调用(使用)，变量名不要加引号\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e4.变量额三要素\u003c/strong\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e1. id():返回的是一串数字\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e2. type():返回的是该变量对应的主句的类型\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e3. value:该变量指向的内存当中数据的值\u003c/p\u003e\n\u003c/blockquote\u003e","title":"【Python】计算机发展史/变量/垃圾回收机制"},{"content":"\nPython是一门编程语言 \u0026raquo;\u0026gt;转到思维导图****** \u0026raquo;\u0026gt;转到**博客园****\n编程\n1.什么是编程\n编写程序，谁来编写，人来编写。\n将自己的思维逻辑和想法通过计算机能够识别的语言写下，告诉计算机按照你的思维逻辑去帮你干活。\n编程的最终结果就是一丢文件。\n2.为什么要编程\n让计算机能够按照你的思维逻辑去工作，解放人力！！！\n语言\n汉语，日语，英语，法语，德育\n事物与事物之间沟通交流的媒介\n操作系统：（windows,linux,unix\u0026hellip;）\n1.操作系统是帮你操作计算机硬件的，它帮你封装好了一系列操作硬件的方法。\n2.程序员只需要按照操作系统提供的操作方法即可简介操作硬件。\n3.一套完整的计算机系统由一下三部分组成\n1.应用程序\n2.操作系统（中间人）\n3.计算机硬件\nps:\n1.程序员开发的程序都叫做应用程序。\n2.操作系统也是应用程序,但是它针对的是计算机硬件。\n操作系统也是一款应用软件，但是它是针对计算机硬件的，将复杂丑陋的操作封装起来，暴露给用户简单快捷的操作接口，帮助你协调，管理，调度计算机各个硬件。\n计算机五大组成部分\n1.控制器\n2.运算器\n控制器+运算器=计算机中央处理器（CPU）\n3.存储器 ：计算机存储系统\n内存\n基于电工作的，通电数据正常读取，一旦断电数据即可丢失。\n优点：读取速度快\n缺点：断电数据丢失\n外存\n优点：能够永久保存数据\n缺点：读取速度慢\n4.输入设备\n键盘，鼠标，触摸屏\n5.输出设备\n显示器，音响，打印机\nps:\n输入设备也称input\n输出设备也称output\n存储器也可以叫着IO设备\n计算机三大核心组件\n1.CPU ：大脑\n2.内存 ：短期记忆\n3.硬盘 ：永远不会消失的本子\n计算机是基于电工作的，而电信号只有高低电频两种状态0和1，计算机里面存储的都是0101的二进制数据。\nCPU工作流程：选取内存中读取命令，将命令解析翻译成自己能够对应上的操作步骤，控制硬件执命令。\n简单指令集\n复杂指令集\nx86架构\nCPU到内存中获取数据最多读32位\n64位 32位\n64位向下兼容32位的软件\n而32位不能够兼容64的软件\n存储器\n寄存器 ：cpu下一步即将要用到的数据（容量小）\n高速缓存 ：将经常需要用到的容量比较小的数据存储在高速缓存\n内存 ：cpu获取数据的主力还是内存\n硬盘 ：硬盘的旋转读取数据\n机械硬盘\n固态硬盘\n磁带 ：服务器数据的备份\nBIOS(basic input output system) 计算机出厂的时候自带的一个系统\n什么是文件\n操作文件其实就是操作系统给你提供的操作硬盘简便接口\n","permalink":"https://zhong-er.com/posts/python%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80/","summary":"\u003cp\u003e\u003cimg alt=\"Python学前准备课(计算机组成原理_编程语言).png\" loading=\"lazy\" src=\"/assets/201908067099_3043_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ePython是一门编程语言 \u0026raquo;\u0026gt;转到\u003c/strong\u003e\u003ca href=\"https://mubu.com/doc/17ZnX4ax0a\" title=\"思维导图\"\u003e\u003cstrong\u003e思维导图\u003c/strong\u003e****\u003c/a\u003e** \u0026raquo;\u0026gt;转到**\u003ca href=\"https://www.cnblogs.com/renvip/p/11098821.html\" title=\"博客园\"\u003e\u003cstrong\u003e博客园\u003c/strong\u003e****\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e编程\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.\u003cem\u003e\u003cstrong\u003e什么是编程\u003c/strong\u003e\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e编写程序，谁来编写，人来编写。\u003c/p\u003e\n\u003cp\u003e将自己的思维逻辑和想法通过计算机能够识别的语言写下，告诉计算机按照你的思维逻辑去帮你干活。\u003c/p\u003e\n\u003cp\u003e编程的最终结果就是一丢文件。\u003c/p\u003e\n\u003cp\u003e2.\u003cem\u003e\u003cstrong\u003e为什么要编程\u003c/strong\u003e\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e让计算机能够按照你的思维逻辑去工作，解放人力！！！\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e语言\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e汉语，日语，英语，法语，德育\u003c/p\u003e\n\u003cp\u003e事物与事物之间沟通交流的媒介\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e操作系统：（windows,linux,unix\u0026hellip;）\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.操作系统是帮你操作计算机硬件的，它帮你封装好了一系列操作硬件的方法。\u003c/p\u003e\n\u003cp\u003e2.程序员只需要按照操作系统提供的操作方法即可简介操作硬件。\u003c/p\u003e\n\u003cp\u003e3.一套完整的计算机系统由一下三部分组成\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e1.应用程序\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e2.操作系统（中间人）\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e3.计算机硬件\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eps:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e1.程序员开发的程序都叫做应用程序。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e2.操作系统也是应用程序,但是它针对的是计算机硬件。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e操作系统也是一款应用软件，但是它是针对计算机硬件的，将复杂丑陋的操作封装起来，暴露给用户简单快捷的操作接口，帮助你协调，管理，调度计算机各个硬件。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e计算机五大组成部分\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.\u003cstrong\u003e\u003cem\u003e控制器\u003c/em\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e2.\u003cstrong\u003e\u003cem\u003e运算器\u003c/em\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e控制器+运算器=计算机中央处理器（CPU）\u003c/p\u003e\n\u003cp\u003e3.\u003cstrong\u003e\u003cem\u003e存储器\u003c/em\u003e\u003c/strong\u003e ：计算机存储系统\u003c/p\u003e\n\u003cp\u003e内存\u003c/p\u003e\n\u003cp\u003e基于电工作的，通电数据正常读取，一旦断电数据即可丢失。\u003c/p\u003e\n\u003cp\u003e优点：读取速度快\u003c/p\u003e\n\u003cp\u003e缺点：断电数据丢失\u003c/p\u003e\n\u003cp\u003e外存\u003c/p\u003e\n\u003cp\u003e优点：能够永久保存数据\u003c/p\u003e\n\u003cp\u003e缺点：读取速度慢\u003c/p\u003e\n\u003cp\u003e4.\u003cstrong\u003e\u003cem\u003e输入设备\u003c/em\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e键盘，鼠标，触摸屏\u003c/p\u003e\n\u003cp\u003e5.\u003cstrong\u003e\u003cem\u003e输出设备\u003c/em\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e显示器，音响，打印机\u003c/p\u003e\n\u003cp\u003eps:\u003c/p\u003e\n\u003cp\u003e输入设备也称input\u003c/p\u003e\n\u003cp\u003e输出设备也称output\u003c/p\u003e\n\u003cp\u003e存储器也可以叫着IO设备\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e计算机三大核心组件\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e1.\u003cstrong\u003e\u003cem\u003eCPU\u003c/em\u003e\u003c/strong\u003e ：大脑\u003c/p\u003e\n\u003cp\u003e2.\u003cstrong\u003e\u003cem\u003e内存\u003c/em\u003e\u003c/strong\u003e ：短期记忆\u003c/p\u003e\n\u003cp\u003e3.\u003cstrong\u003e\u003cem\u003e硬盘\u003c/em\u003e\u003c/strong\u003e ：永远不会消失的本子\u003c/p\u003e\n\u003cp\u003e计算机是基于电工作的，而电信号只有高低电频两种状态0和1，计算机里面存储的都是0101的二进制数据。\u003c/p\u003e\n\u003cp\u003eCPU工作流程：选取内存中读取命令，将命令解析翻译成自己能够对应上的操作步骤，控制硬件执命令。\u003c/p\u003e\n\u003cp\u003e简单指令集\u003c/p\u003e\n\u003cp\u003e复杂指令集\u003c/p\u003e\n\u003cp\u003ex86架构\u003c/p\u003e\n\u003cp\u003eCPU到内存中获取数据最多读32位\u003c/p\u003e\n\u003cp\u003e64位 32位\u003c/p\u003e\n\u003cp\u003e64位向下兼容32位的软件\u003c/p\u003e\n\u003cp\u003e而32位不能够兼容64的软件\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e存储器\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e\u003cstrong\u003e寄存器\u003c/strong\u003e\u003c/em\u003e ：cpu下一步即将要用到的数据（容量小）\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e\u003cstrong\u003e高速缓存\u003c/strong\u003e\u003c/em\u003e ：将经常需要用到的容量比较小的数据存储在高速缓存\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e\u003cstrong\u003e内存\u003c/strong\u003e\u003c/em\u003e ：cpu获取数据的主力还是内存\u003c/p\u003e","title":"【Python】计算机组成原理/编程语言"},{"content":"vps管理面板打开TUN/TAP\n一般在vps管理面板里就可以打开，如果找不到，请联系咨询客服。\n判断是否成功打开TUN/TAP\ncat /dev/net/tun\n返回值如下即为打开成功\nFile descriptor in bad state\n如果本机glibc小于2.14，先更新glibc\nldd \u0026ndash;version\n如果小于2.14，则需要编译安装新版本\u0026gt;2.14\n先安装gcc\nyum install gcc 再编译glibc\nwget http://ftp.gnu.org/gnu/glibc/glibc-2.15.tar.gz wget http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz tar -zxf glibc-2.15.tar.gz tar -zxf glibc-ports-2.15.tar.gz mv glibc-ports-2.15 glibc-2.15/ports mkdir glibc-build-2.15 cd glibc-build-2.15 ../glibc-2.15/configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin make all \u0026amp;\u0026amp; make install 安装BBR\nwget https://raw.githubusercontent.com/kuoruan/shell-scripts/master/ovz-bbr/ovz-bbr-installer.sh chmod +x ovz-bbr-installer.sh ./ovz-bbr-installer.sh 配置加速端口\n安装的时候只配置了一个加速端口，但是你可以配置多端口加速，修改文件\nvi /usr/local/haproxy-lkl/etc/port-rules 在文件里添加需要加速的端口，每行一条，可以配置单个端口或者端口范围，配置完后需要重启haproxy-lkl\nservice haproxy-lkl restart 如果要卸载：\n./ovz-bbr-installer.sh uninstall 检查BBR是否正常\nping 10.0.0.2，如果能通，说明 bbr 已经启动。\n完成后可以查看防火墙规则：（centos6如下操作，centos7则为firewalld）\niptables -t nat -nL ","permalink":"https://zhong-er.com/posts/%E8%84%9A%E6%9C%ACopenvz%E5%AE%89%E8%A3%85bbr%E5%8A%A0%E9%80%9F/","summary":"\u003cp\u003evps管理面板打开TUN/TAP\u003c/p\u003e\n\u003cp\u003e一般在vps管理面板里就可以打开，如果找不到，请联系咨询客服。\u003c/p\u003e\n\u003cp\u003e判断是否成功打开TUN/TAP\u003c/p\u003e\n\u003cp\u003ecat /dev/net/tun\u003c/p\u003e\n\u003cp\u003e返回值如下即为打开成功\u003c/p\u003e\n\u003cp\u003eFile descriptor in bad state\u003c/p\u003e\n\u003cp\u003e如果本机glibc小于2.14，先更新glibc\u003c/p\u003e\n\u003cp\u003eldd \u0026ndash;version\u003c/p\u003e\n\u003cp\u003e如果小于2.14，则需要编译安装新版本\u0026gt;2.14\u003c/p\u003e\n\u003cp\u003e先安装gcc\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eyum install gcc\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e再编译glibc\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewget http://ftp.gnu.org/gnu/glibc/glibc-2.15.tar.gz\nwget http://ftp.gnu.org/gnu/glibc/glibc-ports-2.15.tar.gz\ntar -zxf glibc-2.15.tar.gz\ntar -zxf glibc-ports-2.15.tar.gz\nmv glibc-ports-2.15 glibc-2.15/ports\nmkdir glibc-build-2.15\ncd glibc-build-2.15\n../glibc-2.15/configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin\nmake all \u0026amp;\u0026amp; make install\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e安装BBR\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewget https://raw.githubusercontent.com/kuoruan/shell-scripts/master/ovz-bbr/ovz-bbr-installer.sh\nchmod +x ovz-bbr-installer.sh\n./ovz-bbr-installer.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e配置加速端口\u003c/p\u003e\n\u003cp\u003e安装的时候只配置了一个加速端口，但是你可以配置多端口加速，修改文件\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003evi /usr/local/haproxy-lkl/etc/port-rules\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e在文件里添加需要加速的端口，每行一条，可以配置单个端口或者端口范围，配置完后需要重启haproxy-lkl\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eservice haproxy-lkl restart\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e如果要卸载：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e./ovz-bbr-installer.sh uninstall\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e检查BBR是否正常\u003c/p\u003e","title":"【脚本】OpenVZ安装BBR加速"},{"content":"\n2019-9-5 20:00\n鉴于，1903BUG出奇的多，暂时不要使用了，使用1809版本吧\n1809最新版会提示更新1903，先不要点立即下载了，等几个月这个版本稳定了再用\n【win10】1903 Pro 私人定制版封装首发 2019 系列（一）\n本系列同步win10更新而更迭版本，本次首发版考虑到部分office365订阅用户需求（博主也在用），未安装office软件。需要安装office2019的用户请等待第二版更新，有其他需求的用户欢迎评论区定制，博主会综合用户需求进行相应版本分支的封装。\n此次封装的常用软件请参考上图，\n封装设置优化包含：\n1.Windows设置优化\n2.控制面板优化\n3.服务项优化\n4.组策略优化\n5.添加摄像头补丁\n6.软媒美化大师优化\n7.软媒设置大师优化\n8.DISM++优化\n9.联网更新系统\n10.软媒清理大师清理\n11.DISM++清理\n具体请参看IT天空论坛的封装教程\n使用过程如有报错请及时反馈。\n下载地址\nwin10定制大小：3.83G | 来源：百度网盘\n已经过安全软件检测无毒，请您放心下载。\n","permalink":"https://zhong-er.com/posts/win101903-pro-%E7%A7%81%E4%BA%BA%E5%AE%9A%E5%88%B6%E7%89%88%E5%B0%81%E8%A3%85%E9%A6%96%E5%8F%91/","summary":"\u003cp\u003e\u003cimg alt=\"win10.jpg\" loading=\"lazy\" src=\"/assets/201906105992_5552_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2019-9-5 20:00\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e鉴于，1903BUG出奇的多，暂时不要使用了，使用1809版本吧\u003c/p\u003e\n\u003cp\u003e1809最新版会提示更新1903，先不要点立即下载了，等几个月这个版本稳定了再用\u003c/p\u003e\n\u003cp\u003e【win10】1903 Pro 私人定制版封装首发 2019 系列（一）\u003c/p\u003e\n\u003cp\u003e本系列同步win10更新而更迭版本，本次首发版考虑到部分office365订阅用户需求（博主也在用），未安装office软件。需要安装office2019的用户请等待第二版更新，有其他需求的用户欢迎评论区定制，博主会综合用户需求进行相应版本分支的封装。\u003c/p\u003e\n\u003cp\u003e此次封装的常用软件请参考上图，\u003c/p\u003e\n\u003cp\u003e封装设置优化包含：\u003c/p\u003e\n\u003cp\u003e1.Windows设置优化\u003c/p\u003e\n\u003cp\u003e2.控制面板优化\u003c/p\u003e\n\u003cp\u003e3.服务项优化\u003c/p\u003e\n\u003cp\u003e4.组策略优化\u003c/p\u003e\n\u003cp\u003e5.添加摄像头补丁\u003c/p\u003e\n\u003cp\u003e6.软媒美化大师优化\u003c/p\u003e\n\u003cp\u003e7.软媒设置大师优化\u003c/p\u003e\n\u003cp\u003e8.DISM++优化\u003c/p\u003e\n\u003cp\u003e9.联网更新系统\u003c/p\u003e\n\u003cp\u003e10.软媒清理大师清理\u003c/p\u003e\n\u003cp\u003e11.DISM++清理\u003c/p\u003e\n\u003cp\u003e具体请参看\u003ca href=\"https://www.itsk.com/\" title=\"IT天空\"\u003eIT天空\u003c/a\u003e论坛的封装教程\u003c/p\u003e\n\u003cp\u003e使用过程如有报错请及时反馈。\u003c/p\u003e\n\u003cp\u003e下载地址\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://pan.baidu.com/s/1BsmT9CxTgSvwtHYDd49Hpg\" title=\"点击下载\"\u003ewin10定制\u003c/a\u003e大小：3.83G | 来源：百度网盘\u003c/p\u003e\n\u003cp\u003e已经过安全软件检测无毒，请您放心下载。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://pan.baidu.com/s/1BsmT9CxTgSvwtHYDd49Hpg\" title=\"点击下载\"\u003e \u003c/a\u003e\u003c/p\u003e","title":"【win10】1903 Pro 私人定制版封装首发"},{"content":"\nBBR是来自于Google的黑科技，目的是通过优化和控制TCP的拥塞，充分利用带宽并降低延迟，起到神奇般的加速效果。在BBR出来之前，就只有锐速比较好用，但是锐速是一个国产的闭源软件。\n有些朋友会担心是否会有有害代码的问题。不过BBR就是一个开源在GITHUB的项目。前面我已经介绍过了《【脚本】服务器加速BBR脚本安装》\nBBR在大部分的时间都是比较好用的。但是偶尔也会在某些机器，某些线路上面的效果不明显。\n这里就给大家推荐某大佬修改的一键脚本，集成了BBR原版/魔改/plus+锐速的四合一脚本。\n项目开源地址：https://github.com/cx9208/Linux-NetSpeed\n脚本安装命令：\nwget \u0026quot;https://github.com/cx9208/Linux-NetSpeed/raw/master/tcp.sh\u0026quot; \u0026amp;\u0026amp; chmod +x tcp.sh \u0026amp;\u0026amp; ./tcp.sh 在vultr上Centos 7, Debian 8/9, Ubuntu 16/18测试通过,不支持ovz虚拟方式的vps\ngoogle cloud实机也测试通过。\noracle centos7 暂时无法安装，oracle其他系统未测试\nOracle的Ubuntu可以直接bbr，但centos需要执行以下命令否则报错：\n1. 切换成 root 2. 重新生成 grub2.cfg， 命令如下： grub2-mkconfig -o /boot/grub2/grub.cfg 3. 跑你要跑的 bbr.sh 1.先选择1-3在切换内核（第一次显示为bbr内核也要切换一遍），重启\n2. 重启后不用再下载脚本，直接 ./tcp.sh ，在[4 – 8]中选你要开的加速\n“1. 安装 BBR/BBR魔改版内核” 对应4,5,6（原版，魔改，暴力魔改）\n“2. 安装 BBRplus版内核 ” 对应7（plus）\n“3. 安装 Lotserver(锐速)内核” 对应8（锐速）\n3. 开启后再 ./tcp.sh ， 显示开启成功则启动成功，你也可以自己手动确认\n现在你可以自由的切换你想要的加速，直到你不想折腾为止~\nEND\n","permalink":"https://zhong-er.com/posts/%E8%84%9A%E6%9C%ACbbr%E5%8E%9F%E7%89%88%E9%AD%94%E6%94%B9plus%E9%94%90%E9%80%9F%E5%9B%9B%E5%90%88%E4%B8%80%E5%8A%A0%E9%80%9F%E8%84%9A%E6%9C%AC/","summary":"\u003cp\u003e\u003cimg alt=\"一键脚本.png\" loading=\"lazy\" src=\"/assets/202009032503_7089_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003eBBR是来自于Google的黑科技，目的是通过优化和控制TCP的拥塞，充分利用带宽并降低延迟，起到神奇般的加速效果。在BBR出来之前，就只有锐速比较好用，但是锐速是一个国产的闭源软件。\u003c/p\u003e\n\u003cp\u003e有些朋友会担心是否会有有害代码的问题。不过BBR就是一个开源在GITHUB的项目。前面我已经介绍过了\u003ca href=\"%7B#ZC_BLOG_HOST#%7Dpost/24.html\" title=\"【脚本】服务器加速BBR脚本安装\"\u003e《【脚本】服务器加速BBR脚本安装》\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eBBR在大部分的时间都是比较好用的。但是偶尔也会在某些机器，某些线路上面的效果不明显。\u003c/p\u003e\n\u003cp\u003e这里就给大家推荐某大佬修改的一键脚本，集成了BBR原版/魔改/plus+锐速的四合一脚本。\u003c/p\u003e\n\u003cp\u003e项目开源地址：\u003ca href=\"https://github.com/cx9208/Linux-NetSpeed\"\u003ehttps://github.com/cx9208/Linux-NetSpeed\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e脚本安装命令：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewget \u0026quot;https://github.com/cx9208/Linux-NetSpeed/raw/master/tcp.sh\u0026quot; \u0026amp;\u0026amp; chmod +x tcp.sh \u0026amp;\u0026amp; ./tcp.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e在vultr上Centos 7, Debian 8/9, Ubuntu 16/18测试通过,\u003cstrong\u003e不支持ovz虚拟方式的vps\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003egoogle cloud实机也测试通过。\u003c/p\u003e\n\u003cp\u003eoracle centos7 暂时无法安装，oracle其他系统未测试\u003c/p\u003e\n\u003cp\u003eOracle的Ubuntu可以直接bbr，但centos需要执行以下命令否则报错：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e1. 切换成 root  \n2. 重新生成 grub2.cfg， 命令如下：  \ngrub2-mkconfig -o /boot/grub2/grub.cfg  \n3. 跑你要跑的 bbr.sh  \n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cimg alt=\"QQ截图20190227204001.png\" loading=\"lazy\" src=\"/assets/201906044487_8147_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e1.先选择1-3在切换内核（第一次显示为bbr内核也要切换一遍），重启\u003c/p\u003e\n\u003cp\u003e2. 重启后不用再下载脚本，直接 ./tcp.sh ，在[4 – 8]中选你要开的加速\u003c/p\u003e\n\u003cp\u003e“1. 安装 BBR/BBR魔改版内核” 对应4,5,6（原版，魔改，暴力魔改）\u003cbr\u003e\n“2. 安装 BBRplus版内核 ” 对应7（plus）\u003cbr\u003e\n“3. 安装 Lotserver(锐速)内核” 对应8（锐速）\u003c/p\u003e\n\u003cp\u003e3. 开启后再 ./tcp.sh ， 显示开启成功则启动成功，你也可以自己手动确认\u003c/p\u003e\n\u003cp\u003e现在你可以自由的切换你想要的加速，直到你不想折腾为止~\u003c/p\u003e\n\u003cp\u003eEND\u003c/p\u003e","title":"【脚本】BBR原版/魔改/Plus/锐速四合一加速脚本"},{"content":"\n国内的服务器：阿里云、腾讯云的服务器跟百度云加速都自带监控，它会自动检测你的文章内容跟URL。\n你可能会发现有些网站或者出现关键字会被扫描到提示不安全内容，而且可能会被自动屏蔽这篇文章的URL，甚至要求我们删除这篇文章。\n比如阿里云：\n打开这个页面就可以看到你的阿里云被屏蔽的所有页面：https://yundun.console.aliyun.com/?p=sc#/sc/fine\n阿里云： 一、卸载安骑士\nwget http://update.aegis.aliyun.com/download/quartz_uninstall.sh chmod x quartz_uninstall.sh ./quartz_uninstall.sh 二、删除残留文件\nrm -rf /etc/init.d/agentwatch /usr/sbin/aliyun-service rm -rf /usr/local/aegis* 三、屏蔽云盾\n同时，云盾也是需要屏蔽掉的，这里我们直接将云盾的IP段屏蔽到iptables中。\niptables -I INPUT -s 140.205.201.0/28 -j DROP iptables -I INPUT -s 140.205.201.16/29 -j DROP iptables -I INPUT -s 140.205.201.32/28 -j DROP iptables -I INPUT -s 140.205.225.192/29 -j DROP iptables -I INPUT -s 140.205.225.200/30 -j DROP iptables -I INPUT -s 140.205.225.184/29 -j DROP iptables -I INPUT -s 140.205.225.183/32 -j DROP iptables -I INPUT -s 140.205.225.206/32 -j DROP iptables -I INPUT -s 140.205.225.205/32 -j DROP iptables -I INPUT -s 140.205.225.195/32 -j DROP iptables -I INPUT -s 140.205.225.204/32 -j DROP 写入之后然后保存iptables，这样就完成了屏蔽阿里云安骑士，以及云盾。\n如果需要开启，那可以再安装：\nwget http://update.aegis.aliyun.com/download/install.sh chmod x install.sh sh install.sh 安骑士安装，云盾只要将iptables中释放屏蔽的IP段即可。\n腾讯云： 操作方法跟阿里云也差不多一样。\n如果是新装，在默认安装系统的时候不要勾选安全云镜和安全监控软件。\n如果选择默认安装了监控软件后如何卸载，操作如下：\n./usr/local/qcloud/stargate/admin/uninstall.sh ./usr/local/qcloud/monitor/barad/admin/uninstall.sh ./usr/local/sa/agent/uninstall.sh rm -rf /usr/local/qcloud/ rm -rf /usr/local/sa/ rm -rf /usr/local/agenttools ","permalink":"https://zhong-er.com/posts/%E8%84%9A%E6%9C%AC%E4%B8%80%E9%94%AE%E5%8D%B8%E8%BD%BD%E5%AE%89%E9%AA%91%E5%A3%AB%E8%84%9A%E6%9C%AC-%E5%B1%8F%E8%94%BD%E9%98%BF%E9%87%8C%E4%BA%91ecs%E4%BB%A5%E5%8F%8A%E8%85%BE%E8%AE%AF%E4%BA%91%E5%90%8E%E5%8F%B0%E7%9B%91%E6%8E%A7/","summary":"\u003cp\u003e\u003cimg alt=\"一键脚本.png\" loading=\"lazy\" src=\"/assets/202009032503_7089_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e国内的服务器：阿里云、腾讯云的服务器跟百度云加速都自带监控，它会自动检测你的文章内容跟URL。\u003c/p\u003e\n\u003cp\u003e你可能会发现有些网站或者出现关键字会被扫描到提示不安全内容，而且可能会被自动屏蔽这篇文章的URL，甚至要求我们删除这篇文章。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://cdn.laod.wang/wp-content/uploads/2017/03/20170309165219.png\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"20170309165219.png\" loading=\"lazy\" src=\"/assets/201905271925_5798_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e比如阿里云：\u003c/p\u003e\n\u003cp\u003e打开这个页面就可以看到你的阿里云被屏蔽的所有页面：\u003ca href=\"https://yundun.console.aliyun.com/?p=sc#/sc/fine\"\u003ehttps://yundun.console.aliyun.com/?p=sc#/sc/fine\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"阿里云\"\u003e阿里云：\u003c/h2\u003e\n\u003cp\u003e一、卸载安骑士\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewget http://update.aegis.aliyun.com/download/quartz_uninstall.sh\nchmod x quartz_uninstall.sh\n./quartz_uninstall.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e二、删除残留文件\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003erm -rf /etc/init.d/agentwatch /usr/sbin/aliyun-service\nrm -rf /usr/local/aegis*\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e三、屏蔽云盾\u003c/p\u003e\n\u003cp\u003e同时，云盾也是需要屏蔽掉的，这里我们直接将云盾的IP段屏蔽到iptables中。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eiptables -I INPUT -s 140.205.201.0/28 -j DROP\niptables -I INPUT -s 140.205.201.16/29 -j DROP\niptables -I INPUT -s 140.205.201.32/28 -j DROP\niptables -I INPUT -s 140.205.225.192/29 -j DROP\niptables -I INPUT -s 140.205.225.200/30 -j DROP\niptables -I INPUT -s 140.205.225.184/29 -j DROP\niptables -I INPUT -s 140.205.225.183/32 -j DROP\niptables -I INPUT -s 140.205.225.206/32 -j DROP\niptables -I INPUT -s 140.205.225.205/32 -j DROP\niptables -I INPUT -s 140.205.225.195/32 -j DROP\niptables -I INPUT -s 140.205.225.204/32 -j DROP\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e写入之后然后保存iptables，这样就完成了屏蔽阿里云安骑士，以及云盾。\u003c/p\u003e","title":"【脚本】一键卸载安骑士脚本 屏蔽阿里云ECS以及腾讯云后台监控"},{"content":"\nUnixBench是一款开源的用于测试Unix、BSD、Linux系统基本性能的工具，比较通用于Linux系统VPS性能测试，主要测试项目有：系统调用、读写、进程、图形化测试、2D、3D、管道、运算、C库等系统基准性能提供测试数据。\n一、脚本命令 如果你想使用UnixBench测试你的服务器性能，请使用SSH工具连接你的服务器后执行下面命令。脚本默认使用UnixBench5.1.3版本，因为大多VPS都没有显卡或者是集成显卡，不需要进行图像性能测试，所以作者注释了关于graphic的测试项。此脚本运行测试时间大约10-30分钟，跑分结束后得分越高性能越好。\nwget --no-check-certificate https://github.com/wn789/unixbench/blob/master/unixbench.sh chmod +x unixbench.sh ./unixbench.sh 二、测试效果 ======================================================================== BYTE UNIX Benchmarks (Version 5.1.3) System: vmu8i5vg.hostflyte.com: GNU/Linux OS: GNU/Linux -- 3.10.0-862.6.3.el7.x86_64 -- #1 SMP Tue Jun 26 16:32:21 UTC 2018 Machine: x86_64 (x86_64) Language: en_US.utf8 (charmap=\u0026quot;UTF-8\u0026quot;, collate=\u0026quot;UTF-8\u0026quot;) CPU 0: Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz (5200.0 bogomips) x86-64, MMX, Physical Address Ext, SYSENTER/SYSEXIT, SYSCALL/SYSRET 02:55:47 up 3 days, 10:29, 1 user, load average: 0.00, 0.17, 0.39; runlevel 3 ------------------------------------------------------------------------ Benchmark Run: Mon May 20 2019 02:55:47 - 03:24:31 1 CPU in system; running 1 parallel copy of tests Dhrystone 2 using register variables 20693875.6 lps (10.0 s, 7 samples) Double-Precision Whetstone 3391.4 MWIPS (10.0 s, 7 samples) Execl Throughput 1960.6 lps (30.0 s, 2 samples) File Copy 1024 bufsize 2000 maxblocks 306812.9 KBps (30.0 s, 2 samples) File Copy 256 bufsize 500 maxblocks 85444.0 KBps (30.0 s, 2 samples) File Copy 4096 bufsize 8000 maxblocks 820093.2 KBps (30.0 s, 2 samples) Pipe Throughput 489059.0 lps (10.0 s, 7 samples) Pipe-based Context Switching 101501.3 lps (10.0 s, 7 samples) Process Creation 6539.4 lps (30.0 s, 2 samples) Shell Scripts (1 concurrent) 2674.3 lpm (60.0 s, 2 samples) Shell Scripts (8 concurrent) 364.4 lpm (60.1 s, 2 samples) System Call Overhead 427380.5 lps (10.0 s, 7 samples) System Benchmarks Index Values BASELINE RESULT INDEX Dhrystone 2 using register variables 116700.0 20693875.6 1773.3 Double-Precision Whetstone 55.0 3391.4 616.6 Execl Throughput 43.0 1960.6 456.0 File Copy 1024 bufsize 2000 maxblocks 3960.0 306812.9 774.8 File Copy 256 bufsize 500 maxblocks 1655.0 85444.0 516.3 File Copy 4096 bufsize 8000 maxblocks 5800.0 820093.2 1414.0 Pipe Throughput 12440.0 489059.0 393.1 Pipe-based Context Switching 4000.0 101501.3 253.8 Process Creation 126.0 6539.4 519.0 Shell Scripts (1 concurrent) 42.4 2674.3 630.7 Shell Scripts (8 concurrent) 6.0 364.4 607.4 System Call Overhead 15000.0 427380.5 284.9 ======== System Benchmarks Index Score 584.6 ======= Script description and score comparison completed! ======= 本脚本适用于CentOS的下载脚本、\nwget http://github.itzmx.com/1265578519/unixbench/master/5.1.3/unixbench.sh -O unixbench.sh; sh unixbench.sh 注：系统基准指数得分单核心分数为1000的为优秀，双核心为2000的优秀，以此类推，800为普通，低于500的是垃圾可以扔了\n","permalink":"https://zhong-er.com/posts/%E8%84%9A%E6%9C%AClinux%E7%B3%BB%E7%BB%9F%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%80%A7%E8%83%BD%E8%B7%91%E5%88%86%E6%B5%8B%E8%AF%95%E8%84%9A%E6%9C%ACunixbench%E4%B8%80%E9%94%AE%E8%B7%91%E5%88%86%E6%B5%8B%E8%AF%95%E8%84%9A%E6%9C%AC/","summary":"\u003cp\u003e\u003cimg alt=\"一键脚本.png\" loading=\"lazy\" src=\"/assets/202009032503_7089_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003eUnixBench是一款开源的用于测试Unix、BSD、Linux系统基本性能的工具，比较通用于Linux系统VPS性能测试，主要测试项目有：系统调用、读写、进程、图形化测试、2D、3D、管道、运算、C库等系统基准性能提供测试数据。\u003c/p\u003e\n\u003ch2 id=\"一脚本命令\"\u003e一、脚本命令\u003c/h2\u003e\n\u003cp\u003e如果你想使用UnixBench测试你的服务器性能，请使用SSH工具连接你的服务器后执行下面命令。脚本默认使用UnixBench5.1.3版本，因为大多VPS都没有显卡或者是集成显卡，不需要进行图像性能测试，所以作者注释了关于graphic的测试项。此脚本运行测试时间大约10-30分钟，跑分结束后得分越高性能越好。\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewget --no-check-certificate https://github.com/wn789/unixbench/blob/master/unixbench.sh \nchmod +x unixbench.sh\n./unixbench.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"二测试效果\"\u003e二、测试效果\u003c/h2\u003e\n\u003cpre\u003e\u003ccode\u003e========================================================================\n   BYTE UNIX Benchmarks (Version 5.1.3)\n\n   System: vmu8i5vg.hostflyte.com: GNU/Linux\n   OS: GNU/Linux -- 3.10.0-862.6.3.el7.x86_64 -- #1 SMP Tue Jun 26 16:32:21 UTC 2018\n   Machine: x86_64 (x86_64)\n   Language: en_US.utf8 (charmap=\u0026quot;UTF-8\u0026quot;, collate=\u0026quot;UTF-8\u0026quot;)\n   CPU 0: Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz (5200.0 bogomips)\n          x86-64, MMX, Physical Address Ext, SYSENTER/SYSEXIT, SYSCALL/SYSRET\n   02:55:47 up 3 days, 10:29,  1 user,  load average: 0.00, 0.17, 0.39; runlevel 3\n\n------------------------------------------------------------------------\nBenchmark Run: Mon May 20 2019 02:55:47 - 03:24:31\n1 CPU in system; running 1 parallel copy of tests\n\nDhrystone 2 using register variables       20693875.6 lps   (10.0 s, 7 samples)\nDouble-Precision Whetstone                     3391.4 MWIPS (10.0 s, 7 samples)\nExecl Throughput                               1960.6 lps   (30.0 s, 2 samples)\nFile Copy 1024 bufsize 2000 maxblocks        306812.9 KBps  (30.0 s, 2 samples)\nFile Copy 256 bufsize 500 maxblocks           85444.0 KBps  (30.0 s, 2 samples)\nFile Copy 4096 bufsize 8000 maxblocks        820093.2 KBps  (30.0 s, 2 samples)\nPipe Throughput                              489059.0 lps   (10.0 s, 7 samples)\nPipe-based Context Switching                 101501.3 lps   (10.0 s, 7 samples)\nProcess Creation                               6539.4 lps   (30.0 s, 2 samples)\nShell Scripts (1 concurrent)                   2674.3 lpm   (60.0 s, 2 samples)\nShell Scripts (8 concurrent)                    364.4 lpm   (60.1 s, 2 samples)\nSystem Call Overhead                         427380.5 lps   (10.0 s, 7 samples)\n\nSystem Benchmarks Index Values               BASELINE       RESULT    INDEX\nDhrystone 2 using register variables         116700.0   20693875.6   1773.3\nDouble-Precision Whetstone                       55.0       3391.4    616.6\nExecl Throughput                                 43.0       1960.6    456.0\nFile Copy 1024 bufsize 2000 maxblocks          3960.0     306812.9    774.8\nFile Copy 256 bufsize 500 maxblocks            1655.0      85444.0    516.3\nFile Copy 4096 bufsize 8000 maxblocks          5800.0     820093.2   1414.0\nPipe Throughput                               12440.0     489059.0    393.1\nPipe-based Context Switching                   4000.0     101501.3    253.8\nProcess Creation                                126.0       6539.4    519.0\nShell Scripts (1 concurrent)                     42.4       2674.3    630.7\nShell Scripts (8 concurrent)                      6.0        364.4    607.4\nSystem Call Overhead                          15000.0     427380.5    284.9\n                                                                   ========\nSystem Benchmarks Index Score                                         584.6\n\n\n\n======= Script description and score comparison completed! =======\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e本脚本适用于CentOS的下载脚本、\u003c/p\u003e","title":"【脚本】Linux系统服务器性能跑分测试脚本/UnixBench一键跑分测试脚本"},{"content":"\n通过脚本一键升级内核并安装BBR加速.\nLinux系统中root用户拥有最高权限, 出于安全考虑, GCP默认是以普通用户登录的, 我们需要先切换到root用户, 否则运行某些命令时会提示无权限.\n先切换到root用户.\nXshell连上服务器后, 输入命令:\nsudo -i\n切换后, 命令前面的提示符中也会显示root\n依次运行以下4条命令 (输完一条记得按回车呀)\nyum install -y wget wget --no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh chmod +x bbr.sh ./bbr.sh 如果遇到问题, 记得看提示!\n安装完成后，脚本会提示需要重启VPS，输入 y 并回车后重启, 这时Xshell连接也会自动断开。\nVPS重启可能需要几分钟, 耐心等待一下.\n重启完成后，再用Xhell重新连接VPS, sudo -i切换到root用户.\n现在验证一下是否成功安装了最新内核并开启BBR.\n输入以下命令：\nuname -r\n查看内核版本，如果返回值含有4.13或以上版本, 就表示OK了.\nsysctl net.ipv4.tcp_available_congestion_control\n返回值一般为：\nnet.ipv4.tcp_available_congestion_control = bbr cubic reno\nsysctl net.ipv4.tcp_congestion_control\n返回值一般为：\nnet.ipv4.tcp_congestion_control = bbr\nsysctl net.core.default_qdisc\n返回值一般为：\nnet.core.default_qdisc = fq\nlsmod | grep bbr\n返回值有 tcp_bbr 模块即说明bbr已启动。\n","permalink":"https://zhong-er.com/posts/%E8%84%9A%E6%9C%AC%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8A%A0%E9%80%9Fbbr%E8%84%9A%E6%9C%AC%E5%AE%89%E8%A3%85/","summary":"\u003cp\u003e\u003cimg alt=\"一键脚本.png\" loading=\"lazy\" src=\"/assets/202009032503_7089_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"下载.jpg\" loading=\"lazy\" src=\"/assets/201905201401_6528.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e通过脚本一键升级内核并安装BBR加速.\u003c/p\u003e\n\u003cp\u003eLinux系统中root用户拥有最高权限, 出于安全考虑, GCP默认是以普通用户登录的, 我们需要先切换到root用户, 否则运行某些命令时会提示无权限.\u003c/p\u003e\n\u003cp\u003e先切换到root用户.\u003cbr\u003e\nXshell连上服务器后, 输入命令:\u003cbr\u003e\n\u003ccode\u003esudo -i\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e切换后, 命令前面的提示符中也会显示root\u003c/p\u003e\n\u003cp\u003e依次运行以下4条命令 (输完一条记得按回车呀)\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eyum install -y wget \n\nwget --no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh\n\nchmod +x bbr.sh\n\n./bbr.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e如果遇到问题, 记得看提示!\u003c/p\u003e\n\u003cp\u003e安装完成后，脚本会提示需要重启VPS，输入 \u003ccode\u003ey\u003c/code\u003e 并\u003ccode\u003e回车\u003c/code\u003e后重启, 这时Xshell连接也会自动断开。\u003c/p\u003e\n\u003cp\u003eVPS重启可能需要几分钟, 耐心等待一下.\u003c/p\u003e\n\u003cp\u003e重启完成后，再用Xhell重新连接VPS, \u003ccode\u003esudo -i\u003c/code\u003e切换到root用户.\u003c/p\u003e\n\u003cp\u003e现在验证一下是否成功安装了最新内核并开启BBR.\u003cbr\u003e\n输入以下命令：\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003euname -r\u003c/code\u003e\u003cbr\u003e\n查看内核版本，如果返回值含有4.13或以上版本, 就表示OK了.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"bbr.png\" loading=\"lazy\" src=\"/assets/201905206557_6888.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003esysctl net.ipv4.tcp_available_congestion_control\u003c/code\u003e\u003cbr\u003e\n返回值一般为：\u003cbr\u003e\nnet.ipv4.tcp_available_congestion_control = bbr cubic reno\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003esysctl net.ipv4.tcp_congestion_control\u003c/code\u003e\u003cbr\u003e\n返回值一般为：\u003cbr\u003e\nnet.ipv4.tcp_congestion_control = bbr\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003esysctl net.core.default_qdisc\u003c/code\u003e\u003cbr\u003e\n返回值一般为：\u003cbr\u003e\nnet.core.default_qdisc = fq\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003elsmod | grep bbr\u003c/code\u003e\u003cbr\u003e\n返回值有 tcp_bbr 模块即说明bbr已启动。\u003c/p\u003e","title":"【脚本】服务器加速BBR脚本安装"},{"content":"购买Globe电话卡 在菲律宾当地711或者街边小店可以买到Globe电话卡，不过一般存货不多，不会超过20张，有两个类型，一个是原装Globe，另外一种叫Globe Switch，使用过程中没发现有什么大的区别，唯一区别就是拨打143弹出的菜单顺序不一样。如图：\n有需要电话卡的可联系我。\nGlobe电话卡充值 我一般使用GCash电子钱包给菲律宾的电话卡充值，10P起充，无手续费。使用GCash充值还打折，一般是95折。即充值10P只收9.5P。\nGCash的使用我会再写一篇，随后附上。\n也可以微信搜索：优行旅游 或 Youxing-Travel 关注微信公众号：优行旅游 在线微信支付进行充值。\nGlobe电话卡套餐 流量套餐\n60M免费流量 发送短信FREE到8080即可获得，有效期一天。\n7天无限流量 发送短信SUPERSURF250到8080，费用250P\n7天2G流量 发送短信GOTSCOMBODD90到8080，费用90P\n7天1G流量 发送短信GOTSCOMBODD70到8080，费用70P\n5天无限流量 发送短信SUPERSURF200到8080，费用200P\n3天1G流量 发送短信GoSurf50到8080，费用50P\n语音短信套餐\n20分钟中国通话 发送GOTIPIDD50到8080，费用50P，有效期7天\n上面说的都是菲律宾当地的电话、流量套餐，国内不适用。\nGlobe漫游费用 菲律宾globe电话卡可以在国内使用，漫游后的服务商为中国联通或者中国移动，无月租费，不使用不要钱，打电话120 pesos每分钟，接听电话60 pesos每分钟，发短信20 pesos一条，接收短信免费。充值后有效期为一年，充值10P可延长一年。当手机卡内余额多于60P时，在国内才可以被拨通；当余额多于120P时才可以拨出电话。\n使用Globe电话卡在国内漫游上网？可以上网，但是费用很高，我了解到的情况是：599PHP一天，大约80人民币，不限流量。1150人民币包月，估计没有人会这么干吧。菲律宾Smart电话卡也可以，130买5天的1G流量包。家里有矿的朋友可以试试。\n所以在国内使用的话，还是要先关闭移动数据网。\nGlobe查话费和流量 使用装有Globe手机卡的电话拨打功能强大的 *143#，弹出以下菜单：\n回复2，然后再回复2，继续回复1即可查询本机号码余额及有效期。如图：\n其他功能：一切有关GCash电子钱包的操作也可以在这个菜单里实现，还能开通流量套餐。路径：5 Surfing–\u0026gt;1 GoSURF–\u0026gt;可选\nGoSurf15 2天40M流量，费用15P\nGoSurf50 3天1G流量，费用50P，送2GB看视频、听音乐、打游戏和社交流量\nGoSurf299 30天1.5G流量，费用299P\nGoSurf599 30天4G流量，费用599P\nGoSurf999 30天8G流量，费用999P\n其中视频是YouTube、 HOOQ、Netflix，音乐是Spotify，游戏有:Clash royale,Arena of Valor和Clash Of Clans，社交软件有:Instagram,Facebook,Snapchat,Viber,Twitter\n如果要查流量的使用情况，路径是:5 Surfing–\u0026gt;4 GS Status 流量使用情况会以短信的形式发到你的手机上。或者下载手机APP GServices查询。\nGlobe本机号码 手机卡背面”9″开头的10位数字就是电话号码，也可以插卡后查本机号码。 iPhone菜单路径 ：设置—\u0026gt;电话—\u0026gt;本机号码\nGlobe拨打电话 拨打菲律宾手机 0+菲律宾手机号 如 ：09662043897\n拨打中国座机 0086+区号(区号去掉0)+座机号\n拨打中国手机 0086+手机号（如 0086 13422503902）\n中国拨打本机 0063+手机号码\n发短信的方法与此类似，格式是一样的。\nGlobe上网设置 一般情况下，插上卡并打开”蜂窝移动数据“就可以正常上网，但是不建议这么做，因为此时没有任何购买任何套餐，流量很贵。正确的操作方法是：插卡前先关闭”蜂窝移动数据“，插卡后发短信购买流量套餐，收到购买成功的短信以后，再开”蜂窝移动数据“，否则分分钟花掉几十P。\n如果购买了流量套餐，开了”蜂窝移动数据“，但是依然不能上网，可以试试以下办法：\n苹果手机：\n设置-\u0026gt;通用-\u0026gt;删掉不相关的描述文件\n设置-\u0026gt;蜂窝移动数据-\u0026gt;打开这个选项-\u0026gt;蜂窝移动数据选项-\u0026gt;启用LTE(语音与数据)，数据漫游也打开\n如果仍不能上网，那就要使用还原大法了。设置-\u0026gt;通用-\u0026gt;还原-\u0026gt;还原网络设置-\u0026gt;重启手机\n安卓手机：\n我只有一部Pixel3，没有找到相关的选项，网上流传的方法是这样的：\n设置-\u0026gt;无线和网络-\u0026gt;移动网络设置-\u0026gt;开启数据漫游-\u0026gt;接入点名称- \u0026gt;新建APN-\u0026gt;名称:Globe-\u0026gt;APN:http.globe.com.ph-\u0026gt;保存接入点并选择该接入点。\nGlobe电话卡功能 注册菲律宾虚拟卡，如GCash和PayMaya(《申请美国运通虚拟信用卡》)\n注册微信帐号\n国内漫游上网(如油管)，一般人用不起。\n待补充。。。\nGlobe电话卡有效期 一般是一年，可通过充值延长有效期，最低可充值10p。因为我用Globe注册了GCash和PayMaya，所以一直留着卡，后来去菲律宾办SRRV签证时也能用。\nGlobe电话卡补卡 如果你打算长期使用你的Globe电话卡，记得一定要保存好PUK码，即把那个卡套留着，以后去Globe补卡的时候需要这个，因为这种Prepaid Card没有经过实名，需要用卡套上的PUK证明你对你要求补卡的号码的所有权。如果你不小心弄丢了，此时要被卡就非常麻烦，需要找律师帮你办遗失声明并公证，虽然费用不高但是过程很繁琐，所以还是把PUK码保存好吧。\nGlobe电话卡代购 下单地址→https://shop.zhong-er.com/t/phonecard\n","permalink":"https://zhong-er.com/posts/%E8%8F%B2%E5%BE%8B%E5%AE%BE%E7%94%B5%E8%AF%9D%E5%8D%A1globe%E7%94%B5%E8%AF%9D%E5%8D%A1/","summary":"\u003ch2 id=\"购买globe电话卡\"\u003e购买Globe电话卡\u003c/h2\u003e\n\u003cp\u003e在菲律宾当地711或者街边小店可以买到Globe电话卡，不过一般存货不多，不会超过20张，有两个类型，一个是原装Globe，另外一种叫Globe Switch，使用过程中没发现有什么大的区别，唯一区别就是拨打143弹出的菜单顺序不一样。如图：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"globe.png\" loading=\"lazy\" src=\"/assets/201905067151_9475.png\"\u003e\u003c/p\u003e\n\u003cp\u003e有需要电话卡的可联系我。\u003c/p\u003e\n\u003ch2 id=\"globe电话卡充值\"\u003eGlobe电话卡充值\u003c/h2\u003e\n\u003cp\u003e我一般使用GCash电子钱包给菲律宾的电话卡充值，10P起充，无手续费。使用GCash充值还打折，一般是95折。即充值10P只收9.5P。\u003c/p\u003e\n\u003cp\u003eGCash的使用我会再写一篇，随后附上。\u003c/p\u003e\n\u003cp\u003e也可以微信搜索：优行旅游 或 Youxing-Travel 关注微信公众号：优行旅游 在线微信支付进行充值。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"扫码预约\" loading=\"lazy\" src=\"/assets/201905064783_9975_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"话费充值\" loading=\"lazy\" src=\"/assets/201905062959_5193_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"选择号码\" loading=\"lazy\" src=\"/assets/201905064165_794_1.jpg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"globe电话卡套餐\"\u003eGlobe电话卡套餐\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e流量套餐\u003cbr\u003e\n60M免费流量 发送短信FREE到8080即可获得，有效期一天。\u003cbr\u003e\n7天无限流量 发送短信SUPERSURF250到8080，费用250P\u003cbr\u003e\n7天2G流量 发送短信GOTSCOMBODD90到8080，费用90P\u003cbr\u003e\n7天1G流量 发送短信GOTSCOMBODD70到8080，费用70P\u003cbr\u003e\n5天无限流量 发送短信SUPERSURF200到8080，费用200P\u003cbr\u003e\n3天1G流量 发送短信GoSurf50到8080，费用50P\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e语音短信套餐\u003cbr\u003e\n20分钟中国通话 发送GOTIPIDD50到8080，费用50P，有效期7天\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e上面说的都是菲律宾当地的电话、流量套餐，国内不适用。\u003c/p\u003e\n\u003ch2 id=\"globe漫游费用\"\u003eGlobe漫游费用\u003c/h2\u003e\n\u003cp\u003e菲律宾globe电话卡可以在国内使用，漫游后的服务商为中国联通或者中国移动，无月租费，不使用不要钱，打电话120 pesos每分钟，接听电话60 pesos每分钟，发短信20 pesos一条，接收短信免费。充值后有效期为一年，充值10P可延长一年。当手机卡内余额多于60P时，在国内才可以被拨通；当余额多于120P时才可以拨出电话。\u003c/p\u003e\n\u003cp\u003e使用Globe电话卡在国内漫游上网？可以上网，但是费用很高，我了解到的情况是：599PHP一天，大约80人民币，不限流量。1150人民币包月，估计没有人会这么干吧。菲律宾Smart电话卡也可以，130买5天的1G流量包。家里有矿的朋友可以试试。\u003c/p\u003e\n\u003cp\u003e所以在国内使用的话，还是要先关闭移动数据网。\u003c/p\u003e\n\u003ch2 id=\"globe查话费和流量\"\u003eGlobe查话费和流量\u003c/h2\u003e\n\u003cp\u003e使用装有Globe手机卡的电话拨打功能强大的 *143#，弹出以下菜单：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"menu.png\" loading=\"lazy\" src=\"/assets/201905063485_525_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e回复2，然后再回复2，继续回复1即可查询本机号码余额及有效期。如图：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"balance.png\" loading=\"lazy\" src=\"/assets/201905067653_3433_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e其他功能：一切有关GCash电子钱包的操作也可以在这个菜单里实现，还能开通流量套餐。路径：5 Surfing–\u0026gt;1 GoSURF–\u0026gt;可选\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eGoSurf15 2天40M流量，费用15P\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eGoSurf50 3天1G流量，费用50P，送2GB看视频、听音乐、打游戏和社交流量\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eGoSurf299 30天1.5G流量，费用299P\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eGoSurf599 30天4G流量，费用599P\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eGoSurf999 30天8G流量，费用999P\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e其中视频是YouTube、 HOOQ、Netflix，音乐是Spotify，游戏有:Clash royale,Arena of Valor和Clash Of Clans，社交软件有:Instagram,Facebook,Snapchat,Viber,Twitter\u003c/p\u003e\n\u003cp\u003e如果要查流量的使用情况，路径是:5 Surfing–\u0026gt;4 GS Status 流量使用情况会以短信的形式发到你的手机上。或者下载手机APP GServices查询。\u003c/p\u003e","title":"【菲律宾电话卡】Globe电话卡"},{"content":"易博通eSender是由香港虚拟网络运营商博元讯息集团推出的一个服务，提供中国香港和中国大陆的手机号码，申请成功后可通过微信接收短信。每个微信可以申请两个香港手机号码和一个大陆手机号码。\n资费标准 这里列出的只是无限接收短信的资费标准，语音及数据的套餐可在易博通eSender的微信公众号查询。\n申请方法 易博通eSender的申请及充值均通过微信公众号进行，因此先关注易博通eSender的微信公众号。扫码关注：\n申请中国大陆的手机号码需要输入一个大陆手机号码及身份证号码，申请中国香港的手机号码只需要输入一个大陆手机号码即可。下面是申请大陆手机号码的截图，可免费使用一个星期。大陆号码需要在指定的时间内进行实名认证，否则会暂停使用且不另行通知。香港的手机号码不需要实名。如图：\n其中大陆可以使用的证件有：\nPeople’s Republic Of China Resident(中国内地身份证)\nPeople’s Republic Of China Passport(中华人民共和国护照)\n中文申请界面是这样的\n如果要申请香港手机号码，可以先在微信搜索 易博通 关注微信公众号，或者直接扫下面这个二维码：\n关注以后通过以下方式登记服务，如图：\n点击「易博通」＞点击「登记开通服务」＞选择「开通中国内地号码」或「开通香港号码」\n如果你只需要接收短信，直接选”登记开通服务”即可，如图：\n如果你只想通过微信接收短信，选择“无SIM卡（只限微信收短信）”即可，如果你使用具有eSIM功能的手机，也可以试试eSIM，例如pixel3\n输入你的大陆手机号码，选择一个套餐，然后易博通会提供几个香港手机号码让你选，有可能有机会选取和你大陆手机尾号一样的香港手机号，比如我的香港手机号是57459832，9832就是我大陆手机号码的尾号。可以使用微信和支付宝付款，非常方便。\n易博通提供的香港手机号码可以接收大陆、香港手机号码发的短信，Google Fi和Lycamobile发的短信收不到，应该是易博通拒收了。收到短信后微信有通知，基本没有延迟。如图：\n留言信箱 无SIM卡的易博通号码不能接听电话，但是可以开通留言信箱，不过这个服务默认是开闭的，当别人打你的电话时会听到忙音。你可以通过以下方法开启：\n点击【我的帐号】-\u0026gt;点击【留言信箱】-\u0026gt;选择要设置留言信箱的号码-\u0026gt;选中”开启留言信箱”-\u0026gt;录下你充满磁性的嗓音-\u0026gt;上传\n使用易博通提供的香港手机号码，可以注册香港支付宝，八达通好易畀，拍住赏，香港微信钱包以及香港人的电子钱包TNG等多个电子钱包，更多玩法就靠大家自已去测试了。\n博主测试 我使用的是无实体卡的，微信公众号接受消息，也没有绑定eSIM设备，成功注册微信，支付宝HK，其他暂未测试。微信可能提示已绑定其他微信号，直接选不是本人的，或者通过忘记密码找回即可。\n","permalink":"https://zhong-er.com/posts/%E9%A6%99%E6%B8%AF%E7%94%B5%E8%AF%9D%E5%8D%A1%E6%98%93%E5%8D%9A%E9%80%9Aesender/","summary":"\u003cp\u003e易博通eSender是由香港虚拟网络运营商博元讯息集团推出的一个服务，提供中国香港和中国大陆的手机号码，申请成功后可通过微信接收短信。每个微信可以申请两个香港手机号码和一个大陆手机号码。\u003c/p\u003e\n\u003ch2 id=\"资费标准\"\u003e资费标准\u003c/h2\u003e\n\u003cp\u003e这里列出的只是无限接收短信的资费标准，语音及数据的套餐可在易博通eSender的微信公众号查询。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"fee.jpg\" loading=\"lazy\" src=\"/assets/201905065840_4512_1.jpg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"申请方法\"\u003e申请方法\u003c/h2\u003e\n\u003cp\u003e易博通eSender的申请及充值均通过微信公众号进行，因此先关注易博通eSender的微信公众号。扫码关注：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"二维码\" loading=\"lazy\" src=\"/assets/201905067170_8180_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e申请中国大陆的手机号码需要输入一个大陆手机号码及身份证号码，申请中国香港的手机号码只需要输入一个大陆手机号码即可。下面是申请大陆手机号码的截图，可免费使用一个星期。大陆号码需要在指定的时间内进行实名认证，否则会暂停使用且不另行通知。香港的手机号码不需要实名。如图：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"register-1.png\" loading=\"lazy\" src=\"/assets/201905069000_4054.png\"\u003e\u003c/p\u003e\n\u003cp\u003e其中大陆可以使用的证件有：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003ePeople’s Republic Of China Resident(中国内地身份证)\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ePeople’s Republic Of China Passport(中华人民共和国护照)\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e中文申请界面是这样的\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"chinese.png\" loading=\"lazy\" src=\"/assets/201905065506_8621.png\"\u003e\u003c/p\u003e\n\u003cp\u003e如果要申请香港手机号码，可以先在微信搜索 易博通 关注微信公众号，或者直接扫下面这个二维码：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"hknumber-300x300.jpg\" loading=\"lazy\" src=\"/assets/201905061663_2838.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e关注以后通过以下方式登记服务，如图：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"getanumber.png\" loading=\"lazy\" src=\"/assets/201905062297_4816.png\"\u003e\u003c/p\u003e\n\u003cp\u003e点击「易博通」＞点击「登记开通服务」＞选择「开通中国内地号码」或「开通香港号码」\u003c/p\u003e\n\u003cp\u003e如果你只需要接收短信，直接选”登记开通服务”即可，如图：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"getahknb2.png\" loading=\"lazy\" src=\"/assets/201905069001_1686.png\"\u003e\u003cbr\u003e\n如果你只想通过微信接收短信，选择“无SIM卡（只限微信收短信）”即可，如果你使用具有eSIM功能的手机，也可以试试eSIM，例如pixel3\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"wechatsms.png\" loading=\"lazy\" src=\"/assets/201905069412_5517.png\"\u003e\u003c/p\u003e\n\u003cp\u003e输入你的大陆手机号码，选择一个套餐，然后易博通会提供几个香港手机号码让你选，有可能有机会选取和你大陆手机尾号一样的香港手机号，比如我的香港手机号是57459832，9832就是我大陆手机号码的尾号。可以使用微信和支付宝付款，非常方便。\u003c/p\u003e\n\u003cp\u003e易博通提供的香港手机号码可以接收大陆、香港手机号码发的短信，Google Fi和Lycamobile发的短信收不到，应该是易博通拒收了。收到短信后微信有通知，基本没有延迟。如图：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"sms.png\" loading=\"lazy\" src=\"/assets/201905068828_7649_1.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"留言信箱\"\u003e留言信箱\u003c/h2\u003e\n\u003cp\u003e无SIM卡的易博通号码不能接听电话，但是可以开通留言信箱，不过这个服务默认是开闭的，当别人打你的电话时会听到忙音。你可以通过以下方法开启：\u003c/p\u003e\n\u003cp\u003e点击【我的帐号】-\u0026gt;点击【留言信箱】-\u0026gt;选择要设置留言信箱的号码-\u0026gt;选中”开启留言信箱”-\u0026gt;录下你充满磁性的嗓音-\u0026gt;上传\u003c/p\u003e\n\u003cp\u003e使用易博通提供的香港手机号码，可以注册香港支付宝，八达通好易畀，拍住赏，香港微信钱包以及香港人的电子钱包TNG等多个电子钱包，更多玩法就靠大家自已去测试了。\u003c/p\u003e\n\u003ch2 id=\"博主测试\"\u003e博主测试\u003c/h2\u003e\n\u003cp\u003e我使用的是无实体卡的，微信公众号接受消息，也没有绑定eSIM设备，成功注册微信，支付宝HK，其他暂未测试。微信可能提示已绑定其他微信号，直接选不是本人的，或者通过忘记密码找回即可。\u003c/p\u003e","title":"【香港电话卡】易博通eSender"},{"content":"Globe GCash是菲律宾电信公司Globe旗下的一个小额支付系统，华人通常称之为菲律宾版支付宝，而马云也曾为GCash站台并亲自体验扫描支付。不得不说，GCash的功能和支付宝非常相似，用户可通过GCash给手机充话费、买流量，支付各种生活帐单：电费、水费、网费、还信用卡，简单一模一样。但是支付宝想推出但最终没能实现的功能：虚拟信用卡，GCash通过和美国运通合作实现了。这张美国运通虚拟卡链接到客户的GCash电子钱包(共享余额)，客户可以使用这张卡在国际网站购买电子商品和服务，而这正是我们需要的功能。\n本卡优点 本卡支持AVS(Address Verification Service)验证，帐单地址是美国加州的。\n可以在以下网上或者商户消费：Amazon,PayPal,eBay U.S.,Zappos,Sephora,Drugstore,Gap,Old Navy,Agoda,Booking,AirBnb,Google Play Store,US PlayStation Store,Steam Store.\n不能在Best Buy及iTunes U.S. App Store（以前可以）消费。Supreme要求帐单地址和收货地址一样，因此无法下单。\n由于该卡的帐单地址为美国，可以有效地避免被砍单。\n另外，该卡不需要实名认证即可正常使用，不需要充值即可获得卡号和有效期(但必须充值以后才可以绑卡验证或者消费)。我已将该卡绑定到微信及AirBnb，可正常消费，Airbnb识别此卡为美国，有图为证：\n下载APP 注意：最好使用菲律宾IP地址注册 （博主测试加拿大IP,美国IP，其他未测试，主要是菲律宾节点实在难搞），否则可能出现无法注册或者注册成功后无法登录的情况。我是在菲律宾注册的GCash，回国几天后发现无法登录，发邮件给客服，客服的回复是：\nAs advise you cannot log in your account due to this is not supported to international**.(不支持国际用户)**\nYes you can use it again here in Philippines. 你可以到菲律宾以后再使用。\n掏出你的手机，如果是iPhone，打开App Store，搜索GCash，如图:\n注册GCash电子钱包 打开下载好的软件，输入你的菲律宾电话号码，目前只支持菲律宾电话号码申请，然后点右上角的”Next”\n可以微信扫码的我的推荐码或推荐链接免费获得50比索。官网说要5个工作日才能到帐，可能会有延迟，但确实到账了。\n如图：\nHi, you can get P50 worth of freebies for you to buy load, pay bills and more on the GCash app! Just register here:\nhttps://gcsh.app/r/fhybysm\nGCash会发6位的验证码到你的手机，输入6位验证码，并按右上角的”Submit”，如果没收到验证码，可以点下面的”Resend now”\n输入你的个人信息，依次为:名、姓、生日、地址和电子邮箱，如图：\n核对个人信息，\n创建一个4位数字的密码，要牢记，因为会经常用到这个密码\n点右上角”submit’提交申请，至此GCash就算注册成功了。\n获得虚拟卡卡号 申请菜单为：Pay Online—\u0026gt;Pay With American Express Virtual Pay\n不要急，就快成功了。\n输入你的邮箱地址来激活这张虚拟卡\n点”Next”，终于大功告成了。此时，美国运通虚拟卡的相关信息已通过短信发送给你，包括:卡号，到期时间(Card Expiry)，帐单地址等重要信息。重新进入美国运通虚拟卡界面，卡号、过期时间要等2-3分钟才能显示出来，如图：\nCVV 也会通过短信通知你，如果你没有收到，点上图中的Get New Code即可。\n地址说明:\n街道地址：375-7672 Suite,2807 West Magnolia Boulevard ，每张卡7672这个数字不一样\n城市:Burbank\n州:CA（加利福利亚州）\n邮编:91505\n电话:(818)260-0016\nGCash充值 没验证过的GCash只能线下充值，711充值最方便，菲律宾这个便利店随处可见。通过验证以后，就可以线上充值了，西联或者速汇金都可以。如果你有香港支付宝，也可以通过香港支付宝汇款，实时到帐，免手续费，据说使用了区块链技术。也可以找我代充，我的GCash通过了验证，可以转帐。使用GCash转帐功能时，一定要认真核对号码，GCash转错了号码，是没办法把钱要回来的。GCash美国运通卡和GCash共享余额，GCash钱包有钱就可以正常使用。\nGCash消费记录 在GCash APP主界面的中间，有个Transaction History，点进去就可以看到交易记录。不过，这个交易记录非常简单，查不到有价值的数据。\nGCash身份认证 GCash不认证身份也可以用，只是不可以线上充值(香港支付宝、西联或者速汇金)。身份认证可以去菲律宾的Globe Store，也可以在线认证(有网友成功过)，可以使用中国护照。认证通过以后，还能申请一张实体的GCash万事达卡，可以在ATM机上取现。\nGCash身份认证级别：\nBasic ，实际上是匿名用户，刚注册成功就是这个级别。可以接收转帐，也可以正常消费。\nSemi Verified ，验证过程非常简单，直接在APP上根据提示进行操作：先拍个自拍，然后填写你的个人信息，最后签名就可以了。升级以后，可以转帐给别的GCash帐户。\nFully Verified， 要完成这个级别的认证，需要和客服代表进行视频聊天认证，完成后可以使用所有的功能。难度极高，目前还没人在线通过这个认证。我是去菲律宾的Globe Store现场认证的。通过这个级别的认证以后，才可以通过香港支付宝汇款到GCash。\n菲律宾电话卡及充值 有需要菲律宾Globe电话号码以及给GCash充值的朋友可联系微信号:srjwon\n菲律宾globe电话卡可以在国内使用，漫游后的服务商为中国联通或者中国移动，打电话120 pesos每分钟，接听电话60 pesos每分钟，发短信20 pesos一条，接收短信免费。详情可参考 【菲律宾电话卡】Globe电话卡\n常见问题 GCash卡号能不能换？\n不能换。\nGCash能大量开卡吗？\n注册多个帐号即可。一张电话卡只能注册一个Gcash帐号，也就是只能开通一张虚拟卡。\nGCash帐户金额有限制吗？\n有。Basic和Partially Verified 每月40000P，Fully Verified 每月100000P。每个月是指自然月，每月1号重新开始统计金额。\nGCash能不能收到退款？\n使用GCash开通Netflix试用，绑定App Store时可以收到退款，使用GCash的实体万事达卡收Booking奖励，等了两个月才收到。\n其他问题可参考官方 FAQ或者在网站留言\n","permalink":"https://zhong-er.com/posts/%E8%99%9A%E6%8B%9F%E4%BF%A1%E7%94%A8%E5%8D%A1%E7%BE%8E%E5%9B%BD%E8%BF%90%E9%80%9A%E8%99%9A%E6%8B%9F%E5%8D%A1/","summary":"\u003cp\u003eGlobe GCash是菲律宾电信公司Globe旗下的一个小额支付系统，华人通常称之为菲律宾版支付宝，而马云也曾为GCash站台并亲自体验扫描支付。不得不说，GCash的功能和支付宝非常相似，用户可通过GCash给手机充话费、买流量，支付各种生活帐单：电费、水费、网费、还信用卡，简单一模一样。但是支付宝想推出但最终没能实现的功能：虚拟信用卡，GCash通过和美国运通合作实现了。这张美国运通虚拟卡链接到客户的GCash电子钱包(共享余额)，客户可以使用这张卡在国际网站购买电子商品和服务，而这正是我们需要的功能。\u003c/p\u003e\n\u003ch2 id=\"本卡优点\"\u003e本卡优点\u003c/h2\u003e\n\u003cp\u003e本卡支持AVS(Address Verification Service)验证，帐单地址是美国加州的。\u003c/p\u003e\n\u003cp\u003e可以在以下网上或者商户消费：Amazon,PayPal,eBay U.S.,Zappos,Sephora,Drugstore,Gap,Old Navy,Agoda,Booking,AirBnb,Google Play Store,US PlayStation Store,Steam Store.\u003c/p\u003e\n\u003cp\u003e不能在Best Buy及iTunes U.S. App Store（以前可以）消费。Supreme要求帐单地址和收货地址一样，因此无法下单。\u003c/p\u003e\n\u003cp\u003e由于该卡的帐单地址为美国，可以有效地避免被砍单。\u003c/p\u003e\n\u003cp\u003e另外，该卡不需要实名认证即可正常使用，不需要充值即可获得卡号和有效期(但必须充值以后才可以绑卡验证或者消费)。我已将该卡绑定到微信及AirBnb，可正常消费，Airbnb识别此卡为美国，有图为证：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"airbnbpay.png\" loading=\"lazy\" src=\"/assets/201905064308_1021.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"下载app\"\u003e下载APP\u003c/h2\u003e\n\u003cp\u003e注意：\u003cstrong\u003e最好使用菲律宾IP地址注册\u003c/strong\u003e （博主测试加拿大IP,美国IP，其他未测试，主要是菲律宾节点实在难搞），否则可能出现无法注册或者注册成功后无法登录的情况。我是在菲律宾注册的GCash，回国几天后发现无法登录，发邮件给客服，客服的回复是：\u003c/p\u003e\n\u003cp\u003eAs advise you cannot log in your account due to this is not supported to international**.(不支持国际用户)**\u003c/p\u003e\n\u003cp\u003eYes you can use it again here in Philippines. 你可以到菲律宾以后再使用。\u003c/p\u003e\n\u003cp\u003e掏出你的手机，如果是iPhone，打开App Store，搜索GCash，如图:\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"dapp.png\" loading=\"lazy\" src=\"/assets/201905064236_6285.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"注册gcash电子钱包\"\u003e注册GCash电子钱包\u003c/h2\u003e\n\u003cp\u003e打开下载好的软件，输入你的菲律宾电话号码，目前只支持菲律宾电话号码申请，然后点右上角的”Next”\u003c/p\u003e\n\u003cp\u003e可以微信扫码的我的推荐码或推荐链接免费获得50比索。官网说要5个工作日才能到帐，可能会有延迟，但确实到账了。\u003c/p\u003e\n\u003cp\u003e如图：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"scan.jpg\" loading=\"lazy\" src=\"/assets/201905066194_920_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003eHi, you can get P50 worth of freebies for you to buy load, pay bills and more on the GCash app! Just register here:\u003c/p\u003e","title":"【虚拟信用卡】美国运通虚拟卡"},{"content":"\nWindows 10 (business editions), version 1909 (x64) - DVD (Chinese-Simplified) 详细信息\n文件名 cn_windows_10_business_editions_version_1909_x64_dvd_0ca83907.iso\nSHA1\na153526cdd406a249a93f30b447b259fed560c1d\n文件大小\n4.91GB\n发布时间\n2019-10-16\ned2k://|file|cn_windows_10_business_editions_version_1909_x64_dvd_0ca83907.iso|5275090944|9BCD5FA6C8009E4D0260E4B23008BD47|/ ","permalink":"https://zhong-er.com/posts/win10%E5%AE%98%E6%96%B9%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC1909%E4%B8%8B%E8%BD%BD-%E6%8C%81%E7%BB%AD%E6%9B%B4%E6%96%B0/","summary":"\u003cp\u003e\u003cimg alt=\"1.JPG\" loading=\"lazy\" src=\"/assets/201904258833_590_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003eWindows 10 (business editions), version 1909 (x64) - DVD (Chinese-Simplified) 详细信息\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e文件名\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003ecn_windows_10_business_editions_version_1909_x64_dvd_0ca83907.iso\u003c/p\u003e\n\u003cp\u003eSHA1\u003c/p\u003e\n\u003cp\u003ea153526cdd406a249a93f30b447b259fed560c1d\u003c/p\u003e\n\u003cp\u003e文件大小\u003c/p\u003e\n\u003cp\u003e4.91GB\u003c/p\u003e\n\u003cp\u003e发布时间\u003c/p\u003e\n\u003cp\u003e2019-10-16\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e    ed2k://|file|cn_windows_10_business_editions_version_1909_x64_dvd_0ca83907.iso|5275090944|9BCD5FA6C8009E4D0260E4B23008BD47|/\n\u003c/code\u003e\u003c/pre\u003e","title":"【win10】官方最新版本1909下载-持续更新"},{"content":"\nCentOS 7 防火墙配置常用命令汇总\n# 1.查看防火墙状态 systemctl status firewalld # 2.开启防火墙 systemctl start firewalld # 3.防火墙开机自启动 systemctl enable firewalld # 4.未安装防火墙 sudo yum install firewalld # 5.查看所有打开的端口 firewall-cmd --zone=public --list-ports # 6.添加单端口 firewall-cmd --add-port=22/tcp --permanent # 7.添加端口区间 firewall-cmd --zone=public --add-port=39000-40000/tcp --permanent # 8.重载防火墙 firewall-cmd --reload # 9.删除指定端口 firewall-cmd --permanent --remove-port=123/tcp 一、防火墙的基本使用\n1.查看防火墙状态\nsystemctl status firewalld 2.开启防火墙\nsystemctl start firewalld 3.关闭防火墙\nsystemctl stop firewalld 4.若遇无法开启\n//先用 systemctl unmask firewalld.service //再用 systemctl start firewalld.service #如果还是not found 就是未安装，执行以下命令安装 sudo yum install firewalld //再启动就OK了 5.开机启用防火墙\nsystemctl enable firewalld 6.开机禁用防火墙\nsystemctl disable firewalld 二、systemctl是CentOS7的服务管理工具中主要的工具，它融合之前service和chkconfig的功能于一体。\n1.启动一个服务：\nsystemctl start firewalld.service 2.关闭一个服务：\nsystemctl stop firewalld.service 3.重启一个服务：\nsystemctl restart firewalld.service 4.显示一个服务的状态：\nsystemctl status firewalld.service 5.在开机时启用一个服务：\nsystemctl enable firewalld.service 6.在开机时禁用一个服务：\nsystemctl disable firewalld.service 7.查看服务是否开机启动：\nsystemctl is-enabled firewalld.service 8.查看已启动的服务列表：\nsystemctl list-unit-files|grep enabled 9.查看启动失败的服务列表：\nsystemctl --failed 三、配置firewalld-cmd\n1.查看版本：\nfirewall-cmd --version 2.查看帮助：\nfirewall-cmd --help 3.显示状态：\nfirewall-cmd --state 4.查看所有打开的端口：\nfirewall-cmd --zone=public --list-ports 5.更新防火墙规则：\nfirewall-cmd --reload 6.查看区域信息:\nfirewall-cmd --get-active-zones 7.查看指定接口所属区域：\nfirewall-cmd --get-zone-of-interface=eth0 8.拒绝所有包：\nfirewall-cmd --panic-on 9.取消拒绝状态：\nfirewall-cmd --panic-off 10.查看是否拒绝：\nfirewall-cmd --query-panic 四、对外开放端口\n1.添加指定需要开放的端口：\nfirewall-cmd --add-port=123/tcp --permanent 2.重载入添加的端口：\nfirewall-cmd --reload 3.查询指定端口是否开启成功：\nfirewall-cmd --query-port=123/tcp 4.增加端口区间\nfirewall-cmd --zone=public --add-port=4400-4600/tcp --permanent 五、删除端口\n1.删除指定端口\nfirewall-cmd --permanent --remove-port=123/tcp ","permalink":"https://zhong-er.com/posts/centos7%E9%98%B2%E7%81%AB%E5%A2%99%E7%AB%AF%E5%8F%A3%E5%A2%9E%E5%88%A0/","summary":"\u003cp\u003e\u003cimg alt=\"timg.jpg\" loading=\"lazy\" src=\"/assets/201904237682_3532.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003eCentOS 7 防火墙配置常用命令汇总\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# 1.查看防火墙状态\nsystemctl status firewalld\n# 2.开启防火墙\nsystemctl start firewalld\n# 3.防火墙开机自启动\nsystemctl enable firewalld\n# 4.未安装防火墙\nsudo yum install firewalld\n# 5.查看所有打开的端口\nfirewall-cmd --zone=public --list-ports\n# 6.添加单端口\nfirewall-cmd --add-port=22/tcp --permanent\n# 7.添加端口区间\nfirewall-cmd --zone=public --add-port=39000-40000/tcp --permanent\n# 8.重载防火墙\nfirewall-cmd --reload\n# 9.删除指定端口\nfirewall-cmd --permanent --remove-port=123/tcp\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e一、防火墙的基本使用\u003c/p\u003e\n\u003cp\u003e1.查看防火墙状态\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003esystemctl status firewalld\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e2.开启防火墙\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003esystemctl start firewalld\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e3.关闭防火墙\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003esystemctl stop firewalld\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e4.若遇无法开启\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e//先用\nsystemctl unmask firewalld.service \n//再用\nsystemctl start firewalld.service\n#如果还是not found 就是未安装，执行以下命令安装\nsudo yum install firewalld\n//再启动就OK了\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e5.开机启用防火墙\u003c/p\u003e","title":"【CentOS7】防火墙端口增删"},{"content":"\n目前很多人还是使用密码来登陆服务器，并且服务器都是默认的 22端口，但是这样会有被暴力破解密码的危险（除非足够复杂）。\n为了服务器安全，推荐：非22端口+Key密匙登陆+Key密匙密码，这样搭配最安全。\n为了保险起见，我们先添加一个SSH端口并添加对应的防火墙规则，然后用这个新的端口连接服务器试试，如果没问题了，我们再删除默认的22端口。\n添加SSH端口\nvi /etc/ssh/sshd_config # vi打开文件后，按 I键 进入编辑模式，然后按下面的要求添加端口配置，最后按 ESC键 退出编辑模式并输入 :wq 保存并退出vi编辑器。 我们在默认的SSH端口配置下面添加一个我们要用的新的端口，例如 23456\nPort 22 # 默认的22端口配置 Port 23456 # 新添加的 23456端口配置 然后我们重启SSH:\nservice ssh restart # 如果提示服务不存在，那就用 service sshd restart # CentOS 7 使用 systemctl restart sshd.service 防火墙放通23456端口\nfirewall-cmd --add-port=23456/tcp --permanent #重载防火墙 firewall-cmd --reload #查询指定端口是否开启成功 firewall-cmd --query-port=23456/tcp #返回yes就放通成功了 yes 上面步骤做完了，那么请断开当前的SSH连接，然后把SSH端口改为新的端口（示例端口为 23456），链接新端口试试，如果连接正常，那么继续下面的步骤，如果连接不正常，那么使用旧端口 22 连接上面去排除问题。\n取消默认SSH 22号端口\nvi /etc/ssh/sshd_config 我们再打开SSH配置文件，删除 Port 22 这一行，然后重启SSH即可。\nservice ssh restart # 如果提示服务不存在，那就用 service sshd restart ","permalink":"https://zhong-er.com/posts/centos7%E4%BF%AE%E6%94%B9ssh%E9%BB%98%E8%AE%A422%E5%8F%B7%E7%AB%AF%E5%8F%A3/","summary":"\u003cp\u003e\u003cimg alt=\"Linux.jpg\" loading=\"lazy\" src=\"/assets/201904231935_7469.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e目前很多人还是使用密码来登陆服务器，并且服务器都是默认的 22端口，但是这样会有被暴力破解密码的危险（除非足够复杂）。\u003c/p\u003e\n\u003cp\u003e为了服务器安全，推荐：非22端口+Key密匙登陆+Key密匙密码，这样搭配最安全。\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e为了保险起见，我们先添加一个SSH端口并添加对应的防火墙规则，然后用这个新的端口连接服务器试试，如果没问题了，我们再删除默认的22端口。\u003cbr\u003e\n添加SSH端口\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003evi /etc/ssh/sshd_config\n# vi打开文件后，按 I键  进入编辑模式，然后按下面的要求添加端口配置，最后按 ESC键  退出编辑模式并输入 :wq 保存并退出vi编辑器。\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e我们在默认的SSH端口配置下面添加一个我们要用的新的端口，例如 23456\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ePort 22\n# 默认的22端口配置\nPort 23456\n# 新添加的 23456端口配置\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e然后我们重启SSH:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eservice ssh restart\n# 如果提示服务不存在，那就用\nservice sshd restart\n\n# CentOS 7 使用\nsystemctl restart sshd.service\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e防火墙放通23456端口\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003efirewall-cmd --add-port=23456/tcp --permanent\n#重载防火墙\nfirewall-cmd --reload\n#查询指定端口是否开启成功\nfirewall-cmd --query-port=23456/tcp\n#返回yes就放通成功了\nyes\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e上面步骤做完了，那么请断开当前的SSH连接，然后把SSH端口改为新的端口（示例端口为 23456），链接新端口试试，如果连接正常，那么继续下面的步骤，如果连接不正常，那么使用旧端口 22 连接上面去排除问题。\u003cbr\u003e\n取消默认SSH 22号端口\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003evi /etc/ssh/sshd_config\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e我们再打开SSH配置文件，删除 Port 22 这一行，然后重启SSH即可。\u003c/p\u003e","title":"【CentOS7】修改SSH默认22号端口"},{"content":"只要用过智能手机，所有人都离不开它。但有时候 —— 比如躺在椅子上看小说的时候，我却想要一种不用接触智能手机的方式，然后我找到了 EVA Facial Mouse。\nEVA Facial Mouse 是由沃达丰开发的一款开源应用，目的是让用户无需使用双手，仅仅通过摄像头就可以操作手机。当然，这个 App 肯定不是给四肢健全的「懒鬼」设计的，沃达丰为了能让那些由于某些原因而无法使用手，并依靠其他人来满足其基本需求的人，比如截肢，脑瘫，脊髓损伤，肌营养不良，多发性硬化症，肌萎缩侧索硬化症（ ALS ）或其他残疾的人也能够感受到智能化生活的便利从而制作了 EVA Facial Mouse。在强调包容性发展作为实现可持续发展的正确道路的时代，针对残疾人福利的重点举措至关重要。这个来自沃达丰的应用程序是一项小型举措，可以帮助包容性增长。\n当然我们偶尔用来偷个懒也是十分好用的，接下来我就仔细讲讲它的操控方式吧。\n新奇的脸部操控\n第一次打开 EVA Facial Mouse ，在给了相关权限后会进入教学向导（提供跳过但是不建议跳过）。这里我录制了一个视频让大家直观地看一下它的操控方式和精确度：\n可以看到检测到吴彦祖帅气的脸之后整个流程算是十分流畅（还得加上我对不准位置的锅），即使脸离开前置摄像头一小会儿也不会影响鼠标跟踪。由于手机前置摄像头成像的局限性，EVA Facial Mouse 并没有采用 Win10 支持的眼球追踪，而是将头部作为追踪点控制虚拟鼠标，优点是通用性高，只需要有前置摄像头就行了，缺点是比起眼球追踪还是不够舒服。当然对于那些行动不便的用户来讲做到「便宜还能用」的程度已经比什么都没有好太多了。\n现在我看小说的时候都用它来翻页，架好手机，小说设置好全屏点击翻页之后，只需要盯住页面一小会儿就可以轻松翻到下一页，「葛优躺」更加舒服了。偶尔头累了就控制鼠标点击「 Rest Mode 」休息一下。\n总结\n虽然我拿着「 EVA Facial Mouse 」来做了一些不务正业的事情，但这个应用本身可以能极大地帮助到有需要的人，如果各位朋友周围有很难或者没办法去使用手机的人，请一定要推荐这篇文章给他们，或许他们的生活会变得更加地便利。\n应用已汉化\n此应用可以放开双手，利用前置摄像头捕捉眼球动作来控制手机。功能比较丰富，基本上不用动手，眼神都可以搞定了。并且桌面多了一个类似电脑鼠标箭头，这个箭头会随着面部的移动而移动，非常的神奇。左侧是安卓的操作按键，集成了返回，Home，以及多任务，和下拉状态栏的按键操作。完全摆脱了动手操作。全靠眼球移动操作。当面部让鼠标停留在一个地方一定的时间后会调出菜单。选择点击或者长按。右侧工具栏倒数第二个按钮可以启动或者禁止菜单的弹出，禁止菜单后鼠标默认为点击，不会有长按功能。基本上整体的交互逻辑和日常使用的功能都具备了，可用性还是很高的。\n博主体验了一把，灵敏度还是可以的，就是需要来回摇头点头的，体验一下还是有必要的，但是对于四肢健全的人来说还是不会选择长期使用这款软件的。\n2019年8月8日更新 v3.1.1 Google play store 英文版\n经测试较之前版本有所优化\n黑科技面部大小：25.1MB | 来源：百度云 | 提取码：dbcd\n已经过安全软件检测无毒，请您放心下载。\n","permalink":"https://zhong-er.com/posts/eva-facial-mouse%E8%A7%A3%E6%94%BE%E5%8F%8C%E6%89%8B%E7%94%A8%E8%84%B8%E6%93%8D%E4%BD%9C%E6%89%8B%E6%9C%BA/","summary":"\u003cp\u003e只要用过智能手机，所有人都离不开它。但有时候 —— 比如躺在椅子上看小说的时候，我却想要一种不用接触智能手机的方式，然后我找到了 EVA Facial Mouse。\u003c/p\u003e\n\u003cp\u003eEVA Facial Mouse 是由沃达丰开发的一款开源应用，目的是让用户无需使用双手，仅仅通过摄像头就可以操作手机。当然，这个 App 肯定不是给四肢健全的「懒鬼」设计的，沃达丰为了能让那些由于某些原因而无法使用手，并依靠其他人来满足其基本需求的人，比如截肢，脑瘫，脊髓损伤，肌营养不良，多发性硬化症，肌萎缩侧索硬化症（ ALS ）或其他残疾的人也能够感受到智能化生活的便利从而制作了 EVA Facial Mouse。在强调包容性发展作为实现可持续发展的正确道路的时代，针对残疾人福利的重点举措至关重要。这个来自沃达丰的应用程序是一项小型举措，可以帮助包容性增长。\u003c/p\u003e\n\u003cp\u003e当然我们偶尔用来偷个懒也是十分好用的，接下来我就仔细讲讲它的操控方式吧。\u003c/p\u003e\n\u003cp\u003e新奇的脸部操控\u003c/p\u003e\n\u003cp\u003e第一次打开 EVA Facial Mouse ，在给了相关权限后会进入教学向导（提供跳过但是不建议跳过）。这里我录制了一个视频让大家直观地看一下它的操控方式和精确度：\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"1.gif\" loading=\"lazy\" src=\"/assets/201811228531_2496_1.gif\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"2.gif\" loading=\"lazy\" src=\"/assets/201811228937_6136.gif\"\u003e\u003c/p\u003e\n\u003cp\u003e可以看到检测到吴彦祖帅气的脸之后整个流程算是十分流畅（还得加上我对不准位置的锅），即使脸离开前置摄像头一小会儿也不会影响鼠标跟踪。由于手机前置摄像头成像的局限性，EVA Facial Mouse 并没有采用 Win10 支持的眼球追踪，而是将头部作为追踪点控制虚拟鼠标，优点是通用性高，只需要有前置摄像头就行了，缺点是比起眼球追踪还是不够舒服。当然对于那些行动不便的用户来讲做到「便宜还能用」的程度已经比什么都没有好太多了。\u003c/p\u003e\n\u003cp\u003e现在我看小说的时候都用它来翻页，架好手机，小说设置好全屏点击翻页之后，只需要盯住页面一小会儿就可以轻松翻到下一页，「葛优躺」更加舒服了。偶尔头累了就控制鼠标点击「 Rest Mode 」休息一下。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"3.jpg\" loading=\"lazy\" src=\"/assets/201811227025_9297_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e总结\u003c/p\u003e\n\u003cp\u003e虽然我拿着「 EVA Facial Mouse 」来做了一些不务正业的事情，但这个应用本身可以能极大地帮助到有需要的人，如果各位朋友周围有很难或者没办法去使用手机的人，请一定要推荐这篇文章给他们，或许他们的生活会变得更加地便利。\u003c/p\u003e\n\u003cp\u003e应用已汉化\u003c/p\u003e\n\u003cp\u003e此应用可以放开双手，利用前置摄像头捕捉眼球动作来控制手机。功能比较丰富，基本上不用动手，眼神都可以搞定了。并且桌面多了一个类似电脑鼠标箭头，这个箭头会随着面部的移动而移动，非常的神奇。左侧是安卓的操作按键，集成了返回，Home，以及多任务，和下拉状态栏的按键操作。完全摆脱了动手操作。全靠眼球移动操作。当面部让鼠标停留在一个地方一定的时间后会调出菜单。选择点击或者长按。右侧工具栏倒数第二个按钮可以启动或者禁止菜单的弹出，禁止菜单后鼠标默认为点击，不会有长按功能。基本上整体的交互逻辑和日常使用的功能都具备了，可用性还是很高的。\u003c/p\u003e\n\u003cp\u003e博主体验了一把，灵敏度还是可以的，就是需要来回摇头点头的，体验一下还是有必要的，但是对于四肢健全的人来说还是不会选择长期使用这款软件的。\u003c/p\u003e\n\u003cp\u003e2019年8月8日更新 v3.1.1 Google play store 英文版\u003c/p\u003e\n\u003cp\u003e经测试较之前版本有所优化\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://pan.baidu.com/s/16rQvgUOJsm8HI09LyySLlQ\" title=\"点击下载\"\u003e黑科技面部\u003c/a\u003e大小：25.1MB | 来源：百度云 | 提取码：dbcd\u003c/p\u003e\n\u003cp\u003e已经过安全软件检测无毒，请您放心下载。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://pan.baidu.com/s/16rQvgUOJsm8HI09LyySLlQ\" title=\"点击下载\"\u003e \u003c/a\u003e\u003c/p\u003e","title":"【EVA Facial Mouse】解放双手！用脸操作手机"},{"content":"DNS信息是由/etc/resolv.conf提供的，它是每次开机时，由/sbin/resolvconf生成的\n/etc/resolv.conf是/run/resolvconf/resolv.conf的符号链接\n错误的修改方法\n直接修改/etc/resolv.conf文件添加DNS的方法并不可行，因为重启系统后，该配置文件会被重新覆盖。\n这一点也可以该文件顶部的注释行证明\n原因解释：\n在Ubuntu中有一个 resolvconf的服务，这个服务用来控制/etc/resolv.conf的内容。\n所以一旦我们重启了系统或者该服务，那么/etc/resolv.conf文件中的内容将被还原为原来的内容。\n所以直接修改/etc/resolv.conf并不能解决这个问题。\n正确的修改方法\n修改如下文件，默认是空的\nsudo vim /etc/resolvconf/resolv.conf.d/base\n在里面加入你想添加的DNS服务器，一行一个\n修改好后保存，然后执行\nsudo /sbin/resolvconf -u\n或者\nsudo etc/init.d/resolvconf restart\n或者重启系统\n再查看/etc/resolv.conf文件，已经将我们添加的内容加上了\ncat /etc/resolv.conf\n另一种方法\nsudo vim /etc/network/interfaces\ndns-nameservers 8.8.8.8 114.114.114.114\n遇到的问题\n无法实现上面的功能，不知道是什么原因？\nhttp://jingyan.baidu.com/article/e5c39bf5bbe0e739d7603396.html 似乎有价值\n最后的折衷解决方案，在网卡配置GUI中手动加入dns配置，然后使用下面的命令检查\nnmcli dev list iface wlan0 | grep DNS\n把wlan0换成你网路连接的硬体，看DNS是哪一个，就知道现在使用的DNS是谁\n参考文献\n[1].http://blog.csdn.net/yangcs2009/article/details/26674311\n[2].http://www.linuxidc.com/Linux/2009-08/21425.htm 解决方法在文章最后\n[3].http://www.tuicool.com/articles/iiUjAj 这个是最终解决办法\n[4].http://askubuntu.com/questions/130452/how-do-i-add-a-dns-server-via-resolv-conf\n[5].http://www.tuicool.com/articles/RVZn2y 针对Ubuntu 14.04的\n","permalink":"https://zhong-er.com/posts/ubuntu%E5%9C%A8ubuntu-14.04-64bit%E4%B8%AD%E6%B0%B8%E4%B9%85%E6%B7%BB%E5%8A%A0dns%E7%9A%84%E6%96%B9%E6%B3%95/","summary":"\u003cp\u003eDNS信息是由/etc/resolv.conf提供的，它是每次开机时，由/sbin/resolvconf生成的\u003c/p\u003e\n\u003cp\u003e/etc/resolv.conf是/run/resolvconf/resolv.conf的符号链接\u003c/p\u003e\n\u003cp\u003e错误的修改方法\u003c/p\u003e\n\u003cp\u003e直接修改/etc/resolv.conf文件添加DNS的方法并不可行，因为重启系统后，该配置文件会被重新覆盖。\u003c/p\u003e\n\u003cp\u003e这一点也可以该文件顶部的注释行证明\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"1.png\" loading=\"lazy\" src=\"/assets/201903277344_3426_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e原因解释：\u003c/p\u003e\n\u003cp\u003e在Ubuntu中有一个 resolvconf的服务，这个服务用来控制/etc/resolv.conf的内容。\u003c/p\u003e\n\u003cp\u003e所以一旦我们重启了系统或者该服务，那么/etc/resolv.conf文件中的内容将被还原为原来的内容。\u003c/p\u003e\n\u003cp\u003e所以直接修改/etc/resolv.conf并不能解决这个问题。\u003c/p\u003e\n\u003cp\u003e正确的修改方法\u003c/p\u003e\n\u003cp\u003e修改如下文件，默认是空的\u003c/p\u003e\n\u003cp\u003esudo vim /etc/resolvconf/resolv.conf.d/base\u003c/p\u003e\n\u003cp\u003e在里面加入你想添加的DNS服务器，一行一个\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"2.png\" loading=\"lazy\" src=\"/assets/201903276502_7185_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e修改好后保存，然后执行\u003cbr\u003e\nsudo /sbin/resolvconf -u\u003cbr\u003e\n或者\u003cbr\u003e\nsudo etc/init.d/resolvconf restart\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"3.png\" loading=\"lazy\" src=\"https://p.zhong-er.com/2019/03/201903272387_4216.png\"\u003e\u003c/p\u003e\n\u003cp\u003e或者重启系统\u003c/p\u003e\n\u003cp\u003e再查看/etc/resolv.conf文件，已经将我们添加的内容加上了\u003c/p\u003e\n\u003cp\u003ecat /etc/resolv.conf\u003c/p\u003e\n\u003cp\u003e另一种方法\u003c/p\u003e\n\u003cp\u003esudo vim /etc/network/interfaces\u003c/p\u003e\n\u003cp\u003edns-nameservers 8.8.8.8 114.114.114.114\u003c/p\u003e\n\u003cp\u003e遇到的问题\u003c/p\u003e\n\u003cp\u003e无法实现上面的功能，不知道是什么原因？\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://jingyan.baidu.com/article/e5c39bf5bbe0e739d7603396.html\"\u003ehttp://jingyan.baidu.com/article/e5c39bf5bbe0e739d7603396.html\u003c/a\u003e 似乎有价值\u003c/p\u003e\n\u003cp\u003e最后的折衷解决方案，在网卡配置GUI中手动加入dns配置，然后使用下面的命令检查\u003c/p\u003e\n\u003cp\u003enmcli dev list iface wlan0 | grep DNS\u003c/p\u003e\n\u003cp\u003e把wlan0换成你网路连接的硬体，看DNS是哪一个，就知道现在使用的DNS是谁\u003c/p\u003e\n\u003cp\u003e参考文献\u003c/p\u003e\n\u003cp\u003e[1].http://blog.csdn.net/yangcs2009/article/details/26674311\u003c/p\u003e\n\u003cp\u003e[2].http://www.linuxidc.com/Linux/2009-08/21425.htm 解决方法在文章最后\u003c/p\u003e\n\u003cp\u003e[3].http://www.tuicool.com/articles/iiUjAj 这个是最终解决办法\u003c/p\u003e\n\u003cp\u003e[4].http://askubuntu.com/questions/130452/how-do-i-add-a-dns-server-via-resolv-conf\u003c/p\u003e\n\u003cp\u003e[5].http://www.tuicool.com/articles/RVZn2y 针对Ubuntu 14.04的\u003c/p\u003e","title":"【Ubuntu】在Ubuntu 14.04 64bit中永久添加DNS的方法"},{"content":"经常逛VPS论坛的朋友，都会看到有些人说什么，毒妇、传家宝、吃灰啊，这些词，真的是让人一头雾水，和网络流行语一样，我们也还是有必要知道一点这些VPS切口的，大鸟这里就简单介绍几个常用的。让那些小白们逛vps论坛的时候能跟得上节奏。\n一.oneman 指只有一个人经营的idc服务商，业内比较出名的有ramnode，不过这家严格说并不是oneman，人家是夫妻店，是俩人23333。还有那个常年侮辱中国客户的bugetvz，是个澳洲村炮自己开的oneman店。\n二.毒妇，杜甫 指的是独立服务器，缩写是独服，因为输入法经常打成毒妇或者是杜甫，所以这个称呼也沿用开了。\n三.小鸡 指小型vps，往往配置非常低，但是麻雀虽小五脏俱全，往往都有独立ip，同时价格也很便宜。另外一个意思就是独立服务器里开出来的小vps，也叫小鸡。\n四.母鸡 指独立服务器。因为vps都是拿独立服务器虚拟出来的（其实就是独服开了一堆虚拟机），所以小vps叫小鸡，生出小vps来的独服叫母鸡。\n五.超售 前面说了，vps其实就是独服的虚拟机。超售就是开了太多的小鸡在独服里。举个例子，本来一台独服是4核4g内存，如果用kvm开小鸡，如果要开的套餐是1核1g内存，那么就只能开4台出来。但是实际的情况并不是这样，商家为了多赚钱，强行开了八台出来，还全都是1核512m内存的，其原因就是超了cpu，强行让本来应该为四个人服务的cpu给八个人服务，但是却内存没超。虚拟的主流架构有openvz，kvm和xen，其中ovz啥都能超，kvm可以超cpu但是不能超内存和硬盘。xen不太了解，应该和kvm一样（存疑，超售的这个具体原理我也不是很懂）\n五.传家宝 部分vps卖断货后，往往其价格会飞速飙升。这是因为如果商家不再开卖的话，一台母鸡里就这么多小鸡了，商家不会再超售了。那么小鸡的稳定性就大大提升，求购二手vps的基佬就会趋之若鹜，价格自然水涨船高。当年搬瓦工3.99刀卖断货后价格一度飞涨七八倍，涨幅都超过五道口宇宙中心的房价涨幅了，被戏称为传家宝。传家宝一名因此得来。\n六.黄牛 倒卖各种二手vps的人，称之为黄牛，鄙人也是做这买卖的。干这行非常考验眼力和手速，当然利润也是相当不错。曾经一刀买的800块人民币卖掉了，我会乱说？\n七.石头盘 指硬盘io过低的vps。基本低于100m/s就是石头盘了。有人笑称是用石头做出来的硬盘，因此得名。硬盘转速太低基本干啥都卡，因此大家都不喜欢石头盘的vps。与此同时还有一个说法是钻石盘，指垃圾中的战斗机硬盘。当年cac的vps一度跑出了10m/s的成绩，惊为天人，简直是石头盘中的石头盘，钻石盘这个说法就出来了。\n八.吃灰 指剁手党看到好vps就买买买，结果买回来发现并不需要，于是放着吃灰。\n九.探针 一个php小程序，挂在vps上能够查看vps的各项参数性能。基佬们经常互相交换探针地址来加深友♂谊，是py交易的前奏。\n十.油管 指有图比，很多基佬喜欢用u2b的视频播放效果来衡量线路的好坏，虽然这个并不是一个很精准的检验方法。\n十一.mjj “没鸡鸡”的缩写，hostloc论坛的基佬们的自称。hostloc是中国vps玩家和黄赌毒站长们的聚集地。\n十二.刀 指美元，dollar的音译。大部分vps商家只支持美元支付。\n十三.海外机房缩写： HK香港，KR韩国，JP日本，SG新加坡， LA洛杉矶等等。\n十四.大水管，小水管 在A站B站看电影和电视的时候，会有很多很多老司机提醒说，小水管不要点超清播放，要点高清播放，尤其是2m小水管，有些在贴吧发资源的时候，也常常听到有人说，好像这个词跟看电影有关吧，请问“小水管”是什么意思？2M小水管呢？4M小水管呢？\n简单说下：如果家里的宽带是100M就是大水管，你弄个2M就是小水管，套用在VPS上面，国外的vps带宽动辄G口宽带，国内的带宽太贵，我们买个阿里云的Vps的时候钱太少，只能选择1M的带宽，那G口的就是大水管，1M的就是小水管。\n","permalink":"https://zhong-er.com/posts/vps%E6%B5%81%E8%A1%8C%E8%AF%AD%E5%B8%B8%E7%94%A8%E7%9A%84vps%E6%B1%9F%E6%B9%96%E9%BB%91%E8%AF%9D/","summary":"\u003cp\u003e经常逛VPS论坛的朋友，都会看到有些人说什么，毒妇、传家宝、吃灰啊，这些词，真的是让人一头雾水，和网络流行语一样，我们也还是有必要知道一点这些VPS切口的，大鸟这里就简单介绍几个常用的。让那些小白们逛vps论坛的时候能跟得上节奏。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"2016111612481734.jpg\" loading=\"lazy\" src=\"/assets/201904232543_261.jpg\"\u003e\u003c/p\u003e\n\u003ch4 id=\"一oneman\"\u003e一.oneman\u003c/h4\u003e\n\u003cp\u003e指只有一个人经营的idc服务商，业内比较出名的有ramnode，不过这家严格说并不是oneman，人家是夫妻店，是俩人23333。还有那个常年侮辱中国客户的bugetvz，是个澳洲村炮自己开的oneman店。\u003c/p\u003e\n\u003ch4 id=\"二毒妇杜甫\"\u003e二.毒妇，杜甫\u003c/h4\u003e\n\u003cp\u003e指的是独立服务器，缩写是独服，因为输入法经常打成毒妇或者是杜甫，所以这个称呼也沿用开了。\u003c/p\u003e\n\u003ch4 id=\"三小鸡\"\u003e三.小鸡\u003c/h4\u003e\n\u003cp\u003e指小型vps，往往配置非常低，但是麻雀虽小五脏俱全，往往都有独立ip，同时价格也很便宜。另外一个意思就是独立服务器里开出来的小vps，也叫小鸡。\u003c/p\u003e\n\u003ch4 id=\"四母鸡\"\u003e四.母鸡\u003c/h4\u003e\n\u003cp\u003e指独立服务器。因为vps都是拿独立服务器虚拟出来的（其实就是独服开了一堆虚拟机），所以小vps叫小鸡，生出小vps来的独服叫母鸡。\u003c/p\u003e\n\u003ch4 id=\"五超售\"\u003e五.超售\u003c/h4\u003e\n\u003cp\u003e前面说了，vps其实就是独服的虚拟机。超售就是开了太多的小鸡在独服里。举个例子，本来一台独服是4核4g内存，如果用kvm开小鸡，如果要开的套餐是1核1g内存，那么就只能开4台出来。但是实际的情况并不是这样，商家为了多赚钱，强行开了八台出来，还全都是1核512m内存的，其原因就是超了cpu，强行让本来应该为四个人服务的cpu给八个人服务，但是却内存没超。虚拟的主流架构有openvz，kvm和xen，其中ovz啥都能超，kvm可以超cpu但是不能超内存和硬盘。xen不太了解，应该和kvm一样（存疑，超售的这个具体原理我也不是很懂）\u003c/p\u003e\n\u003ch4 id=\"五传家宝\"\u003e五.传家宝\u003c/h4\u003e\n\u003cp\u003e部分vps卖断货后，往往其价格会飞速飙升。这是因为如果商家不再开卖的话，一台母鸡里就这么多小鸡了，商家不会再超售了。那么小鸡的稳定性就大大提升，求购二手vps的基佬就会趋之若鹜，价格自然水涨船高。当年搬瓦工3.99刀卖断货后价格一度飞涨七八倍，涨幅都超过五道口宇宙中心的房价涨幅了，被戏称为传家宝。传家宝一名因此得来。\u003c/p\u003e\n\u003ch4 id=\"六黄牛\"\u003e六.黄牛\u003c/h4\u003e\n\u003cp\u003e倒卖各种二手vps的人，称之为黄牛，鄙人也是做这买卖的。干这行非常考验眼力和手速，当然利润也是相当不错。曾经一刀买的800块人民币卖掉了，我会乱说？\u003c/p\u003e\n\u003ch4 id=\"七石头盘\"\u003e七.石头盘\u003c/h4\u003e\n\u003cp\u003e指硬盘io过低的vps。基本低于100m/s就是石头盘了。有人笑称是用石头做出来的硬盘，因此得名。硬盘转速太低基本干啥都卡，因此大家都不喜欢石头盘的vps。与此同时还有一个说法是钻石盘，指垃圾中的战斗机硬盘。当年cac的vps一度跑出了10m/s的成绩，惊为天人，简直是石头盘中的石头盘，钻石盘这个说法就出来了。\u003c/p\u003e\n\u003ch4 id=\"八吃灰\"\u003e八.吃灰\u003c/h4\u003e\n\u003cp\u003e指剁手党看到好vps就买买买，结果买回来发现并不需要，于是放着吃灰。\u003c/p\u003e\n\u003ch4 id=\"九探针\"\u003e九.探针\u003c/h4\u003e\n\u003cp\u003e一个php小程序，挂在vps上能够查看vps的各项参数性能。基佬们经常互相交换探针地址来加深友♂谊，是py交易的前奏。\u003c/p\u003e\n\u003ch4 id=\"十油管\"\u003e十.油管\u003c/h4\u003e\n\u003cp\u003e指有图比，很多基佬喜欢用u2b的视频播放效果来衡量线路的好坏，虽然这个并不是一个很精准的检验方法。\u003c/p\u003e\n\u003ch4 id=\"十一mjj\"\u003e十一.mjj\u003c/h4\u003e\n\u003cp\u003e“没鸡鸡”的缩写，hostloc论坛的基佬们的自称。hostloc是中国vps玩家和黄赌毒站长们的聚集地。\u003c/p\u003e\n\u003ch4 id=\"十二刀\"\u003e十二.刀\u003c/h4\u003e\n\u003cp\u003e指美元，dollar的音译。大部分vps商家只支持美元支付。\u003c/p\u003e\n\u003ch4 id=\"十三海外机房缩写\"\u003e十三.海外机房缩写：\u003c/h4\u003e\n\u003cp\u003eHK香港，KR韩国，JP日本，SG新加坡， LA洛杉矶等等。\u003c/p\u003e\n\u003ch4 id=\"十四大水管小水管\"\u003e十四.大水管，小水管\u003c/h4\u003e\n\u003cp\u003e在A站B站看电影和电视的时候，会有很多很多老司机提醒说，小水管不要点超清播放，要点高清播放，尤其是2m小水管，有些在贴吧发资源的时候，也常常听到有人说，好像这个词跟看电影有关吧，请问“小水管”是什么意思？2M小水管呢？4M小水管呢？\u003c/p\u003e\n\u003cp\u003e简单说下：如果家里的宽带是100M就是大水管，你弄个2M就是小水管，套用在VPS上面，国外的vps带宽动辄G口宽带，国内的带宽太贵，我们买个阿里云的Vps的时候钱太少，只能选择1M的带宽，那G口的就是大水管，1M的就是小水管。\u003c/p\u003e","title":"【VPS流行语】常用的VPS江湖黑话"},{"content":"Win10更新到今日，任务栏还是无法实现全透明，最多也是如下图的半透明毛玻璃效果\n今天给大家带来一款插件Private.BlurClear\n双击运行即可一键使任务栏透明\n下面介绍一下如何添加开机启动项，使用到的软件是Uninstall Tool，可以查看另一篇文章下载使用《软件卸载利器：Uninstall Tool Pro》\n这样开机就会自动运行了！\nPrivate大小：27k | 来源：百度网盘\n已经过安全软件检测无毒，请您放心下载。\n","permalink":"https://zhong-er.com/posts/win10%E4%BB%BB%E5%8A%A1%E6%A0%8F%E4%B8%80%E9%94%AE%E9%80%8F%E6%98%8E%E5%B0%8F%E6%8F%92%E4%BB%B6/","summary":"\u003cp\u003eWin10更新到今日，任务栏还是无法实现全透明，最多也是如下图的半透明毛玻璃效果\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"1.jpg\" loading=\"lazy\" src=\"/assets/201905088268_1165_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e今天给大家带来一款插件Private.BlurClear\u003cbr\u003e\n\u003cimg loading=\"lazy\" src=\"%7B#ZC_BLOG_HOST%23%7Dzb_users/upload/2019/05/20190506095921_43741.jpg\"\u003e\u003cbr\u003e\n双击运行即可一键使任务栏透明\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"2.jpg\" loading=\"lazy\" src=\"/assets/201905084980_8586_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e下面介绍一下如何添加开机启动项，使用到的软件是Uninstall Tool，可以查看另一篇文章下载使用\u003ca href=\"%7B#ZC_BLOG_HOST#%7Dpost/14.html\"\u003e《软件卸载利器：Uninstall Tool Pro》\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"3.jpg\" loading=\"lazy\" src=\"/assets/201905089499_2891_1.jpg\"\u003e\u003cbr\u003e\n\u003cimg alt=\"20190506095922_61572.jpg\" loading=\"lazy\" src=\"/assets/201905084727_5347_1.jpg\"\u003e\u003cimg alt=\"20190506095922_26817.jpg\" loading=\"lazy\" src=\"/assets/201905083445_8062_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e这样开机就会自动运行了！\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://pan.baidu.com/s/1She556YwYxzzgQjXhQKB-w\" title=\"点击下载\"\u003ePrivate\u003c/a\u003e大小：27k | 来源：百度网盘\u003c/p\u003e\n\u003cp\u003e已经过安全软件检测无毒，请您放心下载。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://pan.baidu.com/s/1_MGWtu2KPKq86lsMQ7iQCw\" title=\"点击下载\"\u003e \u003c/a\u003e\u003c/p\u003e","title":"【Win10】任务栏一键透明小插件"},{"content":"\n安装要求：\nPython版本： 2.6/2.7（安装宝塔时会自动安装）\n内存：128M以上，推荐512M以上（纯面板约占系统10M内存）\n硬盘：100M以上可用硬盘空间（纯面板约占20M磁盘空间）\n系统：CentOS 6.x / 7.x (Ubuntu、Debian、Fedora )，\n确保是干净的操作系统，不支持32位系统，没有安装过其它环境带的Apache/Nginx/php/MySQL（已有环境不可安装）\nLinux面板5.9（稳定版）安装命令： Centos安装命令：\nyum install -y wget \u0026amp;\u0026amp; wget -O install.sh http://download.bt.cn/install/install.sh \u0026amp;\u0026amp; sh install.sh Ubuntu/Deepin安装命令：\nwget -O install.sh http://download.bt.cn/install/install-ubuntu.sh \u0026amp;\u0026amp; sudo bash install.sh Debian安装命令：\nwget -O install.sh http://download.bt.cn/install/install-ubuntu.sh \u0026amp;\u0026amp; bash install.sh Fedora安装命令：\nwget -O install.sh http://download.bt.cn/install/install.sh \u0026amp;\u0026amp; bash install.sh 备用节点【广东】： yum install -y wget \u0026amp;\u0026amp; wget -O install.sh http://125.88.182.172:5880/install/install.sh \u0026amp;\u0026amp; sh install.sh 备用节点【香港】： yum install -y wget \u0026amp;\u0026amp; wget -O install.sh http://103.224.251.67:5880/install/install.sh \u0026amp;\u0026amp; sh install.sh 备用节点【美国】： yum install -y wget \u0026amp;\u0026amp; wget -O install.sh http://128.1.164.196:5880/install/install.sh \u0026amp;\u0026amp; sh install.sh 若点击更新后没生效，请尝试重启面板服务：\nservice bt restart 需要放通的端口 面板特色功能： 一键配置服务器环境（LAMP/LNMP）\n一键安全重启\n一键创建管理网站、ftp、数据库\n一键配置（定期备份、数据导入、伪静态、301、SSL、子目录、反向代理、切换PHP版本）\n一键安装常用PHP扩展(fileinfo、intl、opcache、imap、memcache、apc、redis、ioncube、imagick)\n数据库一键导入导出\n系统监控（CPU、内存、磁盘IO、网络IO）\n防火墙端口放行\nSSH开启与关闭及SSH端口更改\n禁PING开启或关闭\n方便高效的文件管理器（上传、下载、压缩、解压、查看、编辑等等）\n计划任务（定期备份、日志切割、shell脚本）\n软件管理（一键安装、卸载、版本切换）\n5.9更新内容： 1、优化验证码模块，增强可辨识度\n2、优化面板加载逻辑，提升面板响应速度\n3、修正部分服务器因升级Openssl之后导致面板异常的问题\n4、修正部分地区无法正常绑定小程序的问题\n安装要求： 内存：128M以上，推荐512M以上（纯面板约占系统10M内存）\n硬盘：100M以上可用硬盘空间（纯面板约占20M磁盘空间）\n系统：暂时只对 CentOS 6.x / 7.x /Ubuntu/Debian\n确保是干净的操作系统，没有安装过其它环境带的Apache/Nginx/php/MySQL\n","permalink":"https://zhong-er.com/posts/%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF5.9%E7%89%88%E6%9C%AC%E5%AE%89%E8%A3%85%E6%8C%87%E4%BB%A4/","summary":"\u003cp\u003e\u003cimg alt=\"5.9.jpg\" loading=\"lazy\" src=\"/assets/201904233944_3958.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e安装要求：\u003c/p\u003e\n\u003cp\u003ePython版本： 2.6/2.7（安装宝塔时会自动安装）\u003c/p\u003e\n\u003cp\u003e内存：128M以上，推荐512M以上（纯面板约占系统10M内存）\u003c/p\u003e\n\u003cp\u003e硬盘：100M以上可用硬盘空间（纯面板约占20M磁盘空间）\u003c/p\u003e\n\u003cp\u003e系统：CentOS 6.x / 7.x (Ubuntu、Debian、Fedora )，\u003c/p\u003e\n\u003cp\u003e确保是干净的操作系统，不支持32位系统，没有安装过其它环境带的Apache/Nginx/php/MySQL（已有环境不可安装）\u003c/p\u003e\n\u003ch3 id=\"linux面板59稳定版安装命令\"\u003eLinux面板5.9（稳定版）安装命令：\u003c/h3\u003e\n\u003cp\u003eCentos安装命令：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eyum install -y wget \u0026amp;\u0026amp; wget -O install.sh http://download.bt.cn/install/install.sh \u0026amp;\u0026amp; sh install.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eUbuntu/Deepin安装命令：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewget -O install.sh http://download.bt.cn/install/install-ubuntu.sh \u0026amp;\u0026amp; sudo bash install.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eDebian安装命令：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewget -O install.sh http://download.bt.cn/install/install-ubuntu.sh \u0026amp;\u0026amp; bash install.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eFedora安装命令：\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ewget -O install.sh http://download.bt.cn/install/install.sh \u0026amp;\u0026amp; bash install.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"备用节点广东\"\u003e备用节点【广东】：\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003eyum install -y wget \u0026amp;\u0026amp; wget -O install.sh http://125.88.182.172:5880/install/install.sh \u0026amp;\u0026amp; sh install.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"备用节点香港\"\u003e备用节点【香港】：\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003eyum install -y wget \u0026amp;\u0026amp; wget -O install.sh http://103.224.251.67:5880/install/install.sh \u0026amp;\u0026amp; sh install.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch3 id=\"备用节点美国\"\u003e备用节点【美国】：\u003c/h3\u003e\n\u003cpre\u003e\u003ccode\u003eyum install -y wget \u0026amp;\u0026amp; wget -O install.sh http://128.1.164.196:5880/install/install.sh \u0026amp;\u0026amp; sh install.sh\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003cstrong\u003e若点击更新后没生效，请尝试重启面板服务：\u003c/strong\u003e\u003c/p\u003e","title":"【宝塔面板】5.9版本安装指令"},{"content":"\n虽然目前宝塔面板开始收费，但是免费版本功能还是够用的。至少目前还没有其他相对好的免费面板替换，所以如果希望使用在Linux服务器可视化面板，还是建议使用宝塔面板的。老蒋在平时有帮助网友解决有服务器中安装宝塔面板的问题时候，有需要查看宝塔面板特定的脚本命令。\n所以，在这篇文章中将宝塔面板常用的脚本整理出来，这样下次不用再去搜索。其实我们在做任何事情的时候，记忆力或者不想记，可以记录下来，以便以后使用的时候直接寻找文档。\n第一、宝塔面板安装\n请查看前文《宝塔面板：5.9版本安装指令》\n第二、宝塔面板管理\n停止\n/etc/init.d/bt stop 启动\n/etc/init.d/bt start 重启\n/etc/init.d/bt restart 卸载\n/etc/init.d/bt stop \u0026amp;\u0026amp; chkconfig --del bt \u0026amp;\u0026amp; rm -f /etc/init.d/bt \u0026amp;\u0026amp; rm -rf /www/server/panel 查看当前面本端口\ncat /www/server/panel/data/port.pl 修改面板端口，如要改成8881（centos 6 系统）\necho '8881' \u0026gt; /www/server/panel/data/port.pl \u0026amp;\u0026amp; /etc/init.d/bt restart 修改面板端口，如要改成8881（centos 7 系统）\necho '8881' \u0026gt; /www/server/panel/data/port.pl \u0026amp;\u0026amp; /etc/init.d/bt restart 强制修改MySQL管理(root)密码，如要改成123456\ncd /www/server/panel \u0026amp;\u0026amp; python tools.py root 123456 修改面板密码，如要改成123456\ncd /www/server/panel \u0026amp;\u0026amp; python tools.py panel 123456 查看宝塔日志\ncat /tmp/panelBoot.pl 查看软件安装日志\ncat /tmp/panelExec.log 站点配置文件位置\n/www/server/panel/vhost 删除域名绑定面板\nrm -f /www/server/panel/data/domain.conf 清理登陆限制\nrm -f /www/server/panel/data/*.login 查看面板授权IP\ncat /www/server/panel/data/limitip.conf 关闭访问限制\nrm -f /www/server/panel/data/limitip.conf 查看许可域名\ncat /www/server/panel/data/domain.conf 关闭面板SSL\nrm -f /www/server/panel/data/ssl.pl \u0026amp;\u0026amp; /etc/init.d/bt restart 查看面板错误日志\ncat /tmp/panelBoot 查看数据库错误日志\ncat /www/server/data/*.err 站点配置文件目录(nginx)\n/www/server/panel/vhost/nginx 站点配置文件目录(apache)\n/www/server/panel/vhost/apache 站点默认目录\n/www/wwwroot 数据库备份目录\n/www/backup/database 站点备份目录\n/www/backup/site 站点日志\n/www/wwwlogs 第三、Nginx服务器管理\nnginx安装目录\n/www/server/nginx 启动\n/etc/init.d/nginx start 停止\n/etc/init.d/nginx stop 重启\n/etc/init.d/nginx restart 启载\n/etc/init.d/nginx reload nginx配置文件\n/www/server/nginx/conf/nginx.conf 第四、Apache服务管理\napache安装目录\n/www/server/httpd 启动\n/etc/init.d/httpd start 停止\n/etc/init.d/httpd stop 重启\n/etc/init.d/httpd restart 启载\n/etc/init.d/httpd reload apache配置文件\n/www/server/apache/conf/httpd.conf 第五、MySQL数据库管理\nmysql安装目录\n/www/server/mysql phpmyadmin安装目录\n/www/server/phpmyadmin 数据存储目录\n/www/server/data 启动\n/etc/init.d/mysqld start 停止\n/etc/init.d/mysqld stop 重启\n/etc/init.d/mysqld restart 启载\n/etc/init.d/mysqld reload mysql配置文件\n/etc/my.cnf 第六、FTP管理\nftp安装目录\n/www/server/pure-ftpd 启动\n/etc/init.d/pure-ftpd start 停止\n/etc/init.d/pure-ftpd stop 重启\n/etc/init.d/pure-ftpd restart ftp配置文件\n/www/server/pure-ftpd/etc/pure-ftpd.conf 第七、PHP服务管理\nphp安装目录\n/www/server/php 启动(请根据安装PHP版本号做更改，例如：/etc/init.d/php-fpm-54 start)\n/etc/init.d/php-fpm-{52|53|54|55|56|70|71} start 停止(请根据安装PHP版本号做更改，例如：/etc/init.d/php-fpm-54 stop)\n/etc/init.d/php-fpm-{52|53|54|55|56|70|71} stop 重启(请根据安装PHP版本号做更改，例如：/etc/init.d/php-fpm-54 restart)\n/etc/init.d/php-fpm-{52|53|54|55|56|70|71} restart 启载(请根据安装PHP版本号做更改，例如：/etc/init.d/php-fpm-54 reload)\n/etc/init.d/php-fpm-{52|53|54|55|56|70|71} reload 配置文件(请根据安装PHP版本号做更改，例如：/www/server/php/52/etc/php.ini)\n/www/server/php/{52|53|54|55|56|70|71}/etc/php.ini 第八、Redis服务管理\nredis安装目录\n/www/server/redis 启动\n/etc/init.d/redis start 停止\n/etc/init.d/redis stop redis配置文件\n/www/server/redis/redis.conf 第九、Memcached服务管理\nmemcached安装目录\n/usr/local/memcached 启动\n/etc/init.d/memcached start 停止\n/etc/init.d/memcached stop 重启\n/etc/init.d/memcached restart 启载\n/etc/init.d/memcached reload 这样，有些常用的命令记不住就记录下来，需要的时候直接打开文档。\n","permalink":"https://zhong-er.com/posts/%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF%E5%B8%B8%E7%94%A8%E8%84%9A%E6%9C%AC%E5%91%BD%E4%BB%A4/","summary":"\u003cp\u003e\u003cimg alt=\"2.jpg\" loading=\"lazy\" src=\"/assets/201811228186_8847.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e虽然目前宝塔面板开始收费，但是免费版本功能还是够用的。至少目前还没有其他相对好的免费面板替换，所以如果希望使用在Linux服务器可视化面板，还是建议使用宝塔面板的。老蒋在平时有帮助网友解决有服务器中安装宝塔面板的问题时候，有需要查看宝塔面板特定的脚本命令。\u003c/p\u003e\n\u003cp\u003e所以，在这篇文章中将宝塔面板常用的脚本整理出来，这样下次不用再去搜索。其实我们在做任何事情的时候，记忆力或者不想记，可以记录下来，以便以后使用的时候直接寻找文档。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e第一、宝塔面板安装\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e请查看前文\u003ca href=\"%7B#ZC_BLOG_HOST#%7Dpost/3.html\" title=\"宝塔面版：5.9版本安装指令\"\u003e《宝塔面板：5.9版本安装指令》\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e第二、宝塔面板管理\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e停止\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e/etc/init.d/bt stop\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e启动\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e/etc/init.d/bt start\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e重启\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e/etc/init.d/bt restart\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e卸载\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e/etc/init.d/bt stop \u0026amp;\u0026amp; chkconfig --del bt \u0026amp;\u0026amp; rm -f /etc/init.d/bt \u0026amp;\u0026amp; rm -rf /www/server/panel\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e查看当前面本端口\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ecat /www/server/panel/data/port.pl\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e修改面板端口，如要改成8881（centos 6 系统）\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eecho '8881' \u0026gt; /www/server/panel/data/port.pl \u0026amp;\u0026amp; /etc/init.d/bt restart\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e修改面板端口，如要改成8881（centos 7 系统）\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eecho '8881' \u0026gt; /www/server/panel/data/port.pl \u0026amp;\u0026amp; /etc/init.d/bt restart\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e强制修改MySQL管理(root)密码，如要改成123456\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ecd /www/server/panel \u0026amp;\u0026amp; python tools.py root 123456\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e修改面板密码，如要改成123456\u003c/p\u003e","title":"【宝塔面板】常用脚本命令"},{"content":"昨天在某群看到有大佬在推介《联通沃享小颗粒》的VPN，对于VPN这里不做太多解释，可自行百度，据说是通过法定信道进行国际互联，受国家监管。\n博主还未进行测试。需要企业营业执照进行申请，只有符合申请要求的才能通过审核，主要面向外贸企业、归国留学生等等，具体看一下介绍。\n官网：联通沃享小颗粒\n下面是大佬给出的套餐方案\n有需求的话可以尝试一下。\n","permalink":"https://zhong-er.com/posts/%E6%B3%95%E5%AE%9Avpn%E4%BF%A1%E9%81%93%E8%81%94%E9%80%9A%E6%B2%83%E4%BA%AB%E5%B0%8F%E9%A2%97%E7%B2%92/","summary":"\u003cp\u003e昨天在某群看到有大佬在推介《联通沃享小颗粒》的VPN，对于VPN这里不做太多解释，可自行百度，据说是通过法定信道进行国际互联，受国家监管。\u003c/p\u003e\n\u003cp\u003e博主还未进行测试。需要企业营业执照进行申请，只有符合申请要求的才能通过审核，主要面向外贸企业、归国留学生等等，具体看一下介绍。\u003c/p\u003e\n\u003cp\u003e官网：\u003ca href=\"http://xiaokeli.com.cn/\" title=\"联通沃享小颗粒\"\u003e联通沃享小颗粒\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"111.jpg\" loading=\"lazy\" src=\"/assets/201903307452_9185_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"123.jpg\" loading=\"lazy\" src=\"/assets/201903309907_1553_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e下面是大佬给出的套餐方案\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"FD8AE749F00D12DFD31CF24D964C14F3.jpg\" loading=\"lazy\" src=\"/assets/201903305451_9087_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e有需求的话可以尝试一下。\u003c/p\u003e","title":"【法定VPN信道】联通沃享小颗粒"},{"content":"\n针对淘宝自然人经营者的工商电子营业执照！有需要的可以申请去了！！不过目前申请的人比较多一天估计下不来！\n当然也能自己到当地办理~ 花不了几个钱~ 也有很多大佬说这个个体工商注销很麻烦。 大家酌情上车~~~\n办理地址 https://taobao.yuntrial.com\n简要流程 注册账号，完善信息，实名认证（需要电脑或者手机摄像头）\n办理执照，完善信息，经营范围，申请审核\n常见问题 淘宝小镇电商园政策问答\n关于注册对象：\n问：已经注册公司了，能否迁到园区？能够变更企业名称么，需要走什么流程？\n答：目前线上注册只针对淘宝上的自然人经营者（个体工商户），暂时不对企业开放。已经注册为企业的淘宝商家暂无法在本平台做工商变 更和名称变更。已经注册公司的淘宝商家，后续如想入驻园区，可与园区运营方对接。注册过程的问题：\n问：注册过程中需要填写的注册金额一般写多少？\n答：根据个人实际经营情况填写即可，金额不做具体限制。问：注册地址是否必须填写“浙江省杭州市余杭区五常街道文一西路998号18幢111室”？\n答：是的。该地址是淘宝小镇电商园统一的注册地址，实际地址位于杭州未来科技城海创园，距离阿里巴巴淘宝城仅一路之隔。 只有按此地址注册才能享受园区政策。目前系统已经将注册地址一栏默认为“浙江省杭州市余杭区五常街道文一西路998号18幢111室”，各位商家知晓即可。\n关于营业执照：\n问：淘宝小镇电商园发出的营业执照是真实有效的吗？\n答：淘宝小镇电商园与工商部门等多部门合作开展“最多跑一次”改革，电子营业执照是从工商部门系统发出，真实有效且具有法律效应，后 续可查询全国企业信用公示系统。\n问：刚注册拿到执照，能否注销？注册时选的经营范围少了，以后要增加经营范围，该如何操作？\n答：目前只开放电子营业执照注册，后期将推出电子营业执照的在线变更和注销。\n问：申请营业执照时注册淘宝类别没有找到广告制作行业这块，请问以后会有吗？\n答：目前系统内嵌的经营范围是根据淘宝现有商家的常见经营范围筛选得到，可能存在部分遗漏。欢迎联系我们，后期我们会将符合条件的 淘宝类别添入系统。\n后续服务：\n问：是否提供银行基本户的开立服务？\n答：我们正在工商银行洽谈合作，为产业园入园商户提供线上申请开户，异地见证等创新服务，实现“最多跑一次”的开户体验（商户去其所 在地当地工行网点异地面签）,敬请期待。\n问：升级成个体工商户的公章怎么申请？\n答：我们正在对接公安部门，后续将提供电子签章或实体刻章寄送服务。\n问：发票怎么开？\n答：我们将引入第三方机构提供电子发票服务。\n","permalink":"https://zhong-er.com/posts/%E6%B7%98%E5%AE%9D%E5%B0%8F%E9%95%87%E7%94%B3%E8%AF%B7%E4%B8%AA%E4%BD%93%E5%B7%A5%E5%95%86%E6%88%B7%E7%94%B5%E5%AD%90%E8%90%A5%E4%B8%9A%E6%89%A7%E7%85%A7%E6%96%B0%E6%96%B9%E5%BC%8F/","summary":"\u003cp\u003e\u003cimg alt=\"tbxz.png\" loading=\"lazy\" src=\"/assets/201901033971_7695_1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e针对淘宝自然人经营者的工商电子营业执照！有需要的可以申请去了！！不过目前申请的人比较多一天估计下不来！\u003c/p\u003e\n\u003cp\u003e当然也能自己到当地办理~ 花不了几个钱~ 也有很多大佬说这个个体工商注销很麻烦。 大家酌情上车~~~\u003c/p\u003e\n\u003ch2 id=\"办理地址\"\u003e办理地址\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://taobao.yuntrial.com\"\u003ehttps://taobao.yuntrial.com\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"123.jpg\" loading=\"lazy\" src=\"/assets/201904242343_7136_1.jpg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"简要流程\"\u003e简要流程\u003c/h2\u003e\n\u003cp\u003e注册账号，完善信息，实名认证（需要电脑或者手机摄像头）\u003c/p\u003e\n\u003cp\u003e办理执照，完善信息，经营范围，申请审核\u003c/p\u003e\n\u003ch2 id=\"常见问题\"\u003e常见问题\u003c/h2\u003e\n\u003cp\u003e淘宝小镇电商园政策问答\u003cbr\u003e\n关于注册对象：\u003cbr\u003e\n问：已经注册公司了，能否迁到园区？能够变更企业名称么，需要走什么流程？\u003cbr\u003e\n答：目前线上注册只针对淘宝上的自然人经营者（个体工商户），暂时不对企业开放。已经注册为企业的淘宝商家暂无法在本平台做工商变 更和名称变更。已经注册公司的淘宝商家，后续如想入驻园区，可与园区运营方对接。注册过程的问题：\u003cbr\u003e\n问：注册过程中需要填写的注册金额一般写多少？\u003cbr\u003e\n答：根据个人实际经营情况填写即可，金额不做具体限制。问：注册地址是否必须填写“浙江省杭州市余杭区五常街道文一西路998号18幢111室”？\u003cbr\u003e\n答：是的。该地址是淘宝小镇电商园统一的注册地址，实际地址位于杭州未来科技城海创园，距离阿里巴巴淘宝城仅一路之隔。 只有按此地址注册才能享受园区政策。目前系统已经将注册地址一栏默认为“浙江省杭州市余杭区五常街道文一西路998号18幢111室”，各位商家知晓即可。\u003c/p\u003e\n\u003cp\u003e关于营业执照：\u003cbr\u003e\n问：淘宝小镇电商园发出的营业执照是真实有效的吗？\u003cbr\u003e\n答：淘宝小镇电商园与工商部门等多部门合作开展“最多跑一次”改革，电子营业执照是从工商部门系统发出，真实有效且具有法律效应，后 续可查询全国企业信用公示系统。\u003c/p\u003e\n\u003cp\u003e问：刚注册拿到执照，能否注销？注册时选的经营范围少了，以后要增加经营范围，该如何操作？\u003cbr\u003e\n答：目前只开放电子营业执照注册，后期将推出电子营业执照的在线变更和注销。\u003c/p\u003e\n\u003cp\u003e问：申请营业执照时注册淘宝类别没有找到广告制作行业这块，请问以后会有吗？\u003cbr\u003e\n答：目前系统内嵌的经营范围是根据淘宝现有商家的常见经营范围筛选得到，可能存在部分遗漏。欢迎联系我们，后期我们会将符合条件的 淘宝类别添入系统。\u003c/p\u003e\n\u003cp\u003e后续服务：\u003cbr\u003e\n问：是否提供银行基本户的开立服务？\u003cbr\u003e\n答：我们正在工商银行洽谈合作，为产业园入园商户提供线上申请开户，异地见证等创新服务，实现“最多跑一次”的开户体验（商户去其所 在地当地工行网点异地面签）,敬请期待。\u003c/p\u003e\n\u003cp\u003e问：升级成个体工商户的公章怎么申请？\u003cbr\u003e\n答：我们正在对接公安部门，后续将提供电子签章或实体刻章寄送服务。\u003c/p\u003e\n\u003cp\u003e问：发票怎么开？\u003cbr\u003e\n答：我们将引入第三方机构提供电子发票服务。\u003c/p\u003e","title":"【淘宝小镇】申请个体工商户(电子营业执照)新方式"},{"content":"老友开了个电竞酒店，组了十台电脑，需要装系统还原软件，正好之前也了解过这方面的，就简单做了下对比。\n我用的第一款软件是影子系统PowerShadow，奈何更新太慢，还是14年的版本，安装后电脑加载会变慢，影响用户体验。\n第二款是雨过天晴电脑保护系统，这款软件是付费的，对win7系统支持比较好，但是对win10不太友好，我也就没去购买测试，目前主要使用win10系统。\n第三款冰点还原精灵DeepFreeze，这款软件我之前家里台式一直在用，还不错，当然这个是付费软件，网上的和谐版之前很多，自从思杰马克丁的出现，国内和谐版已经死绝了。只好去Google搜索了，一个链接挂了，还有千千万万个链接。\n我向老友推荐了DeepFreeze,先看看库存的8.3版本还能用不能，挂上虚拟机测试一下。\n貌似安装成功了，检查下注册信息，凉凉，没注册成功。\n只好去Google找新版的了，看到个企业版想着先下载试试，\n尼玛，这么复杂，不适合小白使用，配置好估计还要研究半天，还是换基础版吧\n嗯，这个样子貌似就成功了，删几个文件，重启，还原成功。\nDeepFreeze 8.53.020.5458企业版大小：55.8MB | 来源：蓝奏云\n已经过安全软件检测无毒，请您放心下载。\nDeepFreeze 8.53.020.5458注册机大小：95.6KB | 来源：蓝奏云\n已经过安全软件检测无毒，请您放心下载。\n","permalink":"https://zhong-er.com/posts/%E7%B3%BB%E7%BB%9F%E8%BF%98%E5%8E%9F%E7%BD%97%E5%88%97%E4%B8%BB%E6%B5%81%E7%9A%84%E7%B3%BB%E7%BB%9F%E8%BF%98%E5%8E%9F%E8%BD%AF%E4%BB%B6/","summary":"\u003cp\u003e老友开了个电竞酒店，组了十台电脑，需要装系统还原软件，正好之前也了解过这方面的，就简单做了下对比。\u003c/p\u003e\n\u003cp\u003e我用的第一款软件是\u003ca href=\"http://www.yingzixitong.cn/\" title=\"影子系统PowerShadow\"\u003e影子系统PowerShadow\u003c/a\u003e，奈何更新太慢，还是14年的版本，安装后电脑加载会变慢，影响用户体验。\u003cimg alt=\"111.jpg\" loading=\"lazy\" src=\"/assets/201811082572_8021_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e第二款是\u003ca href=\"http://www.njanyue.com/\" title=\"雨过天晴电脑保护系统\"\u003e雨过天晴电脑保护系统\u003c/a\u003e，这款软件是付费的，对win7系统支持比较好，但是对win10不太友好，我也就没去购买测试，目前主要使用win10系统。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"112.jpg\" loading=\"lazy\" src=\"/assets/201811081563_4216_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e第三款\u003ca href=\"http://www.bingdiancn.com/\" title=\"冰点还原精灵DeepFreeze\"\u003e冰点还原精灵DeepFreeze\u003c/a\u003e，这款软件我之前家里台式一直在用，还不错，当然这个是付费软件，网上的和谐版之前很多，自从思杰马克丁的出现，国内和谐版已经死绝了。只好去Google搜索了，一个链接挂了，还有千千万万个链接。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"113.jpg\" loading=\"lazy\" src=\"/assets/201811082421_2600_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e我向老友推荐了DeepFreeze,先看看库存的8.3版本还能用不能，挂上虚拟机测试一下。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"2SS4JU1J6RA93X91PK~RU1Y.jpg\" loading=\"lazy\" src=\"/assets/201811085068_4266_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e貌似安装成功了，检查下注册信息，凉凉，没注册成功。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"HFL5WCB01HPZ3C`_{T5OGJY.jpg\" loading=\"lazy\" src=\"/assets/201811085402_9720_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e只好去Google找新版的了，看到个企业版想着先下载试试，\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"114.jpg\" loading=\"lazy\" src=\"/assets/201811087297_8297.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e尼玛，这么复杂，不适合小白使用，配置好估计还要研究半天，还是换基础版吧\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"115.jpg\" loading=\"lazy\" src=\"/assets/201811081755_5192_1.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e嗯，这个样子貌似就成功了，删几个文件，重启，还原成功。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.lanzous.com/i2nkddg\" title=\"点击下载\"\u003eDeepFreeze 8.53.020.5458企业版\u003c/a\u003e大小：55.8MB | 来源：蓝奏云\u003c/p\u003e\n\u003cp\u003e已经过安全软件检测无毒，请您放心下载。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.lanzous.com/i2nkddg\" title=\"点击下载\"\u003e \u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.lanzous.com/i2nkdeh\" title=\"点击下载\"\u003eDeepFreeze 8.53.020.5458注册机\u003c/a\u003e大小：95.6KB | 来源：蓝奏云\u003c/p\u003e\n\u003cp\u003e已经过安全软件检测无毒，请您放心下载。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.lanzous.com/i2nkdeh\" title=\"点击下载\"\u003e \u003c/a\u003e\u003c/p\u003e","title":"【系统还原】罗列主流的系统还原软件"},{"content":"2019年6月4日更新\n请直接下载使用：极速迅雷最终优化版\n极速迅雷优化版大小：13.3MB | 来源：百度网盘\n已经过安全软件检测无毒，请您放心下载。\n如果喜欢界面UI质感十足的迅雷X系列软件的请下载：迅雷X（10.1.14.438）去广告版绿色精简版\n迅雷X大小：50.9MB | 来源：蓝奏云\n已经过安全软件检测无毒，请您放心下载。\n以下为旧内容\n最近迅雷极速版开始抽风，关闭界面后开始疯狂弹窗提示升级！\n下面给出解决方法，禁用此弹窗！\n1.先退出迅雷，首先找到安装文件夹下面的Data/ThunderPush这个文件夹，把整个文件夹删除，\n2.建一个ThunderPush的文本文件,不要TXT后缀\n3.右键属性–设置只读，安全-编辑-权限里面全部选拒绝。\n整体来说迅雷极速版的速度还是可以的，推荐使用！\n","permalink":"https://zhong-er.com/posts/%E8%BF%85%E9%9B%B7%E6%9E%81%E9%80%9F%E7%89%88%E7%A6%81%E7%94%A8%E5%8D%87%E7%BA%A7%E5%BC%B9%E7%AA%97/","summary":"\u003cp\u003e2019年6月4日更新\u003c/p\u003e\n\u003cp\u003e请直接下载使用：极速迅雷最终优化版\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://pan.baidu.com/s/1MNJau9nyK_UawfyHqjSyRA\" title=\"点击下载\"\u003e极速迅雷优化版\u003c/a\u003e大小：13.3MB | 来源：百度网盘\u003c/p\u003e\n\u003cp\u003e已经过安全软件检测无毒，请您放心下载。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://pan.baidu.com/s/1MNJau9nyK_UawfyHqjSyRA\" title=\"点击下载\"\u003e \u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e如果喜欢界面UI质感十足的迅雷X系列软件的请下载：迅雷X（10.1.14.438）去广告版绿色精简版\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.lanzous.com/i4ej9ab\" title=\"点击下载\"\u003e迅雷X\u003c/a\u003e大小：50.9MB | 来源：蓝奏云\u003c/p\u003e\n\u003cp\u003e已经过安全软件检测无毒，请您放心下载。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.lanzous.com/i4ej9ab\" title=\"点击下载\"\u003e \u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e以下为旧内容\u003c/p\u003e\n\u003cp\u003e最近迅雷极速版开始抽风，关闭界面后开始疯狂弹窗提示升级！\u003cbr\u003e\n\u003cimg loading=\"lazy\" src=\"/assets/201903132192_5748_1.jpg\"\u003e\u003cbr\u003e\n下面给出解决方法，禁用此弹窗！\u003cbr\u003e\n1.先退出迅雷，首先找到安装文件夹下面的Data/ThunderPush这个文件夹，把整个文件夹删除，\u003cbr\u003e\n2.建一个ThunderPush的文本文件,不要TXT后缀\u003cbr\u003e\n3.右键属性–设置只读，安全-编辑-权限里面全部选拒绝。\u003cbr\u003e\n\u003cimg loading=\"lazy\" src=\"/assets/201903131002_9560_1.gif\"\u003e\u003cbr\u003e\n整体来说迅雷极速版的速度还是可以的，推荐使用！\u003cbr\u003e\n\u003cimg loading=\"lazy\" src=\"/assets/201903131609_326_1.jpg\"\u003e\u003c/p\u003e","title":"【迅雷极速版】禁用升级弹窗！"}]