<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>火之影</title>
  
  <subtitle>有思念你的人的地方就是你的归处</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://yoursite.com/"/>
  <updated>2021-02-01T02:53:11.939Z</updated>
  <id>http://yoursite.com/</id>
  
  <author>
    <name>火之影</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>使用Go处理HTTP压缩文件数据总结</title>
    <link href="http://yoursite.com/2021/02/01/%E4%BD%BF%E7%94%A8Go%E5%A4%84%E7%90%86HTTP%E5%8E%8B%E7%BC%A9%E6%96%87%E4%BB%B6%E6%95%B0%E6%8D%AE%E6%80%BB%E7%BB%93/"/>
    <id>http://yoursite.com/2021/02/01/使用Go处理HTTP压缩文件数据总结/</id>
    <published>2021-01-31T16:01:33.000Z</published>
    <updated>2021-02-01T02:53:11.939Z</updated>
    
    <content type="html"><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>最近遇到了一个HTTP请求返回一个gz压缩包的问题，前前后后搞了3天，总算是把整个流程以及处理的细节搞明白了，总结一篇博客记录分享一下。<a id="more"></a></p><h3 id="问题描述及解决方案"><a href="#问题描述及解决方案" class="headerlink" title="问题描述及解决方案"></a>问题描述及解决方案</h3><h4 id="流量方的优化"><a href="#流量方的优化" class="headerlink" title="流量方的优化"></a>流量方的优化</h4><p>获取某推广告成效数据的时候由于数据量很多，可能会导致Response数据量过大从而造成请求失败的问题。<br>某推服务方设计了这样一种方式：先将请求的到的数据写入到一个json文件中，然后将这个json文件使用gzip压缩（经过实际测试，一个100多k大小的json文件使用gzip压缩后只有十几k的大小，并且gzip压缩后是不保留原文件的）。<br>最终服务端存的是一个压缩后的gz文件，然后返回给用户端一个链接，用户根据这个链接去“下载”压缩后的数据文件，大大减少了网络带宽的占用！<br>比如我们可以通过下面这个链接下载成效数据：<strong><a href="https://ton.twimg.com/advertiser-api-async-analytics/SDJ9NgtdiyZwKaR9eEJQ7vOQm1UXJXWmeAmbZ5XmdBJ5Adj6gXadqEGXMPZNQO2H61cJXkcjMGJcQm6bWGyNB-9SZAId0SL9vVMgdoU5M8w3d6yXALPIrtxFTx5Whf3S.json.gz" target="_blank" rel="noopener">成效数据链接</a></strong><br>也就是说，与我们平时处理的请求不同的是，现在这种情况下发送GET请求得到的是一个gz文件的数据流。</p><h4 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h4><p>其实解决问题的思路有2个：一个是根据gz数据流将gz文件写入到本地，然后在本地解压这个文件，读取解压后的文件；另外一种思路是直接根据gz数据流将json文件解压到本地，省去了中间在本地生成gz压缩文件的过程！<br>一目了然：在上面描述的业务场景中，肯定是第二种解决方案最优！</p><h4 id="优化的第三方包"><a href="#优化的第三方包" class="headerlink" title="优化的第三方包"></a>优化的第三方包</h4><p>从网上找了一个包去处理文件解压缩的问题，但是其源码本身有一些问题，略微修改了一下其源码后便可使用了。<br>最终修改后的项目地址请访问：<strong><a href="https://github.com/Wanghongw/unpackit" target="_blank" rel="noopener">Unpackit</a></strong><br>原文件存在的问题可以看README的描述。</p><h3 id="解决问题的demo"><a href="#解决问题的demo" class="headerlink" title="解决问题的demo"></a>解决问题的demo</h3><p>自己写了一个demo专门用来展示两种解决方案的实现过程。<br>麻雀虽小，五脏俱全，<strong>这个demo用到了自定制错误、http的连接池，以及go中常用的错误返回机制。</strong><br>项目地址如下，欢迎star：<strong><a href="https://gitee.com/huoyingwhw/unpackit-demo" target="_blank" rel="noopener">https://gitee.com/huoyingwhw/unpackit-demo</a></strong></p><h3 id="细节记录"><a href="#细节记录" class="headerlink" title="细节记录"></a>细节记录</h3><p>关于一些解决问题中间的细节请查看笔者另外2篇博客：<br><strong><a href="https://www.cnblogs.com/paulwhw/p/14336742.html" target="_blank" rel="noopener">使用Golang解压缩文件遇到的问题及解决方法</a></strong><br><strong><a href="https://www.cnblogs.com/paulwhw/p/14341913.html" target="_blank" rel="noopener">使用unpackit包解压gz包遇到的一个问题与解决方案</a></strong> </p>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h3&gt;&lt;p&gt;最近遇到了一个HTTP请求返回一个gz压缩包的问题，前前后后搞了3天，总算是把整个流程以及处理的细节搞明白了，总结一篇博客记录分享一下。
    
    </summary>
    
    
      <category term="Golang" scheme="http://yoursite.com/categories/Golang/"/>
    
    
      <category term="学习资源" scheme="http://yoursite.com/tags/%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%BA%90/"/>
    
  </entry>
  
  <entry>
    <title>个人Golang学习资源总结</title>
    <link href="http://yoursite.com/2021/01/30/%E4%B8%AA%E4%BA%BAGolang%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%BA%90/"/>
    <id>http://yoursite.com/2021/01/30/个人Golang学习资源/</id>
    <published>2021-01-30T14:24:05.000Z</published>
    <updated>2021-02-01T02:40:39.765Z</updated>
    
    <content type="html"><![CDATA[<p>下半年做了大半年的Go项目，边学边实现业务功能的的同时也不断记录平时遇到的问题以及解决方案，学习笔记与问题记录主要放在了<code>mkdocs</code>与<code>博客园</code>中，欢迎访问：<br><a href="https://huoyingwhw.com/golangGuide/" target="_blank" rel="noopener">Golang服务端入门与进阶指南</a><br><a href="https://www.cnblogs.com/paulwhw/p/13854308.html" target="_blank" rel="noopener">个人Golang整合博客</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;下半年做了大半年的Go项目，边学边实现业务功能的的同时也不断记录平时遇到的问题以及解决方案，学习笔记与问题记录主要放在了&lt;code&gt;mkdocs&lt;/code&gt;与&lt;code&gt;博客园&lt;/code&gt;中，欢迎访问：&lt;br&gt;&lt;a href=&quot;https://huoyingwhw.co
      
    
    </summary>
    
    
      <category term="Golang" scheme="http://yoursite.com/categories/Golang/"/>
    
    
      <category term="学习资源" scheme="http://yoursite.com/tags/%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%BA%90/"/>
    
  </entry>
  
  <entry>
    <title>个人Python学习资源总结</title>
    <link href="http://yoursite.com/2020/06/08/%E4%B8%AA%E4%BA%BA%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%BA%90%E6%80%BB%E7%BB%93/"/>
    <id>http://yoursite.com/2020/06/08/个人学习资源总结/</id>
    <published>2020-06-08T11:13:01.000Z</published>
    <updated>2021-02-01T02:29:09.619Z</updated>
    
    <content type="html"><![CDATA[<p>这一阵子一直没有更新个人博客，主要是将大部分的精力都放在了使用<code>mkdocs</code>搭建的个人文档上面。<br>使用mkdocs进行知识点的记录与分享确实方便很多，欢迎大家访问我的个人文档：<br><a href="https://huoyingwhw.com/pythonGuide/" target="_blank" rel="noopener">Python服务端入门与进阶指南</a><br><a href="https://huoyingwhw.com/djangoGuide/" target="_blank" rel="noopener">Django使用指南</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;这一阵子一直没有更新个人博客，主要是将大部分的精力都放在了使用&lt;code&gt;mkdocs&lt;/code&gt;搭建的个人文档上面。&lt;br&gt;使用mkdocs进行知识点的记录与分享确实方便很多，欢迎大家访问我的个人文档：&lt;br&gt;&lt;a href=&quot;https://huoyingwhw.co
      
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="学习资源" scheme="http://yoursite.com/tags/%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%BA%90/"/>
    
  </entry>
  
  <entry>
    <title>Python中defaultdict的使用</title>
    <link href="http://yoursite.com/2020/02/10/Python%E4%B8%ADdefaultdict%E7%9A%84%E4%BD%BF%E7%94%A8/"/>
    <id>http://yoursite.com/2020/02/10/Python中defaultdict的使用/</id>
    <published>2020-02-10T14:23:02.000Z</published>
    <updated>2020-02-10T14:31:17.679Z</updated>
    
    <content type="html"><![CDATA[<p>在实际中使用<code>defaultdict</code>会十分效率地为我们构建不同格式的数据，通常需要好几层循环构建的数据如果巧用<code>defaultdict</code>的话使用一层循环便可实现，这一点笔者深有体会！本文就为大家总结一下使用<code>defaultdict</code>构建数据的一些实例。<a id="more"></a></p><h3 id="整合相同类型的数据"><a href="#整合相同类型的数据" class="headerlink" title="整合相同类型的数据"></a>整合相同类型的数据</h3><p>现有如下的数据：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">res = [</span><br><span class="line">&#123;<span class="string">"ID"</span>:<span class="number">111</span>,<span class="string">"HOUSE"</span>:<span class="number">1</span>&#125;,</span><br><span class="line">&#123;<span class="string">"ID"</span>:<span class="number">222</span>,<span class="string">"HOUSE"</span>:<span class="number">1</span>&#125;,</span><br><span class="line">&#123;<span class="string">"ID"</span>:<span class="number">333</span>,<span class="string">"HOUSE"</span>:<span class="number">1</span>&#125;,</span><br><span class="line">&#123;<span class="string">"ID"</span>:<span class="number">444</span>,<span class="string">"HOUSE"</span>:<span class="number">2</span>&#125;,</span><br><span class="line">&#123;<span class="string">"ID"</span>:<span class="number">555</span>,<span class="string">"HOUSE"</span>:<span class="number">2</span>&#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>我们需要将相同<code>HOUSE</code>的<code>ID</code>整合到一起，然后以每个<code>ID</code>为key，找到每个ID对应的详情，想要的结果如下<code>（每个ID对应的detail信息是从其他接口获取的）</code>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="number">1</span>: &#123;</span><br><span class="line">        <span class="number">111</span>: &#123;<span class="number">111</span>:<span class="string">"detail111"</span>&#125;,</span><br><span class="line">        <span class="number">222</span>: &#123;<span class="number">222</span>:<span class="string">"detail222"</span>&#125;, </span><br><span class="line">        <span class="number">333</span>: &#123;<span class="number">333</span>:<span class="string">"detail333"</span>&#125;</span><br><span class="line">       &#125;,</span><br><span class="line">    <span class="number">2</span>: &#123;</span><br><span class="line">        <span class="number">444</span>: &#123;<span class="number">444</span>:<span class="string">"detail444"</span>&#125;, </span><br><span class="line">        <span class="number">555</span>: &#123;<span class="number">555</span>:<span class="string">"detail555"</span>&#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里就不介绍其他的方法了，有兴趣的同学可以自己试试，这里直接给出defaultdict的方法：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> defaultdict</span><br><span class="line"></span><br><span class="line"><span class="comment"># 这里defaultdict默认的元素是dict</span></span><br><span class="line">ret = defaultdict(dict)</span><br><span class="line"></span><br><span class="line">res = [</span><br><span class="line">&#123;<span class="string">"ID"</span>:<span class="number">111</span>,<span class="string">"HOUSE"</span>:<span class="number">1</span>&#125;,</span><br><span class="line">&#123;<span class="string">"ID"</span>:<span class="number">222</span>,<span class="string">"HOUSE"</span>:<span class="number">1</span>&#125;,</span><br><span class="line">&#123;<span class="string">"ID"</span>:<span class="number">333</span>,<span class="string">"HOUSE"</span>:<span class="number">1</span>&#125;,</span><br><span class="line">&#123;<span class="string">"ID"</span>:<span class="number">444</span>,<span class="string">"HOUSE"</span>:<span class="number">2</span>&#125;,</span><br><span class="line">&#123;<span class="string">"ID"</span>:<span class="number">555</span>,<span class="string">"HOUSE"</span>:<span class="number">2</span>&#125;,</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> res:</span><br><span class="line">    <span class="comment"># 可以从其他接口中获取ID对应的detail，这里就省略了，直接用固定的字符串代替</span></span><br><span class="line">    detail = <span class="string">f"detail<span class="subst">&#123;i[<span class="string">'ID'</span>]&#125;</span>"</span></span><br><span class="line">    ret[i[<span class="string">"HOUSE"</span>]].update(&#123;i[<span class="string">"ID"</span>]:&#123;&#125;&#125;)</span><br><span class="line">    ret[i[<span class="string">"HOUSE"</span>]][i[<span class="string">"ID"</span>]].update(&#123;i[<span class="string">"ID"</span>]:detail&#125;)</span><br><span class="line"></span><br><span class="line">print(ret) </span><br><span class="line"><span class="comment"># defaultdict(&lt;class 'dict'&gt;, &#123;1: &#123;111: &#123;111: 'detail111'&#125;, 222: &#123;222: 'detail222'&#125;, 333: &#123;333: 'detail333'&#125;&#125;, 2: &#123;444: &#123;444: 'detail444'&#125;, 555: &#123;555: 'detail555'&#125;&#125;&#125;)</span></span><br></pre></td></tr></table></figure><p>可以看到最终得到一个<code>defaultdict对象</code>，是一个<code>自定义的字典</code>，其实它可以被强转为一个dict对象，但是强转没有意义，将得到的结果可以直接当做是一个字典对象就可以。<br>我们看一下默认字典的声明：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> defaultdict</span><br><span class="line">ret = defaultdict(dict)</span><br></pre></td></tr></table></figure><p>这样的声明之后，我们可以将ret看成一个字典对象，这个字典对象中<code>默认的元素</code>是<code>dict</code>（就是上面defaultdict中的参数），声明之后我们可以将ret当做是一个字典来进行操作，对ret中的元素可以进行字典的操作（defaultdict中用什么元素就可以使用对应的方法，比如上面例子用到了dict的update方法）。</p><h3 id="其他的例子"><a href="#其他的例子" class="headerlink" title="其他的例子"></a>其他的例子</h3><p>如果上面的说明你还没有看明白的话，笔者这里列举一些常见的例子帮助大家理解。</p><h4 id="合并字符串"><a href="#合并字符串" class="headerlink" title="合并字符串"></a>合并字符串</h4><p>下面这个例子其实是笔者之前遇到的一个面试题，在此分享一下：<br>有如下两个列表：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">a = [<span class="string">'a,1'</span>, <span class="string">'b,3,22'</span>, <span class="string">'c,3,4'</span>, <span class="string">'f,5'</span>, ]</span><br><span class="line">b = [<span class="string">'a,2'</span>, <span class="string">'b,4'</span>, <span class="string">'w,12'</span>, <span class="string">'d,2'</span>,<span class="string">'c,123'</span>]</span><br></pre></td></tr></table></figure><p>需要将两个列表中有相同字母的字符串合并，合并后的结果如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">c = [<span class="string">'a,1,2'</span>, <span class="string">'b,3,22,4'</span>, <span class="string">'c,3,4,123'</span>, <span class="string">'f,5'</span>, <span class="string">'w,12'</span>, <span class="string">'d,2'</span>]</span><br></pre></td></tr></table></figure><p>当初年轻的我是这么做的：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line">a = [<span class="string">'a,1'</span>, <span class="string">'b,3,22'</span>, <span class="string">'c,3,4'</span>, <span class="string">'f,5'</span>, ]</span><br><span class="line">b = [<span class="string">'a,2'</span>, <span class="string">'b,4'</span>, <span class="string">'w,12'</span>, <span class="string">'d,2'</span>,<span class="string">'c,123'</span>]</span><br><span class="line"></span><br><span class="line">dic = &#123;&#125;</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> a:</span><br><span class="line">    dic[i[<span class="number">0</span>]] = i</span><br><span class="line">print(dic)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> b:</span><br><span class="line">    <span class="keyword">if</span> i[<span class="number">0</span>] <span class="keyword">in</span> dic:</span><br><span class="line">        dic[i[<span class="number">0</span>]] += i[<span class="number">1</span>:]</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        dic[i[<span class="number">0</span>]] = i</span><br><span class="line">print(dic)</span><br><span class="line">print(list(dic.values()))</span><br></pre></td></tr></table></figure><p>其实使用defaultdict的话会很方便：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> defaultdict</span><br><span class="line"></span><br><span class="line">a = [<span class="string">'a,1'</span>, <span class="string">'b,3,22'</span>, <span class="string">'c,3,4'</span>, <span class="string">'f,5'</span>, ]</span><br><span class="line">b = [<span class="string">'a,2'</span>, <span class="string">'b,4'</span>, <span class="string">'w,12'</span>, <span class="string">'d,2'</span>,<span class="string">'c,123'</span>]</span><br><span class="line"></span><br><span class="line">dic = defaultdict(str)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> a:</span><br><span class="line">    dic[i[<span class="number">0</span>]] = i</span><br><span class="line">dic = dict(dic)</span><br><span class="line">print(dic)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> b:</span><br><span class="line">    <span class="keyword">if</span> i[<span class="number">0</span>] <span class="keyword">in</span> dic:</span><br><span class="line">        dic[i[<span class="number">0</span>]] += i[<span class="number">1</span>:]</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        dic[i[<span class="number">0</span>]] = i</span><br><span class="line">print(dic)</span><br><span class="line">print(list(dic.values()))</span><br></pre></td></tr></table></figure><p>这里可以看到：defaultdict中的元素是str，因此我们在构建数据的时候可以使用字符串拼接的方式。</p><h4 id="统计颜色数量"><a href="#统计颜色数量" class="headerlink" title="统计颜色数量"></a>统计颜色数量</h4><p>现有如下的数据：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lis = [(<span class="string">'红色'</span>,<span class="number">1</span>),(<span class="string">'白色'</span>,<span class="number">2</span>),(<span class="string">'绿色'</span>,<span class="number">3</span>),(<span class="string">'紫色'</span>,<span class="number">1</span>),(<span class="string">'红色'</span>,<span class="number">1</span>),(<span class="string">'白色'</span>,<span class="number">1</span>),(<span class="string">'红色'</span>,<span class="number">1</span>),(<span class="string">'粉色'</span>,<span class="number">1</span>),]</span><br></pre></td></tr></table></figure><p>统计一下每个颜色的数量，期望的结果如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dic = &#123;<span class="string">'红色'</span>: <span class="number">3</span>, <span class="string">'白色'</span>: <span class="number">3</span>, <span class="string">'绿色'</span>: <span class="number">3</span>, <span class="string">'紫色'</span>: <span class="number">1</span>, <span class="string">'粉色'</span>: <span class="number">1</span>&#125;</span><br></pre></td></tr></table></figure><p>默认元素选择list，实现方法如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> defaultdict</span><br><span class="line"></span><br><span class="line"><span class="comment">### 将每种球与其总数量统计出来</span></span><br><span class="line"><span class="comment">###  &#123;'红色':3,'白色':3,...&#125;</span></span><br><span class="line">lis = [(<span class="string">'红色'</span>,<span class="number">1</span>),(<span class="string">'白色'</span>,<span class="number">2</span>),(<span class="string">'绿色'</span>,<span class="number">3</span>),(<span class="string">'紫色'</span>,<span class="number">1</span>),(<span class="string">'红色'</span>,<span class="number">1</span>),(<span class="string">'白色'</span>,<span class="number">1</span>),(<span class="string">'红色'</span>,<span class="number">1</span>),(<span class="string">'粉色'</span>,<span class="number">1</span>),]</span><br><span class="line"></span><br><span class="line">dic = defaultdict(list)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> lis:</span><br><span class="line">    dic[i[<span class="number">0</span>]].append(i[<span class="number">1</span>])</span><br><span class="line">dic = dict(dic)</span><br><span class="line">print(dic)</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> dic:</span><br><span class="line">    dic[i] = sum(dic[i])</span><br><span class="line">print(dic)</span><br></pre></td></tr></table></figure><h4 id="默认元素为int，三次登陆失败锁定的简单例子"><a href="#默认元素为int，三次登陆失败锁定的简单例子" class="headerlink" title="默认元素为int，三次登陆失败锁定的简单例子"></a>默认元素为int，三次登陆失败锁定的简单例子</h4><p><code>userinfo</code>文件中的内容如下，作为登陆的用户名密码测试文件：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">whw|<span class="number">123</span></span><br><span class="line">wanghw|<span class="number">123</span></span><br><span class="line">www|<span class="number">123</span></span><br></pre></td></tr></table></figure><p><code>locked</code>文件中存放的是被锁定的用户名。<br>实现一个简单的三次登陆失败锁定的例子：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> defaultdict</span><br><span class="line"></span><br><span class="line"><span class="comment"># 默认value设置成0</span></span><br><span class="line">dic = defaultdict(int)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 这里设置一个flag，while循环的额条件是flag；</span></span><br><span class="line"><span class="comment"># 如果循环内还有一层循环，跳出内层循环若需要跳出整个循环可以将flag设置成False！</span></span><br><span class="line">flag = <span class="literal">True</span></span><br><span class="line"><span class="keyword">while</span> flag:</span><br><span class="line">    username = input(<span class="string">'用户名：'</span>).strip()</span><br><span class="line">    password = input(<span class="string">'密码：'</span>).strip()</span><br><span class="line">    <span class="comment"># 先判断账号有没有被锁定</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'locked'</span>,<span class="string">'r'</span>,encoding=<span class="string">'utf-8'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        <span class="keyword">for</span> line <span class="keyword">in</span> f:</span><br><span class="line">            <span class="keyword">if</span> username == line.strip():</span><br><span class="line">                print(<span class="string">'该账号已被锁定！'</span>)</span><br><span class="line">                flag = <span class="literal">False</span></span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">    <span class="comment"># 默认字典 —— 确保 “每个用户都有三次机会”</span></span><br><span class="line">    <span class="keyword">if</span> dic[username] &lt; <span class="number">2</span>:</span><br><span class="line">        <span class="keyword">with</span> open(<span class="string">'userinfo'</span>,<span class="string">'r'</span>,encoding=<span class="string">'utf-8'</span>)<span class="keyword">as</span> f:</span><br><span class="line">            <span class="keyword">for</span> line <span class="keyword">in</span> f:</span><br><span class="line">                usr,pwd = line.strip().split(<span class="string">'|'</span>)</span><br><span class="line">                <span class="keyword">if</span> usr == username <span class="keyword">and</span> pwd == password:</span><br><span class="line">                    print(<span class="string">'登陆成功！'</span>)</span><br><span class="line">                    <span class="comment"># 如果想登陆成功后退出while大循环，可以将flag设置为False</span></span><br><span class="line">                    flag = <span class="literal">False</span></span><br><span class="line">                    <span class="comment"># 这个break只是退出for循环</span></span><br><span class="line">                    <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                dic[username] += <span class="number">1</span></span><br><span class="line">                print(<span class="string">'登录失败！用户名或密码错误！'</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">with</span> open(<span class="string">'locked'</span>,<span class="string">'a'</span>,encoding=<span class="string">'utf-8'</span>)<span class="keyword">as</span> f:</span><br><span class="line">            f.write(username+<span class="string">'\n'</span>)</span><br><span class="line">        print(<span class="string">'账号被锁定！'</span>)</span><br><span class="line">        <span class="keyword">break</span></span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在实际中使用&lt;code&gt;defaultdict&lt;/code&gt;会十分效率地为我们构建不同格式的数据，通常需要好几层循环构建的数据如果巧用&lt;code&gt;defaultdict&lt;/code&gt;的话使用一层循环便可实现，这一点笔者深有体会！本文就为大家总结一下使用&lt;code&gt;defaultdict&lt;/code&gt;构建数据的一些实例。
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
      <category term="defaultdict" scheme="http://yoursite.com/categories/Python/defaultdict/"/>
    
    
      <category term="Python" scheme="http://yoursite.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>个人Django学习资源</title>
    <link href="http://yoursite.com/2020/02/09/%E4%B8%AA%E4%BA%BADjango%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%BA%90/"/>
    <id>http://yoursite.com/2020/02/09/个人Django学习资源/</id>
    <published>2020-02-09T09:23:34.000Z</published>
    <updated>2020-02-09T10:41:10.601Z</updated>
    
    <content type="html"><![CDATA[<p>最近笔者将自己工作与学习过程中总结的Django的知识点整理了一下，以一篇整合好的博客的方式呈现给大家，希望能帮到正在使用或者学习Django的你，如有好的建议请在对应的文章下面留言。<a id="more"></a><br><a href="https://www.cnblogs.com/paulwhw/p/12287804.html" target="_blank" rel="noopener">个人Django资源</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;最近笔者将自己工作与学习过程中总结的Django的知识点整理了一下，以一篇整合好的博客的方式呈现给大家，希望能帮到正在使用或者学习Django的你，如有好的建议请在对应的文章下面留言。
    
    </summary>
    
    
      <category term="Django" scheme="http://yoursite.com/categories/Django/"/>
    
    
      <category term="Django学习" scheme="http://yoursite.com/tags/Django%E5%AD%A6%E4%B9%A0/"/>
    
  </entry>
  
  <entry>
    <title>手写web框架之框架概览</title>
    <link href="http://yoursite.com/2020/02/05/%E6%89%8B%E5%86%99web%E6%A1%86%E6%9E%B6%E4%B9%8B%E6%A1%86%E6%9E%B6%E6%A6%82%E8%A7%88/"/>
    <id>http://yoursite.com/2020/02/05/手写web框架之框架概览/</id>
    <published>2020-02-05T14:05:12.000Z</published>
    <updated>2020-02-05T15:57:52.774Z</updated>
    
    <content type="html"><![CDATA[<p>本文介绍的是一个基于WSGI自定制的MVC模式的web框架，拥有<code>路由</code>与<code>模板</code>系统，数据使用<code>pymysql</code>模块进行添加与查询校验，实现的核心是Python内置的<code>wsgiref</code>模块。<a id="more"></a></p><h4 id="项目地址"><a href="#项目地址" class="headerlink" title="项目地址"></a>项目地址</h4><p>本项目上传到了github中，大家有兴趣可以自己pull下来玩玩：<a href="https://github.com/Wanghongw/whwFrame" target="_blank" rel="noopener"><a href="https://github.com/Wanghongw/whwFrame" target="_blank" rel="noopener">https://github.com/Wanghongw/whwFrame</a></a><br>关于web框架（或者说web服务）的基础知识点可以参考我之前写过的一篇文章：<a href="https://huoyingwhw.com/2020/01/12/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%B8%A6%E4%BD%A0%E6%89%8B%E6%92%B8web%E6%9C%8D%E5%8A%A1/" target="_blank" rel="noopener">从零开始带你手撸web服务</a></p><h4 id="项目的目录结构如下"><a href="#项目的目录结构如下" class="headerlink" title="项目的目录结构如下"></a>项目的目录结构如下</h4><p><img src="http://whw.pythonav.cn/123.png" alt="123"><br>如果你使用过Django/Falsk/Tornado或者其他Python框架的话，这样的结构相信你已经非常熟悉了。一个MVC模式的web框架一定会包含<code>数据模型Model</code>、<code>模板</code>、<code>逻辑处理接口</code>、<code>路由控制器</code>这几个要素。<br>本项目使用<code>pymysql</code>模块进行数据的插入与查询操作，utils包中的脚本是其具体的实现；<br><code>templates</code>包中包含该项目用到的模板；<br><code>views.py模块</code>中包含多个逻辑处理的接口；<br><code>urls.py</code>是路由控制的模块。<br><code>manage.py</code>模块是整个框架运行起来的核心，在这里我们使用<code>wsgiref</code>模块中的<code>application</code>方法跑起来一个web服务：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">from</span> urls <span class="keyword">import</span> urlpatterns</span><br><span class="line"><span class="keyword">from</span> wsgiref.simple_server <span class="keyword">import</span> make_server</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">#这个文件里面是框架的主体内容</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">application</span><span class="params">(environ, start_response)</span>:</span></span><br><span class="line">    start_response(<span class="string">'200 OK'</span>, [(<span class="string">'Content-Type'</span>, <span class="string">'text/html'</span>)])</span><br><span class="line">    path = environ[<span class="string">'PATH_INFO'</span>]</span><br><span class="line">    <span class="keyword">for</span> url_tuple <span class="keyword">in</span> urlpatterns:</span><br><span class="line">        <span class="keyword">if</span> url_tuple[<span class="number">0</span>] == path:</span><br><span class="line">            data = url_tuple[<span class="number">1</span>](environ) <span class="comment">#environ要传进去，因为处理逻辑里面可能要用</span></span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            data = <span class="string">b'sorry 404!,not found the page'</span></span><br><span class="line">    <span class="keyword">return</span> [data]</span><br><span class="line">        <span class="comment"># 注意，我们如果直接返回中文，没有给浏览器指定编码格式，所以我们需要gbk来编码一下，浏览器才能识别</span></span><br><span class="line">        <span class="comment"># data='登陆成功！'.encode('gbk')</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">httpd = make_server(<span class="string">'127.0.0.1'</span>, <span class="number">8080</span>, application)</span><br><span class="line"></span><br><span class="line">print(<span class="string">'Serving HTTP on port 8080...'</span>)</span><br><span class="line"><span class="comment"># 开始监听HTTP请求:</span></span><br><span class="line">httpd.serve_forever()</span><br></pre></td></tr></table></figure><h4 id="项目运行方式"><a href="#项目运行方式" class="headerlink" title="项目运行方式"></a>项目运行方式</h4><p>1、需要先安装MySQL5.6及以上版本，并在您的MySQl中创建一个名为<code>auth</code>的数据库<br>2、进入<code>utils</code>包中，运行<code>webData.py</code>文件，在之前创建好的auth数据库中写入数据<br>3、运行外面的<code>manage.py</code>文件<br>4、在浏览器中输入<code>127.0.0.1:8080</code>开始您的体验（设置好的用户名为whw，密码为123）</p><h4 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h4><p>其实使用web框架的一个好处就是可以提高开发效率，比如说，这个个框架写好了之后，如果我们将来想添加一些新的功能，比如说有人想在网址里面输入<a href="http://127.0.0.1:8080/timer" target="_blank" rel="noopener">http://127.0.0.1:8080/timer</a> 来查看当前的时间。<br>这样一个需求只需要两步就可以完成：<br>首先在<code>urls.py</code>中写一个url映射关系：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> views <span class="keyword">import</span> timer</span><br><span class="line"></span><br><span class="line"><span class="comment"># url与视图函数的对应关系</span></span><br><span class="line">urlpatterns=[</span><br><span class="line">    <span class="comment"># 其他的路由</span></span><br><span class="line">    xxx</span><br><span class="line">    <span class="comment"># timer的路由映射关系</span></span><br><span class="line">    (<span class="string">'/timer'</span>,timer),</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>然后在<code>views.py加上</code>应对的处理接口就OK了：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#查看当前时间的</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">timer</span><span class="params">(environ)</span>:</span></span><br><span class="line">    data = str(datetime.datetime.now()).encode(<span class="string">'utf-8'</span>)</span><br><span class="line">    <span class="keyword">return</span> data</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文介绍的是一个基于WSGI自定制的MVC模式的web框架，拥有&lt;code&gt;路由&lt;/code&gt;与&lt;code&gt;模板&lt;/code&gt;系统，数据使用&lt;code&gt;pymysql&lt;/code&gt;模块进行添加与查询校验，实现的核心是Python内置的&lt;code&gt;wsgiref&lt;/code&gt;模块。
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="web框架" scheme="http://yoursite.com/tags/web%E6%A1%86%E6%9E%B6/"/>
    
      <category term="web服务" scheme="http://yoursite.com/tags/web%E6%9C%8D%E5%8A%A1/"/>
    
  </entry>
  
  <entry>
    <title>从零开始带你手撸web服务</title>
    <link href="http://yoursite.com/2020/01/12/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%B8%A6%E4%BD%A0%E6%89%8B%E6%92%B8web%E6%9C%8D%E5%8A%A1/"/>
    <id>http://yoursite.com/2020/01/12/从零开始带你手撸web服务/</id>
    <published>2020-01-12T05:15:13.000Z</published>
    <updated>2020-01-12T09:28:35.317Z</updated>
    
    <content type="html"><![CDATA[<p>本文先用一个实例详细讲解了web请求与响应的具体过程并说明了web应用的本质，然后带大家由浅入深地手写了几个不同需求下的web服务端程序，帮助大家从底层理解服务端对web请求的处理过程以及web服务的运行原理，最后介绍了如何使用Python的wsgiref模块实现web请求与响应的处理。<a id="more"></a></p><h3 id="web应用的本质"><a href="#web应用的本质" class="headerlink" title="web应用的本质"></a>web应用的本质</h3><h4 id="客户端-服务器模型"><a href="#客户端-服务器模型" class="headerlink" title="客户端-服务器模型"></a>客户端-服务器模型</h4><p>其实，对于所有的Web应用来说，从本质上讲我们运行web应用程序的地方就是一个socket服务端，而用户的浏览器就是一个socket客户端，我们可以使用Python的<code>socket</code>模块自己实现一个简单的带并发效果的web服务端: </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"><span class="keyword">from</span> threading <span class="keyword">import</span> Thread</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">run_server</span><span class="params">(conn)</span>:</span></span><br><span class="line">    msg = conn.recv(<span class="number">65105</span>).decode(<span class="string">'utf-8'</span>)</span><br><span class="line">    <span class="comment"># 打印浏览器的请求信息</span></span><br><span class="line">    print(msg)</span><br><span class="line">    <span class="comment">#需要先根据协议向浏览器发送响应的内容</span></span><br><span class="line">    conn.sendall(<span class="string">b'HTTP/1.1 200 OK \r\n\r\n'</span>)</span><br><span class="line">    conn.sendall(<span class="string">b'Hello WHW!'</span>)</span><br><span class="line">    conn.close()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    server = socket.socket()</span><br><span class="line">    <span class="comment">#设置端口重复利用</span></span><br><span class="line">    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,<span class="number">1</span>)</span><br><span class="line">    server.bind((<span class="string">'127.0.0.1'</span>,<span class="number">8990</span>))</span><br><span class="line">    server.listen()</span><br><span class="line">    <span class="comment">#建立连接循环</span></span><br><span class="line">    <span class="comment">#由于HTTP是无连接的协议，因此这里必须加连接循环</span></span><br><span class="line">    <span class="keyword">while</span> <span class="number">1</span>:</span><br><span class="line">        conn,_ = server.accept()</span><br><span class="line">        t = Thread(target=run_server,args=(conn,))</span><br><span class="line">        t.start()</span><br></pre></td></tr></table></figure><p>这里，我们可以看到，在接收到浏览器的请求<code>127.0.0.1:8990</code>后，这个服务器端首先给浏览器（客户端）发送了一个<code>200 OK</code>的HTTP响应信息，然后发送了字符串<code>Hello WHW!</code>。<br>我们先运行这个程序然后在浏览器输入：<code>localhost:8990</code>,就可以看到服务器发送出的这个“Hello WHW!”字符串：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello WHW!</span><br></pre></td></tr></table></figure><p>这里解释一下我们给浏览器发送<code>Hello WHW</code>之前：<code>conn.sendall(b&#39;HTTP/1.1 200 OK \r\n\r\n&#39;)</code>的意思：<br>socket是应用层和传输层之间的抽象层，每一层都有协议，所谓的协议协议其实就是固定的消息格式，传输层的消息格式socket已经帮我们封装好了，但是应用层的协议还是需要开发者遵守的，所以在给浏览器发送消息的时候，如果没有按照应用层的消息格式来写，那么你返回给浏览器的信息，浏览器是没法识别的。<br>而我们web开发用到的应用层的协议都是HTTP协议，所以我们按照HTTP协议规定的消息格式来给浏览器返回消息时浏览器是可以识别的！也就是说，“200 OK”那一行的数据是在告诉浏览器，“请接收并输出我下面发来的数据”，而其实这句话也可以合在一起写：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">conn.sendall(<span class="string">b'HTTP/1.1 200 OK \r\n\r\nHello WHW!'</span>)</span><br></pre></td></tr></table></figure><p>其实上面这些还涉及到<code>HTTP协议的版本</code>以及<code>报文格式</code>的问题；还有我们上面的程序其实还打印了浏览器的请求信息：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">GET / HTTP/<span class="number">1.1</span></span><br><span class="line">Host: <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">8990</span></span><br><span class="line">Connection: keep-alive</span><br><span class="line">Pragma: no-cache</span><br><span class="line">Cache-Control: no-cache</span><br><span class="line">Upgrade-Insecure-Requests: <span class="number">1</span></span><br><span class="line">User-Agent: Mozilla/<span class="number">5.0</span> (Macintosh; Intel Mac OS X <span class="number">10</span>_14_6) AppleWebKit/<span class="number">537.36</span> (KHTML, like Gecko) Chrome/<span class="number">74.0</span><span class="number">.3729</span><span class="number">.108</span> Safari/<span class="number">537.36</span></span><br><span class="line">Accept: text/html,application/xhtml+xml,application/xml;q=<span class="number">0.9</span>,image/webp,image/apng,*/*;q=<span class="number">0.8</span>,application/signed-exchange;v=b3</span><br><span class="line">Accept-Encoding: gzip, deflate, br</span><br><span class="line">Accept-Language: zh-CN,zh;q=<span class="number">0.9</span>,en;q=<span class="number">0.8</span></span><br><span class="line">Cookie: csrftoken=OkwsxAS7MZYJB8jz6QZMZI5bGANdSDONk2TUFLbfxHVl0xbKPtvwwm7XGIF7hSM0</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 。。。 其他请求信息</span></span><br></pre></td></tr></table></figure><p>由于本文篇幅有限这里就不一一详细展开说明了，大家有兴趣的话请参考这篇文章：<a href="https://www.cnblogs.com/ranyonsue/p/5984001.html/%22%20HTTP3%22" target="_blank" rel="noopener">关于HTTP协议，一篇就够了</a><br>接着，我们在函数<code>run_server</code>的第二个conn中加上有<code>样式效果</code>的字符串： </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">conn.sendall(bytes(<span class="string">'&lt;h1 style="background-color:red;"&gt;Hello WHW!'</span>,encoding=<span class="string">'utf-8'</span>))</span><br></pre></td></tr></table></figure><p>再看看浏览器的返回结果，上面的样式生效了：<br><img src="http://whw.pythonav.cn/w1.png" alt="111"><br>也就是说，浏览器自动将服务器发送给它的字符串按照一定的规则呈现出对应的效果！</p><h4 id="web应用本质揭示"><a href="#web应用本质揭示" class="headerlink" title="web应用本质揭示"></a>web应用本质揭示</h4><p><strong>（1）当浏览器作为客户端与运行web程序的服务器端进行交互的时候，服务器给浏览器返回的是“字符串”；</strong> <strong>（2）如果这些“字符串”中有浏览器能够识别的格式，那么浏览器会自动的将这些包含在字符串中的格式解析成用户看着舒服的“效果”；</strong><br><strong>（3）而要想在浏览器实现我们想要的效果，我们就必须去学习浏览器都有哪些规则；</strong><br><strong>（4）我们可以将服务器端send的内容先写进一个文件里，然后将这个文件的内容读出来再发给浏览器，而这个文件，大家“约定俗成”的将其命名成后缀为.html的文件，也就是大家熟悉的html文件。</strong><br><code>所以从web开发者的角度讲，我们需要做的事情大致有以下两点：</code><br><strong>（1）按照Html的规则编写Html文件——充当模板</strong><br><strong>（2）从数据库中获取数据，然后替换到Html文件的数据位置——需要学习web框架</strong></p><h3 id="手写web服务"><a href="#手写web服务" class="headerlink" title="手写web服务"></a>手写web服务</h3><p>了解了web应用的本质后，接下来带大家一步步地手写web服务！</p><h4 id="返回html文件的web框架"><a href="#返回html文件的web框架" class="headerlink" title="返回html文件的web框架"></a>返回html文件的web框架</h4><p>上面说到了，如果返回的内容比较多的话，在服务端我们可以将一个html文件返回给浏览器。<br>准备工作：新建一个html文件:index.html，从本地找到一个图片1.jpg与一个图标文件favicon.ico,并且创建一个css文件存放css样式。<br><strong>index.html文件的内容如下：</strong></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"en"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Title<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--引入本地的ico文件--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"=icon"</span> <span class="attr">href</span>=<span class="string">"favicon.ico"</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 引入本地css文件 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">href</span>=<span class="string">"whw.css"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">h1</span> <span class="attr">class</span>=<span class="string">"content"</span>&gt;</span>你好！世界！<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"d1"</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--引入本地的图片--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">"1.jpg"</span> <span class="attr">alt</span>=<span class="string">""</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="actionscript">    alert(<span class="string">'你好世界！'</span>)</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>server端的代码如下：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"><span class="keyword">from</span> threading <span class="keyword">import</span> Thread</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">run_server</span><span class="params">(conn)</span>:</span></span><br><span class="line">    msg = conn.recv(<span class="number">65105</span>).decode(<span class="string">'utf-8'</span>)</span><br><span class="line">    print(msg)</span><br><span class="line">    <span class="comment">#需要先根据协议向浏览器发送响应的内容</span></span><br><span class="line">    conn.sendall(<span class="string">b'HTTP/1.1 200 OK \r\n\r\n'</span>)</span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'index.html'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        conn.sendall(data)</span><br><span class="line">    conn.close()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    server = socket.socket()</span><br><span class="line">    <span class="comment">#设置端口重复利用</span></span><br><span class="line">    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,<span class="number">1</span>)</span><br><span class="line">    server.bind((<span class="string">'127.0.0.1'</span>,<span class="number">8991</span>))</span><br><span class="line">    server.listen()</span><br><span class="line">    <span class="comment">#建立连接循环</span></span><br><span class="line">    <span class="comment">#由于HTTP是无连接的协议，因此这里必须加连接循环</span></span><br><span class="line">    <span class="keyword">while</span> <span class="number">1</span>:</span><br><span class="line">        conn,_ = server.accept()</span><br><span class="line">        t = Thread(target=run_server,args=(conn,))</span><br><span class="line">        t.start()</span><br></pre></td></tr></table></figure><p><strong>whw.css文件中的内容如下：</strong></p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.content</span>&#123;<span class="attribute">color</span>:red;&#125;</span><br><span class="line"><span class="selector-class">.d1</span>&#123;<span class="attribute">width</span>:<span class="number">123px</span>;<span class="attribute">height</span>:<span class="number">123px</span>;&#125;</span><br><span class="line"><span class="selector-tag">img</span>&#123;<span class="attribute">width</span>:<span class="number">100%</span>;<span class="attribute">height</span>:<span class="number">100%</span>;&#125;</span><br></pre></td></tr></table></figure><p>我们在浏览器中输入<code>127.0.0.1:8991</code>看一下结果:<br><code>alert弹窗与h1标签自带的效果都有，但是网页中没有显示图片与ico图标，css定制的样式也没有呈现！</code><br><code>这是因为：弹窗与h1标签的效果我们随着html文件发送给了浏览器，但是图片、图标与css文件还在server本地，并没有发送给浏览器，浏览器渲染不出来！</code><br>其实针对html文件“引用”的静态文件，浏览器会额外发送相应的请求的，看一下Network中的信息大家就明白了：<br><img src="http://whw.pythonav.cn/w2.png" alt="222"><br>也就是说：获取index页面浏览器发送请求<code>127.0.0.1:8991</code>，但是想要获取index页面中的静态文件的话，浏览器会在前面的请求的基础上加上静态文件的名字再向服务器“索取”对应位置的静态文件！<br>其实这些和标签的属性有有关系，css文件是link标签的href属性：<code>&lt;link rel=&quot;stylesheet&quot; href=&quot;test.css&quot;&gt;</code>，js文件是script标签的src属性：<code>&lt;script src=&quot;test.js&quot;&gt;&lt;/script&gt;</code>，图片文件是img标签的src属性：<code>&lt;img src=&quot;meinv.png&quot; alt=&quot;&quot; width=&quot;100&quot; height=&quot;100&quot;&gt;</code> ，图标ico文件是link标签的属性：<code>&lt;link rel=&quot;icon&quot; href=&quot;whw.ico&quot;&gt;</code>，其实这些属性都会在页面加载的时候，单独到自己对应的属性值里面取请求对应的文件数据，而且<strong>我们如果在值里面写的都是自己本地的路径，那么都会来自己的本地路径来找</strong>，<strong>如果我们写的是相对路径，就会到我们自己的网址+文件名称，这个路径来找它需要的文件</strong>，所以我们只需要在服务接收到这些请求后做出对应的响应，就可以将相应的文件发送给浏览器了！</p><h4 id="返回静态文件的web应用"><a href="#返回静态文件的web应用" class="headerlink" title="返回静态文件的web应用"></a>返回静态文件的web应用</h4><p>既然浏览器可以根据<code>link标签的href</code>、<code>img标签的src</code>、<code>script标签的src</code>后面的值向服务器端请求对应的文件，那我们完全可以根据这些请求信息将对应为文件发送给浏览器，这样浏览器拿到我们发给它的文件后进行渲染，就可以展现出对应的效果了！对应的服务端程序我们可以这样来写：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"><span class="keyword">from</span> threading <span class="keyword">import</span> Thread</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">run_server</span><span class="params">(conn)</span>:</span></span><br><span class="line">    msg = conn.recv(<span class="number">65105</span>).decode(<span class="string">'utf-8'</span>)</span><br><span class="line">    <span class="comment"># print(msg)</span></span><br><span class="line">    <span class="comment"># 通过http协议我们知道，浏览器请求的时候，有一个请求内容的路径，</span></span><br><span class="line">    <span class="comment"># 通过对请求信息的分析，这个路径我们在请求的所有请求信息中可以提炼出来，下面的path就是我们提炼出来的路径</span></span><br><span class="line">    path = msg.split(<span class="string">'\r\n\r\n'</span>)[<span class="number">0</span>].split()[<span class="number">1</span>]</span><br><span class="line">    print(<span class="string">'path&gt;&gt;&gt;:'</span>,path)</span><br><span class="line">    <span class="comment">#需要先根据协议向浏览器发送响应的内容</span></span><br><span class="line">    conn.sendall(<span class="string">b'HTTP/1.1 200 OK \r\n\r\n'</span>)</span><br><span class="line">    <span class="comment">#根据不同的路径返回响应的内容</span></span><br><span class="line">    <span class="comment">#返回html文件</span></span><br><span class="line">    <span class="keyword">if</span> path == <span class="string">'/'</span>:</span><br><span class="line">        <span class="keyword">with</span> open(<span class="string">'index.html'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">            data = f.read()</span><br><span class="line">            conn.sendall(data)</span><br><span class="line">            conn.close()</span><br><span class="line">    <span class="comment">#返回ico文件是固定的</span></span><br><span class="line">    <span class="comment">#注意图标必须叫favicon.ico</span></span><br><span class="line">    <span class="keyword">if</span> path == <span class="string">'/favicon.ico'</span>:</span><br><span class="line">        <span class="keyword">with</span> open(<span class="string">'favicon.ico'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">            data = f.read()</span><br><span class="line">            conn.sendall(data)</span><br><span class="line">            conn.close()</span><br><span class="line">    <span class="comment">#返回图片文件</span></span><br><span class="line">    <span class="keyword">if</span> path == <span class="string">'/1.jpg'</span>:</span><br><span class="line">        <span class="keyword">with</span> open(<span class="string">'1.jpg'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">            data = f.read()</span><br><span class="line">            conn.sendall(data)</span><br><span class="line">            conn.close()</span><br><span class="line">    <span class="comment"># 返回css文件</span></span><br><span class="line">    <span class="keyword">if</span> path == <span class="string">'/whw.css'</span>:</span><br><span class="line">        <span class="keyword">with</span> open(<span class="string">'whw.css'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">            data = f.read()</span><br><span class="line">            conn.sendall(data)</span><br><span class="line">            conn.close()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    server = socket.socket()</span><br><span class="line">    <span class="comment">#设置端口重复利用</span></span><br><span class="line">    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,<span class="number">1</span>)</span><br><span class="line">    server.bind((<span class="string">'127.0.0.1'</span>,<span class="number">8991</span>))</span><br><span class="line">    server.listen()</span><br><span class="line">    <span class="comment">#建立连接循环</span></span><br><span class="line">    <span class="comment">#首先浏览器相当于给我们发送了多个请求，一个是请求我们的html文件，</span></span><br><span class="line">    <span class="comment">#而我们的html文件里面的引入文件的标签又给我们这个网站发送了请求静态文件的请求，</span></span><br><span class="line">    <span class="comment">#所以我们要将建立连接的过程循环起来，才能接受多个请求</span></span><br><span class="line">    <span class="keyword">while</span> <span class="number">1</span>:</span><br><span class="line">        conn,_ = server.accept()</span><br><span class="line">        t = Thread(target=run_server,args=(conn,))</span><br><span class="line">        t.start()</span><br></pre></td></tr></table></figure><p>这时我们再在浏览器中输入<code>127.0.0.1:8991</code>就可以展示图、图标与效果了！</p><h5 id="优化一"><a href="#优化一" class="headerlink" title="优化一"></a>优化一</h5><p>我们可以使用<code>函数</code>与<code>映射</code>的方式优化一下上面的代码:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">index</span><span class="params">(conn)</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'index.html'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        conn.sendall(data)</span><br><span class="line">        conn.close()</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">ico</span><span class="params">(conn)</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'favicon.ico'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        conn.sendall(data)</span><br><span class="line">        conn.close()</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">img</span><span class="params">(conn)</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'1.jpg'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        conn.sendall(data)</span><br><span class="line">        conn.close()</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">css</span><span class="params">(conn)</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'whw.css'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        conn.sendall(data)</span><br><span class="line">        conn.close()</span><br><span class="line">        </span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    <span class="comment"># 处理的函数列表</span></span><br><span class="line">    opt_lst = [</span><br><span class="line">        (<span class="string">'/'</span>,index),</span><br><span class="line">        (<span class="string">'/favicon.ico'</span>,ico),</span><br><span class="line">        (<span class="string">'/1.jpg'</span>,img),</span><br><span class="line">        (<span class="string">'/whw.css'</span>,css),</span><br><span class="line">    ]</span><br><span class="line">    <span class="comment"># 初始化socket</span></span><br><span class="line">    server = socket.socket()</span><br><span class="line">    <span class="comment">#设置端口重复利用</span></span><br><span class="line">    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,<span class="number">1</span>)</span><br><span class="line">    server.bind((<span class="string">'127.0.0.1'</span>,<span class="number">8991</span>))</span><br><span class="line">    server.listen()</span><br><span class="line">    <span class="keyword">while</span> <span class="number">1</span>:</span><br><span class="line">        conn,addr = server.accept()</span><br><span class="line">        msg = conn.recv(<span class="number">65105</span>).decode(<span class="string">'utf-8'</span>)</span><br><span class="line">        <span class="comment"># print(msg)</span></span><br><span class="line">        <span class="comment">#获取path</span></span><br><span class="line">        path = msg.split(<span class="string">'\r\n\r\n'</span>)[<span class="number">0</span>].split()[<span class="number">1</span>]</span><br><span class="line">        <span class="comment"># 先发送响应的协议</span></span><br><span class="line">        conn.sendall(<span class="string">b'HTTP/1.1 200 OK \r\n\r\n'</span>)</span><br><span class="line">        <span class="comment">#根据不同的路径调用相应的函数</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> opt_lst:</span><br><span class="line">            <span class="keyword">if</span> path == i[<span class="number">0</span>]:</span><br><span class="line">                i[<span class="number">1</span>](conn)</span><br></pre></td></tr></table></figure><h5 id="优化二"><a href="#优化二" class="headerlink" title="优化二"></a>优化二</h5><p>当然可以专门为每个传文件的函数开多线程提高效率，代码如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"><span class="keyword">from</span> threading <span class="keyword">import</span> Thread</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">index</span><span class="params">(conn)</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'index.html'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        conn.sendall(data)</span><br><span class="line">        conn.close()</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">ico</span><span class="params">(conn)</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'favicon.ico'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        conn.sendall(data)</span><br><span class="line">        conn.close()</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">img</span><span class="params">(conn)</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'1.jpg'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        conn.sendall(data)</span><br><span class="line">        conn.close()</span><br><span class="line">        </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">css</span><span class="params">(conn)</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'whw.css'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        conn.sendall(data)</span><br><span class="line">        conn.close()</span><br><span class="line">        </span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">handle</span><span class="params">(opt_lst,path,conn)</span>:</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> opt_lst:</span><br><span class="line">        <span class="keyword">if</span> path == i[<span class="number">0</span>]:</span><br><span class="line">            <span class="comment"># 开线程传文件</span></span><br><span class="line">            t = Thread(target=i[<span class="number">1</span>],args=(conn,))</span><br><span class="line">            t.start()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    <span class="comment"># 处理的函数列表</span></span><br><span class="line">    opt_lst = [</span><br><span class="line">        (<span class="string">'/'</span>, index),</span><br><span class="line">        (<span class="string">'/favicon.ico'</span>, ico),</span><br><span class="line">        (<span class="string">'/1.jpg'</span>, img),</span><br><span class="line">        (<span class="string">'/whw.css'</span>,css),</span><br><span class="line">    ]</span><br><span class="line">    <span class="comment"># 初始化socket</span></span><br><span class="line">    server = socket.socket()</span><br><span class="line">    <span class="comment">#设置端口重复利用</span></span><br><span class="line">    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,<span class="number">1</span>)</span><br><span class="line">    server.bind((<span class="string">'127.0.0.1'</span>,<span class="number">8991</span>))</span><br><span class="line">    server.listen()</span><br><span class="line">    <span class="comment">#连接循环</span></span><br><span class="line">    <span class="keyword">while</span> <span class="number">1</span>:</span><br><span class="line">        conn,addr = server.accept()</span><br><span class="line">        msg = conn.recv(<span class="number">65105</span>).decode(<span class="string">'utf-8'</span>)</span><br><span class="line">        <span class="comment"># print(msg)</span></span><br><span class="line">        path = msg.split(<span class="string">'\r\n\r\n'</span>)[<span class="number">0</span>].split()[<span class="number">1</span>]</span><br><span class="line">        print(path)</span><br><span class="line">        <span class="comment"># 先法响应协议</span></span><br><span class="line">        conn.sendall(<span class="string">b'HTTP/1.1 200 OK \r\n\r\n'</span>)</span><br><span class="line">        <span class="comment"># 执行handle函数</span></span><br><span class="line">        handle(opt_lst,path,conn)</span><br></pre></td></tr></table></figure><h5 id="优化三"><a href="#优化三" class="headerlink" title="优化三"></a>优化三</h5><p>在<code>优化二</code>中我们实现了<code>多线程上传文件</code>，结合前面实现的<code>多线程接收请求</code>，两者可以结合起来写，<code>但是考虑到线程安全的问题，不建议大家这样来写：</code></p><details><summary>多线程接收请求结合多线程上传文件</summary> <pre><code>import socketfrom threading import Thread<p>def run_server(conn,opt_lst):<br>    msg = conn.recv(65105).decode(‘utf-8’)<br>    # print(msg)<br>    path = msg.split(‘\r\n\r\n’)[0].split()[1]<br>    print(path)<br>    # 先发响应协议<br>    conn.sendall(b’HTTP/1.1 200 OK \r\n\r\n’)<br>    # 开多线程执行文件操作<br>    t = Thread(target=handle,args=(opt_lst,path,conn))<br>    t.start()</p><p>def index(conn):<br>    with open(‘index.html’,’rb’)as f:<br>        data = f.read()<br>        conn.sendall(data)<br>        conn.close()</p><p>def ico(conn):<br>    with open(‘favicon.ico’,’rb’)as f:<br>        data = f.read()<br>        conn.sendall(data)<br>        conn.close()</p><p>def img(conn):<br>    with open(‘1.jpg’,’rb’)as f:<br>        data = f.read()<br>        conn.sendall(data)<br>        conn.close()</p><p>def handle(opt_lst,path,conn):<br>    for i in opt_lst:<br>        if path == i[0]:<br>            i[1](conn)</p><p>if <strong>name</strong> == ‘<strong>main</strong>‘:<br>    # 处理的函数列表<br>    opt_lst = [<br>        (‘/‘, index),<br>        (‘/favicon.ico’, ico),<br>        (‘/1.jpg’, img),<br>    ]<br>    # 初始化socket<br>    server = socket.socket()<br>    #设置端口重复利用<br>    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)<br>    server.bind((‘127.0.0.1’,8765))<br>    server.listen()<br>    #连接循环<br>    while 1:<br>        conn,addr = server.accept()<br>        # 开线程处理连接<br>        t = Thread(target=run_server, args=(conn,opt_lst))<br>        t.start()</p><p></p></code></pre><p></p></details><h4 id="根据不同路径返回独立的页面"><a href="#根据不同路径返回独立的页面" class="headerlink" title="根据不同路径返回独立的页面"></a>根据不同路径返回独立的页面</h4><p>根据上面介绍的<code>根据不同的路径返回相应的文件</code>，我们也可以根据不同的路径返回独立的页面。<br>之前用到的index.html文件我们不考虑外部文件引入的情况，另外再新建一个home.html文件，同样不考虑外部文件引入的情况。<br>服务端的代码如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">index</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'index.html'</span>,<span class="string">'rb'</span>) <span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        <span class="keyword">return</span> data</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">home</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'home.html'</span>,<span class="string">'rb'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        <span class="keyword">return</span> data</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    opt_lst = [</span><br><span class="line">        (<span class="string">'/index'</span>,index),</span><br><span class="line">        (<span class="string">'/home'</span>,home)</span><br><span class="line">    ]</span><br><span class="line">    server = socket.socket()</span><br><span class="line">    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,<span class="number">1</span>)</span><br><span class="line">    server.bind((<span class="string">'127.0.0.1'</span>,<span class="number">8765</span>))</span><br><span class="line">    server.listen()</span><br><span class="line">    <span class="keyword">while</span> <span class="number">1</span>:</span><br><span class="line">        conn,addr = server.accept()</span><br><span class="line">        msg = conn.recv(<span class="number">65105</span>).decode(<span class="string">'utf-8'</span>)</span><br><span class="line">        <span class="comment">#path是从请求体中经过处理得到的</span></span><br><span class="line">        path = msg.split(<span class="string">'\r\n\r\n'</span>)[<span class="number">0</span>].split()[<span class="number">1</span>]</span><br><span class="line">        print(path)</span><br><span class="line">        <span class="comment">#记得发送响应协议！</span></span><br><span class="line">        conn.sendall(<span class="string">b'HTTP/1.1 200 OK \r\n\r\n'</span>)</span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> opt_lst:</span><br><span class="line">            <span class="keyword">if</span> i[<span class="number">0</span>] == path:</span><br><span class="line">                func = i[<span class="number">1</span>]</span><br><span class="line">                content = func()</span><br><span class="line">                <span class="comment">#一定要记的break！不执行else里的语句</span></span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            content = <span class="string">b'404 Not Found'</span></span><br><span class="line"></span><br><span class="line">        conn.sendall(content)</span><br><span class="line">        conn.close()</span><br></pre></td></tr></table></figure><p>这样，我们在浏览器中输入<code>127.0.0.1:8765/index</code>与<code>127.0.0.1:8765/home</code>就可以看到对应的不同网页了。当然～需要其他的静态文件的话再另外做判断就OK了！</p><h4 id="返回“动态”页面的web应用的实现"><a href="#返回“动态”页面的web应用的实现" class="headerlink" title="返回“动态”页面的web应用的实现"></a>返回“动态”页面的web应用的实现</h4><p>前面我们返回的都是<code>静态网页</code>，实际中的网页都是 <code>动态</code>的——其实所谓的<code>动态网页</code>是里面有可变的数据！<br>这里我们用字符串的替换方式来实现这个<code>动态</code>的需求——利用时间戳来模拟动态的数据。<br>代码如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">index</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'index.html'</span>,<span class="string">'r'</span>,encoding=<span class="string">'utf-8'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        str_now = str(time.time())</span><br><span class="line">        <span class="comment"># 替换源文件中的内容</span></span><br><span class="line">        data = data.replace(<span class="string">'123'</span>,str_now)</span><br><span class="line">        <span class="comment"># 注意返回bytes类型的数据</span></span><br><span class="line">        <span class="keyword">return</span> bytes(data,encoding=<span class="string">'utf-8'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">home</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(<span class="string">'home.html'</span>,<span class="string">'r'</span>,encoding=<span class="string">'utf-8'</span>)<span class="keyword">as</span> f:</span><br><span class="line">        data = f.read()</span><br><span class="line">        str_now = str(time.time())</span><br><span class="line">        <span class="comment"># 替换源文件中的内容</span></span><br><span class="line">        data = data.replace(<span class="string">'456'</span>,str_now)</span><br><span class="line">        <span class="comment">#注意返回bytes类型的数据</span></span><br><span class="line">        <span class="keyword">return</span> bytes(data,encoding=<span class="string">'utf-8'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    opt_lst = [</span><br><span class="line">        (<span class="string">'/index'</span>,index),</span><br><span class="line">        (<span class="string">'/home'</span>,home),</span><br><span class="line">    ]</span><br><span class="line"></span><br><span class="line">    server = socket.socket()</span><br><span class="line">    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,<span class="number">1</span>)</span><br><span class="line">    server.bind((<span class="string">'127.0.0.1'</span>,<span class="number">8765</span>))</span><br><span class="line">    server.listen()</span><br><span class="line">    <span class="keyword">while</span> <span class="number">1</span>:</span><br><span class="line">        conn,addr = server.accept()</span><br><span class="line">        msg = conn.recv(<span class="number">65105</span>).decode(<span class="string">'utf-8'</span>)</span><br><span class="line">        <span class="comment"># print(msg)</span></span><br><span class="line">        <span class="comment"># 记得先给浏览器发送响应协议</span></span><br><span class="line">        conn.sendall(<span class="string">b'HTTP/1.1 200 OK \r\n\r\n'</span>)</span><br><span class="line">        <span class="comment"># 通过请求体的数据筛选出path</span></span><br><span class="line">        path = msg.split(<span class="string">'\r\n\r\n'</span>)[<span class="number">0</span>].split()[<span class="number">1</span>]</span><br><span class="line">        print(path)</span><br><span class="line">        <span class="comment"># for...else</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> opt_lst:</span><br><span class="line">            <span class="keyword">if</span> i[<span class="number">0</span>] == path:</span><br><span class="line">                content = i[<span class="number">1</span>]()</span><br><span class="line">                <span class="comment"># 一定要记得break，不让else中的内容执行！</span></span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            content = <span class="string">b'404 Not Found'</span></span><br><span class="line">        conn.sendall(content)</span><br><span class="line">        conn.close()</span><br></pre></td></tr></table></figure><p>我们需要在index页面中加入这个标签：<code>&lt;h2&gt;123&lt;/h2&gt;</code>，在home页面中加入下面的标签：<code>&lt;h2&gt;456&lt;/h2&gt;</code>，这样的话可以用当前的时间戳代替页面中对应的字符串，实现一下<code>动态</code>的效果 - -！<br>最后我们在浏览器中输入<code>127.0.0.1:8765/index</code>与<code>127.0.0.1:8765/home</code>并不断刷新页面就可以看到<code>动态</code>的效果了！</p><h3 id="wsgiref版web服务"><a href="#wsgiref版web服务" class="headerlink" title="wsgiref版web服务"></a>wsgiref版web服务</h3><p>经过上面的讲解与代码实现，大家肯定感受到了，对于一个web应用来说，浏览器作为客户端是已经成型的了，我们需要自己去实现一个web服务端来处理浏览器的请求并返回正确的响应！<br>而接下来要介绍的<code>wsgiref</code>是<code>世界上最nice的框架——Django</code>（个人认为- -!）内置的一个web服务端，它的作用就是将浏览器的请求进行封装——所有的请求信息都封装到了<code>request</code>对象中！使用<code>request.path</code>就能获取到用户这次请求的路径，<code>request.method</code>就能获取到本次用户请求的请求方式(get还是post)等，使用<code>wsgiref模块</code>极大的简化了我们写web应用的工作！<br>对于web后端开发者来说，有了这样的一个wen服务端模块我们不用再去过度的关注浏览器中纷繁复杂的请求信息与厚重的HTTP协议规范了——这样可以将绝大多数的时间放在业务逻辑的处理上！<br>其实<code>wsgiref</code>只是基于<code>WSGI</code>协议下的一个性能比较低的<code>web服务端</code>，实际生产中部署Django项目的时候我们都会选择性能更好的<code>Uwsgi</code>模块，当然个人调试的时候<code>wsgiref</code>是足够了的。<br>关于<code>WSGI</code>协议有兴趣的老铁可以参考这篇文章简单看看：<a href="https://juejin.im/post/5db45073518825645015338d" target="_blank" rel="noopener">Python进阶:何为WSGI协议</a></p><h4 id="利用wsgiref实现一个简单的web服务端程序"><a href="#利用wsgiref实现一个简单的web服务端程序" class="headerlink" title="利用wsgiref实现一个简单的web服务端程序"></a>利用wsgiref实现一个简单的web服务端程序</h4><p>接下来我们使用Python的<code>wsgiref</code>模块实现一个简单的web server程序：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> wsgiref.simple_server <span class="keyword">import</span> make_server</span><br><span class="line"></span><br><span class="line"><span class="comment"># wsgiref本身就是个web框架，提供了一些固定的功能</span></span><br><span class="line"><span class="comment"># 请求和响应信息的封装，不需要我们自己写原生的socket了也不需要我们自己来完成请求信息的提取，用起来很方便</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">application</span><span class="params">(environ,start_response)</span>:</span></span><br><span class="line">    <span class="string">'''</span></span><br><span class="line"><span class="string">    environ:是全部加工好的请求信息，加工成了一个字典，通过字典取值的方式就能拿到很多你想要拿到的信息</span></span><br><span class="line"><span class="string">    start_response: 帮你封装响应信息的（响应行和响应头），注意下面的参数</span></span><br><span class="line"><span class="string">    '''</span></span><br><span class="line">    <span class="comment">#200 OK必须得有~后面的两个键值对可以不加~具体看需求</span></span><br><span class="line">    start_response(<span class="string">'200 OK'</span>,[(<span class="string">'Content-Type'</span>,<span class="string">'text/html'</span>),(<span class="string">'k1'</span>,<span class="string">'v1'</span>)])</span><br><span class="line">    </span><br><span class="line">    print(environ)</span><br><span class="line">    <span class="comment">##输入地址127.0.0.1:8080，这个打印的是'/',输入的是127.0.0.1:8080/index，打印结果是'/index'</span></span><br><span class="line">    print(environ[<span class="string">'PATH_INFO'</span>])</span><br><span class="line">    <span class="comment">#注意最后return的是：列表里面是bytes类型的数据！</span></span><br><span class="line">    <span class="keyword">return</span> [<span class="string">b'&lt;h1&gt;Hello Web&lt;/h1&gt;'</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    http_obj = make_server(<span class="string">'127.0.0.1'</span>,<span class="number">8080</span>,application)</span><br><span class="line"></span><br><span class="line">    print(<span class="string">'Serving HTTP on port 8080...'</span>)</span><br><span class="line">    <span class="comment">#开始监听HTTP请求</span></span><br><span class="line">    http_obj.serve_forever()</span><br></pre></td></tr></table></figure><p>启动程序后，在浏览器中输入<code>http://127.0.0.1:8080</code>就可以看到响应的字符串了：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello Web</span><br></pre></td></tr></table></figure><p>关于wsgiref的深入理解建议大家看这篇文章：<a href="https://cizixs.com/2014/11/09/dive-into-wsgiref/" target="_blank" rel="noopener">wsgiref 源码解析</a></p><h3 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h3><p>由于篇幅有限，本文只带大家介绍一下web程序的流程以及服务端是如何处理浏览器请求并将响应正确的返回给浏览器端的，并且最后引出了Python自带的一个web服务端模块——<code>wsgiref</code>。<br>下一篇文章将利用wsgiref模块实现一个简单的web框架，带大家深入理解web框架的本质，敬请期待！</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文先用一个实例详细讲解了web请求与响应的具体过程并说明了web应用的本质，然后带大家由浅入深地手写了几个不同需求下的web服务端程序，帮助大家从底层理解服务端对web请求的处理过程以及web服务的运行原理，最后介绍了如何使用Python的wsgiref模块实现web请求与响应的处理。
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="web框架" scheme="http://yoursite.com/tags/web%E6%A1%86%E6%9E%B6/"/>
    
      <category term="web服务" scheme="http://yoursite.com/tags/web%E6%9C%8D%E5%8A%A1/"/>
    
  </entry>
  
  <entry>
    <title>optparse模块解析命令行参数的说明及优化</title>
    <link href="http://yoursite.com/2019/12/22/optparse%E6%A8%A1%E5%9D%97%E8%A7%A3%E6%9E%90%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%8F%82%E6%95%B0%E7%9A%84%E8%AF%B4%E6%98%8E%E5%8F%8A%E4%BC%98%E5%8C%96/"/>
    <id>http://yoursite.com/2019/12/22/optparse模块解析命令行参数的说明及优化/</id>
    <published>2019-12-22T14:13:01.000Z</published>
    <updated>2019-12-22T14:41:07.426Z</updated>
    
    <content type="html"><![CDATA[<p>关于“解析命令行参数”的方法我们一般都会用到sys.argv跟optparse模块。<a id="more"></a>关于sys.argv，网上有一篇非常优秀的博客已经介绍的很详细了，大家可以去这里参考：<a href="https://www.cnblogs.com/aland-1415/p/6613449.html" target="_blank" rel="noopener">https://www.cnblogs.com/aland-1415/p/6613449.html</a>。这里为大家介绍一个比sys.argv更强大的optparse模块。<br>这里说一句题外话，点开optparse的源码，第一行注释是这样的：<code>A powerful, extensible, and easy-to-use option parser</code>，是否感受到了作者强有力的……<br>说回正题，当我们要利用server与client进行FTP文件传输的时候，在client端往往需要输入诸如 <code>-s 10.10.10.1 -p 9001</code> 这样的信息，当然我们不能控制用户的输入，如果用户随意的输入错误的命令，比如不写IP地址，只是写<code>-s -p 9001</code>，或者只写<code>-s -p</code>。我们如果还用sys.argv获取参数的话需要做很多麻烦的逻辑判断，这给我们开发程序带来了很大的不便。<br>但是如果我们利用optparse会十分便捷的解决这样的问题。</p><h4 id="optparse介绍"><a href="#optparse介绍" class="headerlink" title="optparse介绍"></a>optparse介绍</h4><h5 id="optparse的用法如下"><a href="#optparse的用法如下" class="headerlink" title="optparse的用法如下"></a>optparse的用法如下</h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> optparse</span><br><span class="line">parser = optparse.OptionParser()</span><br><span class="line">parser.add_option(<span class="string">"-s"</span>, <span class="string">"--server"</span>, dest=<span class="string">"server"</span>, help=<span class="string">"ftp server ip_address"</span>)</span><br><span class="line">parser.add_option(<span class="string">"-P"</span>, <span class="string">"--port"</span>, type=<span class="string">"int"</span>, dest=<span class="string">"port"</span>, help=<span class="string">"ftp server port"</span>)</span><br><span class="line">parser.add_option(<span class="string">"-u"</span>, <span class="string">"--username"</span>, dest=<span class="string">"username"</span>, help=<span class="string">"username info"</span>)</span><br><span class="line">parser.add_option(<span class="string">"-p"</span>, <span class="string">"--password"</span>, dest=<span class="string">"password"</span>, help=<span class="string">"password info"</span>)</span><br></pre></td></tr></table></figure><p>首先import optparse类，然后创建optparse对象parser，再使用add_option()来定义命令行参数，最后使用parse_args()来解析命令行。</p><h5 id="举例说明"><a href="#举例说明" class="headerlink" title="举例说明"></a>举例说明</h5><p>我们先新建一个test.py文件，代码如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> optparse</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Wang_opt</span>:</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="comment">#初始化</span></span><br><span class="line">        parser = optparse.OptionParser()</span><br><span class="line">        parser.add_option(<span class="string">"-s"</span>, <span class="string">"--server"</span>, dest=<span class="string">"server"</span>, help=<span class="string">"ftp server ip_address"</span>)</span><br><span class="line">        parser.add_option(<span class="string">"-P"</span>, <span class="string">"--port"</span>, type=<span class="string">"int"</span>, dest=<span class="string">"port"</span>, help=<span class="string">"ftp server port"</span>)</span><br><span class="line">        parser.add_option(<span class="string">"-u"</span>, <span class="string">"--username"</span>, dest=<span class="string">"username"</span>, help=<span class="string">"username info"</span>)</span><br><span class="line">        parser.add_option(<span class="string">"-p"</span>, <span class="string">"--password"</span>, dest=<span class="string">"password"</span>, help=<span class="string">"password info"</span>)</span><br><span class="line">        <span class="comment">#解析参数</span></span><br><span class="line">        self.options, self.args = parser.parse_args()</span><br><span class="line">        print(self.options,self.args)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    whw_opt = Wang_opt()</span><br></pre></td></tr></table></figure><p>然后在test.py文件的目录下运行python test.py，后面不跟任何参数，看看效果：<br><img src="http://whw.pythonav.cn/1.gif" alt="1"><br>运行的结果一个是”字典”，里面存放的是我们需要的信息，后面有一个“空列表”，我们再试试加上符合条件的参数以及不符合条件的参数：<br><img src="http://whw.pythonav.cn/2.gif" alt="2"><br>这就说明，”字典”中存放的是我们需要的信息，如果用户没有输入默认设置为None；而“列表”’中存放的是“错误”信息。再返回头看看源代码，其实这个”字典”就是上面的self.options变量，“列表”就是self.args。optparse模块解析的结果就是“我们想得到的信息”以及“用户误操作的输入信息”——的确很强大！<br>当然，如果用户不知道他需要输入什么命令，我们可以在后面输入 -h，寻求帮助：<br><img src="http://whw.pythonav.cn/3.gif" alt="3"><br>其实大家可以对照着源代码，这些信息的关键字都是我们在add_option()方法中设置的——嗯，确实是<code>A powerful, extensible, and easy-to-use option parser</code></p><h4 id="重点说明"><a href="#重点说明" class="headerlink" title="重点说明"></a>重点说明</h4><h5 id="关于得到的“字典”与“列表”的说明"><a href="#关于得到的“字典”与“列表”的说明" class="headerlink" title="关于得到的“字典”与“列表”的说明"></a>关于得到的“字典”与“列表”的说明</h5><p>其实我们解析得到的参数<code>self.options</code>与<code>self.args</code>并不是真正意义上的字典与列表，只是“字典与列表的形象”而已！实际上，这两个参数是<code>对象</code>，我们可以进行如下验证，在程序最后打印：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">print(whw_opt.options.server)</span><br></pre></td></tr></table></figure><p><img src="http://whw.pythonav.cn/4.png" alt="4"><br>我们可以看到：用操作符‘.’可以取得server的值 0.0.0.0。<br>但是，我们如果利用字典的key-value的取值方式会报错：<br><img src="http://whw.pythonav.cn/5.png" alt="5"></p><h5 id="一个小优化："><a href="#一个小优化：" class="headerlink" title="一个小优化："></a>一个小优化：</h5><p>当然，再厉害的工具也必然会有缺点。<code>如果要求用户必须有输入的话</code>，我们可以这样来优化一下程序：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> optparse</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Wang_opt</span>:</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="comment">#初始化</span></span><br><span class="line">        parser = optparse.OptionParser()</span><br><span class="line">        parser.add_option(<span class="string">"-s"</span>, <span class="string">"--server"</span>, dest=<span class="string">"server"</span>, help=<span class="string">"ftp server ip_address"</span>)</span><br><span class="line">        parser.add_option(<span class="string">"-P"</span>, <span class="string">"--port"</span>, type=<span class="string">"int"</span>, dest=<span class="string">"port"</span>, help=<span class="string">"ftp server port"</span>)</span><br><span class="line">        parser.add_option(<span class="string">"-u"</span>, <span class="string">"--username"</span>, dest=<span class="string">"username"</span>, help=<span class="string">"username info"</span>)</span><br><span class="line">        parser.add_option(<span class="string">"-p"</span>, <span class="string">"--password"</span>, dest=<span class="string">"password"</span>, help=<span class="string">"password info"</span>)</span><br><span class="line">        <span class="comment">#解析参数</span></span><br><span class="line">        self.options, self.args = parser.parse_args()</span><br><span class="line">        print(self.options,self.args)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">verification</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> self.options.server <span class="keyword">or</span> <span class="keyword">not</span> self.options.port:</span><br><span class="line">            exit(<span class="string">'ERROR!must support server and port parameters!'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    whw_opt = Wang_opt()</span><br><span class="line">    whw_opt.verification()</span><br></pre></td></tr></table></figure><p>效果如下：<br><img src="http://whw.pythonav.cn/6.gif" alt="6"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;关于“解析命令行参数”的方法我们一般都会用到sys.argv跟optparse模块。
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="optparse模块" scheme="http://yoursite.com/tags/optparse%E6%A8%A1%E5%9D%97/"/>
    
      <category term="模块与包" scheme="http://yoursite.com/tags/%E6%A8%A1%E5%9D%97%E4%B8%8E%E5%8C%85/"/>
    
  </entry>
  
  <entry>
    <title>tornado入门系列</title>
    <link href="http://yoursite.com/2019/12/17/tornado%E5%85%A5%E9%97%A8%E7%B3%BB%E5%88%97/"/>
    <id>http://yoursite.com/2019/12/17/tornado入门系列/</id>
    <published>2019-12-17T14:23:03.000Z</published>
    <updated>2020-02-09T10:36:55.195Z</updated>
    
    <content type="html"><![CDATA[<p>本文是自己初学tornado框架的时候做的学习笔记，里面的代码与实践都是亲测有效的，希望能帮到tornado入门的同学。<a id="more"></a><br>所有笔记在我的博客园：<a href="https://www.cnblogs.com/paulwhw/category/1576953.html" target="_blank" rel="noopener">tornado入门系列</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文是自己初学tornado框架的时候做的学习笔记，里面的代码与实践都是亲测有效的，希望能帮到tornado入门的同学。
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="Tornado" scheme="http://yoursite.com/tags/Tornado/"/>
    
  </entry>
  
  <entry>
    <title>基于tornado实现jwt认证</title>
    <link href="http://yoursite.com/2019/12/08/%E5%9F%BA%E4%BA%8Etornado%E5%AE%9E%E7%8E%B0jwt%E8%AE%A4%E8%AF%81md/"/>
    <id>http://yoursite.com/2019/12/08/基于tornado实现jwt认证md/</id>
    <published>2019-12-08T13:12:02.000Z</published>
    <updated>2019-12-08T14:24:26.184Z</updated>
    
    <content type="html"><![CDATA[<p>本文将使用tornado框架实现jwt认证并对其中的关键点进行讲解。<a id="more"></a></p><h3 id="项目地址"><a href="#项目地址" class="headerlink" title="项目地址"></a>项目地址</h3><p><a href="https://github.com/Wanghongw/jwtDemos" target="_blank" rel="noopener"><a href="https://github.com/Wanghongw/jwtDemos" target="_blank" rel="noopener">https://github.com/Wanghongw/jwtDemos</a></a><br><code>实现的思路：</code><strong>用户先进行登陆认证，认证成功后生成token值（实际中可以将token保存在本地或者redis缓存中，本文为了方便直接打印在浏览器上，下一次请求直接复制粘贴到get请求的参数上或者放在Authorization头里），用户在访问<code>需要进行token校验的url时</code>带上之前生成的token的值让服务器进行校验，如果校验成功则返回正常的响应，否则返回错误信息。</strong></p><h3 id="基于tornado实现jwt"><a href="#基于tornado实现jwt" class="headerlink" title="基于tornado实现jwt"></a>基于tornado实现jwt</h3><h4 id="项目目录结构"><a href="#项目目录结构" class="headerlink" title="项目目录结构"></a>项目目录结构</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">├── app.py</span><br><span class="line">├── handlers</span><br><span class="line">│   ├── base_handler.py</span><br><span class="line">│   └── views.py</span><br><span class="line">├── templates</span><br><span class="line">│   └── login.html</span><br><span class="line">└── utils</span><br><span class="line">    └── jwt_auth.py</span><br></pre></td></tr></table></figure><h5 id="脚本说明"><a href="#脚本说明" class="headerlink" title="脚本说明"></a>脚本说明</h5><ul><li><strong>utils中的jwt_auth.py文件是生成与校验token的脚本</strong></li><li><strong>handlers中的base_handler.py中的基类用于预定制与进行token的校验</strong><ul><li>支持用户通过<code>url传参</code>或者使用<code>Authorization请求头</code>两种方式传递token</li><li>特别注意：这两个校验方式同一时间只能使用一个，不可以同时使用<h4 id="过程讲解"><a href="#过程讲解" class="headerlink" title="过程讲解"></a>过程讲解</h4></li></ul></li></ul><p>1.用户先在浏览器中访问根目录：<code>127.0.0.1:9898/v1</code>，根据设计会自动分发到登陆页面<br>2.在登陆页面输入用户名（whw）密码（666），点击提交<br>3.系统自动生成token值，如：<br>   <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IndodyIsImV4cCI6MTU3NTgxNTM1MH0.mFW0ktlxnjqiu2Bf2UOEw9sxxS8eb61xcM0oCw1CQYA"</span></span><br></pre></td></tr></table></figure></p><p>4.如果选择<code>url传参的方式传递token</code>，在浏览器的地址栏输入：<br>   <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://127.0.0.1:9898/v1/order/?token="eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IndodyIsImV4cCI6MTU3NTgxNTM1MH0.mFW0ktlxnjqiu2Bf2UOEw9sxxS8eb61xcM0oCw1CQYA"</span><br></pre></td></tr></table></figure></p><p>这样就可以正常访问订单列表了。<br>5.如果选择<code>Authorization请求头</code>的方法传递token，我这里使用<code>postman</code>工具模拟，注意请求头后面的value的值，形式是 <code>jwt token值</code>：<br>   <img src="http://whw.pythonav.cn/token%E8%AE%A4%E8%AF%812.png" alt="jwt2"></p><h4 id="基类中使用prepare方法实现token校验"><a href="#基类中使用prepare方法实现token校验" class="headerlink" title="基类中使用prepare方法实现token校验"></a>基类中使用prepare方法实现token校验</h4><p>关于token的生成这里就不介绍了，可以在代码中找登陆那部分的逻辑。介绍一下token的校验：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> tornado.web</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> utils <span class="keyword">import</span> jwt_auth</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 进行预设 继承tornado的RequestHandler</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BaseHandler</span><span class="params">(tornado.web.RequestHandler)</span>:</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">prepare</span><span class="params">(self)</span>:</span></span><br><span class="line">        super(BaseHandler, self).prepare()</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">set_default_headers</span><span class="params">(self)</span>:</span></span><br><span class="line">        super().set_default_headers()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 进行token校验，继承上面的BaseHandler</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">TokenHandler</span><span class="params">(BaseHandler)</span>:</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">prepare</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="comment">### 通过url传递token ###</span></span><br><span class="line">        <span class="string">"""</span></span><br><span class="line"><span class="string">        # print(self.request.uri)</span></span><br><span class="line"><span class="string">        # print(self.request.uri.get("token"))# /order?token=666</span></span><br><span class="line"><span class="string">        # print(self.request.arguments)# &#123;'token': [b'666']&#125;</span></span><br><span class="line"><span class="string">        ret = self.request.arguments.get("token","")</span></span><br><span class="line"><span class="string">        # print(ret,type(ret[0])) # b'666' bytes</span></span><br><span class="line"><span class="string">        # 最终结果</span></span><br><span class="line"><span class="string">        # print(ret[0].decode("utf-8"))</span></span><br><span class="line"><span class="string">        token = ret[0].decode("utf-8")</span></span><br><span class="line"><span class="string">        result = (jwt_auth.parse_payload(token))</span></span><br><span class="line"><span class="string">        if not result["status"]:</span></span><br><span class="line"><span class="string">            self.token_passed = False</span></span><br><span class="line"><span class="string">        else:</span></span><br><span class="line"><span class="string">            self.token_passed = True</span></span><br><span class="line"><span class="string">        self.token_msg = json.dumps(result, ensure_ascii=False)</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        <span class="comment">### 通过Authorization请求头传递token ###</span></span><br><span class="line">        head = self.request.headers</span><br><span class="line">        authorization = head.get(<span class="string">"Authorization"</span>,<span class="string">""</span>)</span><br><span class="line">        auth = authorization.split()</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> auth:</span><br><span class="line">            self.token_passed = <span class="literal">False</span></span><br><span class="line">            result = &#123;<span class="string">'error'</span>: <span class="string">'未获取到Authorization请求头'</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;</span><br><span class="line">            self.token_msg = json.dumps(result, ensure_ascii=<span class="literal">False</span>)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        <span class="keyword">if</span> auth[<span class="number">0</span>].lower() != <span class="string">'jwt'</span>:</span><br><span class="line">            self.token_passed = <span class="literal">False</span></span><br><span class="line">            result = &#123;<span class="string">'error'</span>: <span class="string">'Authorization请求头中认证方式错误'</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;</span><br><span class="line">            self.token_msg = json.dumps(result, ensure_ascii=<span class="literal">False</span>)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        <span class="keyword">if</span> len(auth) == <span class="number">1</span>:</span><br><span class="line">            self.token_passed = <span class="literal">False</span></span><br><span class="line">            result = &#123;<span class="string">'error'</span>: <span class="string">"非法Authorization请求头"</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;</span><br><span class="line">            self.token_msg = json.dumps(result, ensure_ascii=<span class="literal">False</span>)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        <span class="keyword">elif</span> len(auth) &gt; <span class="number">2</span>:</span><br><span class="line">            self.token_passed = <span class="literal">False</span></span><br><span class="line">            result = &#123;<span class="string">'error'</span>: <span class="string">"非法Authorization请求头"</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;</span><br><span class="line">            self.token_msg = json.dumps(result, ensure_ascii=<span class="literal">False</span>)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        token = auth[<span class="number">1</span>]</span><br><span class="line">        result = jwt_auth.parse_payload(token)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> result[<span class="string">"status"</span>]:</span><br><span class="line">            self.token_passed = <span class="literal">False</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            self.token_passed = <span class="literal">True</span></span><br><span class="line">        self.token_msg = json.dumps(result, ensure_ascii=<span class="literal">False</span>)</span><br></pre></td></tr></table></figure><p>在写tornado项目时都会先创建基类，在基类中做一下预定制。本demo也一样，我在<code>TokenHandler</code>中的<code>prepare</code>方法里做了token的认证：<br>对于登陆逻辑不需要token认证，继承BaseHandler；<br>对于其他需要token认证的视图，继承TokenHandler。</p><h4 id="views-py中子视图类的写法如下："><a href="#views-py中子视图类的写法如下：" class="headerlink" title="views.py中子视图类的写法如下："></a>views.py中子视图类的写法如下：</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> tornado.web</span><br><span class="line"><span class="keyword">from</span> tornado.escape <span class="keyword">import</span> json_encode</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> utils <span class="keyword">import</span> jwt_auth</span><br><span class="line"><span class="keyword">from</span> . <span class="keyword">import</span> base_handler</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 用于生成token的登陆逻辑继承BaseHandler！</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MainHandler</span><span class="params">(base_handler.BaseHandler)</span>:</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">get</span><span class="params">(self,*args,**kwargs)</span>:</span></span><br><span class="line">        self.render(<span class="string">"login.html"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">post</span><span class="params">(self, *args, **kwargs)</span>:</span></span><br><span class="line">        err_dic = &#123;<span class="string">"status"</span>:<span class="literal">False</span>,<span class="string">"error"</span>:<span class="string">"用户名或密码错误"</span>&#125;</span><br><span class="line"></span><br><span class="line">        name = self.get_argument(<span class="string">"username"</span>)</span><br><span class="line">        pwd = self.get_argument(<span class="string">"password"</span>)</span><br><span class="line">        <span class="keyword">if</span> name == <span class="string">"whw"</span> <span class="keyword">and</span> pwd == <span class="string">"666"</span>:</span><br><span class="line">            <span class="comment"># 用户名密码正确 给用户生成token并返回</span></span><br><span class="line">            token = jwt_auth.create_token(&#123;<span class="string">"username"</span>:<span class="string">"whw"</span>&#125;)</span><br><span class="line">            self.write(json_encode(token))</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            ret = json.dumps(err_dic,ensure_ascii=<span class="literal">False</span>)</span><br><span class="line">            self.write(ret)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 其他需要jwt校验的继承TokenHandler！</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">OrderHandler</span><span class="params">(base_handler.TokenHandler)</span>:</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">get</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> self.token_passed:</span><br><span class="line">            self.write(<span class="string">"&lt;h1&gt;订单列表&lt;/h1&gt;"</span>)</span><br><span class="line">            self.write(self.token_msg)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            self.write(self.token_msg)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">post</span><span class="params">(self, *args, **kwargs)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> self.token_passed:</span><br><span class="line">            self.write(<span class="string">"添加订单"</span>)</span><br><span class="line">            self.write(self.token_msg)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            self.write(self.token_msg)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">put</span><span class="params">(self, *args, **kwargs)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> self.token_passed:</span><br><span class="line">            self.write(<span class="string">"修改订单"</span>)</span><br><span class="line">            self.write(self.token_msg)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            self.write(self.token_msg)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">delete</span><span class="params">(self, *args, **kwargs)</span>:</span></span><br><span class="line">        <span class="keyword">if</span> self.token_passed:</span><br><span class="line">            self.write(<span class="string">"删除订单"</span>)</span><br><span class="line">            self.write(self.token_msg)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            self.write(self.token_msg)</span><br></pre></td></tr></table></figure>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文将使用tornado框架实现jwt认证并对其中的关键点进行讲解。
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="Python" scheme="http://yoursite.com/tags/Python/"/>
    
      <category term="jwt" scheme="http://yoursite.com/tags/jwt/"/>
    
  </entry>
  
  <entry>
    <title>jwt的多种实现方式</title>
    <link href="http://yoursite.com/2019/11/03/jwt%E7%9A%84%E5%A4%9A%E7%A7%8D%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F/"/>
    <id>http://yoursite.com/2019/11/03/jwt的多种实现方式/</id>
    <published>2019-11-03T14:12:02.000Z</published>
    <updated>2019-11-23T09:22:43.409Z</updated>
    
    <content type="html"><![CDATA[<p>本文将使用多种框架实现jwt认证并对每种方式进行讲解。<a id="more"></a></p><h3 id="项目地址"><a href="#项目地址" class="headerlink" title="项目地址"></a>项目地址</h3><p><a href="https://github.com/Wanghongw/jwtDemos" target="_blank" rel="noopener"><a href="https://github.com/Wanghongw/jwtDemos" target="_blank" rel="noopener">https://github.com/Wanghongw/jwtDemos</a></a><br><code>实现的思路：</code><strong>用户先进行登陆认证，认证成功后生成token值（实际中可以将token保存在本地或者缓存中，本文为了方便直接打印在浏览器上，下一次请求直接复制粘贴到get请求的参数上或者Authorization头里），用户在访问<code>需要进行token校验的url时</code>带上之前生成的token的值让服务器进行校验，如果校验成功则返回正常的响应，否则返回错误信息。</strong></p><h3 id="基于django实现jwt"><a href="#基于django实现jwt" class="headerlink" title="基于django实现jwt"></a>基于django实现jwt</h3><h4 id="项目目录结构"><a href="#项目目录结构" class="headerlink" title="项目目录结构"></a>项目目录结构</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">jwtDjangoDemo</span><br><span class="line">├── api</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   ├── admin.py</span><br><span class="line">│   ├── apps.py</span><br><span class="line">│   ├── migrations</span><br><span class="line">│   │   ├── __init__.py</span><br><span class="line">│   ├── models.py</span><br><span class="line">│   ├── tests.py</span><br><span class="line">│   └── views.py</span><br><span class="line">├── db.sqlite3</span><br><span class="line">├── jwtDjangoDemo</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   ├── settings.py</span><br><span class="line">│   ├── urls.py</span><br><span class="line">│   └── wsgi.py</span><br><span class="line">├── manage.py</span><br><span class="line">├── middlewares</span><br><span class="line">│   └── jwt.py</span><br><span class="line">├── templates</span><br><span class="line">│   └── login.html</span><br><span class="line">└── utils</span><br><span class="line">    └── jwt_auth.py</span><br></pre></td></tr></table></figure><h5 id="脚本说明"><a href="#脚本说明" class="headerlink" title="脚本说明"></a>脚本说明</h5><ul><li><strong>utils中的jet_auth.py文件是生成与校验token的脚本</strong></li><li><strong>middlewares中的jwt.py文件是中间件进行校验的脚本</strong><ul><li>支持用户通过<code>url传参</code>或者使用<code>Authorization请求头</code>两种方式传递token</li><li>特别注意：这两个中间件同一时间只能使用一个，不可以同时注册</li></ul></li></ul><h4 id="过程讲解"><a href="#过程讲解" class="headerlink" title="过程讲解"></a>过程讲解</h4><p>1.用户先在浏览器中访问根目录：<code>127.0.0.1:8001</code>，根据设计会自动分发到登陆页面<br>2.在登陆页面输入用户名（whw）密码（666），点击提交<br>3.系统自动生成token值，如：<br>   <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;<span class="string">"status"</span>: true, <span class="string">"token"</span>: <span class="string">"eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IndodyIsImV4cCI6MTU3NDQ5NTU5Nn0.DzS1oMBK967KS3ZUUvvF5RKlf3B9cOPL3eXLNpF5wMY"</span>&#125;</span><br></pre></td></tr></table></figure></p><p>4.如果选择<code>url传参的方式传递token</code>，在浏览器的地址栏输入：<br>   <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://127.0.0.1:8001/order/?token=eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IndodyIsImV4cCI6MTU3NDQ5NTU5Nn0.DzS1oMBK967KS3ZUUvvF5RKlf3B9cOPL3eXLNpF5wMY</span><br></pre></td></tr></table></figure></p><p>这样就可以正常访问订单列表了。<br>5.如果选择<code>Authorization请求头</code>的方法传递token，我这里使用<code>postman</code>工具模拟，注意请求头后面的value的值，形式是 <code>jwt token值</code>：<br>   <img src="http://whw.pythonav.cn/jwt2.png" alt="jwt2"></p><h5 id="关于生成加密token与token的解密的方法详见上一篇博客"><a href="#关于生成加密token与token的解密的方法详见上一篇博客" class="headerlink" title="关于生成加密token与token的解密的方法详见上一篇博客"></a>关于生成加密token与token的解密的方法详见上一篇博客</h5><h5 id="中间件中的校验"><a href="#中间件中的校验" class="headerlink" title="中间件中的校验"></a>中间件中的校验</h5><p>这里给出中间件的写法：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"><span class="keyword">from</span> django.utils.deprecation <span class="keyword">import</span> MiddlewareMixin</span><br><span class="line"><span class="keyword">from</span> django.http <span class="keyword">import</span> JsonResponse</span><br><span class="line"><span class="keyword">from</span> utils.jwt_auth <span class="keyword">import</span> parse_payload</span><br><span class="line"><span class="comment">#   白名单</span></span><br><span class="line">WHITE_LIST = [<span class="string">"/admin/.*"</span>,<span class="string">"/login/"</span>,<span class="string">"/"</span>]</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">JwtQueryParamMiddleware</span><span class="params">(MiddlewareMixin)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    用户需要在url中通过参数进行传输token，例如：</span></span><br><span class="line"><span class="string">    127.0.0.1:8001/order?token=eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IndodyIsImV4cCI6MTU3NDQ5MTQ0MX0.K-k40u6XCvjDiYjhyfIqKsbrN4DCYkytqmdPHmBl9k0</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">process_request</span><span class="params">(self, request)</span>:</span></span><br><span class="line">        print(request.path_info)</span><br><span class="line">        <span class="comment"># 白名单放行</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> WHITE_LIST:</span><br><span class="line">            <span class="keyword">if</span> re.search(request.path_info,i):</span><br><span class="line">                <span class="keyword">return</span></span><br><span class="line">        <span class="comment"># 校验非登录页面的get请求</span></span><br><span class="line">        token = request.GET.get(<span class="string">'token'</span>)</span><br><span class="line">        result = parse_payload(token)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> result[<span class="string">'status'</span>]:</span><br><span class="line">            <span class="keyword">return</span> JsonResponse(result,json_dumps_params=&#123;<span class="string">'ensure_ascii'</span>:<span class="literal">False</span>&#125;)</span><br><span class="line">        request.user_info = result[<span class="string">'data'</span>]</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">JwtAuthorizationMiddleware</span><span class="params">(MiddlewareMixin)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    用户需要通过请求头的方式来进行传输token，例如（注意必须写成下面这种格式）：</span></span><br><span class="line"><span class="string">    Authorization:jwt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">process_request</span><span class="params">(self, request)</span>:</span></span><br><span class="line">        <span class="comment"># 白名单放行</span></span><br><span class="line">        <span class="keyword">for</span> i <span class="keyword">in</span> WHITE_LIST:</span><br><span class="line">            <span class="keyword">if</span> re.search(request.path_info, i):</span><br><span class="line">                <span class="keyword">return</span></span><br><span class="line">        <span class="comment"># 非登录页面需要校验token</span></span><br><span class="line">        authorization = request.META.get(<span class="string">'HTTP_AUTHORIZATION'</span>, <span class="string">''</span>)</span><br><span class="line">        auth = authorization.split()</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> auth:</span><br><span class="line">            <span class="keyword">return</span> JsonResponse(&#123;<span class="string">'error'</span>: <span class="string">'未获取到Authorization请求头'</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;)</span><br><span class="line">        <span class="keyword">if</span> auth[<span class="number">0</span>].lower() != <span class="string">'jwt'</span>:</span><br><span class="line">            <span class="keyword">return</span> JsonResponse(&#123;<span class="string">'error'</span>: <span class="string">'Authorization请求头中认证方式错误'</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;)</span><br><span class="line">        <span class="keyword">if</span> len(auth) == <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">return</span> JsonResponse(&#123;<span class="string">'error'</span>: <span class="string">"非法Authorization请求头"</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;)</span><br><span class="line">        <span class="keyword">elif</span> len(auth) &gt; <span class="number">2</span>:</span><br><span class="line">            <span class="keyword">return</span> JsonResponse(&#123;<span class="string">'error'</span>: <span class="string">"非法Authorization请求头"</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;)</span><br><span class="line">        token = auth[<span class="number">1</span>]</span><br><span class="line">        result = parse_payload(token)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> result[<span class="string">'status'</span>]:</span><br><span class="line">            <span class="keyword">return</span> JsonResponse(result)</span><br><span class="line">        request.user_info = result[<span class="string">'data'</span>]</span><br></pre></td></tr></table></figure><h3 id="基于DRF实现jwt"><a href="#基于DRF实现jwt" class="headerlink" title="基于DRF实现jwt"></a>基于DRF实现jwt</h3><p>此示例在drf的认证组件中对token进行校验，内部编写了两个认证组件来支持用户通过两种方式传递token。</p><ul><li><code>url</code>传参</li><li><code>Authorization</code>请求头<h4 id="过程简介"><a href="#过程简介" class="headerlink" title="过程简介"></a>过程简介</h4></li></ul><p>1.在浏览器中访问：<code>http://127.0.0.1:8002/api/v1/login/</code>进入登陆页面<br>2.输入用户名（whw）密码（666）<br>3.生成token的值：<br>   <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">"status"</span>: true,</span><br><span class="line">    <span class="string">"token"</span>: <span class="string">"eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IndodyIsImV4cCI6MTU3NDQ5ODczNX0.fDs0s9AryVMKIXOKLidsE3MiC4d8Dybtm5384fou83c"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>4.访问order：<code>http://127.0.0.1:8002/api/v1/order/?token=eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IndodyIsImV4cCI6MTU3NDQ5ODczNX0.fDs0s9AryVMKIXOKLidsE3MiC4d8Dybtm5384fou83c</code><br>   此时可以看到正确的响应：<br>   <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="string">"data"</span>: <span class="string">"订单列表"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>5.使用<code>Authorization请求头</code>的方式与上面django的方法一致，这里就不再多余说明了，结果如下：<br>   <img src="http://whw.pythonav.cn/jwt3.png" alt="jwt3"></p><h4 id="相关代码"><a href="#相关代码" class="headerlink" title="相关代码"></a>相关代码</h4><h5 id="认证类的写法"><a href="#认证类的写法" class="headerlink" title="认证类的写法"></a>认证类的写法</h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python</span></span><br><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">from</span> rest_framework.authentication <span class="keyword">import</span> BaseAuthentication</span><br><span class="line"><span class="keyword">from</span> rest_framework <span class="keyword">import</span> exceptions</span><br><span class="line"><span class="keyword">from</span> utils.jwt_auth <span class="keyword">import</span> parse_payload</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">JwtQueryParamAuthentication</span><span class="params">(BaseAuthentication)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    用户需要在url中通过参数进行传输token，例如：</span></span><br><span class="line"><span class="string">    http://127.0.0.1:8002/api/v1/order?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">authenticate</span><span class="params">(self, request)</span>:</span></span><br><span class="line">        token = request.query_params.get(<span class="string">'token'</span>)</span><br><span class="line">        payload = parse_payload(token)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> payload[<span class="string">'status'</span>]:</span><br><span class="line">            <span class="keyword">raise</span> exceptions.AuthenticationFailed(payload)</span><br><span class="line">        <span class="comment"># 如果想要request.user等于用户对象，此处可以根据payload去数据库中获取用户对象。</span></span><br><span class="line">        <span class="keyword">return</span> (payload, token)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">JwtAuthorizationAuthentication</span><span class="params">(BaseAuthentication)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    用户需要通过请求头的方式来进行传输token，例如：</span></span><br><span class="line"><span class="string">    Authorization:jwt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoid3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">authenticate</span><span class="params">(self, request)</span>:</span></span><br><span class="line">        <span class="comment"># 非登录页面需要校验token</span></span><br><span class="line">        authorization = request.META.get(<span class="string">'HTTP_AUTHORIZATION'</span>, <span class="string">''</span>)</span><br><span class="line">        auth = authorization.split()</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> auth:</span><br><span class="line">            <span class="keyword">raise</span> exceptions.AuthenticationFailed(&#123;<span class="string">'error'</span>: <span class="string">'未获取到Authorization请求头'</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;)</span><br><span class="line">        <span class="keyword">if</span> auth[<span class="number">0</span>].lower() != <span class="string">'jwt'</span>:</span><br><span class="line">            <span class="keyword">raise</span> exceptions.AuthenticationFailed(&#123;<span class="string">'error'</span>: <span class="string">'Authorization请求头中认证方式错误'</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;)</span><br><span class="line">        <span class="keyword">if</span> len(auth) == <span class="number">1</span>:</span><br><span class="line">            <span class="keyword">raise</span> exceptions.AuthenticationFailed(&#123;<span class="string">'error'</span>: <span class="string">"非法Authorization请求头"</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;)</span><br><span class="line">        <span class="keyword">elif</span> len(auth) &gt; <span class="number">2</span>:</span><br><span class="line">            <span class="keyword">raise</span> exceptions.AuthenticationFailed(&#123;<span class="string">'error'</span>: <span class="string">"非法Authorization请求头"</span>, <span class="string">'status'</span>: <span class="literal">False</span>&#125;)</span><br><span class="line">        token = auth[<span class="number">1</span>]</span><br><span class="line">        result = parse_payload(token)</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> result[<span class="string">'status'</span>]:</span><br><span class="line">            <span class="keyword">raise</span> exceptions.AuthenticationFailed(result)</span><br><span class="line">        <span class="comment"># 如果想要request.user等于用户对象，此处可以根据payload去数据库中获取用户对象。</span></span><br><span class="line">        <span class="keyword">return</span> (result, token)</span><br></pre></td></tr></table></figure><h5 id="视图类的写法"><a href="#视图类的写法" class="headerlink" title="视图类的写法"></a>视图类的写法</h5><p>直接注册认证类就好了：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> rest_framework.views <span class="keyword">import</span> APIView</span><br><span class="line"><span class="keyword">from</span> rest_framework.response <span class="keyword">import</span> Response</span><br><span class="line"><span class="keyword">from</span> utils.jwt_auth <span class="keyword">import</span> create_token</span><br><span class="line"><span class="keyword">from</span> extensions.auth <span class="keyword">import</span> JwtQueryParamAuthentication, JwtAuthorizationAuthentication</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">LoginView</span><span class="params">(APIView)</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">post</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line">        <span class="string">""" 用户登录 """</span></span><br><span class="line">        user = request.POST.get(<span class="string">'username'</span>)</span><br><span class="line">        pwd = request.POST.get(<span class="string">'password'</span>)</span><br><span class="line">        <span class="comment"># 检测用户和密码是否正确，此处可以在数据进行校验。</span></span><br><span class="line">        <span class="keyword">if</span> user == <span class="string">'wupeiqi'</span> <span class="keyword">and</span> pwd == <span class="string">'123'</span>:</span><br><span class="line">            <span class="comment"># 用户名和密码正确，给用户生成token并返回</span></span><br><span class="line">            token = create_token(&#123;<span class="string">'username'</span>: <span class="string">'wupeiqi'</span>&#125;)</span><br><span class="line">            <span class="keyword">return</span> Response(&#123;<span class="string">'status'</span>: <span class="literal">True</span>, <span class="string">'token'</span>: token&#125;)</span><br><span class="line">        <span class="keyword">return</span> Response(&#123;<span class="string">'status'</span>: <span class="literal">False</span>, <span class="string">'error'</span>: <span class="string">'用户名或密码错误'</span>&#125;)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">OrderView</span><span class="params">(APIView)</span>:</span></span><br><span class="line">    <span class="comment"># 通过url传递token</span></span><br><span class="line">    authentication_classes = [JwtQueryParamAuthentication, ]</span><br><span class="line">    <span class="comment"># 通过Authorization请求头传递token</span></span><br><span class="line">    <span class="comment"># authentication_classes = [JwtAuthorizationAuthentication, ]</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">get</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line">        print(request.user, request.auth)</span><br><span class="line">        <span class="keyword">return</span> Response(&#123;<span class="string">'data'</span>: <span class="string">'订单列表'</span>&#125;)</span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">post</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line">        print(request.user, request.auth)</span><br><span class="line">        <span class="keyword">return</span> Response(&#123;<span class="string">'data'</span>: <span class="string">'添加订单'</span>&#125;)</span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">put</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line">        print(request.user, request.auth)</span><br><span class="line">        <span class="keyword">return</span> Response(&#123;<span class="string">'data'</span>: <span class="string">'修改订单'</span>&#125;)</span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">delete</span><span class="params">(self, request, *args, **kwargs)</span>:</span></span><br><span class="line">        print(request.user, request.auth)</span><br><span class="line">        <span class="keyword">return</span> Response(&#123;<span class="string">'data'</span>: <span class="string">'删除订单'</span>&#125;)</span><br></pre></td></tr></table></figure><h3 id="基于Flask实现jwt"><a href="#基于Flask实现jwt" class="headerlink" title="基于Flask实现jwt"></a>基于Flask实现jwt</h3><p>Flask的实现方式类似于Django中间件的实现方式，使用装饰器<code>before_request</code>，在路由分发之前处理一下请求，生成与校验token的代码与之前一样，Flask项目的代码如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask, request, jsonify, render_template, g</span><br><span class="line"><span class="keyword">from</span> utils.jwt_auth <span class="keyword">import</span> create_token, parse_payload</span><br><span class="line">app = Flask(__name__)</span><br><span class="line"><span class="comment"># 通过url传递token</span></span><br><span class="line"><span class="meta">@app.before_request</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">jwt_query_params_auth</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">if</span> request.path == <span class="string">'/login/'</span>:</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    token = request.args.get(<span class="string">'token'</span>)</span><br><span class="line">    result = parse_payload(token)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> result[<span class="string">'status'</span>]:</span><br><span class="line">        <span class="keyword">return</span> jsonify(result)</span><br><span class="line">    g.user_info = result[<span class="string">'data'</span>]</span><br><span class="line"><span class="comment"># 通过Authorization请求头传递token</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="string">@app.before_request</span></span><br><span class="line"><span class="string">def jwt_authorization_auth():</span></span><br><span class="line"><span class="string">    if request.path == '/login/':</span></span><br><span class="line"><span class="string">        return</span></span><br><span class="line"><span class="string">    authorization = request.headers.get('Authorization', '')</span></span><br><span class="line"><span class="string">    auth = authorization.split()</span></span><br><span class="line"><span class="string">    if not auth:</span></span><br><span class="line"><span class="string">        return jsonify(&#123;'error': '未获取到Authorization请求头', 'status': False&#125;)</span></span><br><span class="line"><span class="string">    if auth[0].lower() != 'jwt':</span></span><br><span class="line"><span class="string">        return jsonify(&#123;'error': 'Authorization请求头中认证方式错误', 'status': False&#125;)</span></span><br><span class="line"><span class="string">    if len(auth) == 1:</span></span><br><span class="line"><span class="string">        return jsonify(&#123;'error': "非法Authorization请求头", 'status': False&#125;)</span></span><br><span class="line"><span class="string">    elif len(auth) &gt; 2:</span></span><br><span class="line"><span class="string">        return jsonify(&#123;'error': "非法Authorization请求头", 'status': False&#125;)</span></span><br><span class="line"><span class="string">    token = auth[1]</span></span><br><span class="line"><span class="string">    result = parse_payload(token)</span></span><br><span class="line"><span class="string">    if not result['status']:</span></span><br><span class="line"><span class="string">        return jsonify(result)</span></span><br><span class="line"><span class="string">    g.user_info = result['data']</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="meta">@app.route('/login/', methods=['GET','POST'])</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">login</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="comment"># GET请求返回登陆页面</span></span><br><span class="line">    <span class="keyword">if</span> request.method == <span class="string">"GET"</span>:</span><br><span class="line">        <span class="keyword">return</span> render_template(<span class="string">"login.html"</span>)</span><br><span class="line">    <span class="comment"># POST 方法 认证</span></span><br><span class="line">    user = request.form.get(<span class="string">'username'</span>)</span><br><span class="line">    pwd = request.form.get(<span class="string">'password'</span>)</span><br><span class="line">    <span class="comment"># 检测用户和密码是否正确，此处可以在数据进行校验。</span></span><br><span class="line">    <span class="keyword">if</span> user == <span class="string">'whw'</span> <span class="keyword">and</span> pwd == <span class="string">'666'</span>:</span><br><span class="line">        <span class="comment"># 用户名和密码正确，给用户生成token并返回</span></span><br><span class="line">        token = create_token(&#123;<span class="string">'username'</span>: <span class="string">'wupeiqi'</span>&#125;)</span><br><span class="line">        <span class="keyword">return</span> jsonify(&#123;<span class="string">'status'</span>: <span class="literal">True</span>, <span class="string">'token'</span>: token&#125;)</span><br><span class="line">    <span class="keyword">return</span> jsonify(&#123;<span class="string">'status'</span>: <span class="literal">False</span>, <span class="string">'error'</span>: <span class="string">'用户名或密码错误'</span>&#125;)</span><br><span class="line"><span class="meta">@app.route('/order/', methods=['GET', "POST", "PUT", "DELETE"])</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">order</span><span class="params">()</span>:</span></span><br><span class="line">    print(g.user_info)</span><br><span class="line">    <span class="keyword">if</span> request.method == <span class="string">'GET'</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"订单列表"</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">"订单信息"</span></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    app.run()</span><br></pre></td></tr></table></figure><h3 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h3><p><a href="https://www.cnblogs.com/wupeiqi/p/11854573.html" target="_blank" rel="noopener"><a href="https://www.cnblogs.com/wupeiqi/p/11854573.html" target="_blank" rel="noopener">https://www.cnblogs.com/wupeiqi/p/11854573.html</a></a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文将使用多种框架实现jwt认证并对每种方式进行讲解。
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="Python" scheme="http://yoursite.com/tags/Python/"/>
    
      <category term="jwt" scheme="http://yoursite.com/tags/jwt/"/>
    
  </entry>
  
  <entry>
    <title>从用户认证到jwt的说明</title>
    <link href="http://yoursite.com/2019/10/30/%E4%BB%8E%E7%94%A8%E6%88%B7%E8%AE%A4%E8%AF%81%E5%88%B0jwt%E7%9A%84%E8%AF%B4%E6%98%8E/"/>
    <id>http://yoursite.com/2019/10/30/从用户认证到jwt的说明/</id>
    <published>2019-10-30T13:22:32.000Z</published>
    <updated>2019-11-23T09:38:17.249Z</updated>
    
    <content type="html"><![CDATA[<p>Json Web Token (JWT) 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的，特别适用于分布式站点的单点登录（SSO）场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息，以便于从资源服务器获取资源，也可以增加一些额外的其它业务逻辑所必须的声明信息，该token也可直接被用于认证，也可被加密。目前，jwt广泛应用在系统的用户认证方面，特别是现在前后端分离项目。<a id="more"></a></p><h3 id="session与token"><a href="#session与token" class="headerlink" title="session与token"></a>session与token</h3><p>关于用户认证肯定避不开session与token这两个技术点，因此在讨论jwt前我们很有必要谈一谈基于session与token的认证模式。</p><h4 id="传统的session认证"><a href="#传统的session认证" class="headerlink" title="传统的session认证"></a>传统的session认证</h4><p>我们知道，http协议本身是一种无状态的协议，而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证，那么下一次请求时，用户还要再一次进行用户认证才行，因为根据http协议，我们并不能知道是哪个用户发出的请求，所以为了让我们的应用能识别是哪个用户发出的请求，我们只能在服务器存储一份用户登录的信息，这份登录信息会在响应时传递给浏览器，告诉其保存为cookie,以便下次请求时发送给我们的应用，这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span>、用户向服务器发送用户名和密码。</span><br><span class="line"><span class="number">2</span>、服务器验证通过后，在当前对话（session）里面保存相关数据，比如用户角色、登录时间等等。</span><br><span class="line"><span class="number">3</span>、服务器向用户返回一个 session_id，写入用户的 Cookie。</span><br><span class="line"><span class="number">4</span>、用户随后的每一次请求，都会通过 Cookie，将 session_id 传回服务器。</span><br><span class="line"><span class="number">5</span>、服务器收到 session_id，找到前期保存的数据，由此得知用户的身份。</span><br></pre></td></tr></table></figure><p>但是这种基于session的认证使应用本身很难得到扩展，随着不同客户端用户的增加，独立的服务器已无法承载更多的用户，而这时候基于session认证应用的问题就会暴露出来.</p><h5 id="基于session认证所显露的问题"><a href="#基于session认证所显露的问题" class="headerlink" title="基于session认证所显露的问题"></a>基于session认证所显露的问题</h5><p><strong>Session</strong>: 每个用户经过我们的应用认证之后，我们的应用都要在服务端做一次记录，以方便用户下次请求的鉴别，通常而言session都是保存在内存中，而随着认证用户的增多，<code>服务端的开销</code>会明显增大。<br><strong>扩展性</strong>: 用户认证之后，服务端做认证记录，如果认证的记录被保存在内存中的话，这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源，这样在<code>分布式</code>的应用上，相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。<br><strong>CSRF</strong>: 因为是基于cookie来进行用户识别的, cookie如果被截获，用户就会很容易受到<code>跨站请求伪造</code>的攻击。</p><h4 id="基于token的校验机制"><a href="#基于token的校验机制" class="headerlink" title="基于token的校验机制"></a>基于token的校验机制</h4><p>基于token的鉴权机制类似于http协议也是无状态的，它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了，这就为应用的扩展提供了便利。<br>基于token的认证流程如下：<br><img src="http://whw.pythonav.cn/jwt1.png" alt="jwt1"><br>其实jwt也是基于这种流程实现的，只不过<code>jwt</code>与<code>传统的token</code>方式有一些区别。</p><h4 id="传统的token方式与jwt的差异"><a href="#传统的token方式与jwt的差异" class="headerlink" title="传统的token方式与jwt的差异"></a>传统的token方式与jwt的差异</h4><h5 id="传统的token方式"><a href="#传统的token方式" class="headerlink" title="传统的token方式"></a>传统的token方式</h5><p><strong>用户登录成功后，服务端生成一个随机token给用户，并且在服务端(数据库或缓存)中保存一份token，以后用户再来访问时需携带token，服务端接收到token之后，去数据库或缓存中进行校验token的是否超时、是否合法。</strong></p><h5 id="jwt方式"><a href="#jwt方式" class="headerlink" title="jwt方式"></a>jwt方式</h5><h5 id="用户登录成功后，服务端通过jwt生成一个随机token给用户（服务端无需保留token），以后用户再来访问时需携带token，服务端接收到token之后，通过jwt对token进行校验是否超时、是否合法。"><a href="#用户登录成功后，服务端通过jwt生成一个随机token给用户（服务端无需保留token），以后用户再来访问时需携带token，服务端接收到token之后，通过jwt对token进行校验是否超时、是否合法。" class="headerlink" title="用户登录成功后，服务端通过jwt生成一个随机token给用户（服务端无需保留token），以后用户再来访问时需携带token，服务端接收到token之后，通过jwt对token进行校验是否超时、是否合法。"></a><strong>用户登录成功后，服务端通过jwt生成一个随机token给用户（服务端无需保留token），以后用户再来访问时需携带token，服务端接收到token之后，通过jwt对token进行校验是否超时、是否合法。</strong></h5><h3 id="jwt的说明"><a href="#jwt的说明" class="headerlink" title="jwt的说明"></a>jwt的说明</h3><h4 id="jwt的原理"><a href="#jwt的原理" class="headerlink" title="jwt的原理"></a>jwt的原理</h4><p>jwt的原理是，服务器认证以后，生成一个 JSON 对象，发回给用户，就像下面这样。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"姓名"</span>: <span class="string">"whw"</span>,</span><br><span class="line">  <span class="string">"角色"</span>: <span class="string">"火影"</span>,</span><br><span class="line">  <span class="string">"到期时间"</span>: <span class="string">"9999年09月09日09点09分"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以后，用户与服务端通信的时候，都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据，服务器在生成这个对象的时候，会加上签名。<br>特别注意：<code>服务器不保存任何 session 数据</code>，也就是说，服务器变成无状态了，这样比较容易实现扩展。</p><h4 id="jwt的数据结构"><a href="#jwt的数据结构" class="headerlink" title="jwt的数据结构"></a>jwt的数据结构</h4><p>实际的jwt大概像这样：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xxx.xxxx.xx</span><br></pre></td></tr></table></figure><p>它是一个很长的字符串，中间用点（<code>.</code>）分隔成三个部分。<br>jwt的三部分依次如下：</p><ul><li>头部（Header）</li><li>负载（Payload）</li><li>签名（Signature）<br>写成一行就是下面这样：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Header.Payload.Signature</span><br></pre></td></tr></table></figure></li></ul><h5 id="这三部分的生成规则如下："><a href="#这三部分的生成规则如下：" class="headerlink" title="这三部分的生成规则如下："></a>这三部分的生成规则如下：</h5><ul><li><p>第一段Header部分，固定包含算法和token类型，对此json进行base64url加密：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"alg"</span>: <span class="string">"HS256"</span>,</span><br><span class="line">  <span class="string">"typ"</span>: <span class="string">"JWT"</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>第二段Payload部分，包含一些数据，对此json进行base64url加密：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"sub"</span>: <span class="string">"1234567890"</span>,</span><br><span class="line">  <span class="string">"name"</span>: <span class="string">"whw"</span>,</span><br><span class="line">  <span class="string">"iat"</span>: <span class="number">1516239022</span></span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>第三段SIGNATURE部分，把前两段的base密文通过<code>.</code>拼接起来，然后对其进行<code>HS256</code>加密，再然后对<code>hs256</code>密文进行base64url加密，最终得到token的第三段：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">base64url(</span><br><span class="line">    HMACSHA256(</span><br><span class="line">      base64UrlEncode(header) + <span class="string">"."</span> + base64UrlEncode(payload),</span><br><span class="line">      your<span class="number">-256</span>-bit-secret (秘钥加盐)</span><br><span class="line">    )</span><br><span class="line">)</span><br></pre></td></tr></table></figure></li></ul><p><code>最后将三段字符串通过</code>.<code>拼接起来就生成了jwt的token。</code><br><strong>注意</strong>：base64url加密是先做base64加密，然后再将 <code>-</code> 替代 <code>+</code> 及 <code>_</code> 替代 <code>/</code> 。</p><h3 id="代码实现token加密"><a href="#代码实现token加密" class="headerlink" title="代码实现token加密"></a>代码实现token加密</h3><ul><li><p>先安装jwt模块</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip3 install pyjwt</span><br></pre></td></tr></table></figure></li><li><p>代码实现</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> datetime</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> jwt</span><br><span class="line"><span class="keyword">from</span> jwt <span class="keyword">import</span> exceptions</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">SALT = <span class="string">"huoyingwhw666"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">create_token</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="comment"># 构造header</span></span><br><span class="line">    headers = &#123;</span><br><span class="line">        <span class="string">"typ"</span>:<span class="string">"jwt"</span>,</span><br><span class="line">        <span class="string">"alg"</span>:<span class="string">"HS256"</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment"># 构造payload</span></span><br><span class="line">    payload = &#123;</span><br><span class="line">        <span class="string">"id"</span>:<span class="number">1</span>,</span><br><span class="line">        <span class="string">"name"</span>:<span class="string">"whw"</span>,</span><br><span class="line">        <span class="string">"exp"</span>:datetime.datetime.now() + datetime.timedelta(hours=<span class="number">1</span>) <span class="comment"># 超时时间</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ret = jwt.encode(headers=headers,payload=payload,key=SALT,algorithm=<span class="string">"HS256"</span>).decode(<span class="string">"utf-8"</span>)</span><br><span class="line">    <span class="keyword">return</span> ret</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    token = create_token()</span><br><span class="line">    print(token)</span><br></pre></td></tr></table></figure></li><li><p>结果如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6IndodyIsImV4cCI6MTU3NDUxNTk5MX0.AkUg9tnDhVX5iSUYXB2arGDlm9VOpKD1LAqpXwL3Ka8</span><br></pre></td></tr></table></figure></li></ul><h3 id="jwt校验token"><a href="#jwt校验token" class="headerlink" title="jwt校验token"></a>jwt校验token</h3><p>一般在认证成功后，把jwt生成的token返回给用户，以后用户再次访问时候需要携带token，此时jwt需要对token进行<code>超时</code>及<code>合法性</code>校验。</p><h4 id="校验过程"><a href="#校验过程" class="headerlink" title="校验过程"></a>校验过程</h4><p>获取token之后，会按照以下步骤进行校验：</p><ul><li>将token分割成 <code>header_segment</code>、<code>payload_segment</code>、<code>crypto_segment</code> 三部分</li><li>对第一部分<code>header_segment</code>进行base64url解密，得到<code>header</code></li><li>对第二部分<code>payload_segment</code>进行base64url解密，得到<code>payload</code></li><li>对第三部分<code>crypto_segment</code>进行base64url解密，得到<code>signature</code></li><li>对第三部分<code>signature</code>部分数据进行合法性校验<ul><li>拼接前两段密文，即：<code>signing_input</code></li><li>从第一段明文中获取加密算法，默认：<code>HS256</code></li><li>使用 算法+盐 对<code>signing_input</code> 进行加密，将得到的结果和<code>signature</code>密文进行比较。<h4 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">import</span> jwt</span><br><span class="line"><span class="keyword">import</span> datetime</span><br><span class="line"><span class="keyword">from</span> jwt <span class="keyword">import</span> exceptions</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">SALT = <span class="string">"huoyingwhw666"</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 根据token获取payload的值</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_payload</span><span class="params">(token)</span>:</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="comment"># 从token中获取payload「不校验合法性」</span></span><br><span class="line">        <span class="string">"""</span></span><br><span class="line"><span class="string">        unverified_payload = jwt.decode(token,None,False)</span></span><br><span class="line"><span class="string">        print(unverified_payload)</span></span><br><span class="line"><span class="string">        """</span></span><br><span class="line">        <span class="comment"># 从token中获取payload「校验合法性」</span></span><br><span class="line">        verified_payload = jwt.decode(token,SALT,<span class="literal">True</span>)</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">"status"</span>:<span class="literal">True</span>,<span class="string">"msg"</span>:<span class="string">"认证成功"</span>,<span class="string">"payload"</span>:verified_payload&#125;</span><br><span class="line">    <span class="keyword">except</span> exceptions.ExpiredSignatureError:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">"status"</span>:<span class="literal">False</span>,<span class="string">"msg"</span>:<span class="string">"token已失效"</span>&#125;</span><br><span class="line">    <span class="keyword">except</span> jwt.DecodeError:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">"status"</span>: <span class="literal">False</span>, <span class="string">"msg"</span>: <span class="string">"token认证失败"</span>&#125;</span><br><span class="line">    <span class="keyword">except</span> jwt.InvalidTokenError:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">"status"</span>: <span class="literal">False</span>, <span class="string">"msg"</span>: <span class="string">"非法的token"</span>&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    <span class="comment"># 使用上一段代码生成的token值</span></span><br><span class="line">    token = <span class="string">"eyJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6IndodyIsImV4cCI6MTU3NDUxNTk5MX0.AkUg9tnDhVX5iSUYXB2arGDlm9VOpKD1LAqpXwL3Ka8"</span></span><br><span class="line">    payload = get_payload(token)</span><br><span class="line">    print(payload)</span><br></pre></td></tr></table></figure></li></ul></li></ul><p>结果如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;<span class="string">'status'</span>: <span class="literal">True</span>, <span class="string">'msg'</span>: <span class="string">'认证成功'</span>, <span class="string">'payload'</span>: &#123;<span class="string">'id'</span>: <span class="number">1</span>, <span class="string">'name'</span>: <span class="string">'whw'</span>, <span class="string">'exp'</span>: <span class="number">1574515991</span>&#125;&#125;</span><br></pre></td></tr></table></figure><h3 id="jwt的具体实现"><a href="#jwt的具体实现" class="headerlink" title="jwt的具体实现"></a>jwt的具体实现</h3><p>jwt实现的实例放在了我的github中：<a href="https://github.com/Wanghongw/jwtDemos" target="_blank" rel="noopener"><a href="https://github.com/Wanghongw/jwtDemos" target="_blank" rel="noopener">https://github.com/Wanghongw/jwtDemos</a></a><br>下一篇文章将详细讲解jwt实现的细节。</p><h4 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h4><p><a href="https://yq.aliyun.com/articles/636281" target="_blank" rel="noopener">单点登陆SSO介绍</a><br><a href="https://www.jianshu.com/p/576dbf44b2ae" target="_blank" rel="noopener"><a href="https://www.jianshu.com/p/576dbf44b2ae" target="_blank" rel="noopener">https://www.jianshu.com/p/576dbf44b2ae</a></a><br><a href="https://www.cnblogs.com/wupeiqi/p/11854573.html" target="_blank" rel="noopener"><a href="https://www.cnblogs.com/wupeiqi/p/11854573.html" target="_blank" rel="noopener">https://www.cnblogs.com/wupeiqi/p/11854573.html</a></a><br><a href="http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html" target="_blank" rel="noopener"><a href="http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html" target="_blank" rel="noopener">http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html</a></a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Json Web Token (JWT) 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的，特别适用于分布式站点的单点登录（SSO）场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息，以便于从资源服务器获取资源，也可以增加一些额外的其它业务逻辑所必须的声明信息，该token也可直接被用于认证，也可被加密。目前，jwt广泛应用在系统的用户认证方面，特别是现在前后端分离项目。
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="Python" scheme="http://yoursite.com/tags/Python/"/>
    
      <category term="jwt" scheme="http://yoursite.com/tags/jwt/"/>
    
  </entry>
  
  <entry>
    <title>Celery介绍及在Django中的使用</title>
    <link href="http://yoursite.com/2019/10/07/Celery%E4%BB%8B%E7%BB%8D%E5%8F%8A%E5%9C%A8Django%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8/"/>
    <id>http://yoursite.com/2019/10/07/Celery介绍及在Django中的使用/</id>
    <published>2019-10-07T11:30:22.000Z</published>
    <updated>2020-01-07T14:52:39.852Z</updated>
    
    <content type="html"><![CDATA[<p>本文详细介绍了Celery的原理、简单使用以及在Django中的应用实例。<a id="more"></a><br>Celery博客我写在了<code>博客园</code>中，因为博客园的文本编辑器相对来说格式更加丰富，Celery这篇博客个人认为还是有很多需要重点注意的细节的，所以重要的地方都用特殊的格式标注了下来，希望对大家学习Celery以及在Django中使用Celery有所帮助！<br><strong>博客链接：<a href="https://www.cnblogs.com/paulwhw/p/11631735.html" target="_blank" rel="noopener">异步任务分发模块Celery</a></strong></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文详细介绍了Celery的原理、简单使用以及在Django中的应用实例。
    
    </summary>
    
    
      <category term="Django" scheme="http://yoursite.com/categories/Django/"/>
    
    
      <category term="Celery" scheme="http://yoursite.com/tags/Celery/"/>
    
      <category term="Django基础" scheme="http://yoursite.com/tags/Django%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>Django中contenttypes组件的使用</title>
    <link href="http://yoursite.com/2019/09/22/Django%E4%B8%ADcontenttypes%E7%BB%84%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8/"/>
    <id>http://yoursite.com/2019/09/22/Django中contenttypes组件的使用/</id>
    <published>2019-09-22T09:21:23.000Z</published>
    <updated>2019-09-22T13:53:04.184Z</updated>
    
    <content type="html"><![CDATA[<p>contenttypes是Django内置的一个非常有用的组件，它对当前项目中所有基于Django驱动的model提供了更高层次的接口，简单点说，它可以追踪项目中所有的应用与model的对应关系并将这些关系记录在ContentType表中，每当我们创建了新的model并执行数据库迁移指令后，ContentType表中就会自动新增一条不同应用和这个应用中的model之间对应关系的记录；更重要的是，我们不仅可以使用contenttypes提供的<code>GenericForeignKey类</code>与<code>GenericReleation类</code>去避免因大量复杂的ORM查询导致的程序效率降低，而且在实际项目中使用contenttypes还会优化项目数据库表结构的设计，避免数据库中产生许多无用的数据。<br>本文用一个简单的业务为例详细为大家介绍一下使用contenttypes组件有何优点、组件的具体用法及其使用场景等等。<a id="more"></a></p><h3 id="Django中的contenttypes组件"><a href="#Django中的contenttypes组件" class="headerlink" title="Django中的contenttypes组件"></a><code>Django中的contenttypes组件</code></h3><p>假如说，公司A的主要业务是培训青少年人工智能与机器人技术，主要分类是<code>基础课程</code>与<code>高级课程</code>，而这两个大的分类中又有其他具体的课程。每个课程根据培训周期的不同培训费用的计算方式也不一样。也许没有做过相关业务的同学还没有get到相关的点，这里我先粗略的画一下表结构，后面再详细阐述。</p><h4 id="初版表结构设计"><a href="#初版表结构设计" class="headerlink" title="初版表结构设计"></a><code>初版表结构设计</code></h4><p>如果不用contenttypes，大致的表结构会这么设计：<br><img src="http://whw.pythonav.cn/contenttypes1.png" alt="image-20190922200038809"><br>由上图我们可以看到基础课程与高级课程下有多个不同的子课程。<br>接下来看一下<code>价格策略</code>这张表，每个小课程（比如Python初级与Python高级）由于培训周期的不同价格是不一样的<code>（这里只是模拟，请勿对号入座）</code>，并且PricePolicy表中<code>journeycourse_id</code>与<code>seniorcourse_id</code>是与两个课程表建立外键的字段——此时聪明的你立刻就发现问题了：初级课程的周期最多周期只有21天，而高级课程的培训周期是按月来计算的！如果这样会导致数据库中产生许多无用的空数据！<br>上面这个问题这是一个很典型的<code>一张表与多张表动态的创建ForeignKey关系</code>的问题，而要解决上面产生无用数据的问题<code>可以选择使用Django中的contenttypes</code>。</p><h4 id="使用contenttypes的表结构"><a href="#使用contenttypes的表结构" class="headerlink" title="使用contenttypes的表结构"></a><code>使用contenttypes的表结构</code></h4><p><img src="http://whw.pythonav.cn/contenttypes2.png" alt="image-20190922202135890"><br><strong>详细说明如下：</strong></p><ol><li>新增了一个ContentType表（settings中默认注册了contenttypes组件，Django启动时会自动生成），这个表中存放了model的名称；</li><li>价格策略表中的content_type_id字段是<code>PricePolicy</code>表与<code>ContentType</code>表之间外键关联的字段；</li><li>价格策略表中的object_id字段对应的是<code>基础课程</code>或者<code>高级课程</code>中具体的子类课程的id；</li><li>在价格策略表中，我们可以根据大课程的分类<code>content_type_id</code>、子课程的id<code>object_id</code>以及培训的周期<code>period</code>确定一条唯一的记录！</li><li>特别注意<code>object_id</code>只是一个普通的字段，它并没有与任何表建立外键关联！<br>这样设计的话就不会出现上面那种空的无效数据了！<h4 id="contenttypes的具体实现"><a href="#contenttypes的具体实现" class="headerlink" title="contenttypes的具体实现"></a><code>contenttypes的具体实现</code></h4>老规矩，先上代码，后面给出具体的说明！<br>项目应用中的models.py文件如下：<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.db <span class="keyword">import</span> models</span><br><span class="line"><span class="keyword">from</span> django.contrib.contenttypes.models <span class="keyword">import</span> ContentType</span><br><span class="line"><span class="keyword">from</span> django.contrib.contenttypes.fields <span class="keyword">import</span> GenericForeignKey,GenericRelation</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">JuniorCourse</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    <span class="string">"""初级课程"""</span></span><br><span class="line">    name = models.CharField(max_length=<span class="number">64</span>,verbose_name=<span class="string">'初级课程名称'</span>)</span><br><span class="line">    course_img = models.CharField(max_length=<span class="number">255</span>, verbose_name=<span class="string">"课程缩略图"</span>)</span><br><span class="line">    brief = models.TextField(verbose_name=<span class="string">"初级课程简介"</span>, )</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 不会在数据库中生成列，只是帮我们添加与查询数据用的</span></span><br><span class="line">    <span class="comment"># 建立GenericRelation的字段如果被删除的话，PricePolicy中对应的记录也会被删掉！</span></span><br><span class="line">    policy_lst = GenericRelation(<span class="string">'PricePolicy'</span>)</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SeniorCourse</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    <span class="string">"""高级课程"""</span></span><br><span class="line">    name = models.CharField(max_length=<span class="number">64</span>, verbose_name=<span class="string">'进阶级课程名称'</span>)</span><br><span class="line">    course_img = models.CharField(max_length=<span class="number">255</span>, verbose_name=<span class="string">"课程缩略图"</span>)</span><br><span class="line">    brief = models.TextField(verbose_name=<span class="string">"进阶级课程简介"</span>, )</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 不会在数据库中生成列，只是帮我们添加与查询数据用的</span></span><br><span class="line">    <span class="comment"># 建立GenericRelation的字段如果被删除的话，PricePolicy中对应的记录也会被删掉！</span></span><br><span class="line">    policy_lst = GenericRelation(<span class="string">'PricePolicy'</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PricePolicy</span><span class="params">(models.Model)</span>:</span></span><br><span class="line">    <span class="string">"""价格策略——课程有效期与价格表"""</span></span><br><span class="line">    <span class="comment"># 与ContentType表做关联 —— 找到大的分类</span></span><br><span class="line">    content_type = models.ForeignKey(to=ContentType,on_delete=models.CASCADE)</span><br><span class="line">    <span class="comment"># 对应"大分类中的课程的id"，用正整数表示 —— 名字必须叫object_id</span></span><br><span class="line">    object_id = models.PositiveIntegerField()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 不会在数据库中生成列，只是帮我们添加与查询查询数据用的</span></span><br><span class="line">    content_object = GenericForeignKey(<span class="string">'content_type'</span>,<span class="string">'object_id'</span>)</span><br><span class="line"></span><br><span class="line">    valid_period_choices = (</span><br><span class="line">        (<span class="number">1</span>,<span class="string">'1天'</span>),</span><br><span class="line">        (<span class="number">3</span>,<span class="string">'3天'</span>),</span><br><span class="line">        (<span class="number">7</span>,<span class="string">'1周'</span>),</span><br><span class="line">        (<span class="number">14</span>,<span class="string">'2周'</span>),</span><br><span class="line">        (<span class="number">21</span>,<span class="string">'3周'</span>),</span><br><span class="line">        (<span class="number">30</span>,<span class="string">'1个月'</span>),</span><br><span class="line">        (<span class="number">60</span>,<span class="string">'2个月'</span>),</span><br><span class="line">        (<span class="number">90</span>,<span class="string">'3个月'</span>),</span><br><span class="line">    )</span><br><span class="line">    valid_period = models.SmallIntegerField(choices=valid_period_choices,verbose_name=<span class="string">'课程周期'</span>)</span><br><span class="line">    price = models.DecimalField(max_digits=<span class="number">8</span>,decimal_places=<span class="number">1</span>)</span><br></pre></td></tr></table></figure></li></ol><p><strong>详细说明如下：</strong><br><strong>1、</strong>大家首先要注意一点：由于项目的settings.py文件中默认注册了<code>django.contrib.contenttypes</code>这个组件，所以我们在<code>执行数据库迁移指令</code>的时候，Django会自动为我们创建一张铭文<code>django_content_type</code>的表，存放项目应用与应用里面model对应关系：<br><img src="http://whw.pythonav.cn/contenttypes3.png" alt="image-20190922203753923"><br>可以看到，最下面那三条记录就是我上面的代码执行<code>数据库迁移指令</code>后生成的——我的应用名叫testapp。<br><strong>2、</strong>引用这张<code>django_content_type</code>表的方式是：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.contrib.contenttypes.models <span class="keyword">import</span> ContentType</span><br></pre></td></tr></table></figure><p>注意不要改这张表！我们从里面查数据就好了——基本上都是查model。<br><strong>3、</strong>我们来看一下<code>PricePolicy</code>这个类：首先它与ContentType表建立了外键关联，<code>content_object</code>这个属性是<code>GenericForeignKey</code>类实例化的对象，参数是上面的<code>content_type</code>与<code>object_id</code>属性——这步操作不会在数据库中创建新的列，是为了方便我们后续添加与查询数据用的。我们在知道了价格策略的条件下可以根据<code>GenericForeignKey</code>对应的属性查到具体的课程。<br><strong>4、</strong><code>GenericRelation</code>写在了具体课程的类下面，我们可以它实例化的属性，拿课程对象去查询这个课程对应的价格策略都有哪些——但是要注意一点：GenericRelation对应字段的对象如果被删除的话，PricePolicy中对应的记录也会被删掉！也就是说，根据上面建立的model关系，如果我删除了一条seniorcourse中的记录，那与这条记录对应的pricepolicy中的记录也会被删除！</p><h4 id="视图函数中的增删改查"><a href="#视图函数中的增删改查" class="headerlink" title="视图函数中的增删改查"></a><code>视图函数中的增删改查</code></h4><p>上面介绍了在<code>models.py</code>中如何用contenttypes，接下来我们来看看如何在视图函数中使用它。</p><h5 id="向价格策略表中添加数据"><a href="#向价格策略表中添加数据" class="headerlink" title="向价格策略表中添加数据"></a><code>向价格策略表中添加数据</code></h5><p>由于上面的<code>models.py</code>文件中我们在<code>PricePolicy</code>类中定义了<code>content_object</code>这个<code>GenericForeignkey</code>类的实例化对象，在创建数据的时候直接指定<code>content_object</code>就可以了：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 与seniorcourse表的关系</span></span><br><span class="line">models.PricePolicy.objects.create(</span><br><span class="line">    valid_period=<span class="number">7</span>,</span><br><span class="line">    price=<span class="number">123.45</span>,</span><br><span class="line">    <span class="comment"># 直接根据SeniorCourse对象添加</span></span><br><span class="line">    content_object = models.SeniorCourse.objects.get(pk=<span class="number">2</span>),</span><br><span class="line">)</span><br><span class="line"><span class="comment"># 与juniorcourse表的关系</span></span><br><span class="line">models.PricePolicy.objects.create(</span><br><span class="line">    valid_period=<span class="number">3</span>,</span><br><span class="line">    price=<span class="number">35.66</span>,</span><br><span class="line">    <span class="comment"># # 直接根据JuniorCourse对象添加</span></span><br><span class="line">    content_object = models.JuniorCourse.objects.get(pk=<span class="number">1</span>),</span><br></pre></td></tr></table></figure><p>其他两张课程表直接添加数据就好了，没有任何的约束。</p><h5 id="根据某个价格策略对象去找与它对应的表的数据"><a href="#根据某个价格策略对象去找与它对应的表的数据" class="headerlink" title="根据某个价格策略对象去找与它对应的表的数据"></a><code>根据某个价格策略对象去找与它对应的表的数据</code></h5><p>这里其实还是利用了<code>GenericForeignKey</code>，自动<code>跨表查询</code>：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.contrib.contenttypes.models <span class="keyword">import</span> ContentType</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在ContentType表中找到具体的表</span></span><br><span class="line">contenttype_obj = ContentType.objects.get(model=<span class="string">'seniorcourse'</span>)</span><br><span class="line"><span class="comment"># 找到价格策略对象</span></span><br><span class="line"><span class="comment"># 注意get是查询唯一对象的方法，所以给出的条件必须保证只查到一个对象！</span></span><br><span class="line">obj=models.PricePolicy.objects.get(content_type=contenttype_obj,object_id=<span class="number">1</span>,valid_period=)</span><br><span class="line">    <span class="comment">### 直接“跨表查询”就好了</span></span><br><span class="line">    print(obj.content_object.name)</span><br></pre></td></tr></table></figure><h5 id="找到某个课程相关的所有价格策略"><a href="#找到某个课程相关的所有价格策略" class="headerlink" title="找到某个课程相关的所有价格策略***"></a><code>找到某个课程相关的所有价格策略***</code></h5><p>这个在实际中用的还是很多的！这里用到了<code>GenericRelation</code>，也就是上面<code>models.py</code>文件中定义在高级课程与初级课程类中的那两个属性。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">obj = models.SeniorCourse.objects.get(pk=<span class="number">1</span>)</span><br><span class="line"><span class="comment"># 注意这里必须用all()方法</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> obj.policy_lst.all():</span><br><span class="line">        print(i.pk,i.valid_period,i.price)</span><br></pre></td></tr></table></figure><h5 id="课程中如果用了GenericRelation类实例化出来的对象作为属性的话，删除课程中的数据时，价格策略中与之相关的数据也会一起被删除！"><a href="#课程中如果用了GenericRelation类实例化出来的对象作为属性的话，删除课程中的数据时，价格策略中与之相关的数据也会一起被删除！" class="headerlink" title="课程中如果用了GenericRelation类实例化出来的对象作为属性的话，删除课程中的数据时，价格策略中与之相关的数据也会一起被删除！"></a><code>课程中如果用了GenericRelation类实例化出来的对象作为属性的话，删除课程中的数据时，价格策略中与之相关的数据也会一起被删除！</code></h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 删除了SeniorCourse中的一个课程后，PricePublic中与之相关的数据都删除了！！！</span></span><br><span class="line">models.SeniorCourse.objects.get(pk=<span class="number">1</span>).delete()</span><br></pre></td></tr></table></figure><h3 id="contenttypes使用小结"><a href="#contenttypes使用小结" class="headerlink" title="contenttypes使用小结"></a><code>contenttypes使用小结</code></h3><p>根据上面的介绍，其实contenttypes的使用大致分为这几步：</p><ol><li>创建ContentType表（Django的settings中默认注册了contenttypes应用，启动时会自动生成）；</li><li>价格策略表与ContentType表建立外键关联关系；</li><li>定义<code>GenericForeignKey</code>属性，根据价格策略表中的记录去查询具体课程的信息或者根据课程对象创建价格策略的记录；</li><li>定义<code>GenericRelation</code>的属性，根据具体的课程对象在价格策略表中查找与这个课程相关的所有的价格策略；</li><li>定义了<code>GenericRelation</code>的属性的话需要注意：删除课程中的一条记录价格策略表中与这个课程相关的所有记录也都会被删除掉！<h3 id="contenttypes的使用场景"><a href="#contenttypes的使用场景" class="headerlink" title="contenttypes的使用场景"></a><code>contenttypes的使用场景</code></h3>在实际中，contenttypes绝大多数都用在<code>一张表与N张表动态的创建ForeignKey关系</code>的场景下。<br>其实实际中contenttypes的使用场景还是很多的，比如<code>评论功能</code>：如果一个网站的文章、视频、图片等都需要加评论的话，我们可以把评论这张表与文章、图片、视频都建立向上面那样的<code>外键关联</code>；再拿上面的例子来说，我们可以为不同分类下的没门课程加入<code>优惠券</code>，优惠券这张表跟<code>价格策略表</code>性质一样，也需要与那两张课程表建立外键关系，此时使用contenttypes会十分高效！<h3 id="更多参考文章"><a href="#更多参考文章" class="headerlink" title="更多参考文章"></a><code>更多参考文章</code></h3><a href="https://docs.djangoproject.com/en/2.1/ref/contrib/contenttypes/" target="_blank" rel="noopener">官方文档</a><br><a href="https://blog.csdn.net/laughing2333/article/details/53014267" target="_blank" rel="noopener">https://blog.csdn.net/laughing2333/article/details/53014267</a><br><a href="https://blog.csdn.net/weixin_42134789/article/details/80690182" target="_blank" rel="noopener">https://blog.csdn.net/weixin_42134789/article/details/80690182</a></li></ol>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;contenttypes是Django内置的一个非常有用的组件，它对当前项目中所有基于Django驱动的model提供了更高层次的接口，简单点说，它可以追踪项目中所有的应用与model的对应关系并将这些关系记录在ContentType表中，每当我们创建了新的model并执行数据库迁移指令后，ContentType表中就会自动新增一条不同应用和这个应用中的model之间对应关系的记录；更重要的是，我们不仅可以使用contenttypes提供的&lt;code&gt;GenericForeignKey类&lt;/code&gt;与&lt;code&gt;GenericReleation类&lt;/code&gt;去避免因大量复杂的ORM查询导致的程序效率降低，而且在实际项目中使用contenttypes还会优化项目数据库表结构的设计，避免数据库中产生许多无用的数据。&lt;br&gt;本文用一个简单的业务为例详细为大家介绍一下使用contenttypes组件有何优点、组件的具体用法及其使用场景等等。
    
    </summary>
    
    
      <category term="Django" scheme="http://yoursite.com/categories/Django/"/>
    
    
      <category term="Django" scheme="http://yoursite.com/tags/Django/"/>
    
      <category term="contenttypes组件" scheme="http://yoursite.com/tags/contenttypes%E7%BB%84%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>在CentOS7系统中部署Django项目</title>
    <link href="http://yoursite.com/2019/09/21/%E5%9C%A8CentOS7%E7%B3%BB%E7%BB%9F%E4%B8%AD%E9%83%A8%E7%BD%B2Django%E9%A1%B9%E7%9B%AE/"/>
    <id>http://yoursite.com/2019/09/21/在CentOS7系统中部署Django项目/</id>
    <published>2019-09-21T04:23:34.000Z</published>
    <updated>2019-09-21T08:54:41.559Z</updated>
    
    <content type="html"><![CDATA[<p>作为一名后端开发的程序员不仅要会写业务代码、解决各种BUG、优化项目性能，也应该知道如何将自己写好的项目部署到服务器中让别人去访问。<br>本文详细讲解了用一台安装了CentOS7.5系统的裸奔Linux机器（虚拟机）从零开始部署django项目的过程。<a id="more"></a></p><h2 id="安装必要的工具"><a href="#安装必要的工具" class="headerlink" title="安装必要的工具"></a>安装必要的工具</h2><h3 id="配置yum源"><a href="#配置yum源" class="headerlink" title="配置yum源"></a>配置yum源</h3><p>至于什么是yum源大家请自行百度，本人用的是<a href="https://yq.aliyun.com/zt/59505" target="_blank" rel="noopener">阿里云的yum源</a>，因此需要在裸机上配置一下：</p><h4 id="进入yum源的目录"><a href="#进入yum源的目录" class="headerlink" title="进入yum源的目录"></a>进入yum源的目录</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cd /etc/yum.repos.d/</span><br></pre></td></tr></table></figure><h4 id="查看yum源文件"><a href="#查看yum源文件" class="headerlink" title="查看yum源文件"></a>查看yum源文件</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ls -l</span><br></pre></td></tr></table></figure><p><img src="http://whw.pythonav.cn/%E9%83%A8%E7%BD%B21.png" alt="img11"> </p><h4 id="配置阿里云yum源"><a href="#配置阿里云yum源" class="headerlink" title="配置阿里云yum源"></a>配置阿里云yum源</h4><p><strong>1.好习惯，备份yum源</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mkdir repo_bak</span><br><span class="line">mv *.repo repo_bak/</span><br></pre></td></tr></table></figure><p><strong>2.下载阿里云repo文件</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget http://mirrors.aliyun.com/repo/Centos<span class="number">-7.</span>repo</span><br></pre></td></tr></table></figure><p><strong>3.清空yum缓存并且生成新的yum缓存</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">yum clean all</span><br><span class="line">yum makecache</span><br></pre></td></tr></table></figure><p><strong>4.安装软件扩展源</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install -y epel-release</span><br></pre></td></tr></table></figure><h3 id="安装pthon3"><a href="#安装pthon3" class="headerlink" title="安装pthon3"></a>安装pthon3</h3><p>推荐利用编译的方式安装Python3。</p><h4 id="安装依赖环境——非常重要"><a href="#安装依赖环境——非常重要" class="headerlink" title="安装依赖环境——非常重要"></a>安装依赖环境——非常重要</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install gcc patch libffi-devel python-devel  zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel -y</span><br></pre></td></tr></table></figure><h4 id="下载解压源码包"><a href="#下载解压源码包" class="headerlink" title="下载解压源码包"></a>下载解压源码包</h4><p>需要注意的是，Linux系统的第三方软件都约定俗成的安装在/opt目录下~因此我们需要把这个包下载到/opt目录下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd /opt</span><br><span class="line">wget https://www.python.org/ftp/python/<span class="number">3.6</span><span class="number">.5</span>/Python<span class="number">-3.6</span><span class="number">.5</span>.tgz</span><br></pre></td></tr></table></figure><p>解压</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tar -zxvf Python<span class="number">-3.6</span><span class="number">.5</span>.tgz</span><br></pre></td></tr></table></figure><h4 id="开始编译安装"><a href="#开始编译安装" class="headerlink" title="开始编译安装"></a>开始编译安装</h4><p><strong>configure</strong><br>进入解压出出来的Python-3.6.5目录，里面有一个绿色的可执行文件configure~<br>这一步一般用来生成 Makefile，为下一步的编译做准备，你可以通过在 configure 后加上参数来对安装进行控制，比如代码:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./configure --prefix=/opt/py365</span><br></pre></td></tr></table></figure><p>上面的意思是将该软件安装在 /opt/py365 下面，执行文件就会安装在 /opt/py365/bin （而不是默认的 /usr/local/bin)，资源文件就会安装在 /usr/share（而不是默认的/usr/local/share）。<br>同时一些软件的配置文件你可以通过指定 –sys-config= 参数进行设定。有一些软件还可以加上 –with、–enable、–without、–disable 等等参数对编译加以控制，你可以通过允许 ./configure –help 察看详细的说明帮助。<br><strong>——我这里没有加后面的参数，直接执行 ./configure，默认安装在了/usr/local/bin中~</strong><br><strong>make</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">这一步就是编译，大多数的源代码包都经过这一步进行编译（当然有些perl或python编写的软件需要调用perl或python来进行编译）。</span><br><span class="line">如果 在 make 过程中出现 error ，你就要记下错误代码（注意不仅仅是最后一行），然后你可以向开发者提交 bugreport（一般在 INSTALL 里有提交地址），或者你的系统少了一些依赖库等，这些需要自己仔细研究错误代码。</span><br><span class="line">make 的作用是开始进行源代码编译，以及一些功能的提供，这些功能由他的 Makefile 设置文件提供相关的功能，比如 make install 一般表示进行安装，make uninstall 是卸载，不加参数就是默认的进行源代码编译。</span><br><span class="line">make 是 [Linux](http://www.icultivator.com/tag/linux) 开发套件里面自动化编译的一个控制程序，他通过借助 Makefile 里面编写的编译规范进行自动化的调用 [gcc](http://www.icultivator.com/tag/gcc) 、ld 以及运行某些需要的程序进行编译的程序。一般情况下，他所使用的 Makefile 控制代码，由 configure 这个设置脚本根据给定的参数和系统环境生成。</span><br></pre></td></tr></table></figure><p><strong>make install</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make install</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">这条命令来进行安装（当然有些软件需要先运行 make check 或 make test来进行一些测试），这一步一般需要你有 root 权限（因为要向系统写入文件）</span><br></pre></td></tr></table></figure><h3 id="安装虚拟环境包virtualenv及管理工具virtualenvwrapper"><a href="#安装虚拟环境包virtualenv及管理工具virtualenvwrapper" class="headerlink" title="安装虚拟环境包virtualenv及管理工具virtualenvwrapper"></a>安装虚拟环境包virtualenv及管理工具virtualenvwrapper</h3><p>我自己之前总结过两篇相关的博客（原创哦）：<br><a href="https://www.cnblogs.com/paulwhw/articles/11103162.html" target="_blank" rel="noopener">linux下虚拟环境模块virtualenv及管理工具virtualenvwrapper的使用</a><br><a href="https://www.cnblogs.com/paulwhw/articles/11105376.html" target="_blank" rel="noopener">windows与mac下virtualenv与Pycharm的结合使用</a></p><h3 id="安装nginx"><a href="#安装nginx" class="headerlink" title="安装nginx"></a>安装nginx</h3><h4 id="提示"><a href="#提示" class="headerlink" title="提示"></a>提示</h4><p>nginx的安装也推荐编译安装！<br>由于本文用的是一个裸机，如果大家之前用yum安装了nginx，请卸载yum安装的nginx！！！</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum remove nginx -y</span><br></pre></td></tr></table></figure><h4 id="解决软件包依赖——特别重要"><a href="#解决软件包依赖——特别重要" class="headerlink" title="解决软件包依赖——特别重要"></a>解决软件包依赖——特别重要</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install gcc patch libffi-devel python-devel  zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel openssl openssl-devel -y</span><br></pre></td></tr></table></figure><h4 id="下载nginx的源码包并解压"><a href="#下载nginx的源码包并解压" class="headerlink" title="下载nginx的源码包并解压"></a>下载nginx的源码包并解压</h4><p>我这里用的是淘宝的<a href="http://tengine.taobao.org/" target="_blank" rel="noopener">Tengine</a>——在原始nginx上扩展了许多功能<del>很强大</del><br>需要注意的是，Linux系统的第三方软件都约定俗成的安装在/opt目录下~因此我们需要把这个包下载到/opt目录下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd /opt</span><br><span class="line">wget http://tengine.taobao.org/download/tengine<span class="number">-2.3</span><span class="number">.1</span>.tar.gz</span><br></pre></td></tr></table></figure><p>解压tengine包：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tar -zxvf tengine<span class="number">-2.3</span><span class="number">.1</span>.tar.gz</span><br></pre></td></tr></table></figure><h4 id="安装tengine"><a href="#安装tengine" class="headerlink" title="安装tengine"></a>安装tengine</h4><p>进入源码目录：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cd tengine<span class="number">-2.3</span><span class="number">.1</span>/</span><br></pre></td></tr></table></figure><p>里面有一个绿色的可执行文件configure<br>这里我们指定将tengine安装在/opt/tngx231这个目录下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./configure --prefix=/opt/tngx231/</span><br></pre></td></tr></table></figure><p>然后进行编译安装：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make &amp;&amp; make install</span><br></pre></td></tr></table></figure><p>结束后我们可以看到新生成了一个/opt/tngx321目录<br>安装好的nginx放在了 /opt/tngx231/sbin/ 这个目录下了~</p><h4 id="修改环境变量让nginx命令生效"><a href="#修改环境变量让nginx命令生效" class="headerlink" title="修改环境变量让nginx命令生效"></a>修改环境变量让nginx命令生效</h4><p>安装完成后需要每次用/opt/tngx231/sbin/ 这个目录加上nginx才能操作，很烦躁，我们可以通过修改环境变量，以后只需要执行nginx就可以启用nginx的操作了~<br><strong>&lt;1&gt;首先确认下当前的环境变量有哪些目录：</strong>         </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">echo $PATH   </span><br><span class="line"><span class="comment">#/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin</span></span><br></pre></td></tr></table></figure><p><strong>&lt;2&gt; 编辑/etc/profile文件</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/profile</span><br></pre></td></tr></table></figure><p><strong>&lt;3&gt;在文件的最后一行输入 ：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PATH=<span class="string">"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/opt/tngx231/sbin"</span></span><br></pre></td></tr></table></figure><p>并 :wq 保存退出~~<br><strong>&lt;4&gt;最后一定要记得source一下 /etc/profile 文件！</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">source /etc/profile</span><br></pre></td></tr></table></figure><h4 id="nginx的启停命令"><a href="#nginx的启停命令" class="headerlink" title="nginx的启停命令"></a>nginx的启停命令</h4><p>在没有修改环境变量之前，我们需要进入nginx的安装目录（本文是在 /opt/tngx231/sbin/ ）这样执行：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">./nginx <span class="comment">#启动</span></span><br><span class="line">./nginx -s stop <span class="comment">#关闭</span></span><br><span class="line">./nginx -s reload <span class="comment">#重新加载</span></span><br></pre></td></tr></table></figure><p>修改了环境变量后可以直接这样执行：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">nginx <span class="comment">#启动</span></span><br><span class="line">nginx -t <span class="comment">#查看nginx状态</span></span><br><span class="line">nginx -s stop <span class="comment">#关闭</span></span><br><span class="line">nginx -s reload <span class="comment">#重新加载</span></span><br></pre></td></tr></table></figure><p>测试nginx的服务<br>安装完成后别忘了检测一下nginx的服务</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">netstat -tunlp |grep <span class="number">80</span></span><br><span class="line">curl -I <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br></pre></td></tr></table></figure><p>结果如下：<br><img src="http://whw.pythonav.cn/%E9%83%A8%E7%BD%B22.png" alt="img"><br><strong>如果访问不了，检查selinux，iptables</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#关防火墙</span></span><br><span class="line">systemctl disable firewalled</span><br><span class="line">setenforce <span class="number">0</span></span><br></pre></td></tr></table></figure><h2 id="项目部署过程"><a href="#项目部署过程" class="headerlink" title="项目部署过程"></a>项目部署过程</h2><h3 id="创建虚拟环境并在虚拟环境中安装必要的包"><a href="#创建虚拟环境并在虚拟环境中安装必要的包" class="headerlink" title="创建虚拟环境并在虚拟环境中安装必要的包"></a>创建虚拟环境并在虚拟环境中安装必要的包</h3><p>利用virtualenvwrapper工具创建虚拟环境</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkvirtualenv whw_dj1</span><br></pre></td></tr></table></figure><p>创建完虚拟环境后直接会进入这个虚拟环境。<br>在这个环境中安装项目需要的包，安装过程略（直接用pip安装即可），结果如下：<br><img src="http://whw.pythonav.cn/%E9%83%A8%E7%BD%B23.png" alt="img"><br>这里需要记录一下uwsgi的执行路径以及虚拟环境的目录，后面要用：<br><strong>uwsgi的执行路径</strong><br><strong>特别注意：如果在虚拟环境中运行的话，后面执行uwsgi命令需要用绝对路径的时候一定是虚拟环境中的这个绝对路径！</strong><br><strong>(补充说明一点：如果虚拟环境中没有安装uwsgi模块的话会显示外部环境中的位置，因此一定要记得在虚拟环境中安装uwsgi模块!)</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(whw_di1) [root@bogon opt]<span class="comment"># which uwsgi</span></span><br><span class="line">/root/Envs/whw_di1/bin/uwsgi</span><br></pre></td></tr></table></figure><p><strong>虚拟环境的目录</strong><br><strong>特别注意：如果在虚拟环境中运行的话，后面uwsgi的配置文件一定要写当前虚拟环境下的uwsgi的绝对路径！</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">(whw_di1) [root@bogon opt]<span class="comment"># cdvirtualenv </span></span><br><span class="line">(whw_di1) [root@bogon whw_di1]<span class="comment"># pwd</span></span><br><span class="line">/root/Envs/whw_di1</span><br></pre></td></tr></table></figure><h3 id="项目文件settings与静态文件相关的配置"><a href="#项目文件settings与静态文件相关的配置" class="headerlink" title="项目文件settings与静态文件相关的配置"></a>项目文件settings与静态文件相关的配置</h3><p>将项目传到服务器的/opt目录~进入项目的二级目录，配置一下里面的settings文件，这里只写与部署相关的配置</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 线上部署一定要记得把DEBUG改成False</span></span><br><span class="line">DEBUG = <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 94这个ip是我的云服务器的Ip，一般情况下是这样配置</span></span><br><span class="line"><span class="comment"># ALLOWED_HOSTS = ['localhost','94.191.41.167','0.0.0.0:8000','127.0.0.1' ]</span></span><br><span class="line"><span class="comment"># 自己测试的话直接allow所有的ip就好了</span></span><br><span class="line">ALLOWED_HOSTS = [<span class="string">'*'</span> ]</span><br><span class="line"></span><br><span class="line"><span class="comment"># sqllit数据库的配置</span></span><br><span class="line">DATABASES = &#123;</span><br><span class="line">     <span class="string">'default'</span>: &#123;</span><br><span class="line">         <span class="string">'ENGINE'</span>: <span class="string">'django.db.backends.sqlite3'</span>,</span><br><span class="line">         <span class="string">'NAME'</span>: os.path.join(BASE_DIR, <span class="string">'db.sqlite3'</span>),</span><br><span class="line">     &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 静态文件配置</span></span><br><span class="line"><span class="comment"># 第一个参数是将静态文件copy到服务器的目录的位置</span></span><br><span class="line">STATIC_ROOT=<span class="string">'/opt/whw_static/'</span></span><br><span class="line">STATIC_URL = <span class="string">'/static/'</span></span><br><span class="line">STATICFILES_DIRS = [</span><br><span class="line">    os.path.join(BASE_DIR,<span class="string">'staticfiles'</span>),</span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">## mysql的配置——本文用的是sqllit，因此这个配置注释掉</span></span><br><span class="line"><span class="comment">#DATABASES = &#123;</span></span><br><span class="line"><span class="comment">#    'default': &#123;</span></span><br><span class="line"><span class="comment">#        'ENGINE': 'django.db.backends.mysql',#引擎，选mysql</span></span><br><span class="line"><span class="comment">#        'NAME':'whw1',#要连接的数据库，连接前需要创建好</span></span><br><span class="line"><span class="comment">#        'USER':'root',#连接数据库的用户名</span></span><br><span class="line"><span class="comment">#        'PASSWORD':'123',#连接数据库的密码</span></span><br><span class="line"><span class="comment">#        'HOST':'127.0.0.1',#连接主机，默认本本机</span></span><br><span class="line"><span class="comment">#        'PORT':3306,#端口 默认3306</span></span><br><span class="line"><span class="comment">#        #Django中设置数据库的严格模式</span></span><br><span class="line"><span class="comment">#       'OPTIONS':&#123;</span></span><br><span class="line"><span class="comment">#           'init_command':"set sql_mode='STRICT_TRANS_TABLES' ",</span></span><br><span class="line"><span class="comment">#           &#125;</span></span><br><span class="line"><span class="comment">#       &#125;</span></span><br><span class="line"><span class="comment">#   &#125;</span></span><br></pre></td></tr></table></figure><p>由于uwsgi不能处理项目的静态文件，我们需要用nginx来处理，因此需要<strong>将项目的静态文件copy到STATIC_ROOT参数对应的位置：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3  /opt/whw/manage.py  collectstatic</span><br></pre></td></tr></table></figure><p>whw是我的项目的第一层目录，就是我的项目的名字。</p><h3 id="uwsgi的配置"><a href="#uwsgi的配置" class="headerlink" title="uwsgi的配置"></a>uwsgi的配置</h3><p>提醒一点：需要提前在虚拟环境中安装好uwsgi包~~</p><h4 id="uwsgi-ini文件的配置"><a href="#uwsgi-ini文件的配置" class="headerlink" title="uwsgi.ini文件的配置"></a>uwsgi.ini文件的配置</h4><p>配置uwsgi之前需要新建一个uwsgi.ini文件。<br>由于每个项目对应一个uwsgi.ini文件，这里建议大家将这个文件创建在自己项目的第一层目录中：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd /opt/whw/</span><br><span class="line">touch uwsgi.ini</span><br></pre></td></tr></table></figure><p>然后编辑这个文件</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim uwsgi.ini</span><br></pre></td></tr></table></figure><p>文件中的配置如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">[uwsgi]</span><br><span class="line"><span class="comment"># Django-related settings</span></span><br><span class="line"><span class="comment"># the base directory (full path)</span></span><br><span class="line"><span class="comment">#项目的绝对路径,定位到项目的第一层</span></span><br><span class="line">chdir = /opt/whw</span><br><span class="line"><span class="comment"># Django's wsgi file</span></span><br><span class="line"><span class="comment"># 找到项目第二层的wsgi文件</span></span><br><span class="line">module = whw.wsgi</span><br><span class="line"><span class="comment"># the virtualenv (full path)</span></span><br><span class="line"><span class="comment"># 找到虚拟环境的绝对路径~注意最后不要加/!!!</span></span><br><span class="line">home = /root/Envs/whw_di1</span><br><span class="line"><span class="comment"># process-related settings</span></span><br><span class="line"><span class="comment"># master</span></span><br><span class="line"><span class="comment"># 主进程</span></span><br><span class="line">master = true</span><br><span class="line"><span class="comment"># maximum number of worker processes</span></span><br><span class="line"><span class="comment"># 开启uwsgi的多进程数,根据cpu核数来定义</span></span><br><span class="line">processes = <span class="number">16</span></span><br><span class="line"><span class="comment"># the socket (use the full path to be safe</span></span><br><span class="line"><span class="comment"># 基于socket链接运行项目,只有与nginx结合的时候,才使用socket形式</span></span><br><span class="line">socket = <span class="number">0.0</span><span class="number">.0</span><span class="number">.0</span>:<span class="number">8000</span></span><br><span class="line"><span class="comment"># 当你没用nginx,调试项目的时候,使用http形式 </span></span><br><span class="line"><span class="comment">#http =  0.0.0.0:8000</span></span><br><span class="line"><span class="comment"># ... with appropriate permissions - may be needed</span></span><br><span class="line"><span class="comment"># chmod-socket    = 664</span></span><br><span class="line"><span class="comment"># clear environment on exit</span></span><br><span class="line">vacuum = true</span><br><span class="line"><span class="comment"># 记录pid与日志的文件</span></span><br><span class="line">pidfile=uwsgi.pid</span><br><span class="line">daemonize=uwsgi.log</span><br></pre></td></tr></table></figure><h4 id="启动uwsgi"><a href="#启动uwsgi" class="headerlink" title="启动uwsgi"></a>启动uwsgi</h4><p>进入项目的第一层目录，这里有写好的uwsgi.ini文件，执行这个文件：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uwsgi --ini  uwsgi.ini</span><br></pre></td></tr></table></figure><p>特别注意，这里用的都是相对路径~真正的执行命令其实是这样的：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/root/Envs/whw_di1/bin/uwsgi --ini /opt/whw/uwsgi.ini</span><br></pre></td></tr></table></figure><p>——这样的执行结果是在前台运行的，我们的终端会夯住，如果关掉这个终端uwsgi的进行也会关掉，需要我们再打开一个终端进行下一步的操作~<br><strong>——让uwsgi后台执行的话~只需要在命令里面加上-d参数就好了：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">uwsgi -d --ini uwsgi.ini</span><br></pre></td></tr></table></figure><h3 id="nginx的配置"><a href="#nginx的配置" class="headerlink" title="nginx的配置"></a>nginx的配置</h3><p>nginx的配置文件是 /opt/tngx231/conf/nginx.conf 。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 这里需要说明一下：</span></span><br><span class="line">如果安装的是nginx不是tengine，且没有指定安装目录，那么它的配置文件默认是 </span><br><span class="line">/etc/nginx目录中的nginx.conf文件</span><br></pre></td></tr></table></figure><p>编辑这个文件</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /opt/tngx231/conf/nginx.conf</span><br></pre></td></tr></table></figure><p>我们只配置第一个server里面的参数：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">server &#123;</span><br><span class="line">        listen       <span class="number">80</span>;</span><br><span class="line">        server_name  localhost;</span><br><span class="line">        location / &#123;</span><br><span class="line">            <span class="comment"># 支持uwsgi的配置</span></span><br><span class="line">            include uwsgi_params;</span><br><span class="line">            <span class="comment"># ip与端口是uwsgi服务器的ip与端口~本例二者在一个机器里因此用环回地址</span></span><br><span class="line">            uwsgi_pass <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">8000</span>;</span><br><span class="line">            <span class="comment">#root   html;</span></span><br><span class="line">            <span class="comment">#index  index.html index.htm;</span></span><br><span class="line">        &#125;</span><br><span class="line">    </span><br><span class="line">        <span class="comment"># 存放静态文件的配置</span></span><br><span class="line">        location /static&#123;</span><br><span class="line">            alias /opt/whw_static;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">　　　　<span class="comment"># xxxxxxxxxxxxxxx</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>nginx配置完后启动nginx服务：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nginx</span><br></pre></td></tr></table></figure><p>如果中途修改了nginx.conf的参数的话，保存完后需要重启nginx服务，记得在重启前-t一下查看nginx的状态：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">(whw_di1) [root@bogon whw]<span class="comment"># nginx -t</span></span><br><span class="line">nginx: the configuration file /opt/tngx231//conf/nginx.conf syntax <span class="keyword">is</span> ok</span><br><span class="line">nginx: configuration file /opt/tngx231//conf/nginx.conf test <span class="keyword">is</span> successful</span><br><span class="line">(whw_di1) [root@bogon whw]<span class="comment"># </span></span><br><span class="line">(whw_di1) [root@bogon whw]<span class="comment"># nginx -s reload</span></span><br></pre></td></tr></table></figure><p>至此，项目部署的配置就这么多了。</p><h2 id="请求流程简介"><a href="#请求流程简介" class="headerlink" title="请求流程简介"></a>请求流程简介</h2><p>（1）用户发起请求<br>（2）访问IP(或者访问域名) 请求走到nginx这一层代理<br>（3）nginx直接转发(uwsgi_pass)给后端django的地址<br>（4）django处理完毕<br>（5）响应给nginx<br>（6）nginx返回结果给浏览器界面</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;作为一名后端开发的程序员不仅要会写业务代码、解决各种BUG、优化项目性能，也应该知道如何将自己写好的项目部署到服务器中让别人去访问。&lt;br&gt;本文详细讲解了用一台安装了CentOS7.5系统的裸奔Linux机器（虚拟机）从零开始部署django项目的过程。
    
    </summary>
    
    
      <category term="Linux" scheme="http://yoursite.com/categories/Linux/"/>
    
    
      <category term="Django" scheme="http://yoursite.com/tags/Django/"/>
    
      <category term="Linux" scheme="http://yoursite.com/tags/Linux/"/>
    
      <category term="项目部署" scheme="http://yoursite.com/tags/%E9%A1%B9%E7%9B%AE%E9%83%A8%E7%BD%B2/"/>
    
  </entry>
  
  <entry>
    <title>Django中的信号signal</title>
    <link href="http://yoursite.com/2019/09/21/Django%E4%B8%AD%E7%9A%84%E4%BF%A1%E5%8F%B7signal/"/>
    <id>http://yoursite.com/2019/09/21/Django中的信号signal/</id>
    <published>2019-09-21T03:11:29.000Z</published>
    <updated>2019-09-21T08:32:19.224Z</updated>
    
    <content type="html"><![CDATA[<p>在真实的企业生产环境中，我们会遇到各种各样的需求，比如在全局对客户端请求进行过滤，将不满足某些条件的客户端请求过滤掉，这时我们可以利用Django的中间件来实现该需求；或者，我们希望每次model的save()方法被调用后，都要写一条日志到日志文件中，而此时我们可以通过Django提供的内置信号post_save来实现，本文介绍一下Django的高级功能——信号。<br>Django的“信号分发器”允许解耦的应用在框架的其它地方发生操作时会被通知到。 简单来说，信号允许特定的sender通知一组receiver某些操作已经发生。 这在多处代码和同一事件有关联的情况下很有用。<a id="more"></a></p><h3 id="信号的概念"><a href="#信号的概念" class="headerlink" title="信号的概念"></a><code>信号的概念</code></h3><p>Django2.1官文对signal的解释如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Django includes a <span class="string">"signal dispatcher"</span> which helps allow decoupled applications get notified when actions occur elsewhere <span class="keyword">in</span> the framework.</span><br><span class="line">In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place. </span><br><span class="line">They are especially useful when many pieces of code may be interested <span class="keyword">in</span> the same events.</span><br></pre></td></tr></table></figure><p>翻译过来就是：Django框架内部包含了一个信号调度器，它的作用是可以将框架内部发生的任何操作都通知到功能独立的应用程序，当然，我们也可以缩小发送者和接收者的范围，即指定具体的发送者和接受者，假设我们的程序中有多个业务逻辑都在等待某一个事件发生之后再继续执行后面的代码，那么此时，信号是非常有用的。</p><h3 id="Django内置的信号"><a href="#Django内置的信号" class="headerlink" title="Django内置的信号"></a><code>Django内置的信号</code></h3><p><strong>Django内置的信号如下:</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">Model signals</span><br><span class="line">    pre_init                    <span class="comment"># django的model执行其构造方法前，自动触发</span></span><br><span class="line">    post_init                   <span class="comment"># django的model执行其构造方法后，自动触发</span></span><br><span class="line">    pre_save                    <span class="comment"># django的model对象保存前，自动触发</span></span><br><span class="line">    post_save                   <span class="comment"># django的model对象保存后，自动触发</span></span><br><span class="line">    pre_delete                  <span class="comment"># django的model对象删除前，自动触发</span></span><br><span class="line">    post_delete                 <span class="comment"># django的model对象删除后，自动触发</span></span><br><span class="line">    m2m_changed                 <span class="comment"># django的model中使用m2m字段操作第三张表（add,remove,clear）前后，自动触发</span></span><br><span class="line">    class_prepared              <span class="comment"># 程序启动时，检测已注册的app中modal类，对于每一个类，自动触发</span></span><br><span class="line">Management signals</span><br><span class="line">    pre_migrate                 <span class="comment"># 执行migrate命令前，自动触发</span></span><br><span class="line">    post_migrate                <span class="comment"># 执行migrate命令后，自动触发</span></span><br><span class="line">Request/response signals</span><br><span class="line">    request_started             <span class="comment"># 请求到来前，自动触发</span></span><br><span class="line">    request_finished            <span class="comment"># 请求结束后，自动触发</span></span><br><span class="line">    got_request_exception       <span class="comment"># 请求异常后，自动触发</span></span><br><span class="line">Test signals</span><br><span class="line">    setting_changed             <span class="comment"># 使用test测试修改配置文件时，自动触发</span></span><br><span class="line">    template_rendered           <span class="comment"># 使用test测试渲染模板时，自动触发</span></span><br><span class="line">Database Wrappers</span><br><span class="line">    connection_created          <span class="comment"># 创建数据库连接时，自动触发</span></span><br></pre></td></tr></table></figure><h3 id="Django内置信号的使用"><a href="#Django内置信号的使用" class="headerlink" title="Django内置信号的使用"></a><code>Django内置信号的使用</code></h3><h4 id="注册内置信号"><a href="#注册内置信号" class="headerlink" title="注册内置信号"></a><code>注册内置信号</code></h4><p>对于Django内置的信号，仅需注册指定信号，当程序执行相应操作时，自动触发注册函数。注册信号，写入与project同名的文件夹下的<em>init</em>.py文件中，也是换数据库引擎的地方。<br><img src="http://whw.pythonav.cn/signal1.png" alt="2223"><br>如上图，项目名为signalTest，我们在与项目同名的包的<strong>init</strong>.py文件中注册Django内置的信号，然后使用就可以了。</p><h4 id="监听信号的2种方式"><a href="#监听信号的2种方式" class="headerlink" title="监听信号的2种方式"></a><code>监听信号的2种方式</code></h4><h5 id="直接监听"><a href="#直接监听" class="headerlink" title="直接监听"></a><code>直接监听</code></h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">my_callback</span><span class="params">(sender, **kwargs)</span>:</span></span><br><span class="line">    print(<span class="string">"Request finished!"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> django.core.signals <span class="keyword">import</span> request_finished</span><br><span class="line"></span><br><span class="line">request_finished.connect(my_callback)</span><br></pre></td></tr></table></figure><h5 id="使用装饰器监听"><a href="#使用装饰器监听" class="headerlink" title="使用装饰器监听"></a><code>使用装饰器监听</code></h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">my_callback</span><span class="params">(sender, **kwargs)</span>:</span></span><br><span class="line">    print(<span class="string">"Request finished!"</span>)</span><br><span class="line"><span class="keyword">from</span> django.core.signals <span class="keyword">import</span> request_finished</span><br><span class="line"><span class="keyword">from</span> django.dispatch <span class="keyword">import</span> receiver</span><br><span class="line"></span><br><span class="line"><span class="meta">@receiver(request_finished)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">my_callback</span><span class="params">(sender, **kwargs)</span>:</span></span><br><span class="line">    print(<span class="string">"Request finished!"</span>)</span><br></pre></td></tr></table></figure><h4 id="注册时指定发送者"><a href="#注册时指定发送者" class="headerlink" title="注册时指定发送者"></a><code>注册时指定发送者</code></h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.db.models.signals <span class="keyword">import</span> pre_save</span><br><span class="line"><span class="keyword">from</span> django.dispatch <span class="keyword">import</span> receiver</span><br><span class="line"><span class="keyword">from</span> myapp.models <span class="keyword">import</span> MyModel</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在MyModel对应的数据保存前执行my_handler函数</span></span><br><span class="line"><span class="meta">@receiver(pre_save, sender=MyModel)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">my_handler</span><span class="params">(sender, **kwargs)</span>:</span></span><br><span class="line">    xxx</span><br></pre></td></tr></table></figure><p><code>注册完内置信号后，只要满足触发条件，对应的函数会在相应的条件下去执行，不用人手动去调用。</code></p><h3 id="内置信号详细介绍"><a href="#内置信号详细介绍" class="headerlink" title="内置信号详细介绍"></a><code>内置信号详细介绍</code></h3><p>更多内置信号的说明请参考这篇博客：<a href="https://www.cnblogs.com/liwenzhou/p/9745331.html" target="_blank" rel="noopener">https://www.cnblogs.com/liwenzhou/p/9745331.html</a></p><h3 id="自定义信号的使用"><a href="#自定义信号的使用" class="headerlink" title="自定义信号的使用"></a><code>自定义信号的使用</code></h3><p>首先要知道的是，我们用的信号都是<strong>django.dispatch.Signal</strong>这个类的实例。</p><h4 id="首先定义信号"><a href="#首先定义信号" class="headerlink" title="首先定义信号"></a><code>首先定义信号</code></h4><p>在任意的py文件中定义信号：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> django.dispatch</span><br><span class="line">signal_done = django.dispatch.Signal(providing_args=[<span class="string">"height"</span>, <span class="string">"width"</span>])</span><br></pre></td></tr></table></figure><h4 id="然后在与项目同名的包的-init-文件中注册信号"><a href="#然后在与项目同名的包的-init-文件中注册信号" class="headerlink" title="然后在与项目同名的包的__init__文件中注册信号"></a><code>然后在与项目同名的包的__init__文件中注册信号</code></h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">callback</span><span class="params">(sender, **kwargs)</span>:</span></span><br><span class="line">    print(<span class="string">"callback"</span>)</span><br><span class="line">    print(sender,kwargs)</span><br><span class="line"> </span><br><span class="line">signal_done.connect(callback)</span><br></pre></td></tr></table></figure><h4 id="最后在需要触发信号的地方使用自定义信号"><a href="#最后在需要触发信号的地方使用自定义信号" class="headerlink" title="最后在需要触发信号的地方使用自定义信号"></a><code>最后在需要触发信号的地方使用自定义信号</code></h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> 路径 <span class="keyword">import</span> signal_done</span><br><span class="line"> </span><br><span class="line">signal_done.send(sender=<span class="string">'Naruto'</span>,height=<span class="number">123</span>, width=<span class="number">456</span>)</span><br></pre></td></tr></table></figure><p><code>最后需要注意：由于内置信号的触发者已经集成到Django中，所以其会自动调用；而对于自定义信号则需要开发者在对应位置指定触发。</code></p><h3 id="在路由分发前注册信号的方法"><a href="#在路由分发前注册信号的方法" class="headerlink" title="在路由分发前注册信号的方法"></a><code>在路由分发前注册信号的方法</code></h3><p>上面介绍的一种方式是<strong>在项目同名的包的<code>__init__.py</code>文件中去注册信号</strong>。<br>我们也可以利用Django在路由分发之前做一下信号的注册操作。<br>这里用到了Django启动的机制：<strong>django.dispatch.Signal在django.setup()的过程中，它会遍历settings.INSTALLED_APPS列表中的每一项，并调用该AppConfig的ready方法，因此，将recevier订阅signal的过程放置于ready方法中就能保证该代码的执行。</strong><br>我们来拿一个具体的项目为例。</p><h4 id="项目的目录结构如下"><a href="#项目的目录结构如下" class="headerlink" title="项目的目录结构如下"></a><code>项目的目录结构如下</code></h4><p><img src="http://whw.pythonav.cn/signal3.png" alt="22331"><br><code>（1）把所有的信号都写在了signals包中，并且signals包中的__init__.py文件中实例化Signal类的对象（注意Python在import一个包的时候会执行里面的__init__文件），执行的操作我写在了handlers.py文件中；</code><br><code>（2）然后，利用Django的启动的原理，我把信号的注册写在了apps.py的SignalappConfig类的ready方法中，保证在路由分发之前就注册自定义的信号。</code></p><h4 id="注册信号的具体写法"><a href="#注册信号的具体写法" class="headerlink" title="注册信号的具体写法"></a><code>注册信号的具体写法</code></h4><h5 id="signals-init-py"><a href="#signals-init-py" class="headerlink" title="signals/__init__.py"></a><code>signals/__init__.py</code></h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="keyword">from</span> django.dispatch <span class="keyword">import</span> Signal</span><br><span class="line"></span><br><span class="line">my_signal = Signal(providing_args=[])</span><br></pre></td></tr></table></figure><h5 id="signals-handlers-py："><a href="#signals-handlers-py：" class="headerlink" title="signals/handlers.py："></a><code>signals/handlers.py：</code></h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding:utf-8 -*-</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">my_callback</span><span class="params">(sender,**kwargs)</span>:</span></span><br><span class="line">    print(<span class="string">'my_callback...'</span>)</span><br></pre></td></tr></table></figure><h5 id="signalapp-apps-py"><a href="#signalapp-apps-py" class="headerlink" title="signalapp/apps.py"></a><code>signalapp/apps.py</code></h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.apps <span class="keyword">import</span> AppConfig</span><br><span class="line"></span><br><span class="line"><span class="comment"># 从外部导入自定义信号及处理的函数</span></span><br><span class="line"><span class="keyword">from</span> signals <span class="keyword">import</span> my_signal</span><br><span class="line"><span class="keyword">from</span> signals.handlers <span class="keyword">import</span> my_callback</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SignalappConfig</span><span class="params">(AppConfig)</span>:</span></span><br><span class="line">    name = <span class="string">'signalapp'</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">ready</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="comment"># 注册信号</span></span><br><span class="line">        my_signal.connect(my_callback)</span><br></pre></td></tr></table></figure><h4 id="使用自定义的信号"><a href="#使用自定义的信号" class="headerlink" title="使用自定义的信号"></a><code>使用自定义的信号</code></h4><p>做一个简单的路由与视图测试一下这个自定义的信号是否成功：</p><h5 id="signalTest-urls-py"><a href="#signalTest-urls-py" class="headerlink" title="signalTest/urls.py"></a><code>signalTest/urls.py</code></h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.contrib <span class="keyword">import</span> admin</span><br><span class="line"><span class="keyword">from</span> django.urls <span class="keyword">import</span> path</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> signalapp <span class="keyword">import</span> views</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">urlpatterns = [</span><br><span class="line">    path(<span class="string">'admin/'</span>, admin.site.urls),</span><br><span class="line">    path(<span class="string">'index/'</span>,views.index,name=<span class="string">'index'</span>),</span><br><span class="line">]</span><br></pre></td></tr></table></figure><h5 id="sjgnalapp-views-py"><a href="#sjgnalapp-views-py" class="headerlink" title="sjgnalapp/views.py"></a><code>sjgnalapp/views.py</code></h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.shortcuts <span class="keyword">import</span> render,HttpResponse</span><br><span class="line"><span class="comment"># 导入自定义信号的处理函数</span></span><br><span class="line"><span class="keyword">from</span> signals.handlers <span class="keyword">import</span> my_callback</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">index</span><span class="params">(request)</span>:</span></span><br><span class="line">    <span class="comment"># 使用自定义信号</span></span><br><span class="line">    my_callback(sender=<span class="string">'index'</span>)</span><br><span class="line">    <span class="keyword">return</span> HttpResponse(<span class="string">'OK'</span>)</span><br></pre></td></tr></table></figure><p>启动Django程序后我们在浏览器中输入127.0.0.1:8000/index，可以看到在后台打印出了自定义信号处理函数中所打印的数据： </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">my_callback...</span><br></pre></td></tr></table></figure><h3 id="定制信号的发送者"><a href="#定制信号的发送者" class="headerlink" title="定制信号的发送者"></a><code>定制信号的发送者</code></h3><p>默认情况下，某些信号会被多次发送，但是，通常，我们只希望接收某个或者某些特定的发送者发出的信号，比如说django.db.models.signals.pre_saves，该信号，它在每个model的save()方法被执行的时候被发送，不过，很多情况下，我们只想记录某个特定的model的save()方法被执行时的日志。<br>在上述情况下，我们可以指定只接受我们指定的信号发送者发出的信号。<br>还是用django.db.models.signals.pre_saves举例，下面我们来演示如何指定发送者</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> django.db.models.signals <span class="keyword">import</span> pre_save</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> django.dispatch <span class="keyword">import</span> receiver</span><br><span class="line"><span class="keyword">from</span> myapp.models <span class="keyword">import</span> MyModel</span><br><span class="line"></span><br><span class="line"><span class="meta">@receiver(pre_save, sender=MyModel)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">my_handler</span><span class="params">(sender, **kwargs)</span>:</span></span><br><span class="line">    xxx</span><br></pre></td></tr></table></figure><h3 id="断开信号"><a href="#断开信号" class="headerlink" title="断开信号"></a><code>断开信号</code></h3><p>如果不希望再接收某个信号，我们可以调用<code>Signal.disconnect()</code>方法。</p><h3 id="更多内容详见官网文档"><a href="#更多内容详见官网文档" class="headerlink" title="更多内容详见官网文档"></a><code>更多内容详见官网文档</code></h3><p><a href="https://docs.djangoproject.com/zh-hans/2.1/topics/signals/" target="_blank" rel="noopener">Django2.1信号signal文档</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在真实的企业生产环境中，我们会遇到各种各样的需求，比如在全局对客户端请求进行过滤，将不满足某些条件的客户端请求过滤掉，这时我们可以利用Django的中间件来实现该需求；或者，我们希望每次model的save()方法被调用后，都要写一条日志到日志文件中，而此时我们可以通过Django提供的内置信号post_save来实现，本文介绍一下Django的高级功能——信号。&lt;br&gt;Django的“信号分发器”允许解耦的应用在框架的其它地方发生操作时会被通知到。 简单来说，信号允许特定的sender通知一组receiver某些操作已经发生。 这在多处代码和同一事件有关联的情况下很有用。
    
    </summary>
    
    
      <category term="Django" scheme="http://yoursite.com/categories/Django/"/>
    
    
      <category term="Django" scheme="http://yoursite.com/tags/Django/"/>
    
      <category term="信号signal" scheme="http://yoursite.com/tags/%E4%BF%A1%E5%8F%B7signal/"/>
    
  </entry>
  
  <entry>
    <title>Pycharm与Python3中的TypeHint</title>
    <link href="http://yoursite.com/2019/08/30/Pycharm%E4%B8%8EPython3%E4%B8%AD%E7%9A%84TypeHint/"/>
    <id>http://yoursite.com/2019/08/30/Pycharm与Python3中的TypeHint/</id>
    <published>2019-08-30T14:23:34.000Z</published>
    <updated>2019-09-21T04:28:21.223Z</updated>
    
    <content type="html"><![CDATA[<p>也许有很多同学跟我一样一直使用Pycharm这个IDE作为主要的开发工具。但是在大型项目中Python这门动态语言本身并没有像C++/Java那样的类型声明的限制，这会导致我们在review代码或者接手别人的代码时产生很多困难。<br>还好自python3.5开始，PEP484为python引入了类型提示(TypeHint)。<br>类型提示是一种可以将你的函数变量声明为一种特定类型的声明。当然，类型提示并不是绑定，它仅仅是暗示，所以这种机制并不能阻止工程师传入他们不应该传入的参数。但是个人认为TypeHint是Python非常贴心的一个改进，因为添加了类型提示后，我们仅仅通过观察函数的定义就可以知道变量应该是什么类型。<br>另外需要注意Pycharm一定要用2017或更高的版本，否则你可能看不到Pycharm关于“类型错误”的提示。<a id="more"></a></p><h3 id="类型提示的使用"><a href="#类型提示的使用" class="headerlink" title="类型提示的使用"></a>类型提示的使用</h3><p>不管是函数中传入的参数还是其返回值，基本上都会包括以下4类：</p><ul><li><strong>基本内置类型，如 str，int，list</strong></li><li><strong>基本类型组合产生的复杂类型，如 list[int], dict[str, int]</strong></li><li><strong>用户自定的class作为类型提示</strong></li><li><strong>函数参数，即 callable，比如回调参数：(value: str, index: int) -&gt; str</strong><h4 id="基本内置类型"><a href="#基本内置类型" class="headerlink" title="基本内置类型"></a>基本内置类型</h4>基本内置类型的提示很简单，只需要在函数定义阶段规定参数的类型及返回值类型即可<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">int_hint</span><span class="params">(a:int,b:int)</span>-&gt;int:</span></span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line"></span><br><span class="line">print(int_hint(<span class="number">11</span>,<span class="number">22</span>))</span><br><span class="line"><span class="comment"># 程序本身不会报错，但是Pycharm会有提示</span></span><br><span class="line">print(int_hint(<span class="string">'aa'</span>,<span class="string">'bb'</span>))</span><br></pre></td></tr></table></figure></li></ul><p>我们来看一下实际中Pycharm的提示：<br><img src="http://whw.pythonav.cn/1.png" alt="tishi1"></p><h4 id="基本类型组合产生的复杂类型"><a href="#基本类型组合产生的复杂类型" class="headerlink" title="基本类型组合产生的复杂类型"></a>基本类型组合产生的复杂类型</h4><p>这里我们需要使用Python的<code>typing</code>模块:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> typing</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">my_hint</span><span class="params">(x:typing.List[str],y:typing.List[str])</span>-&gt;typing.Dict[str,typing.List[str]]:</span></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">'x'</span>:x,</span><br><span class="line">        <span class="string">'y'</span>:y</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">a = [<span class="string">'a1'</span>]</span><br><span class="line">b = [<span class="string">'b1'</span>]</span><br><span class="line"></span><br><span class="line">print(my_hint(a,b))</span><br></pre></td></tr></table></figure><p>由上面的代码可知：<code>my_hint</code>函数接收2个参数，分别是列表里面嵌套一个字符串，它的返回值是一个字典，并且这个字典的key是str类型，value是列表嵌套字符串类型的值。<br>上面代码的结果为：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;<span class="string">'x'</span>: [<span class="string">'a1'</span>], <span class="string">'y'</span>: [<span class="string">'b1'</span>]&#125;</span><br></pre></td></tr></table></figure><h4 id="用户自定的class作为类型提示"><a href="#用户自定的class作为类型提示" class="headerlink" title="用户自定的class作为类型提示"></a>用户自定的class作为类型提示</h4><p>我们来看一下这个面向对象的例子：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">People</span><span class="params">(object)</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self,name)</span>:</span></span><br><span class="line">        self.name = name</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__str__</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> self.name</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Dog</span><span class="params">(object)</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self,name)</span>:</span></span><br><span class="line">        self.name = name</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__str__</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> self.name</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">sing</span><span class="params">(name:People)</span>-&gt;<span class="keyword">None</span>:</span></span><br><span class="line">    print(<span class="string">'&#123;&#125; is singing...'</span>.format(name))</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">bark</span><span class="params">(name:Dog)</span>-&gt;<span class="keyword">None</span>:</span></span><br><span class="line">    print(<span class="string">'&#123;&#125; is barking...'</span>.format(name))</span><br><span class="line"></span><br><span class="line">Tom = People(<span class="string">'Tom'</span>)</span><br><span class="line">Jerry = Dog(<span class="string">'Jerry'</span>)</span><br><span class="line"></span><br><span class="line">sing(Tom)</span><br><span class="line"><span class="comment"># Pycharm也会有提示</span></span><br><span class="line">sing(Jerry)</span><br><span class="line">bark(Jerry)</span><br></pre></td></tr></table></figure><p>sing函数需要传入一个People对象，但是实际中传入的是一个Dog类实例化的对象，Pycharm也会提示：<br><img src="http://whw.pythonav.cn/2.png" alt="xsa"></p><h4 id="函数参数-callable"><a href="#函数参数-callable" class="headerlink" title="函数参数 callable"></a>函数参数 callable</h4><p><strong>这种情况是将一个函数作为参数实现参数的回调：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> typing</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">create_dic</span><span class="params">(name:str,age:int)</span>-&gt;dict:</span></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="string">'name'</span>:name,</span><br><span class="line">        <span class="string">'age'</span>:age   </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">my_callable</span><span class="params">(name:str,age:int,callback:typing.Callable[[str,int],dict])</span>:</span></span><br><span class="line">    ret = callback(name,age)</span><br><span class="line">    <span class="keyword">return</span> ret</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将create_dict函数传进去，注意是在my_callable函数中执行</span></span><br><span class="line">print(my_callable(<span class="string">'wanghw'</span>,<span class="number">18</span>,create_dic))</span><br></pre></td></tr></table></figure><p>执行结果为：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;<span class="string">'name'</span>: <span class="string">'wanghw'</span>, <span class="string">'age'</span>: <span class="number">18</span>&#125;</span><br></pre></td></tr></table></figure><p>当然，这种方式在实际中使用的并不多，但是遇到的话我们也应该能够读懂代码。</p><h4 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h4><p><a href="https://juejin.im/post/5a8ed8f9f265da4e6e2bd920" target="_blank" rel="noopener"><a href="https://juejin.im/post/5a8ed8f9f265da4e6e2bd920" target="_blank" rel="noopener">https://juejin.im/post/5a8ed8f9f265da4e6e2bd920</a></a><br><a href="https://www.cnblogs.com/sddai/p/11458757.html" target="_blank" rel="noopener"><a href="https://www.cnblogs.com/sddai/p/11458757.html" target="_blank" rel="noopener">https://www.cnblogs.com/sddai/p/11458757.html</a></a><br><a href="https://www.cnblogs.com/zhbzz2007/p/6200515.html" target="_blank" rel="noopener"><a href="https://www.cnblogs.com/zhbzz2007/p/6200515.html" target="_blank" rel="noopener">https://www.cnblogs.com/zhbzz2007/p/6200515.html</a></a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;也许有很多同学跟我一样一直使用Pycharm这个IDE作为主要的开发工具。但是在大型项目中Python这门动态语言本身并没有像C++/Java那样的类型声明的限制，这会导致我们在review代码或者接手别人的代码时产生很多困难。&lt;br&gt;还好自python3.5开始，PEP484为python引入了类型提示(TypeHint)。&lt;br&gt;类型提示是一种可以将你的函数变量声明为一种特定类型的声明。当然，类型提示并不是绑定，它仅仅是暗示，所以这种机制并不能阻止工程师传入他们不应该传入的参数。但是个人认为TypeHint是Python非常贴心的一个改进，因为添加了类型提示后，我们仅仅通过观察函数的定义就可以知道变量应该是什么类型。&lt;br&gt;另外需要注意Pycharm一定要用2017或更高的版本，否则你可能看不到Pycharm关于“类型错误”的提示。
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="Pycharm" scheme="http://yoursite.com/tags/Pycharm/"/>
    
      <category term="Python" scheme="http://yoursite.com/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title>Python异常处理</title>
    <link href="http://yoursite.com/2019/08/17/Python%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/"/>
    <id>http://yoursite.com/2019/08/17/Python异常处理/</id>
    <published>2019-08-17T13:34:01.000Z</published>
    <updated>2020-01-06T14:54:40.441Z</updated>
    
    <content type="html"><![CDATA[<p>本文将详细介绍Python中常见的错误与异常以及处理异常的具体方法。<a id="more"></a></p><h4 id="Python异常的分类"><a href="#Python异常的分类" class="headerlink" title="Python异常的分类"></a>Python异常的分类</h4><p>Python中的错误主要分为2种：一种是<code>语法错误</code>，另外一种是<code>逻辑错误</code>。<br>语法错误在Python解释器进行语法检测的时候就会被“捕获”到，也就是说，如果代码中存在语法错误，那这段代码所在的整个程序都不会执行！<br>常见的语法错误示范如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 函数后没加引号</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">test</span><span class="params">()</span></span></span><br><span class="line"><span class="function">    <span class="title">pass</span></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"># 只有一个关键字</span></span><br><span class="line"><span class="function"><span class="title">for</span></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"># 调用方法没写完整</span></span><br><span class="line"><span class="function"><span class="title">def</span> <span class="title">test</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">pass</span></span><br><span class="line">test(</span><br><span class="line">    </span><br><span class="line"><span class="comment"># and so on</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>第二种<code>逻辑错误</code>是在我们执行Python代码期间产生的异常，它虽然可以通过Python的“语法检测”，但是因为逻辑问题会抛出异常：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># int强转一个非数字的字符串</span></span><br><span class="line">s = <span class="string">"wanghw"</span></span><br><span class="line">d = int(s)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 分母为0</span></span><br><span class="line">a = <span class="number">1</span>/<span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># and so on</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure><h4 id="代码中捕获的异常"><a href="#代码中捕获的异常" class="headerlink" title="代码中捕获的异常"></a>代码中捕获的异常</h4><p>在Python的IDE下捕获的异常一般是这样的：<br><img src="http://whw.pythonav.cn/%E5%BC%82%E5%B8%B8%E5%8D%9A%E5%AE%A2.png" alt="123"></p><h4 id="常见的异常"><a href="#常见的异常" class="headerlink" title="常见的异常"></a>常见的异常</h4><p>Python中常见的异常如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"AttributeError"</span> 试图访问一个对象没有的属性，比如foo.x，但是foo没有属性x</span><br><span class="line"><span class="string">"IOError"</span> 输入/输出异常；基本上是无法打开文件</span><br><span class="line"><span class="string">"ImportError"</span> 无法引入模块或包；基本上是路径问题或名称错误</span><br><span class="line"><span class="string">"IndentationError"</span> 语法错误（的子类） ；代码没有正确对齐</span><br><span class="line"><span class="string">"IndexError"</span> 下标索引超出序列边界，比如当x只有三个元素，却试图访问x[<span class="number">5</span>]</span><br><span class="line"><span class="string">"KeyError"</span> 试图访问字典里不存在的键</span><br><span class="line"><span class="string">"KeyboardInterrupt"</span> Ctrl+C被按下</span><br><span class="line"><span class="string">"NameError"</span> 使用一个还未被赋予对象的变量</span><br><span class="line"><span class="string">"SyntaxError"</span> Python代码非法，代码不能编译(个人认为这是语法错误，写错了）</span><br><span class="line"><span class="string">"TypeError"</span> 传入对象类型与要求的不符合</span><br><span class="line"><span class="string">"UnboundLocalError"</span> 试图访问一个还未被设置的局部变量，基本上是由于另有一个同名的全局变量，导致你以为正在访问它</span><br><span class="line"><span class="string">"ValueError"</span> 传入一个调用者不期望的值，即使值的类型是正确的</span><br></pre></td></tr></table></figure><p>其他的异常信息如下：</p><details><summary>其他更多异常</summary><pre><code>ArithmeticErrorAssertionErrorAttributeErrorBaseExceptionBufferErrorBytesWarningDeprecationWarningEnvironmentErrorEOFErrorExceptionFloatingPointErrorFutureWarningGeneratorExitImportErrorImportWarningIndentationErrorIndexErrorIOErrorKeyboardInterruptKeyErrorLookupErrorMemoryErrorNameErrorNotImplementedErrorOSErrorOverflowErrorPendingDeprecationWarningReferenceErrorRuntimeErrorRuntimeWarningStandardErrorStopIterationSyntaxErrorSyntaxWarningSystemErrorSystemExitTabErrorTypeErrorUnboundLocalErrorUnicodeDecodeErrorUnicodeEncodeErrorUnicodeErrorUnicodeTranslateErrorUnicodeWarningUserWarningValueErrorWarningZeroDivisionError</code></pre></details><h4 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h4><p>上面介绍了什么是异常，接下来详细说明一下Python中如何进行异常处理。</p><h5 id="异常处理的概念"><a href="#异常处理的概念" class="headerlink" title="异常处理的概念"></a>异常处理的概念</h5><p>python解释器检测到错误，触发异常（也允许程序员自己触发异常）。<br>程序员编写特定的代码，专门用来捕捉这个异常（这段代码与程序逻辑无关，与异常处理有关）。<br>如果捕捉成功则进入另外一个处理分支，执行你为其定制的逻辑，使程序不会崩溃，这就是异常处理。</p><h5 id="为什么要进行异常处理"><a href="#为什么要进行异常处理" class="headerlink" title="为什么要进行异常处理"></a>为什么要进行异常处理</h5><p>python解析器去执行程序，检测到了一个错误时，触发异常，异常触发后且没被处理的情况下，程序就在当前异常处终止，后面的代码不会运行，谁会去用一个运行着突然就崩溃的软件。<br>所以你必须提供一种异常处理机制来增强你程序的健壮性与容错性。</p><h4 id="如何进行异常处理"><a href="#如何进行异常处理" class="headerlink" title="如何进行异常处理"></a>如何进行异常处理</h4><p><strong>首先须知，异常是由程序的错误引起的，语法上的错误跟异常处理无关，必须在程序运行前就修正！</strong> </p><h5 id="Python中的异常处理"><a href="#Python中的异常处理" class="headerlink" title="Python中的异常处理"></a>Python中的异常处理</h5><p>Python中使用<code>try...except</code>代码块来处理异常。<br>基本语法如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>:</span><br><span class="line">     被检测的代码块</span><br><span class="line"><span class="keyword">except</span> 异常类型：</span><br><span class="line">     <span class="keyword">try</span>中一旦检测到异常，就执行这个位置的逻辑</span><br></pre></td></tr></table></figure><p>如果在执行 try 块里的业务逻辑代码时出现异常，系统自动生成一个异常对象，该异常对象被提交给 Python 解释器，这个过程被称为引发异常。<br>当 Python 解释器收到异常对象时，会寻找能处理该异常对象的 except 块，如果找到合适的 except 块，则把该异常对象交给该 except 块处理，这个过程被称为捕获异常。如果 Python 解释器找不到捕获异常的 except 块，则运行时环境终止，Python 解释器也将退出。<br>不管程序代码块是否处于 try 块中，甚至包括 except 块中的代码，只要执行该代码块时出现了异常，系统总会自动生成一个 Error 对象。如果程序没有为这段代码定义任何的 except 块，则 Python 解释器无法找到处理该异常的 except 块，程序就在此退出。<br>拿一个具体的实例来说：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    f = open(<span class="string">'a.txt'</span>)</span><br><span class="line">    g = (line.strip() <span class="keyword">for</span> line <span class="keyword">in</span> f)</span><br><span class="line">    print(next(g))</span><br><span class="line">    print(next(g))</span><br><span class="line">    print(next(g))</span><br><span class="line">    print(next(g))</span><br><span class="line">    print(next(g))</span><br><span class="line"><span class="keyword">except</span> StopIteration:</span><br><span class="line">    f.close()</span><br><span class="line"><span class="string">'''</span></span><br><span class="line"><span class="string">next(g)会触发迭代f，依次next(g)就可以读取文件的一行行内容，无论文件a.txt有多大，同一时刻内存中只有一行内容。</span></span><br><span class="line"><span class="string">提示：g是基于文件句柄f而存在的，因而只能在next(g)抛出异常StopIteration后才可以执行f.close()</span></span><br><span class="line"><span class="string">'''</span></span><br></pre></td></tr></table></figure><h5 id="单个except块只能处理指定的异常"><a href="#单个except块只能处理指定的异常" class="headerlink" title="单个except块只能处理指定的异常"></a>单个except块只能处理指定的异常</h5><p><strong>异常类只能用来处理指定的异常情况，如果非指定异常则无法处理</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 未捕获到异常，程序直接报错</span></span><br><span class="line">s1 = <span class="string">'hello'</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    int(s1)</span><br><span class="line"><span class="keyword">except</span> IndexError <span class="keyword">as</span> e:</span><br><span class="line">    <span class="keyword">print</span> e</span><br></pre></td></tr></table></figure><h5 id="多分支捕获多种异常"><a href="#多分支捕获多种异常" class="headerlink" title="多分支捕获多种异常"></a>多分支捕获多种异常</h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">s1 = <span class="string">'hello'</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    int(s1)</span><br><span class="line"><span class="keyword">except</span> IndexError <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br><span class="line"><span class="keyword">except</span> KeyError <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br><span class="line"><span class="keyword">except</span> ValueError <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br></pre></td></tr></table></figure><h5 id="万能异常"><a href="#万能异常" class="headerlink" title="万能异常"></a>万能异常</h5><p>使用万能异常<code>Exception</code>可以捕获所有的异常：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">s1 = <span class="string">'hello'</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    int(s1)</span><br><span class="line"><span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br></pre></td></tr></table></figure><h5 id="多分支-VS-万能异常"><a href="#多分支-VS-万能异常" class="headerlink" title="多分支 VS 万能异常"></a>多分支 VS 万能异常</h5><p>1、如果你想要的效果是，无论出现什么异常，我们统一丢弃，或者使用同一段代码逻辑去处理他们，那么骚年，大胆的去做吧，只有一个Exception就足够了。 </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">s1 = <span class="string">'hello'</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    int(s1)</span><br><span class="line"><span class="keyword">except</span> Exception,e:</span><br><span class="line">    <span class="string">'丢弃或者执行其他逻辑'</span></span><br><span class="line">    print(e)</span><br><span class="line"></span><br><span class="line"><span class="comment">#如果你统一用Exception，没错，是可以捕捉所有异常，但意味着你在处理所有异常时都使用同一个逻辑去处理（这里说的逻辑即当前expect下面跟的代码块）</span></span><br></pre></td></tr></table></figure><p>2、如果你想要的效果是，对于不同的异常我们需要定制不同的处理逻辑，那就需要用到多分支了。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">s1 = <span class="string">'hello'</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    int(s1)</span><br><span class="line"><span class="keyword">except</span> IndexError <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br><span class="line"><span class="keyword">except</span> KeyError <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br><span class="line"><span class="keyword">except</span> ValueError <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">s1 = <span class="string">'hello'</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    int(s1)</span><br><span class="line"><span class="keyword">except</span> IndexError <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br><span class="line"><span class="keyword">except</span> KeyError <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br><span class="line"><span class="keyword">except</span> ValueError <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br><span class="line"><span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br></pre></td></tr></table></figure><h5 id="主动触发异常"><a href="#主动触发异常" class="headerlink" title="主动触发异常"></a>主动触发异常</h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    <span class="keyword">raise</span> TypeError(<span class="string">'类型错误'</span>)</span><br><span class="line"><span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br></pre></td></tr></table></figure><h5 id="自定义异常"><a href="#自定义异常" class="headerlink" title="自定义异常"></a>自定义异常</h5><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 自定义异常</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">HuoException</span><span class="params">(Exception)</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self,msg)</span>:</span></span><br><span class="line">        self.msg = msg</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__str__</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> self.msg</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line"><span class="keyword">raise</span> HuoException(<span class="string">"huoyingwhw aaa"</span>)</span><br><span class="line"><span class="keyword">except</span> HuoException <span class="keyword">as</span> e:</span><br><span class="line">print(e)</span><br></pre></td></tr></table></figure><h5 id="Python异常类的继承顺序"><a href="#Python异常类的继承顺序" class="headerlink" title="Python异常类的继承顺序"></a>Python异常类的继承顺序</h5><p><img src="http://whw.pythonav.cn/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%862.gif" alt="12322"><br>从上图 中可以看出，Python 的所有异常类的基类是 BaseException，但如果用户要实现自定义异常，则不应该继承这个基类，而是应该继承 Exception 类。<br>BaseException 的主要子类就是 Exception，不管是系统的异常类，还是用户自定义的异常类，都应该从 Exception 派生。 </p><details><summary>异常的继承关系</summary><pre><code>BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception      +-- StopIteration      +-- StandardError      |    +-- BufferError      |    +-- ArithmeticError      |    |    +-- FloatingPointError      |    |    +-- OverflowError      |    |    +-- ZeroDivisionError      |    +-- AssertionError      |    +-- AttributeError      |    +-- EnvironmentError      |    |    +-- IOError      |    |    +-- OSError      |    |         +-- WindowsError (Windows)      |    |         +-- VMSError (VMS)      |    +-- EOFError      |    +-- ImportError      |    +-- LookupError      |    |    +-- IndexError      |    |    +-- KeyError      |    +-- MemoryError      |    +-- NameError      |    |    +-- UnboundLocalError      |    +-- ReferenceError      |    +-- RuntimeError      |    |    +-- NotImplementedError      |    +-- SyntaxError      |    |    +-- IndentationError      |    |         +-- TabError      |    +-- SystemError      |    +-- TypeError      |    +-- ValueError      |         +-- UnicodeError      |              +-- UnicodeDecodeError      |              +-- UnicodeEncodeError      |              +-- UnicodeTranslateError      +-- Warning           +-- DeprecationWarning           +-- PendingDeprecationWarning           +-- RuntimeWarning           +-- SyntaxWarning           +-- UserWarning           +-- FutureWarning       +-- ImportWarning       +-- UnicodeWarning       +-- BytesWarning</code></pre></details><h4 id="Python异常处理的else块"><a href="#Python异常处理的else块" class="headerlink" title="Python异常处理的else块"></a>Python异常处理的else块</h4><p>在 Python 的异常处理流程中还可添加一个 else 块，当 try 块没有出现异常时，程序会执行 else 块。例如如下程序： </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">s = input(<span class="string">'请输入除数:'</span>)</span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    result = <span class="number">20</span> / int(s)</span><br><span class="line">    print(<span class="string">'20除以%s的结果是: %g'</span> % (s , result))</span><br><span class="line"><span class="keyword">except</span> ValueError:</span><br><span class="line">    print(<span class="string">'值错误，您必须输入数值'</span>)</span><br><span class="line"><span class="keyword">except</span> ArithmeticError:</span><br><span class="line">    print(<span class="string">'算术错误，您不能输入0'</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    print(<span class="string">'没有出现异常'</span>)</span><br></pre></td></tr></table></figure><p>上面程序为异常处理流程添加了 else 块，当程序中的 try 块没有出现异常时，程序就会执行 else 块。运行上面程序，如果用户输入导致程序中的 try 块出现了异常，则运行结果如下： </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">请输入除数：a</span><br><span class="line">值错误，您必须输入数值</span><br></pre></td></tr></table></figure><p>如果用户输入让程序中的 try 块顺利完成，则运行结果如下： </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">请输入除数：3</span><br><span class="line">20 除以3 的结果是：6.66667</span><br><span class="line">没有出现异常</span><br></pre></td></tr></table></figure><p>看到这里，可能有读者觉得奇怪：既然只有当 try 块没有异常时才会执行 else 块，那么直接把 else 块的代码放在 try 块的代码的后面不就行了？<br>实际上大部分语言的异常处理都没有 else 块，它们确实是将 else 块的代码直接放在 try 块的代码的后面的，因为对于大部分场景而言，直接将 else 块的代码放在 try 块的代码的后面即可。<br>但 Python 的异常处理使用 else 块绝不是多余的语法，当 try 块没有异常，而 else 块有异常时，就能体现出 else 块的作用了。例如如下程序： </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">else_test</span><span class="params">()</span>:</span></span><br><span class="line">    s = input(<span class="string">'请输入除数:'</span>)</span><br><span class="line">    result = <span class="number">20</span> / int(s)</span><br><span class="line">    print(<span class="string">'20除以%s的结果是: %g'</span> % (s , result))</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">right_main</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        print(<span class="string">'try块的代码，没有异常'</span>)</span><br><span class="line">    <span class="keyword">except</span>:</span><br><span class="line">        print(<span class="string">'程序出现异常'</span>)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="comment"># 将else_test放在else块中</span></span><br><span class="line">        else_test()</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">wrong_main</span><span class="params">()</span>:</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        print(<span class="string">'try块的代码，没有异常'</span>)</span><br><span class="line">        <span class="comment"># 将else_test放在try块代码的后面</span></span><br><span class="line">        else_test()</span><br><span class="line">    <span class="keyword">except</span>:</span><br><span class="line">        print(<span class="string">'程序出现异常'</span>)</span><br><span class="line">wrong_main()</span><br><span class="line">right_main()</span><br></pre></td></tr></table></figure><p>上面程序中定义了一个 else_test() 函数，该函数在运行时需要接收用户输入的参数，随着用户输入数据的不同可能导致异常。接下来程序定义了 right_main() 和 wrong_main() 两个函数，其中 right_main() 将 else_test() 函数放在 else 块内；而 wrong_main() 将 else_test() 函数放在 try 块的代码的后面。<br>正如上面所介绍的，当 try 块和 else 块都没有异常时，将 else_test() 函数放在 try 块的代码的后面和放在 else 块中没有任何区别。例如，如果用户输入的数据没有导致程序出现异常，则将看到程序产生如下输出结果： </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>块的代码，没有异常</span><br><span class="line">请输入除数:<span class="number">4</span></span><br><span class="line"><span class="number">20</span>除以<span class="number">4</span>的结果是: <span class="number">5</span></span><br><span class="line"><span class="keyword">try</span>块的代码，没有异常</span><br><span class="line">请输入除数:<span class="number">4</span></span><br><span class="line"><span class="number">20</span>除以<span class="number">4</span>的结果是: <span class="number">5</span></span><br></pre></td></tr></table></figure><p>但如果用户输入的数据让 else_test() 函数出现异常（try 块依然没有任何异常），此时程序就会产生如下输出结果： </p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>块的代码，没有异常</span><br><span class="line">请输入除数:<span class="number">0</span></span><br><span class="line">程序出现异常</span><br><span class="line"><span class="keyword">try</span>块的代码，没有异常</span><br><span class="line">请输入除数:<span class="number">0</span></span><br><span class="line">Traceback (most recent call last):</span><br><span class="line">  File <span class="string">"C:\Users\mengma\Desktop\1.py"</span>, line <span class="number">21</span>, <span class="keyword">in</span> &lt;module&gt;</span><br><span class="line">    right_main()</span><br><span class="line">  File <span class="string">"C:\Users\mengma\Desktop\1.py"</span>, line <span class="number">12</span>, <span class="keyword">in</span> right_main</span><br><span class="line">    else_test()</span><br><span class="line">  File <span class="string">"C:\Users\mengma\Desktop\1.py"</span>, line <span class="number">3</span>, <span class="keyword">in</span> else_test</span><br><span class="line">    result = <span class="number">20</span> / int(s)</span><br><span class="line">ZeroDivisionError: division by zero</span><br></pre></td></tr></table></figure><p>对比上面两个输出结果，用户输入的都是 0，这样都会导致 else_test() 函数出现异常。如果将 else_test() 函数放在 try 块的代码的后面，此时 else_test() 函数运行产生的异常将会被 try 块对应的 except 捕获，这正是 Python 异常处理机制的执行流程：但如果将 else_test() 函数放在 else 块中，当 else_test() 函数出现异常时，程序没有 except 块来处理该异常，该异常将会传播给 Python 解释器，导致程序中止。<br><code>对比上面两个输出结果，不难发现，放在 else 块中的代码所引发的异常不会被 except 块捕获。</code><br>所以，<strong>如果希望某段代码的异常能被后面的 except 块捕获，那么就应该将这段代码放在 try 块的代码之后；如果希望某段代码的异常能向外传播（不被 except 块捕获），那么就应该将这段代码放在 else 块中</strong>。 </p><h4 id="Python异常处理的finally块"><a href="#Python异常处理的finally块" class="headerlink" title="Python异常处理的finally块"></a>Python异常处理的finally块</h4><p><code>finally</code>块中的代码无论是否发生了异常都会执行，通常进行操作系统资源的清理工作。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># finally块</span></span><br><span class="line"><span class="keyword">try</span>:</span><br><span class="line">    f = open(<span class="string">"lru1.py"</span>)</span><br><span class="line">    contents = f.read()</span><br><span class="line">    print(contents)</span><br><span class="line"><span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">    print(e)</span><br><span class="line"><span class="keyword">finally</span>:</span><br><span class="line">    f.close()</span><br></pre></td></tr></table></figure><h4 id="如何合理的使用异常处理"><a href="#如何合理的使用异常处理" class="headerlink" title="如何合理的使用异常处理"></a>如何合理的使用异常处理</h4><p>有的同学会这么想，学完了异常处理后，好强大，我要为我的每一段程序都加上try…except，干毛线去思考它会不会有逻辑错误啊，这样就很好啊，多省脑细胞 - -!<br>try…except应该尽量少用，因为它本身就是你附加给你的程序的一种异常处理的逻辑，与你的主要的工作是没有关系的<br>这种东西加的多了，会导致你的代码可读性变差，只有在有些异常无法预知的情况下，才应该加上try…except，其他的逻辑错误应该尽量修正</p><h3 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h3><p><a href="http://c.biancheng.net/view/2315.html" target="_blank" rel="noopener">http://c.biancheng.net/view/2315.html</a><br><a href="https://www.cnblogs.com/Eva-J/articles/7281240.html" target="_blank" rel="noopener">https://www.cnblogs.com/Eva-J/articles/7281240.html</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;本文将详细介绍Python中常见的错误与异常以及处理异常的具体方法。
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="异常处理" scheme="http://yoursite.com/tags/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86/"/>
    
      <category term="模块与包" scheme="http://yoursite.com/tags/%E6%A8%A1%E5%9D%97%E4%B8%8E%E5%8C%85/"/>
    
  </entry>
  
  <entry>
    <title>Flask的版本与返回字典数据的问题</title>
    <link href="http://yoursite.com/2019/08/10/Flask%E7%9A%84%E7%89%88%E6%9C%AC%E4%B8%8E%E8%BF%94%E5%9B%9E%E5%AD%97%E5%85%B8%E6%95%B0%E6%8D%AE%E7%9A%84%E9%97%AE%E9%A2%98/"/>
    <id>http://yoursite.com/2019/08/10/Flask的版本与返回字典数据的问题/</id>
    <published>2019-08-10T01:33:59.000Z</published>
    <updated>2019-09-21T04:28:21.222Z</updated>
    
    <content type="html"><![CDATA[<p>以往我们用flask返回一个字典的时候都会先用<code>jsonify</code>方法把这个字典序列化一下再返回：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> flask <span class="keyword">import</span> Flask,jsonify</span><br><span class="line"></span><br><span class="line">app = Flask(__name__)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.route("/")</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">home</span><span class="params">()</span>:</span></span><br><span class="line">dic = &#123;<span class="string">"name"</span>:<span class="string">"whw"</span>,<span class="string">"age"</span>:<span class="number">18</span>&#125;</span><br><span class="line"><span class="keyword">return</span> jsonify(dic)</span><br></pre></td></tr></table></figure><a id="more"></a>如果直接返回字典的话会报一个`TypeError`的错误！<p><img src="http://whw.pythonav.cn/f1.png" alt="123"><br>如果访问<code>index</code>的话会报错：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">TypeError:<span class="string">'dict'</span> object <span class="keyword">is</span> <span class="keyword">not</span> callable</span><br></pre></td></tr></table></figure><h3 id="1-1-1版本的flask可以直接返回字典"><a href="#1-1-1版本的flask可以直接返回字典" class="headerlink" title="1.1.1版本的flask可以直接返回字典"></a>1.1.1版本的flask可以直接返回字典</h3><p>但是，最近发现了一个十分奇妙的问题：1.11版本的flask是可以直接返回字典的：<br><img src="http://whw.pythonav.cn/f2.png" alt="222"><br>可以访问<code>index</code>看一下结果，字典被成功返回了！<br><img src="http://whw.pythonav.cn/f3.png" alt="333"><br>很神奇，看来flask改版以后优化了返回的数据的结构！</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;以往我们用flask返回一个字典的时候都会先用&lt;code&gt;jsonify&lt;/code&gt;方法把这个字典序列化一下再返回：&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; flask &lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; Flask,jsonify&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;app = Flask(__name__)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;meta&quot;&gt;@app.route(&quot;/&quot;)&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	dic = &amp;#123;&lt;span class=&quot;string&quot;&gt;&quot;name&quot;&lt;/span&gt;:&lt;span class=&quot;string&quot;&gt;&quot;whw&quot;&lt;/span&gt;,&lt;span class=&quot;string&quot;&gt;&quot;age&quot;&lt;/span&gt;:&lt;span class=&quot;number&quot;&gt;18&lt;/span&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;	&lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; jsonify(dic)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
    
      <category term="Flask" scheme="http://yoursite.com/categories/Flask/"/>
    
    
      <category term="Flask基础" scheme="http://yoursite.com/tags/Flask%E5%9F%BA%E7%A1%80/"/>
    
  </entry>
  
  <entry>
    <title>浅谈Python中字典的打散传参</title>
    <link href="http://yoursite.com/2019/08/09/%E6%B5%85%E8%B0%88Python%E4%B8%AD%E5%AD%97%E5%85%B8%E7%9A%84%E6%89%93%E6%95%A3%E4%BC%A0%E5%8F%82/"/>
    <id>http://yoursite.com/2019/08/09/浅谈Python中字典的打散传参/</id>
    <published>2019-08-09T08:56:59.000Z</published>
    <updated>2019-09-21T04:28:21.225Z</updated>
    
    <content type="html"><![CDATA[<p>实际开发中我们常会遇到将用户提交的数据经过校验后存入数据库的操作。<br>假设用户使用form表单以post方式往Customer表中提交数据，后台接收到数据后往往会这样进行处理：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 将csrf校验的键值对去除</span></span><br><span class="line">request.POST.pop(<span class="string">"csrfmiddlewaretoken"</span>)</span><br><span class="line"><span class="comment"># 以打散的方式将数据传入数据库的表中</span></span><br><span class="line">Customer.objects.update_or_create(**request.POST)</span><br></pre></td></tr></table></figure><p>我们知道，request.POST是一个<code>QueryDict</code>对象，实际上它是一个自定义的<code>字典</code>，除了修改的时候需要注意其<code>不可变特性</code>以外，在进行传参的时候我们可以把它当作一个字典。<br>而且需要注意的是，这种“打散”的方式传参要求这个<code>字典</code>中的所有的<code>key</code>必须跟数据库的表中的字段的名字一致！否则传参的时候会发生错误！<a id="more"></a></p><h4 id="一个小demo解释字典打散传参的机理"><a href="#一个小demo解释字典打散传参的机理" class="headerlink" title="一个小demo解释字典打散传参的机理"></a>一个小demo解释字典打散传参的机理</h4><p>那么问题来了：这种<code>打散</code>的方式的机制是什么呢？<br>来看下面的例子：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">func</span><span class="params">(username,password)</span>:</span></span><br><span class="line"><span class="keyword">global</span> count</span><br><span class="line">print(count,username,password)</span><br><span class="line">count += <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">count = <span class="number">1</span></span><br><span class="line">dic = &#123;<span class="string">"username"</span>:<span class="string">'whw'</span>,<span class="string">'password'</span>:<span class="number">123</span>&#125;</span><br><span class="line"><span class="comment"># 打散传参与位置参数传参</span></span><br><span class="line">func(**dic)</span><br><span class="line">func(username=<span class="string">'whw'</span>,password=<span class="number">123</span>)</span><br></pre></td></tr></table></figure><p>执行的结果如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1</span> whw <span class="number">123</span></span><br><span class="line"><span class="number">2</span> whw <span class="number">123</span></span><br></pre></td></tr></table></figure><p>首先，我们先定义了一个函数<code>func</code>，里面有两个位置参数username与password，然后又定义了一个字典<code>dic={&quot;username&quot;:&quot;whw&quot;,&quot;password&quot;:123}</code>，count变量作为一个计数器使用。<br>然后，执行func函数的时候我们采用了两种方式去传参：一种只<code>**dic</code>方式，也就是将dic打散；另外一种是常规的传参方式。<br>两种方式都正常执行，说明上述两种传参方式是等价的！<br>也就是说：<code>**dic</code>将字典<code>打散</code>成了一个个的<code>key=value</code>的形式！<br>另外，还需要特别注意的一点是：这种传参的方式对字典的key是有要求的，被打散的字典的key值必须与函数的位置参数的值一致，否则程序会报错！</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;实际开发中我们常会遇到将用户提交的数据经过校验后存入数据库的操作。&lt;br&gt;假设用户使用form表单以post方式往Customer表中提交数据，后台接收到数据后往往会这样进行处理：&lt;/p&gt;
&lt;figure class=&quot;highlight python&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 将csrf校验的键值对去除&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;request.POST.pop(&lt;span class=&quot;string&quot;&gt;&quot;csrfmiddlewaretoken&quot;&lt;/span&gt;)&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# 以打散的方式将数据传入数据库的表中&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;Customer.objects.update_or_create(**request.POST)&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;

&lt;p&gt;我们知道，request.POST是一个&lt;code&gt;QueryDict&lt;/code&gt;对象，实际上它是一个自定义的&lt;code&gt;字典&lt;/code&gt;，除了修改的时候需要注意其&lt;code&gt;不可变特性&lt;/code&gt;以外，在进行传参的时候我们可以把它当作一个字典。&lt;br&gt;而且需要注意的是，这种“打散”的方式传参要求这个&lt;code&gt;字典&lt;/code&gt;中的所有的&lt;code&gt;key&lt;/code&gt;必须跟数据库的表中的字段的名字一致！否则传参的时候会发生错误！
    
    </summary>
    
    
      <category term="Python" scheme="http://yoursite.com/categories/Python/"/>
    
    
      <category term="Python" scheme="http://yoursite.com/tags/Python/"/>
    
  </entry>
  
</feed>
