<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>呆萌&#39;s Blog</title>
  
  
  <link href="https://www.xdxmblog.cn/atom.xml" rel="self"/>
  
  <link href="https://www.xdxmblog.cn/"/>
  <updated>2025-12-10T04:40:52.000Z</updated>
  <id>https://www.xdxmblog.cn/</id>
  
  <author>
    <name>小呆&amp;小萌</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>一文搞定音标、音节划分、自然拼读规则</title>
    <link href="https://www.xdxmblog.cn/posts/40574.html"/>
    <id>https://www.xdxmblog.cn/posts/40574.html</id>
    <published>2025-12-10T04:40:45.000Z</published>
    <updated>2025-12-10T04:40:52.000Z</updated>
    
    <content type="html"><![CDATA[<p>整理一下英语的音标、音节划分、自然拼读规则，方便娃在学习的过程中可以快速查找、记忆以及巩固相关知识点。</p><h2 id="英语音标：48个国际音标"><a href="#英语音标：48个国际音标" class="headerlink" title="英语音标：48个国际音标"></a>英语音标：48个国际音标</h2><table><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td></td><td>单元音（12个）</td><td>[ɑː] [ɔ:] [ɜː] [i:] [u:]</td></tr><tr><td>元</td><td></td><td>[ʌ ] [ɒ ] [ə ] [ɪ ] [ʊ ] [æ ] [e ]</td></tr><tr><td>音</td><td>双元音（8个）</td><td>[eɪ] [aɪ] [ɔɪ] [əʊ] [aʊ]</td></tr><tr><td></td><td></td><td>[ɪə] [eə] [ʊə]</td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td>清辅音</td><td>[p] [t] [k] [f] [θ] [s] [ʃ] [tʃ] [tr] [ts] [h]</td></tr><tr><td>辅</td><td>浊辅音</td><td>[b] [d] [g] [v] [ð] [z] [ʒ] [dʒ] [dr] [dz] [r]</td></tr><tr><td></td><td>鼻音</td><td>[m] [n] [ŋ]</td></tr><tr><td>音</td><td>舌侧音</td><td>[l]</td></tr><tr><td></td><td>半元音</td><td>[w] [j]</td></tr></tbody></table><h2 id="音节与音节划分知识"><a href="#音节与音节划分知识" class="headerlink" title="音节与音节划分知识"></a>音节与音节划分知识</h2><h3 id="1-音节"><a href="#1-音节" class="headerlink" title="1.音节"></a>1.音节</h3><p>以元音为主体构成的发音单位，一般说来元音发音响亮，可以构成音节，辅音发音不响亮，不能单独构成音节（[m] [n] [ŋ ] [l]例外）。从单词拼写形式上看，有几个元音字母就有几个音节。</p><h3 id="2-音节的划分"><a href="#2-音节的划分" class="headerlink" title="2.音节的划分"></a>2.音节的划分</h3><p>① 在重读音节和非重读音节的相邻处有两个辅音字母时，一个辅音字母组属于前面的音节，一个属于后面的音节。</p><p><strong>例如：</strong>let-ter，mem-ber，chil-dren，daugh-ter</p><p>② 在重读和非重读音节的相邻处只有一个辅音字母时，如果前面重读音节里的元音是长音则辅字组属于后面一个音节，如果重读音节里的元音是短音，则辅音字母属于重读音节。</p><p><strong>例如：</strong>长音 pa-per，stu-dent，fa-ther，ze-ro，mo-tor，far-ther</p><p>　　　短音 sev-en，stud-y，moth-er，ver-y，mod-le ，weath-er</p><h3 id="3-重读音节"><a href="#3-重读音节" class="headerlink" title="3.重读音节"></a>3.重读音节</h3><p>单词中读音特别响亮的音节。用音标标记双音节、多音节词的读音时，应使用重读符号。单音节词多数是重读音节，标记读音时不需要使用重读符号。</p><h3 id="4-开音节"><a href="#4-开音节" class="headerlink" title="4.开音节"></a>4.开音节</h3><p>① 绝对开音节：单个元音字母后面没有辅音字母的重读音节。</p><p><strong>例如：</strong>no，blue，ba-by，stu-dent，se-cret</p><p>② 相对开音节：单个元音字母后面加单个辅音字母，再加一个不发音字母e构成的重读音节。</p><p><strong>例如：</strong>name，these，bike，home，excuse</p><h3 id="5-闭音节"><a href="#5-闭音节" class="headerlink" title="5.闭音节"></a>5.闭音节</h3><p>单个元音字母后面有辅音字母（r、w、y 除外）且以辅音字母结尾的重读音节。</p><p><strong>例如：</strong>bag，egg，fish，not，cup</p><h3 id="6-双音节词重读规则"><a href="#6-双音节词重读规则" class="headerlink" title="6.双音节词重读规则"></a>6.双音节词重读规则</h3><p>① 双音节词的第一个音节通常是重读音节。</p><p><strong>例如：</strong> stu-dent，Chi-na，sec-ond，au-tumn</p><p>② 含有a- be- de- re- in- ex- 等前缀的双音节词往往是在第二个音节上重读。双音节词的重读位置不会因增加前缀或后缀而发生改变。</p><p><strong>例如：</strong>a-bout，be-fore，ex-cuse，re-pair，for-get-ful，in-ven-tor</p><h3 id="7-多音节词重读规则"><a href="#7-多音节词重读规则" class="headerlink" title="7. 多音节词重读规则"></a>7. 多音节词重读规则</h3><p>多音节词通常在倒数第三个音节重读。</p><p><strong>例如：</strong> el-e-phant</p><p>词尾有-ic 或-tion,-sion 的词，在-ic或-sion,-tion前的一个音节上重读。</p><p><strong>例如：</strong>scien-tific，im-pression，na-tion</p><h2 id="英语单词拼读规则"><a href="#英语单词拼读规则" class="headerlink" title="英语单词拼读规则"></a>英语单词拼读规则</h2><h3 id="1、元音字母在重读音节中的读音"><a href="#1、元音字母在重读音节中的读音" class="headerlink" title="1、元音字母在重读音节中的读音"></a>1、元音字母在重读音节中的读音</h3><table><thead><tr><th>元音字母</th><th></th><th>读 音</th><th>例 词</th></tr></thead><tbody><tr><td>a</td><td>在开音节中</td><td>[ei]</td><td>name plane Jane baby cake</td></tr><tr><td></td><td>在闭音节中</td><td>[æ]</td><td>bag dad hat map black back</td></tr><tr><td>e</td><td>在开音节中</td><td>[i:]</td><td>he these me Chinese</td></tr><tr><td></td><td>在闭音节中</td><td>[e]</td><td>bed let pen desk yes egg</td></tr><tr><td>i</td><td>在开音节中</td><td>[ai]</td><td>bike fly drive time nice kite</td></tr><tr><td></td><td>在闭音节中</td><td>[i]</td><td>fish big drink sit milk swim</td></tr><tr><td>o</td><td>在开音节中</td><td>[əu]</td><td>those close go hoe home no</td></tr><tr><td></td><td>在闭音节中</td><td>[ɔ]</td><td>clock not box shop sock</td></tr><tr><td>u</td><td>在开音节中</td><td>[ju:]</td><td>student excuse duty Tuesday</td></tr><tr><td></td><td>在闭音节中</td><td>[ʌ]</td><td>bus cup jump much lunch</td></tr></tbody></table><blockquote><p>在开音节中，元音字母u在辅音字母j l r s后面时读[u:]音，例如：June blue ruler super</p></blockquote><h3 id="2、元音字母在非重读音节中的读音"><a href="#2、元音字母在非重读音节中的读音" class="headerlink" title="2、元音字母在非重读音节中的读音"></a>2、元音字母在非重读音节中的读音</h3><table><thead><tr><th>元音字母</th><th>读 音</th><th>例 词</th></tr></thead><tbody><tr><td>a</td><td>[ə]</td><td>China another woman breakfast</td></tr><tr><td></td><td>[i]</td><td>orange comrade village cabbage</td></tr><tr><td>e</td><td>[ə]</td><td>hundred student open weekend</td></tr><tr><td></td><td>[i]</td><td>chicken pocket begin children</td></tr><tr><td>i</td><td>[i]</td><td>holiday beautiful family animal</td></tr><tr><td></td><td>[ai]</td><td>exercise satellite</td></tr><tr><td>o</td><td>[ə]</td><td>second tonight somebody welcome</td></tr><tr><td></td><td>[əu]</td><td>also zero photo</td></tr><tr><td>u</td><td>[ə]</td><td>autumn diffcult</td></tr><tr><td></td><td>[ju:]</td><td>popular congratulation January</td></tr></tbody></table><blockquote><p>动词中的a如果处在开音节位置，a读[ei]音，例如：operate</p></blockquote><blockquote><p>u处在开音节位置，又在辅音字母j l r s后面时，读[u(:)]音，例如：July influence February issue</p></blockquote><blockquote><p>在非重读音节中，许多单词中的元音字母a e i 既可以读作[ə]音可以读作[i]音。</p></blockquote><h3 id="3、元音字母在重读音节中的特殊读音"><a href="#3、元音字母在重读音节中的特殊读音" class="headerlink" title="3、元音字母在重读音节中的特殊读音"></a>3、元音字母在重读音节中的特殊读音</h3><table><thead><tr><th>元音字母</th><th>读 音</th><th>例 词</th></tr></thead><tbody><tr><td>a在[w]音后面</td><td>[ɔ]</td><td>want what watch wash quality</td></tr><tr><td>a在f n sk ph sp ss st th前</td><td>[α:]</td><td>after plant graph ask grasp glass fast father</td></tr><tr><td>i在-nd -ld和gh前</td><td>[ai]</td><td>find child light high</td></tr><tr><td>o在-st -ld前</td><td>[əu]</td><td>most postcard old cold</td></tr><tr><td>o在m n v th前</td><td>[ʌ]</td><td>come monkey love mother</td></tr></tbody></table><h3 id="4、-r音节元音字组在重读音节中的读音"><a href="#4、-r音节元音字组在重读音节中的读音" class="headerlink" title="4、-r音节元音字组在重读音节中的读音"></a>4、-r音节元音字组在重读音节中的读音</h3><table><thead><tr><th>元音字组</th><th>读 音</th><th>例 词</th></tr></thead><tbody><tr><td>ar</td><td>[α:]</td><td>car farm dark sharpener</td></tr><tr><td>ar在[w]音后面</td><td>[ɔ:]</td><td>warm quarter towards</td></tr><tr><td>or</td><td>[ɔ:]</td><td>forty morning short</td></tr><tr><td>or在[w]音后面</td><td>[ə:]</td><td>word worker worse</td></tr><tr><td>er ir ur</td><td>[ə:]</td><td>certainly bird Thursday</td></tr></tbody></table><blockquote><p>辅音字母r双写时，前面的元音字母不能与r构成-r音节,而是按重读闭音节的拼读规则发音。例如：carry sorry hurry </p></blockquote><blockquote><p>-r音节在非重读音节中通常读[ə]音，例如：dollar teacher forget Saturday </p></blockquote><h3 id="5、-re音节元音字组在重读音节中的读音"><a href="#5、-re音节元音字组在重读音节中的读音" class="headerlink" title="5、-re音节元音字组在重读音节中的读音"></a>5、-re音节元音字组在重读音节中的读音</h3><table><thead><tr><th>元音字组</th><th>读 音</th><th>例 词</th></tr></thead><tbody><tr><td>are</td><td>[εə]</td><td>care dare hare</td></tr><tr><td>ere</td><td>[iə]</td><td>here mere</td></tr><tr><td>ire</td><td>[aiə]</td><td>fire hire wire</td></tr><tr><td>ore</td><td>[ɔ:]</td><td>more score before</td></tr><tr><td>ure</td><td>[juə]</td><td>pure cure</td></tr></tbody></table><blockquote><p>are ere ire ore很少出现在非重读音节中，ure在非重读音节中读[tʃə]音或[ʒə]，例如：picture pleasure</p></blockquote><blockquote><p>重读元音字母加Rr，再加非重读元字组时，重读元音字母应按-re音节拼读规则拼读，字母Rr读[r]音。 例如：parent zero story during inspiring　</p></blockquote><blockquote><p>某些常用词及多音节词经常出现长音短化现象。 例如：orange very American paragraph</p></blockquote><h3 id="6、元音字组在重读音节中的读音"><a href="#6、元音字组在重读音节中的读音" class="headerlink" title="6、元音字组在重读音节中的读音"></a>6、元音字组在重读音节中的读音</h3><table><thead><tr><th>元音字组</th><th>读 音</th><th>例 词</th></tr></thead><tbody><tr><td>ai&#x2F;ay</td><td>[ei]</td><td>afraid rain wait day play</td></tr><tr><td>air</td><td>[εə]</td><td>air hair chair pair repair</td></tr><tr><td>al</td><td>[ɔ:l]</td><td>small ball talk wall all</td></tr><tr><td>al在f m前</td><td>[ɔ:l]</td><td>always also salt almost</td></tr><tr><td></td><td>[α:]</td><td>half calm</td></tr><tr><td>au&#x2F;aw</td><td>[ɔ:]</td><td>autumn daughter draw</td></tr><tr><td>ea</td><td>[i:]</td><td>teach easy cheap please</td></tr><tr><td></td><td>[e]</td><td>heavy bread sweater weather</td></tr><tr><td></td><td>[ei]</td><td>break great</td></tr><tr><td>ear</td><td>[iə]</td><td>hear dear near clear year</td></tr><tr><td></td><td>[εə]</td><td>bear pear wear swear</td></tr><tr><td></td><td>[ə:]</td><td>earth learn early</td></tr><tr><td>ee</td><td>[i:]</td><td>jeep week green three</td></tr><tr><td>eer</td><td>[iə]</td><td>pioneer deer beer</td></tr><tr><td>ei&#x2F;ey</td><td>[ei]</td><td>eight neighbour they</td></tr><tr><td></td><td>[i:]</td><td>either  key</td></tr><tr><td>eu&#x2F;ew在 j l r s后</td><td>[ju:]</td><td>new few newspaper</td></tr><tr><td></td><td>[u:]</td><td>flew brew jewelry</td></tr><tr><td>ie&#x2F;ei[s]音之后</td><td>[i:]</td><td>piece field receive</td></tr><tr><td>oa</td><td>[əu]</td><td>coat Joan boat goal</td></tr><tr><td>oar&#x2F;oor</td><td>[ɔ:]</td><td>roar board door floor</td></tr><tr><td>oi&#x2F;oy</td><td>[ɔi]</td><td>noise point boy toilet</td></tr><tr><td>oo</td><td>[u:]</td><td>broom food tooth school</td></tr><tr><td></td><td>[u]</td><td>book look cook foot good</td></tr><tr><td>ou&#x2F;ow</td><td>[au]</td><td>flower house count down</td></tr><tr><td></td><td>[əu]</td><td>know row throw though</td></tr><tr><td></td><td>[ʌ]</td><td>young country enough</td></tr><tr><td></td><td>[u:]</td><td>group you soup</td></tr><tr><td>our</td><td>[ɔ:]</td><td>course your four</td></tr><tr><td></td><td>[auə]</td><td>our hour ours</td></tr><tr><td></td><td>[ə:]</td><td>journey</td></tr><tr><td>ui 在j l r s后</td><td>[ju:i]</td><td>fluid suicide tuition suit</td></tr><tr><td></td><td>[u:]</td><td>juice fruit</td></tr></tbody></table><h3 id="7、非重读音节中元音字组和字群的读音"><a href="#7、非重读音节中元音字组和字群的读音" class="headerlink" title="7、非重读音节中元音字组和字群的读音"></a>7、非重读音节中元音字组和字群的读音</h3><table><thead><tr><th>元音字组或字群</th><th>读 音</th><th>例 词</th></tr></thead><tbody><tr><td>ai&#x2F;ay ei&#x2F;ey</td><td>[i]</td><td>Sunday foreign monkey</td></tr><tr><td>ow</td><td>[əu]</td><td>yellow sparrow tomorrow</td></tr><tr><td>-sion -tion</td><td>[ʃən]</td><td>impression nation</td></tr><tr><td>-sion在元音字母后</td><td>[ʒən]</td><td>vision decision occasion</td></tr><tr><td>-tion在s后</td><td>[tʃən]</td><td>question suggestion</td></tr><tr><td>-sten</td><td>[sn]</td><td>listen</td></tr><tr><td>-stle</td><td>[sl]</td><td>whistle</td></tr><tr><td>-sure</td><td>[ʒə]</td><td>pleasure measure</td></tr><tr><td>-ture</td><td>[tʃə]</td><td>picture culture</td></tr></tbody></table><h3 id="8、元字组在复合词非重读音节中的读音"><a href="#8、元字组在复合词非重读音节中的读音" class="headerlink" title="8、元字组在复合词非重读音节中的读音"></a>8、元字组在复合词非重读音节中的读音</h3><table><thead><tr><th></th></tr></thead><tbody><tr><td>复合词中的第二部分不标注重音符号，但其中的元音字母或元音字组仍按重读音节拼读规则拼读。 <br />例如：<br />everyday[ei]<br />handbag[æ]<br />blackboard[ɔ:]</td></tr><tr><td>有些词随着语言的发展，前后两部分已失去其单独存在的意义，融合成为一个词。其中的非重读部分要按非重读音节的读音规则发音。 <br />例如：<br />sun太阳 + day[ei]日子 ＞ Sunday[i] 星期天<br />holy神圣 + day[ei]日子 ＞ holiday[i] 假日<br />break中断 + fast[a:]斋戒 ＞ breakfast[ə]早餐<br />cup茶杯 + board木板[ɔ:] ＞ cupboard[ɔ:] 碗柜</td></tr></tbody></table><h3 id="9、辅字组的读音"><a href="#9、辅字组的读音" class="headerlink" title="9、辅字组的读音"></a>9、辅字组的读音</h3><table><thead><tr><th>辅字组</th><th>读 音</th><th>例 词</th></tr></thead><tbody><tr><td>b</td><td>[b]</td><td>bike bus bag</td></tr><tr><td></td><td>[&#x2F;]</td><td>bomb tomb</td></tr><tr><td>c</td><td>[k]</td><td>cake picture coat music</td></tr><tr><td>c在e前或在i&#x2F;y前</td><td>[s]</td><td>face decide cinema</td></tr><tr><td>ch</td><td>[tʃ]</td><td>much chick rich teacher</td></tr><tr><td></td><td>[k]</td><td>school headache chemistry</td></tr><tr><td></td><td>[ʃ]</td><td>machine Chicago</td></tr><tr><td>-ck</td><td>[k]</td><td>cock pocket black knock</td></tr><tr><td>d</td><td>[d]</td><td>doctor bread hand day</td></tr><tr><td>-dge</td><td>[dʒ]</td><td>bridge fridge</td></tr><tr><td>dr-</td><td>[dr]</td><td>children driver drink</td></tr><tr><td>f</td><td>[f]</td><td>five four breakfast</td></tr><tr><td>g</td><td>[g]</td><td>bag gardon go</td></tr><tr><td>g在e i&#x2F;y前</td><td>[dʒ]</td><td>orange large German</td></tr><tr><td>gh</td><td>[f]</td><td>cough enough photo</td></tr><tr><td></td><td>[&#x2F;]</td><td>light daughter high</td></tr><tr><td>gu- -gue gu在非重读音节中</td><td>[g]</td><td>guess league dialogue</td></tr><tr><td></td><td>[gw]</td><td>language anguish</td></tr><tr><td>h</td><td>[h]</td><td>hot head house hand</td></tr><tr><td></td><td>[&#x2F;]</td><td>hour honest</td></tr><tr><td>j</td><td>[dʒ]</td><td>jeep jar joke join July</td></tr><tr><td>k</td><td>[k]</td><td>kind bike skate make week</td></tr><tr><td>kn-</td><td>[n]</td><td>knife know knock</td></tr><tr><td>l</td><td>[l]</td><td>life milk school tall</td></tr><tr><td>m</td><td>[m]</td><td>monkey come autumn</td></tr><tr><td>-mn</td><td>[m]</td><td>autumn column solemn</td></tr><tr><td>n</td><td>[n]</td><td>not shine ten note</td></tr><tr><td>n在[k] [g]音前</td><td>[ŋ]</td><td>uncle thank hungry</td></tr><tr><td>-ng</td><td>[n]</td><td>morning young wrong</td></tr><tr><td>p</td><td>[p]</td><td>paper plane pig ship pen</td></tr><tr><td>ph</td><td>[f]</td><td>elephant photo telephone</td></tr><tr><td>q</td><td>[k]</td><td>Iraq</td></tr><tr><td>qu-</td><td>[kw]</td><td>quality quite</td></tr><tr><td>r</td><td>[r]</td><td>red rubber ruler</td></tr><tr><td>s在词首或清辅音前</td><td>[s]</td><td>sit sleep desk</td></tr><tr><td>在元音字母间或浊辅音前</td><td>[z]</td><td>music husband</td></tr><tr><td>sc-</td><td>[sk]</td><td>scar</td></tr><tr><td></td><td>[s]</td><td>muscle science</td></tr><tr><td>sh</td><td>[ʃ]</td><td>she fish shirt wash</td></tr><tr><td>t在通常情况下</td><td>[t]</td><td>ten letter meet</td></tr><tr><td>在弱读字母ia ie io前</td><td>[ʃ]</td><td>patient nation</td></tr><tr><td>tch</td><td>[tʃ]</td><td>watch</td></tr><tr><td>th在通常情况下</td><td>[θ]</td><td>thin thirty method</td></tr><tr><td>在冠词 代词 介词 连词中</td><td>[ð]</td><td>the these with than</td></tr><tr><td>在词尾-the -ther中</td><td>[ð]</td><td>clothe father weather</td></tr><tr><td>tr-</td><td>[tr]</td><td>tree train country truck</td></tr><tr><td>v</td><td>[v]</td><td>very voice love leave</td></tr><tr><td>w</td><td>[w]</td><td>week win wake sweet wait</td></tr><tr><td></td><td>[&#x2F;]</td><td>answer two</td></tr><tr><td>wh-</td><td>[w]</td><td>what when white why</td></tr><tr><td>wh-在字母o前</td><td>[h]</td><td>who whose whole</td></tr><tr><td>x</td><td>[ks]</td><td>box text exercise</td></tr><tr><td>在重读元音前</td><td>[gz]</td><td>examle exist exact</td></tr><tr><td>wr</td><td>-[r]</td><td>write</td></tr><tr><td>y</td><td>-[j]</td><td>yes yard yellow young</td></tr><tr><td>z</td><td>[z]</td><td>puzzle zero zoo</td></tr></tbody></table>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;整理一下英语的音标、音节划分、自然拼读规则，方便娃在学习的过程中可以快速查找、记忆以及巩固相关知识点。&lt;/p&gt;
&lt;h2 id=&quot;英语音标：48个国际音标&quot;&gt;&lt;a href=&quot;#英语音标：48个国际音标&quot; class=&quot;headerlink&quot; title=&quot;英语音标：48个国</summary>
      
    
    
    
    <category term="幼苗成长" scheme="https://www.xdxmblog.cn/categories/%E5%B9%BC%E8%8B%97%E6%88%90%E9%95%BF/"/>
    
    
    <category term="英语启蒙" scheme="https://www.xdxmblog.cn/tags/%E8%8B%B1%E8%AF%AD%E5%90%AF%E8%92%99/"/>
    
  </entry>
  
  <entry>
    <title>基于Vue3和Element Plus实现Tab导航栏</title>
    <link href="https://www.xdxmblog.cn/posts/47739.html"/>
    <id>https://www.xdxmblog.cn/posts/47739.html</id>
    <published>2025-11-27T04:17:33.000Z</published>
    <updated>2025-11-27T04:17:33.000Z</updated>
    
    <content type="html"><![CDATA[<p>在开发后台管理系统的时候，我们通常会在脚手架的基础上，利用UI框架快速搭建起Layout模板，而导航栏便是Layout里最常用的一个组件之一了。这篇文章主要用<strong>Vue + Vue Router + Pinia + Element Plus</strong>来快速实现一套Tab导航栏。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_47739_01.webp" alt="效果预览"></p><h2 id="前置准备"><a href="#前置准备" class="headerlink" title="前置准备"></a>前置准备</h2><p>首先要保证你的项目中已经安装了以下依赖，它们是实现Tab导航栏必不可缺的一部分。</p><figure class="highlight json"><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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>  <span class="hljs-attr">&quot;dependencies&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;@element-plus/icons-vue&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;^2.3.2&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;element-plus&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;^2.11.8&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;pinia&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;^3.0.4&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;vue&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;^3.5.24&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;vue-router&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;^4.6.3&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>如果你是从头创建一个后台管理系统，你可以使用以下命令来初始化项目。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">pnpm create vite@latest<br></code></pre></td></tr></table></figure><p>然后你会得到以下的项目结构：</p><figure class="highlight text"><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><code class="hljs text">vite-project/<br>├── public/                 # 静态资源目录<br>├── src/                    # 源代码目录<br>│   ├── assets/            # 项目资源文件<br>│   ├── components/        # 公共组件<br>│   ├── App.vue          # 根组件<br>│   ├── style.css         # 全局样式文件<br>│   └── main.js            # 入口文件<br>├── .gitignore             # Git忽略文件<br>├── index.html            # HTML模板<br>├── package.json           # 项目配置<br>├── vite.config.js         # Vite配置<br>└── README.md             # 项目说明<br></code></pre></td></tr></table></figure><p>接着安装上述依赖</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">pnpm install vue vue-router pinia element-plus @element-plus/icons-vue<br></code></pre></td></tr></table></figure><h2 id="开发Tab导航栏"><a href="#开发Tab导航栏" class="headerlink" title="开发Tab导航栏"></a>开发Tab导航栏</h2><h3 id="框架基本改造"><a href="#框架基本改造" class="headerlink" title="框架基本改造"></a>框架基本改造</h3><p>首先我们对脚手架生成的项目进行基本的改造，引入我们安装好的依赖。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">/* src/main.js */</span><br><br><span class="hljs-keyword">import</span> &#123; createApp &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;vue&#x27;</span><br><span class="hljs-keyword">import</span> &#123; createPinia &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;pinia&#x27;</span><br><span class="hljs-keyword">import</span> router <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./router&#x27;</span><br><span class="hljs-keyword">import</span> <span class="hljs-title class_">App</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./App.vue&#x27;</span><br><br><span class="hljs-keyword">import</span> <span class="hljs-title class_">ElementPlus</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;element-plus&#x27;</span><br><br><span class="hljs-keyword">import</span> <span class="hljs-string">&#x27;./style.css&#x27;</span><br><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> <span class="hljs-title class_">ElementPlusIconsVue</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@element-plus/icons-vue&#x27;</span><br><br><span class="hljs-keyword">const</span> app = <span class="hljs-title function_">createApp</span>(<span class="hljs-title class_">App</span>)<br><span class="hljs-keyword">const</span> pinia = <span class="hljs-title function_">createPinia</span>()<br><br><span class="hljs-comment">// 全局注册所有图标</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> [key, component] <span class="hljs-keyword">of</span> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">entries</span>(<span class="hljs-title class_">ElementPlusIconsVue</span>)) &#123;<br>  app.<span class="hljs-title function_">component</span>(key, component)<br>&#125;<br><br>app.<span class="hljs-title function_">use</span>(pinia)<br>app.<span class="hljs-title function_">use</span>(router)<br>app.<span class="hljs-title function_">use</span>(<span class="hljs-title class_">ElementPlus</span>, &#123; <span class="hljs-attr">size</span>: <span class="hljs-string">&#x27;small&#x27;</span>, <span class="hljs-attr">zIndex</span>: <span class="hljs-number">3000</span> &#125;)<br>app.<span class="hljs-title function_">mount</span>(<span class="hljs-string">&#x27;#app&#x27;</span>)<br></code></pre></td></tr></table></figure><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><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs css">// <span class="hljs-attribute">src</span>/style<span class="hljs-selector-class">.css</span><br><span class="hljs-selector-tag">body</span> &#123;<br>  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>; <br>&#125;<br></code></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-comment">&lt;!-- src/App.vue --&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">router-view</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">router-view</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span><br></code></pre></td></tr></table></figure><h3 id="编写路由"><a href="#编写路由" class="headerlink" title="编写路由"></a>编写路由</h3><p>接着我们在src目录下新建router文件夹，用来存放我们的路由文件。这里我们通过<code>meta</code>对象的<code>hidden</code>属性来决定该路由是否在菜单栏中隐藏。通过<code>isShow</code>属性来决定该路由是否为一级菜单。</p><figure class="highlight javascript"><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><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// src/router/index.js</span><br><span class="hljs-keyword">import</span> &#123; createWebHashHistory, createRouter &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;vue-router&#x27;</span><br><span class="hljs-keyword">import</span> &#123; useNavStore &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@/stores/nav&#x27;</span><br><br><span class="hljs-keyword">const</span> routes = [<br>  &#123;<br>    <span class="hljs-attr">path</span>: <span class="hljs-string">&#x27;/login&#x27;</span>,<br>    <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;login&#x27;</span>,<br>    <span class="hljs-attr">component</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&#x27;@/views/login/index.vue&#x27;</span>),<br>    <span class="hljs-attr">meta</span>: &#123;<br>      <span class="hljs-attr">title</span>: <span class="hljs-string">&#x27;登录&#x27;</span>,<br>      <span class="hljs-attr">hidden</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// 不在菜单中显示</span><br>    &#125;,<br>  &#125;,<br>  &#123;<br>    <span class="hljs-attr">path</span>: <span class="hljs-string">&#x27;/&#x27;</span>,<br>    <span class="hljs-attr">component</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&#x27;@/layout/index.vue&#x27;</span>),<br>    <span class="hljs-attr">redirect</span>: <span class="hljs-string">&#x27;/home&#x27;</span>,<br>    <span class="hljs-attr">meta</span>: &#123;<br>      <span class="hljs-attr">title</span>: <span class="hljs-string">&#x27;根目录&#x27;</span>,<br>      <span class="hljs-attr">hidden</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// 不在菜单中显示</span><br>    &#125;,<br>    <span class="hljs-attr">children</span>: [<br>      &#123;<br>        <span class="hljs-attr">path</span>: <span class="hljs-string">&#x27;/home&#x27;</span>,<br>        <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;home&#x27;</span>,<br>        <span class="hljs-attr">component</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&#x27;@/views/home/index.vue&#x27;</span>),<br>        <span class="hljs-attr">meta</span>: &#123;<br>          <span class="hljs-attr">title</span>: <span class="hljs-string">&#x27;首页&#x27;</span>,<br>          <span class="hljs-attr">icon</span>: <span class="hljs-string">&#x27;HomeFilled&#x27;</span>,<br>          <span class="hljs-attr">isShow</span>: <span class="hljs-literal">true</span>,<br>        &#125;,<br>      &#125;,<br>      &#123;<br>        <span class="hljs-attr">path</span>: <span class="hljs-string">&#x27;/system&#x27;</span>,<br>        <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;system&#x27;</span>,<br>        <span class="hljs-attr">meta</span>: &#123;<br>          <span class="hljs-attr">title</span>: <span class="hljs-string">&#x27;系统管理&#x27;</span>,<br>          <span class="hljs-attr">icon</span>: <span class="hljs-string">&#x27;Setting&#x27;</span>,<br>          <span class="hljs-attr">isShow</span>: <span class="hljs-literal">true</span>,<br>        &#125;,<br>        <span class="hljs-attr">children</span>: [<br>          &#123;<br>            <span class="hljs-attr">path</span>: <span class="hljs-string">&#x27;/system/user&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;user&#x27;</span>,<br>            <span class="hljs-attr">component</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&#x27;@/views/system/user/index.vue&#x27;</span>),<br>            <span class="hljs-attr">meta</span>: &#123;<br>              <span class="hljs-attr">title</span>: <span class="hljs-string">&#x27;用户管理&#x27;</span>,<br>              <span class="hljs-attr">icon</span>: <span class="hljs-string">&#x27;User&#x27;</span>,<br>            &#125;,<br>          &#125;,<br>          &#123;<br>            <span class="hljs-attr">path</span>: <span class="hljs-string">&#x27;/system/role&#x27;</span>,<br>            <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;role&#x27;</span>,<br>            <span class="hljs-attr">component</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&#x27;@/views/system/role/index.vue&#x27;</span>),<br>            <span class="hljs-attr">meta</span>: &#123;<br>              <span class="hljs-attr">title</span>: <span class="hljs-string">&#x27;角色管理&#x27;</span>,<br>              <span class="hljs-attr">icon</span>: <span class="hljs-string">&#x27;UserFilled&#x27;</span>,<br>            &#125;,<br>          &#125;,<br>        ],<br>      &#125;,<br>    ],<br>  &#125;,<br>  &#123;<br>    <span class="hljs-attr">path</span>: <span class="hljs-string">&#x27;/404&#x27;</span>,<br>    <span class="hljs-attr">name</span>: <span class="hljs-string">&#x27;404&#x27;</span>,<br>    <span class="hljs-attr">component</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&#x27;@/views/404/index.vue&#x27;</span>),<br>    <span class="hljs-attr">meta</span>: &#123;<br>      <span class="hljs-attr">title</span>: <span class="hljs-string">&#x27;404&#x27;</span>,<br>      <span class="hljs-attr">hidden</span>: <span class="hljs-literal">true</span>,<br>    &#125;,<br>  &#125;,<br>]<br><br><span class="hljs-keyword">const</span> router = <span class="hljs-title function_">createRouter</span>(&#123;<br>  <span class="hljs-attr">history</span>: <span class="hljs-title function_">createWebHashHistory</span>(),<br>  routes,<br>&#125;)<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router<br></code></pre></td></tr></table></figure><h3 id="编写Layout组件"><a href="#编写Layout组件" class="headerlink" title="编写Layout组件"></a>编写Layout组件</h3><p>在<code>src</code>目录下新建<code>layout</code>文件夹，用来存放后台管理系统的框架。同时在<code>layout</code>目录下新建<code>components</code>文件夹，用来存放layout相关的组件。这里我们的layout组件的布局采用较为常见的上，下（左右）结构。</p><blockquote><p>我的项目用了sass，所以style lang&#x3D;”scss”，没用sass的话把lang属性去掉，然后把层级嵌套去掉即可。</p></blockquote><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><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></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-comment">&lt;!-- src/layout/index.vue --&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;common-layout&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">el-container</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;layout-container&quot;</span>&gt;</span><br>      <span class="hljs-tag">&lt;<span class="hljs-name">el-aside</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;layout-aside&quot;</span> <span class="hljs-attr">:width</span>=<span class="hljs-string">&quot;isCollapse ? &#x27;64px&#x27; : &#x27;200px&#x27;&quot;</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">Sidebar</span> <span class="hljs-attr">:is-collapse</span>=<span class="hljs-string">&quot;isCollapse&quot;</span> /&gt;</span><br>      <span class="hljs-tag">&lt;/<span class="hljs-name">el-aside</span>&gt;</span><br>      <span class="hljs-tag">&lt;<span class="hljs-name">el-container</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">el-header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;layout-header&quot;</span>&gt;</span><br>          <span class="hljs-tag">&lt;<span class="hljs-name">HeaderBar</span> <span class="hljs-attr">:is-collapse</span>=<span class="hljs-string">&quot;isCollapse&quot;</span> @<span class="hljs-attr">change-collapse</span>=<span class="hljs-string">&quot;updateIsCollapse&quot;</span> /&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">el-header</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">el-main</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;layout-main&quot;</span>&gt;</span><br>          <span class="hljs-tag">&lt;<span class="hljs-name">NavTabs</span> /&gt;</span><br>          <span class="hljs-tag">&lt;<span class="hljs-name">el-container</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;layout-content&quot;</span>&gt;</span><br>            <span class="hljs-tag">&lt;<span class="hljs-name">router-view</span> /&gt;</span><br>          <span class="hljs-tag">&lt;/<span class="hljs-name">el-container</span>&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">el-main</span>&gt;</span><br>      <span class="hljs-tag">&lt;/<span class="hljs-name">el-container</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">el-container</span>&gt;</span><br>  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span><br><br><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">setup</span>&gt;</span><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-keyword">import</span> &#123; ref &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;vue&#x27;</span></span><br><span class="language-javascript"><span class="hljs-keyword">import</span> <span class="hljs-title class_">HeaderBar</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./components/HeaderBar/index.vue&#x27;</span></span><br><span class="language-javascript"><span class="hljs-keyword">import</span> <span class="hljs-title class_">Sidebar</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./components/Sidebar/index.vue&#x27;</span></span><br><span class="language-javascript"><span class="hljs-keyword">import</span> <span class="hljs-title class_">NavTabs</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./components/NavTabs/index.vue&#x27;</span></span><br><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-comment">// 侧边栏折叠状态</span></span><br><span class="language-javascript"><span class="hljs-keyword">const</span> isCollapse = <span class="hljs-title function_">ref</span>(<span class="hljs-literal">false</span>)</span><br><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-keyword">const</span> <span class="hljs-title function_">updateIsCollapse</span> = value =&gt; &#123;</span><br><span class="language-javascript">  isCollapse.<span class="hljs-property">value</span> = value</span><br><span class="language-javascript">&#125;</span><br><span class="language-javascript"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span><br><br><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">&quot;scss&quot;</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="language-css"></span><br><span class="language-css"><span class="hljs-selector-class">.common-layout</span> &#123;</span><br><span class="language-css">  <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;</span><br><span class="language-css"></span><br><span class="language-css">  <span class="hljs-selector-class">.layout-container</span> &#123;</span><br><span class="language-css">    <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;</span><br><span class="language-css"></span><br><span class="language-css">    <span class="hljs-selector-class">.layout-header</span> &#123;</span><br><span class="language-css">      <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fff</span>;</span><br><span class="language-css">      <span class="hljs-attribute">border-bottom</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#e6e6e6</span>;</span><br><span class="language-css">    &#125;</span><br><span class="language-css"></span><br><span class="language-css">    <span class="hljs-selector-class">.layout-aside</span> &#123;</span><br><span class="language-css">      <span class="hljs-attribute">transition</span>: width <span class="hljs-number">0.3s</span> ease;</span><br><span class="language-css">      <span class="hljs-attribute">overflow</span>: hidden;</span><br><span class="language-css">      <span class="hljs-attribute">overflow-y</span>: auto;</span><br><span class="language-css">    &#125;</span><br><span class="language-css"></span><br><span class="language-css">    <span class="hljs-selector-class">.layout-main</span> &#123;</span><br><span class="language-css">      <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span> <span class="hljs-number">20px</span>;</span><br><span class="language-css">      <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f5f7f9</span>;</span><br><span class="language-css">      <span class="hljs-attribute">overflow</span>: hidden;</span><br><span class="language-css"></span><br><span class="language-css">      <span class="hljs-selector-class">.layout-content</span> &#123;</span><br><span class="language-css">        <span class="hljs-attribute">padding</span>: <span class="hljs-number">15px</span>;</span><br><span class="language-css">        <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fff</span>;</span><br><span class="language-css">        <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;</span><br><span class="language-css">        <span class="hljs-attribute">overflow-y</span>: auto;</span><br><span class="language-css">      &#125;</span><br><span class="language-css">    &#125;</span><br><span class="language-css">  &#125;</span><br><span class="language-css">&#125;</span><br><span class="language-css"></span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span><br></code></pre></td></tr></table></figure><h3 id="编写Store数据"><a href="#编写Store数据" class="headerlink" title="编写Store数据"></a>编写Store数据</h3><p>Layout组件编写好以后，我们来编写Store数据，这里我们没有用<code>Vuex</code>，而是选用<code>pinia</code>，毕竟使用起来比<code>Vuex</code>顺手多了，而且不需要再写那么多的冗余操作。这里的Store主要用于菜单栏和Tab导航栏之间的数据通信。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// src/stores/nav.js</span><br><span class="hljs-keyword">import</span> &#123; ref &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;vue&#x27;</span><br><span class="hljs-keyword">import</span> &#123; defineStore &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;pinia&#x27;</span><br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useNavStore = <span class="hljs-title function_">defineStore</span>(<span class="hljs-string">&#x27;nav&#x27;</span>, <span class="hljs-function">() =&gt;</span> &#123;<br>  <span class="hljs-keyword">const</span> tabList = <span class="hljs-title function_">ref</span>([&#123; <span class="hljs-attr">meta</span>: &#123; <span class="hljs-attr">title</span>: <span class="hljs-string">&#x27;首页&#x27;</span>, <span class="hljs-attr">icon</span>: <span class="hljs-string">&#x27;HomeFilled&#x27;</span> &#125;, <span class="hljs-attr">path</span>: <span class="hljs-string">&#x27;/home&#x27;</span> &#125;])<br>  <span class="hljs-keyword">const</span> tabActivePath = <span class="hljs-title function_">ref</span>(<span class="hljs-string">&#x27;/home&#x27;</span>)<br><br>  <span class="hljs-keyword">function</span> <span class="hljs-title function_">addTab</span>(<span class="hljs-params">tabItem</span>) &#123;<br>    <span class="hljs-keyword">const</span> hasTab = tabList.<span class="hljs-property">value</span>.<span class="hljs-title function_">find</span>(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> item.<span class="hljs-property">path</span> == tabItem.<span class="hljs-property">path</span>)<br><br>    <span class="hljs-keyword">if</span> (!hasTab) &#123;<br>      tabList.<span class="hljs-property">value</span>.<span class="hljs-title function_">push</span>(tabItem)<br>    &#125;<br><br>    tabActivePath.<span class="hljs-property">value</span> = tabItem.<span class="hljs-property">path</span><br>  &#125;<br><br>  <span class="hljs-keyword">function</span> <span class="hljs-title function_">removeTab</span>(<span class="hljs-params">path</span>) &#123;<br>    <span class="hljs-keyword">if</span> (path == <span class="hljs-string">&#x27;/home&#x27;</span>) <span class="hljs-keyword">return</span><br><br>    <span class="hljs-keyword">const</span> curTabIndex = tabList.<span class="hljs-property">value</span>.<span class="hljs-title function_">findIndex</span>(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> item.<span class="hljs-property">path</span> == path)<br>    tabList.<span class="hljs-property">value</span>.<span class="hljs-title function_">splice</span>(curTabIndex, <span class="hljs-number">1</span>)<br>  &#125;<br><br>  <span class="hljs-keyword">return</span> &#123; tabList, tabActivePath, addTab, removeTab &#125;<br>&#125;)<br><br></code></pre></td></tr></table></figure><h3 id="编写菜单栏组件"><a href="#编写菜单栏组件" class="headerlink" title="编写菜单栏组件"></a>编写菜单栏组件</h3><p>菜单栏主要用到element-plus的<code>el-menu</code>组件，子菜单我们通过编写<code>SidebarItme</code>组件递归调用，将路由文件里的children数据传递到子组件里。 </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><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><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-comment">&lt;!-- src/layout/components/Sidebar/index.vue --&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;sidebar-container&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;sidebar-logo&quot;</span>&gt;</span><br>      <span class="hljs-comment">&lt;!-- &lt;img src=&quot;@/assets/logo.png&quot; alt=&quot;logo&quot; class=&quot;logo-icon&quot; /&gt; --&gt;</span><br>      <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;sidebar-title&quot;</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">&quot;!isCollapse&quot;</span>&gt;</span>后台管理系统<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">el-menu</span> <span class="hljs-attr">:default-active</span>=<span class="hljs-string">&quot;activeMenu&quot;</span> <span class="hljs-attr">:unique-opened</span>=<span class="hljs-string">&quot;true&quot;</span> <span class="hljs-attr">:collapse</span>=<span class="hljs-string">&quot;props.isCollapse&quot;</span> <span class="hljs-attr">router</span>&gt;</span><br>      <span class="hljs-tag">&lt;<span class="hljs-name">sidebar-item</span> <span class="hljs-attr">:router-list</span>=<span class="hljs-string">&quot;menuRoutes&quot;</span> <span class="hljs-attr">:base-path</span>=<span class="hljs-string">&quot;&#x27;/&#x27;&quot;</span> /&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">el-menu</span>&gt;</span><br>  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span><br><br><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">setup</span>&gt;</span><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-keyword">import</span> &#123; computed &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;vue&#x27;</span></span><br><span class="language-javascript"><span class="hljs-keyword">import</span> &#123; useRoute, useRouter &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;vue-router&#x27;</span></span><br><span class="language-javascript"><span class="hljs-keyword">import</span> <span class="hljs-title class_">SidebarItem</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./SidebarItem.vue&#x27;</span></span><br><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-keyword">const</span> props = <span class="hljs-title function_">defineProps</span>(&#123;</span><br><span class="language-javascript">  <span class="hljs-attr">isCollapse</span>: &#123;</span><br><span class="language-javascript">    <span class="hljs-attr">type</span>: <span class="hljs-title class_">Boolean</span>,</span><br><span class="language-javascript">    <span class="hljs-attr">default</span>: <span class="hljs-literal">false</span>,</span><br><span class="language-javascript">  &#125;,</span><br><span class="language-javascript">&#125;)</span><br><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-keyword">const</span> route = <span class="hljs-title function_">useRoute</span>()</span><br><span class="language-javascript"><span class="hljs-keyword">const</span> router = <span class="hljs-title function_">useRouter</span>()</span><br><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-comment">// 获取当前激活的菜单路径</span></span><br><span class="language-javascript"><span class="hljs-keyword">const</span> activeMenu = <span class="hljs-title function_">computed</span>(<span class="hljs-function">() =&gt;</span> &#123;</span><br><span class="language-javascript">  <span class="hljs-keyword">const</span> &#123; path &#125; = route</span><br><span class="language-javascript">  <span class="hljs-keyword">return</span> path</span><br><span class="language-javascript">&#125;)</span><br><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-comment">// 获取过滤后的菜单路由</span></span><br><span class="language-javascript"><span class="hljs-keyword">const</span> menuRoutes = <span class="hljs-title function_">computed</span>(<span class="hljs-function">() =&gt;</span> &#123;</span><br><span class="language-javascript">  <span class="hljs-keyword">const</span> routes = router.<span class="hljs-title function_">getRoutes</span>()</span><br><span class="language-javascript">  <span class="hljs-keyword">return</span> routes.<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">route</span> =&gt;</span> route.<span class="hljs-property">meta</span> &amp;&amp; route.<span class="hljs-property">meta</span>.<span class="hljs-property">isShow</span> &amp;&amp; !route.<span class="hljs-property">meta</span>.<span class="hljs-property">hidden</span>)</span><br><span class="language-javascript">&#125;)</span><br><span class="language-javascript"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span><br><br><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">&quot;scss&quot;</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="language-css"></span><br><span class="language-css"><span class="hljs-selector-class">.sidebar-container</span> &#123;</span><br><span class="language-css">  <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;</span><br><span class="language-css"></span><br><span class="language-css">  <span class="hljs-selector-class">.sidebar-logo</span> &#123;</span><br><span class="language-css">    <span class="hljs-attribute">display</span>: flex;</span><br><span class="language-css">    <span class="hljs-attribute">align-items</span>: center;</span><br><span class="language-css">    <span class="hljs-attribute">justify-content</span>: center;</span><br><span class="language-css">    <span class="hljs-attribute">height</span>: <span class="hljs-number">59px</span>;</span><br><span class="language-css">    <span class="hljs-attribute">border-bottom</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#e6e6e6</span>;</span><br><span class="language-css"></span><br><span class="language-css">    <span class="hljs-selector-class">.sidebar-title</span> &#123;</span><br><span class="language-css">      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">18px</span>;</span><br><span class="language-css">      <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">600</span>;</span><br><span class="language-css">    &#125;</span><br><span class="language-css">  &#125;</span><br><span class="language-css"></span><br><span class="language-css">  <span class="hljs-selector-class">.el-menu</span> &#123;</span><br><span class="language-css">    <span class="hljs-attribute">height</span>: <span class="hljs-built_in">calc</span>(<span class="hljs-number">100%</span> - <span class="hljs-number">60px</span>);</span><br><span class="language-css">    <span class="hljs-attribute">border-right</span>: none;</span><br><span class="language-css">    <span class="hljs-attribute">overflow-y</span>: auto;</span><br><span class="language-css"></span><br><span class="language-css">    &amp;<span class="hljs-selector-pseudo">:not</span>(<span class="hljs-selector-class">.el-menu--collapse</span>) &#123;</span><br><span class="language-css">      <span class="hljs-attribute">width</span>: <span class="hljs-number">200px</span>;</span><br><span class="language-css">    &#125;</span><br><span class="language-css">  &#125;</span><br><span class="language-css">&#125;</span><br><span class="language-css"></span><br><span class="language-css">// 滚动条样式</span><br><span class="language-css"><span class="hljs-selector-class">.el-menu</span>::-webkit-scrollbar &#123;</span><br><span class="language-css">  <span class="hljs-attribute">width</span>: <span class="hljs-number">3px</span>;</span><br><span class="language-css">&#125;</span><br><span class="language-css"></span><br><span class="language-css"><span class="hljs-selector-class">.el-menu</span>::-webkit-scrollbar-thumb &#123;</span><br><span class="language-css">  <span class="hljs-attribute">background</span>: <span class="hljs-number">#324157</span>;</span><br><span class="language-css">  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">3px</span>;</span><br><span class="language-css">&#125;</span><br><span class="language-css"></span><br><span class="language-css"><span class="hljs-selector-class">.el-menu</span>::-webkit-scrollbar-track &#123;</span><br><span class="language-css">  <span class="hljs-attribute">background</span>: <span class="hljs-number">#001529</span>;</span><br><span class="language-css">&#125;</span><br><span class="language-css"></span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span><br><br></code></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-comment">&lt;!-- src/layout/components/SidebarItem/index.vue --&gt;</span><br><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span><br>  <span class="hljs-tag">&lt;<span class="hljs-name">template</span> <span class="hljs-attr">v-for</span>=<span class="hljs-string">&quot;item in props.routerList&quot;</span> <span class="hljs-attr">:key</span>=<span class="hljs-string">&quot;item.path&quot;</span>&gt;</span><br>    <span class="hljs-tag">&lt;<span class="hljs-name">el-sub-menu</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">&quot;item.children &amp;&amp; item.children.length &gt; 0 &amp;&amp; !item.meta?.hidden&quot;</span> <span class="hljs-attr">:index</span>=<span class="hljs-string">&quot;item.path&quot;</span>&gt;</span><br>      <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">title</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">el-icon</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">&quot;item.meta?.icon&quot;</span>&gt;</span><br>          <span class="hljs-tag">&lt;<span class="hljs-name">component</span> <span class="hljs-attr">:is</span>=<span class="hljs-string">&quot;item.meta.icon&quot;</span> /&gt;</span><br>        <span class="hljs-tag">&lt;/<span class="hljs-name">el-icon</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>&#123;&#123; item.meta?.title &#125;&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><br>      <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span><br><br>      <span class="hljs-comment">&lt;!-- 递归调用 --&gt;</span><br>      <span class="hljs-tag">&lt;<span class="hljs-name">sidebar-item</span> <span class="hljs-attr">:router-list</span>=<span class="hljs-string">&quot;item.children&quot;</span> <span class="hljs-attr">:base-path</span>=<span class="hljs-string">&quot;item.path&quot;</span> /&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">el-sub-menu</span>&gt;</span><br><br>    <span class="hljs-tag">&lt;<span class="hljs-name">el-menu-item</span> <span class="hljs-attr">v-else-if</span>=<span class="hljs-string">&quot;!item.meta?.hidden&quot;</span> <span class="hljs-attr">:index</span>=<span class="hljs-string">&quot;item.path&quot;</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">&quot;handleClickMenuItem(item.path, item.meta)&quot;</span>&gt;</span><br>      <span class="hljs-tag">&lt;<span class="hljs-name">el-icon</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">&quot;item.meta?.icon&quot;</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">component</span> <span class="hljs-attr">:is</span>=<span class="hljs-string">&quot;item.meta.icon&quot;</span> /&gt;</span><br>      <span class="hljs-tag">&lt;/<span class="hljs-name">el-icon</span>&gt;</span><br>      <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">title</span>&gt;</span><br>        <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>&#123;&#123; item.meta?.title &#125;&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><br>      <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span><br>    <span class="hljs-tag">&lt;/<span class="hljs-name">el-menu-item</span>&gt;</span><br>  <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span><br><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span><br><br><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">setup</span>&gt;</span><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-keyword">import</span> &#123; defineProps &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;vue&#x27;</span></span><br><span class="language-javascript"><span class="hljs-keyword">import</span> &#123; useNavStore &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@/stores/nav&#x27;</span></span><br><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-keyword">const</span> store = <span class="hljs-title function_">useNavStore</span>()</span><br><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-keyword">const</span> props = <span class="hljs-title function_">defineProps</span>(&#123;</span><br><span class="language-javascript">  <span class="hljs-attr">routerList</span>: &#123;</span><br><span class="language-javascript">    <span class="hljs-attr">type</span>: <span class="hljs-title class_">Array</span>,</span><br><span class="language-javascript">    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,</span><br><span class="language-javascript">  &#125;,</span><br><span class="language-javascript">  <span class="hljs-attr">basePath</span>: &#123;</span><br><span class="language-javascript">    <span class="hljs-attr">type</span>: <span class="hljs-title class_">String</span>,</span><br><span class="language-javascript">    <span class="hljs-attr">default</span>: <span class="hljs-string">&#x27;&#x27;</span>,</span><br><span class="language-javascript">  &#125;,</span><br><span class="language-javascript">&#125;)</span><br><span class="language-javascript"></span><br><span class="language-javascript"><span class="hljs-keyword">const</span> <span class="hljs-title function_">handleClickMenuItem</span> = (<span class="hljs-params">path, meta</span>) =&gt; &#123;</span><br><span class="language-javascript">  store.<span class="hljs-title function_">addTab</span>(&#123; path, meta &#125;)</span><br><span class="language-javascript">&#125;</span><br><span class="language-javascript"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span><br><br><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="language-css"></span><br><span class="language-css"><span class="hljs-selector-class">.el-menu-item</span><span class="hljs-selector-class">.is-active</span> &#123;</span><br><span class="language-css">  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">var</span>(--el-menu-hover-bg-color);</span><br><span class="language-css">  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">500</span>;</span><br><span class="language-css">&#125;</span><br><span class="language-css"></span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span><br><br></code></pre></td></tr></table></figure><h3 id="编写Tab导航组件"><a href="#编写Tab导航组件" class="headerlink" title="编写Tab导航组件"></a>编写Tab导航组件</h3><p>依旧在<code>layout/components</code>目录下创建好对应的文件，编写以下代码。</p><figure class="highlight javascript"><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><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><code class="hljs javascript">&lt;!-- src/layout/components/<span class="hljs-title class_">NavTabs</span>/index.<span class="hljs-property">vue</span> --&gt;<br><span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span></span><br><span class="language-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;navtabs-container&quot;</span>&gt;</span></span><br><span class="language-xml">    <span class="hljs-tag">&lt;<span class="hljs-name">keep-alive</span>&gt;</span></span><br><span class="language-xml">      <span class="hljs-tag">&lt;<span class="hljs-name">el-tabs</span></span></span><br><span class="hljs-tag"><span class="language-xml">        <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;card&quot;</span></span></span><br><span class="hljs-tag"><span class="language-xml">        <span class="hljs-attr">v-model</span>=<span class="hljs-string">&quot;store.tabActivePath&quot;</span></span></span><br><span class="hljs-tag"><span class="language-xml">        <span class="hljs-attr">closable</span></span></span><br><span class="hljs-tag"><span class="language-xml">        @<span class="hljs-attr">tab-click</span>=<span class="hljs-string">&quot;handleSwitchTab&quot;</span></span></span><br><span class="hljs-tag"><span class="language-xml">        @<span class="hljs-attr">tab-remove</span>=<span class="hljs-string">&quot;handleRemoveTab&quot;</span></span></span><br><span class="hljs-tag"><span class="language-xml">      &gt;</span></span><br><span class="language-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">el-tab-pane</span> <span class="hljs-attr">v-for</span>=<span class="hljs-string">&quot;(item, index) in store.tabList&quot;</span> <span class="hljs-attr">:key</span>=<span class="hljs-string">&quot;index&quot;</span> <span class="hljs-attr">:name</span>=<span class="hljs-string">&quot;item.path&quot;</span>&gt;</span></span><br><span class="language-xml">          <span class="hljs-tag">&lt;<span class="hljs-name">template</span> #<span class="hljs-attr">label</span>&gt;</span></span><br><span class="language-xml">            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;nav-tab-label&quot;</span>&gt;</span></span><br><span class="language-xml">              <span class="hljs-tag">&lt;<span class="hljs-name">el-icon</span> <span class="hljs-attr">v-if</span>=<span class="hljs-string">&quot;item.meta?.icon&quot;</span>&gt;</span></span><br><span class="language-xml">                <span class="hljs-tag">&lt;<span class="hljs-name">component</span> <span class="hljs-attr">:is</span>=<span class="hljs-string">&quot;item.meta.icon&quot;</span> /&gt;</span></span><br><span class="language-xml">              <span class="hljs-tag">&lt;/<span class="hljs-name">el-icon</span>&gt;</span></span><br><span class="language-xml">              <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>&#123;&#123; item.meta?.title &#125;&#125;<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span><br><span class="language-xml">            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span><br><span class="language-xml">          <span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span><br><span class="language-xml">        <span class="hljs-tag">&lt;/<span class="hljs-name">el-tab-pane</span>&gt;</span></span><br><span class="language-xml">      <span class="hljs-tag">&lt;/<span class="hljs-name">el-tabs</span>&gt;</span></span><br><span class="language-xml">    <span class="hljs-tag">&lt;/<span class="hljs-name">keep-alive</span>&gt;</span></span><br><span class="language-xml">  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span><br><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span></span><br><br><span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">setup</span>&gt;</span><span class="language-javascript"></span></span><br><span class="language-javascript"><span class="language-xml"><span class="hljs-keyword">import</span> &#123; useRouter &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;vue-router&#x27;</span></span></span><br><span class="language-javascript"><span class="language-xml"><span class="hljs-keyword">import</span> &#123; useNavStore &#125; <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@/stores/nav&#x27;</span></span></span><br><span class="language-javascript"><span class="language-xml"></span></span><br><span class="language-javascript"><span class="language-xml"><span class="hljs-keyword">const</span> router = <span class="hljs-title function_">useRouter</span>()</span></span><br><span class="language-javascript"><span class="language-xml"><span class="hljs-keyword">const</span> store = <span class="hljs-title function_">useNavStore</span>()</span></span><br><span class="language-javascript"><span class="language-xml"></span></span><br><span class="language-javascript"><span class="language-xml"><span class="hljs-keyword">const</span> <span class="hljs-title function_">handleSwitchTab</span> = tabItem =&gt; &#123;</span></span><br><span class="language-javascript"><span class="language-xml">  <span class="hljs-keyword">const</span> path = tabItem.<span class="hljs-property">props</span>.<span class="hljs-property">name</span></span></span><br><span class="language-javascript"><span class="language-xml">  store.<span class="hljs-property">tabActivePath</span> = path</span></span><br><span class="language-javascript"><span class="language-xml"></span></span><br><span class="language-javascript"><span class="language-xml">  router.<span class="hljs-title function_">push</span>(path)</span></span><br><span class="language-javascript"><span class="language-xml">&#125;</span></span><br><span class="language-javascript"><span class="language-xml"></span></span><br><span class="language-javascript"><span class="language-xml"><span class="hljs-keyword">const</span> <span class="hljs-title function_">handleRemoveTab</span> = path =&gt; &#123;</span></span><br><span class="language-javascript"><span class="language-xml">  <span class="hljs-keyword">if</span> (path == <span class="hljs-string">&#x27;/home&#x27;</span>) <span class="hljs-keyword">return</span></span></span><br><span class="language-javascript"><span class="language-xml"></span></span><br><span class="language-javascript"><span class="language-xml">  store.<span class="hljs-title function_">removeTab</span>(path)</span></span><br><span class="language-javascript"><span class="language-xml"></span></span><br><span class="language-javascript"><span class="language-xml">  <span class="hljs-keyword">const</span> lastTab = store.<span class="hljs-property">tabList</span>[store.<span class="hljs-property">tabList</span>.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>]</span></span><br><span class="language-javascript"><span class="language-xml">  store.<span class="hljs-property">tabActivePath</span> = lastTab.<span class="hljs-property">path</span></span></span><br><span class="language-javascript"><span class="language-xml"></span></span><br><span class="language-javascript"><span class="language-xml">  router.<span class="hljs-title function_">push</span>(lastTab.<span class="hljs-property">path</span>)</span></span><br><span class="language-javascript"><span class="language-xml">&#125;</span></span><br><span class="language-javascript"><span class="language-xml"></span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span><br><br><span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">&quot;scss&quot;</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="language-css"></span></span><br><span class="language-css"><span class="language-xml"><span class="hljs-selector-class">.nav-tab-label</span> &#123;</span></span><br><span class="language-css"><span class="language-xml">  <span class="hljs-attribute">display</span>: flex;</span></span><br><span class="language-css"><span class="language-xml">  <span class="hljs-attribute">align-items</span>: center;</span></span><br><span class="language-css"><span class="language-xml">  &gt; <span class="hljs-selector-class">.el-icon</span> &#123;</span></span><br><span class="language-css"><span class="language-xml">    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">16px</span>;</span></span><br><span class="language-css"><span class="language-xml">    <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">5px</span>;</span></span><br><span class="language-css"><span class="language-xml">  &#125;</span></span><br><span class="language-css"><span class="language-xml">&#125;</span></span><br><span class="language-css"><span class="language-xml">:<span class="hljs-built_in">deep</span>(.el-tabs--card &gt; .el-tabs__header .el-tabs__item) &#123;</span></span><br><span class="language-css"><span class="language-xml">  <span class="hljs-attribute">border-bottom-color</span>: <span class="hljs-built_in">var</span>(--el-border-color-light);</span></span><br><span class="language-css"><span class="language-xml">&#125;</span></span><br><span class="language-css"><span class="language-xml">:<span class="hljs-built_in">deep</span>(.el-tabs--card &gt; .el-tabs__header .el-tabs__item.is-active) &#123;</span></span><br><span class="language-css"><span class="language-xml">  <span class="hljs-attribute">border-bottom-color</span>: transparent;</span></span><br><span class="language-css"><span class="language-xml">&#125;</span></span><br><span class="language-css"><span class="language-xml">:<span class="hljs-built_in">deep</span>(.el-tabs--card &gt; .el-tabs__header) &#123;</span></span><br><span class="language-css"><span class="language-xml">  <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0</span>;</span></span><br><span class="language-css"><span class="language-xml">&#125;</span></span><br><span class="language-css"><span class="language-xml">:<span class="hljs-built_in">deep</span>(.el-tabs--card &gt; .el-tabs__header .el-tabs__nav) &#123;</span></span><br><span class="language-css"><span class="language-xml">  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fff</span>;</span></span><br><span class="language-css"><span class="language-xml">&#125;</span></span><br><span class="language-css"><span class="language-xml"></span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></span><br><br></code></pre></td></tr></table></figure><p>通过上面的代码，我们已经能够实现点击左侧菜单，跳转对应页面，并实现Tab导航栏自动联动的功能。</p><h3 id="编写路由守护"><a href="#编写路由守护" class="headerlink" title="编写路由守护"></a>编写路由守护</h3><p>但整体来说，还是有一点小小的缺陷在里面，当我们手动在<code>url</code>输入页面地址的时候，Tab导航栏并没有响应，下面我们通过编写路由守护来完善它。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// src/router/index.js</span><br>...<br><span class="hljs-keyword">const</span> router = <span class="hljs-title function_">createRouter</span>(&#123;<br>  <span class="hljs-attr">history</span>: <span class="hljs-title function_">createWebHashHistory</span>(),<br>  routes,<br>&#125;)<br><br><span class="hljs-comment">// 添加以下代码</span><br>router.<span class="hljs-title function_">beforeEach</span>(<span class="hljs-function">(<span class="hljs-params">to, <span class="hljs-keyword">from</span></span>) =&gt;</span> &#123;<br>  <span class="hljs-keyword">const</span> routes = router.<span class="hljs-title function_">getRoutes</span>()<br>  <span class="hljs-keyword">const</span> hasRoute = routes.<span class="hljs-title function_">find</span>(<span class="hljs-function"><span class="hljs-params">route</span> =&gt;</span> route.<span class="hljs-property">path</span> == to.<span class="hljs-property">path</span>)<br><br>  <span class="hljs-keyword">if</span> (hasRoute) &#123;<br>    <span class="hljs-keyword">const</span> navStore = <span class="hljs-title function_">useNavStore</span>()<br>    navStore.<span class="hljs-title function_">addTab</span>(&#123; <span class="hljs-attr">path</span>: to.<span class="hljs-property">path</span>, <span class="hljs-attr">meta</span>: to.<span class="hljs-property">meta</span> &#125;)<br>  &#125;<br>&#125;)<br><br><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router<br></code></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>整个功能实现起来并不复杂，其实就是对数据的操作和组件的响应。对于很多前端同行来说，也就是看个热闹，但是对于一些初入前端的同学来说，也能给他们提供一些思路和经验。最后希望这篇笔记能帮助到屏幕前的你。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在开发后台管理系统的时候，我们通常会在脚手架的基础上，利用UI框架快速搭建起Layout模板，而导航栏便是Layout里最常用的一个组件之一了。这篇文章主要用&lt;strong&gt;Vue + Vue Router + Pinia + Element Plus&lt;/strong&gt;来快</summary>
      
    
    
    
    <category term="前端积累" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/"/>
    
    <category term="JavaScript" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://www.xdxmblog.cn/tags/JavaScript/"/>
    
    <category term="Vue" scheme="https://www.xdxmblog.cn/tags/Vue/"/>
    
  </entry>
  
  <entry>
    <title>使用宝塔部署NodeJs+Express API接口保姆级教程</title>
    <link href="https://www.xdxmblog.cn/posts/53311.html"/>
    <id>https://www.xdxmblog.cn/posts/53311.html</id>
    <published>2025-11-18T08:56:52.000Z</published>
    <updated>2025-11-18T08:56:52.000Z</updated>
    
    <content type="html"><![CDATA[<p>给娃做了一个自律的小程序，刚好也涉及到了使用服务器去部署Node项目，这对于我来说还是一个挺有趣的事情，以前一直也没用过宝塔面板，刚好借着这次机会记录一下整个流程。</p><h2 id="前置准备"><a href="#前置准备" class="headerlink" title="前置准备"></a>前置准备</h2><ul><li>一台服务器</li><li>完整的NodeJS项目（需要注意依赖包所需的最低NodeJS版本）</li></ul><p>我这里全程以阿里云ESC服务器实例操作，<strong>系统版本：Debian 12.12 64位</strong>。很多人安装系统的时候喜欢选CentOS 7，包括网上有大量老旧资料也都在推荐这个系统，但是需要注意的是CentOS 7安装宝塔面板后，NodeJS版本最高只支持到16。比如我的项目最低需要NodeJS 18+，那就不能选CentOS 7，否则项目跑步起来。</p><h3 id="安装宝塔面板"><a href="#安装宝塔面板" class="headerlink" title="安装宝塔面板"></a>安装宝塔面板</h3><p>点击实例&#x3D;&gt;远程连接，进入服务器（阿里云选免密登录即可）。输入以下命令，执行安装。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_01.webp"></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">wget -O install_panel.sh https://download.bt.cn/install/install_panel.sh &amp;&amp; bash install_panel.sh ed8484bec<br></code></pre></td></tr></table></figure><p>中途会询问**Do you want to install Bt-Panel to the &#x2F;ww directory now?(y&#x2F;n)**，输入y回车。这里的意思是询问你宝塔面板的安装路径。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">Do you want to install Bt-Panel to the /ww directory now?(y/n) y<br></code></pre></td></tr></table></figure><p>然后大概等个1~2分钟，会提示以下信息，就表示安装好了。</p><figure class="highlight shell"><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><code class="hljs shell">=============注意：首次打开面板浏览器将提示不安全=================<br><br> 请选择以下其中一种方式解决不安全提醒<br> 1、下载证书，地址：https://dg2.bt.cn/ssl/baota_root.pfx，双击安装,密码【www.bt.cn】<br> 2、点击【高级】-【继续访问】或【接受风险并继续】访问<br> 教程：https://www.bt.cn/bbs/thread-117246-1-1.html<br> mac用户请下载使用此证书：https://dg2.bt.cn/ssl/mac.crt<br><br>========================面板账户登录信息==========================<br><br> 【云服务器】请在安全组放行 45678 端口<br> 外网ipv4面板地址: https://1.1.1.1:45678/4b5ce23c<br> 内网面板地址:     https://172.22.103.99:45678/4b5ce23c<br> username: ezxceqwe<br> password: 350fjk53<br><br> 浏览器访问以下链接，添加宝塔客服<br> https://www.bt.cn/new/wechat_customer<br>==================================================================<br></code></pre></td></tr></table></figure><p>把面板账户登录信息保存好，给你电脑里建个文档存一份。一会儿会用到，这个时候可以关掉远程连接了，暂时用不到它了。</p><h3 id="安全组策略"><a href="#安全组策略" class="headerlink" title="安全组策略"></a>安全组策略</h3><p>划重点，这玩意就是你服务器的防火墙，能不能访问服务器主要靠它来控制。<strong>后续我们在宝塔配置的数据库，NodeJS等项目需要配置安全组策略时，除了在宝塔面板的安全里需要放行，还需要在云服务器的安全组策略里放行。</strong></p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_02.webp"></p><p>回到云服务器ESC实例面板&#x3D;&gt;网络与安全组。添加入方向规则，把刚才面板登录信息里的端口号填进去。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_03.webp"></p><p>这时我们在浏览器里输入外网ipv4面板地址，用保存好的用户名和密码，就可以登录宝塔面板了。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_04.webp"></p><h2 id="宝塔面板配置"><a href="#宝塔面板配置" class="headerlink" title="宝塔面板配置"></a>宝塔面板配置</h2><p>从这里开始，大部分的操作基本都是在我们的宝塔面板里进行了。大部分的操作都是图形化界面，对后端新手和纯前端同学也是比较友好的，不太需要向运维那样纯命令行操作。</p><h3 id="安装基本环境"><a href="#安装基本环境" class="headerlink" title="安装基本环境"></a>安装基本环境</h3><p>第一次登录进去会提示绑定宝塔账号，我没绑，直接点击左侧导航栏首页。这时会弹窗，推荐你一键安装环境，直接勾选LNMP一键安装即可。这里我没截图安装过程，点击左侧软件商店&#x3D;&gt;已安装，可以看到我们已经安装好的服务。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_05.webp"></p><h3 id="安装数据库"><a href="#安装数据库" class="headerlink" title="安装数据库"></a>安装数据库</h3><p>左侧导航栏选择数据库，进入页面之后，最好是先点一下root密码，自定义或随机生成一个数据库密码。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_06.webp"></p><p>然后添加数据库，这里数据库名称，用户名和密码，就按照你自己的项目正常填就行。<strong>然后访问权限一定要打开，否则之后我们在自己电脑用Navicat等工具是访问不到的。</strong></p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_07.webp"></p><h3 id="导入SQL"><a href="#导入SQL" class="headerlink" title="导入SQL"></a>导入SQL</h3><p>导入SQL之前我们需要确定一下自己的SQL文件大小，如果超过50M，要在PHP配置一下上传参数。小于50M的忽略这一步。</p><p>左侧导航栏选择软件商店&#x3D;&gt;已安装，找到PHP，点击设置。在弹窗中选择：配置修改，在下图框选处调整参数大小。修改完之后，点击弹窗里的服务，重启一下PHP，否则配置不生效。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_08.webp"></p><p>SQL导入我们可以通过宝塔安装的phpMyAdmin，也可以用自己熟悉的SQL工具，比如Navicat Premium，这里两种方式我都讲一下。</p><h4 id="通过phpMyAdmin导入SQL"><a href="#通过phpMyAdmin导入SQL" class="headerlink" title="通过phpMyAdmin导入SQL"></a>通过phpMyAdmin导入SQL</h4><p>左侧导航栏选择数据库&#x3D;&gt;phpMyAdmin&#x3D;&gt;通过面板访问，进入phpMyAdmin管理页面。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_09.webp"></p><p>选择刚才已经添加好的数据，点导入。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_10.webp"></p><h4 id="通过Navicat-Premium导入SQL"><a href="#通过Navicat-Premium导入SQL" class="headerlink" title="通过Navicat Premium导入SQL"></a>通过Navicat Premium导入SQL</h4><p>在安全组策略里，添加3306端口放行。忘了怎么操作的小伙伴回到前置准备的第二步。</p><p>之后打开Navicat Premium&#x3D;&gt;文件&#x3D;&gt;新建连接&#x3D;&gt;选择数据库，我这里是MySQL。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_11.webp"></p><p>连接名随便写，自己能分清是连的哪个环境的数据库就行。主机填宝塔面板左上角的IP地址。端口默认3306，用户名和密码就是你要连哪个数据库，就填哪个数据库的用户名和密码。在宝塔面板的数据库列表可以看到。最后点一下测试连接，提示：连接成功，就说明没问题了。</p><h3 id="Node项目部署"><a href="#Node项目部署" class="headerlink" title="Node项目部署"></a>Node项目部署</h3><p>数据库配置完成以后，我们来配置NodeJS环境。</p><h4 id="环境配置"><a href="#环境配置" class="headerlink" title="环境配置"></a>环境配置</h4><p>左侧导航栏选择网站&#x3D;&gt;Node项目，通常第一次进入会提示你安装Node版本管理器。点击安装，安装完成后点击Node版本管理器，切换为官方源，选择支持你的NodeJS版本进行安装。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_12.webp"></p><h4 id="文件上传"><a href="#文件上传" class="headerlink" title="文件上传"></a>文件上传</h4><p>左侧导航栏选择文件，选择wwwroot目录，创建一个新文件夹，名字随意，用来存放你的Node项目代码。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_13.webp"></p><p>打开你电脑的Node项目代码，修改项目连接数据的配置，将数据的配置项，比如host、port、user、password、database等，改成刚才导入SQL时填写的那些值。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_14.webp"></p><p>接着将项目里除了node_module以外的全部文件，上传到刚才创建的文件夹里。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_15.webp"></p><h4 id="添加项目"><a href="#添加项目" class="headerlink" title="添加项目"></a>添加项目</h4><p>上传完成之后，左侧导航栏选择网站&#x3D;&gt;Node项目&#x3D;&gt;添加项目。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_53311_16.webp"></p><ul><li>项目目录选择刚才上传的目录。</li><li>名称随便填</li><li>启动选项可以用它带出来的，也可以自己填，比如 Node app.js等。</li><li>Node版本，包管理器根据项目自行选择。</li><li>项目端口，填你项目里的真实端口，比如项目在本地跑是127.0.0.1:8888，这里端口就填8888。</li></ul><p>完成之后，启动项目，会自动安装依赖，运行。<strong>然后去安全策略组把对应（对应例子里是8888）端口号放行，这点很重要！同时把ESC服务器的策略组放行80端口（http服务端口），这个只需要放行服务器安全策略组就行，宝塔的默认放行了。</strong></p><h3 id="Vue项目部署"><a href="#Vue项目部署" class="headerlink" title="Vue项目部署"></a>Vue项目部署</h3><p>我这里用的是axios，需要在配置文件里将baseURL改为服务端（也就是上面的Node项目）的地址。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> axiosInstance = axios.<span class="hljs-title function_">create</span>(&#123;<br>  <span class="hljs-attr">baseURL</span>: <span class="hljs-string">&#x27;http://192.168.1.217:3000/api&#x27;</span>, <span class="hljs-comment">//这里改为服务端地址，后面如果配了域名，再替换为域名</span><br>  <span class="hljs-attr">timeout</span>: <span class="hljs-number">5000</span>,<br>  <span class="hljs-attr">adapter</span>: adapter,<br>&#125;)<br></code></pre></td></tr></table></figure><p>与Node项目一样，在宝塔的<code>wwwroot</code>目录下创建一个新文件夹，用来存放Vue项目的代码。之后将Vue项目打包生成dist文件夹。将dist文件夹里的全部文件上传到刚才新建的文件夹里。然后左侧导航栏选择网站&#x3D;&gt;HTML项目&#x3D;&gt;添加项目。</p><ul><li>域名填宝塔左上角ip（后面域名备案好了过来替换）</li><li>根目录选择创建好的文件夹</li></ul><p>由于之前我们已经放行了80端口，这里就不用再次操作了。</p><h2 id="测试项目"><a href="#测试项目" class="headerlink" title="测试项目"></a>测试项目</h2><p>在浏览器输入宝塔左上角的IP地址，验证vue项目是否正常展示，然后打开F12调试面板，观察接口请求是否正常。如果有问题，检查一下是不是哪一步漏掉了。至此，从宝塔面板的安装，环境配置，数据库的安装，到Node后端接口的部署，前端页面的部署这一整套的流程就全部结束了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;给娃做了一个自律的小程序，刚好也涉及到了使用服务器去部署Node项目，这对于我来说还是一个挺有趣的事情，以前一直也没用过宝塔面板，刚好借着这次机会记录一下整个流程。&lt;/p&gt;
&lt;h2 id=&quot;前置准备&quot;&gt;&lt;a href=&quot;#前置准备&quot; class=&quot;headerlink&quot; t</summary>
      
    
    
    
    <category term="后端探索" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/"/>
    
    <category term="Node" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/Node/"/>
    
    
    <category term="Node" scheme="https://www.xdxmblog.cn/tags/Node/"/>
    
    <category term="Express" scheme="https://www.xdxmblog.cn/tags/Express/"/>
    
    <category term="宝塔面板" scheme="https://www.xdxmblog.cn/tags/%E5%AE%9D%E5%A1%94%E9%9D%A2%E6%9D%BF/"/>
    
  </entry>
  
  <entry>
    <title>MySQL数据库原理、设计与应用(一)数据库的基本操作</title>
    <link href="https://www.xdxmblog.cn/posts/58637.html"/>
    <id>https://www.xdxmblog.cn/posts/58637.html</id>
    <published>2025-04-30T07:52:05.000Z</published>
    <updated>2025-04-30T07:52:05.000Z</updated>
    
    <content type="html"><![CDATA[<p>这个系列的笔记主要是读《MySQL数据库原理、设计与应用》一书过程中，整理的读书笔记和书中的练习作业。主要目的是解决因为MySQL数据库使用不熟练，导致在利用Node.js编写后端API时，知道逻辑却不知道对应的SQL语句怎么写的问题。</p><p>这篇笔记的内容对应书中的第二章部分。</p><h2 id="学习目标"><a href="#学习目标" class="headerlink" title="学习目标"></a>学习目标</h2><ul><li>掌握数据库的创建、查看、选择与删除操作</li><li>掌握数据表的创建、查看、修改与删除操作</li><li>掌握数据的添加、查询、修改与删除操作</li></ul><h2 id="数据库操作"><a href="#数据库操作" class="headerlink" title="数据库操作"></a>数据库操作</h2><h3 id="创建数据库"><a href="#创建数据库" class="headerlink" title="创建数据库"></a>创建数据库</h3><p>一个MySQL服务器可以有多个数据库，分别存储不同的数据。创建数据的基本语法格式如下：</p><blockquote><p>CREATE DATABASE 数据名称 [库选项];</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">CREATE</span> DATABASE mydb;<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><p>如果创建的数据库已存在，则程序会报错如下。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">CREATE</span> DATABASE mydb;<br>ERROR <span class="hljs-number">1007</span> (HY000): Can<span class="hljs-string">&#x27;t create database &#x27;</span>mydb<span class="hljs-string">&#x27;; database exists</span><br></code></pre></td></tr></table></figure><p>为了防止这种情况，在创建数据库时可以在“数据库名称”前添加<code>IF NOT EXISTS</code>,表示指定的数据库不存在时执行创建操作，否则忽略此操作。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">CREATE</span> DATABASE IF <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> mydb;<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected, <span class="hljs-number">1</span> warning (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><p>在添加了<code>IF NOT EXISTS</code>后，执行结果并没有报错，但是可以观察到返回了一条警告信息。通过<code>SHOW WARNINGS</code>可以查看错误信息。</p><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SHOW</span> WARNINGS;<br><span class="hljs-operator">+</span><span class="hljs-comment">-------+------+-----------------------------------------------+</span><br><span class="hljs-operator">|</span> Level <span class="hljs-operator">|</span> Code <span class="hljs-operator">|</span> Message                                       <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+------+-----------------------------------------------+</span><br><span class="hljs-operator">|</span> Note  <span class="hljs-operator">|</span> <span class="hljs-number">1007</span> <span class="hljs-operator">|</span> Can<span class="hljs-string">&#x27;t create database &#x27;</span>mydb<span class="hljs-string">&#x27;; database exists |</span><br><span class="hljs-string">+-------+------+-----------------------------------------------+</span><br><span class="hljs-string">1 row in set (0.00 sec)</span><br></code></pre></td></tr></table></figure><h3 id="查看数据库"><a href="#查看数据库" class="headerlink" title="查看数据库"></a>查看数据库</h3><h4 id="查看MySQL服务器下的所有数据库"><a href="#查看MySQL服务器下的所有数据库" class="headerlink" title="查看MySQL服务器下的所有数据库"></a>查看MySQL服务器下的所有数据库</h4><p>当需要查看MySQL服务器中已经存在的数据库时，基本语法格式如下：</p><blockquote><p>SHOW DATABASES;</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SHOW</span> DATABASES;<br><span class="hljs-operator">+</span><span class="hljs-comment">--------------------+</span><br><span class="hljs-operator">|</span> Database           <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">--------------------+</span><br><span class="hljs-operator">|</span> information_schema <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> mydb               <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> mysql              <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> performance_schema <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> sys                <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">--------------------+</span><br><span class="hljs-number">5</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><p>上面的结果中，除了<code>mydb</code>是我们刚才创建的数据库以为，其他数据库都是MySQL安装时自动创建的。对于初学者来说，不建议去修改和删除这些数据库，以免造成服务器故障。</p><ul><li>information_schema和performance_schema数据库分别是MySQL服务器的数据字典（保存所有数据表和库的结构信息）和性能字典（保存全局变量等的设置）。</li><li>mysql数据库主要负责MySQL服务器自己需要使用的控制和管理信息，如用户的权限关系等。</li><li>sys是系统数据库，包括了存储过程、自定义函数等信息。</li></ul><h4 id="查看指定数据库的创建信息"><a href="#查看指定数据库的创建信息" class="headerlink" title="查看指定数据库的创建信息"></a>查看指定数据库的创建信息</h4><p>查看某个数据库的信息，基本语法格式如下。</p><blockquote><p>SHOW CREATE DATABASE 数据库名称;</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SHOW</span> <span class="hljs-keyword">CREATE</span> DATABASE mydb;<br><span class="hljs-operator">+</span><span class="hljs-comment">----------+-----------------------------------------------------------------+</span><br><span class="hljs-operator">|</span> Database <span class="hljs-operator">|</span> <span class="hljs-keyword">Create</span> Database                                                 <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">----------+-----------------------------------------------------------------+</span><br><span class="hljs-operator">|</span> mydb     <span class="hljs-operator">|</span> <span class="hljs-keyword">CREATE</span> DATABASE `mydb` <span class="hljs-comment">/*!40100 DEFAULT CHARACTER SET latin1 */</span> <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">----------+-----------------------------------------------------------------+</span><br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><p>上面的输出结果显示了创建mydb数据库的SQL语句，以及数据库的默认字符集。</p><h3 id="选择数据库"><a href="#选择数据库" class="headerlink" title="选择数据库"></a>选择数据库</h3><p>MySQL服务器中的数据都是存储在数据表中，而数据表又需要存储到对应的数据库下，所以在对数据和数据表进行操作前，要先选择数据库。其基本语法格式如下。</p><blockquote><p>USE 数据库名称;</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> use mydb;<br>Database changed<br></code></pre></td></tr></table></figure><p>数据库的选择除了使用<code>USE</code>关键字以外，在登录MySQL服务器时也可以直接选择要操作的数据库。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">mysql <span class="hljs-operator">-</span>u 用户名 <span class="hljs-operator">-</span>p 密码 数据库名<br></code></pre></td></tr></table></figure><h3 id="删除数据库"><a href="#删除数据库" class="headerlink" title="删除数据库"></a>删除数据库</h3><p>删除数据库的基本语法格式如下。这命令一定谨慎在谨慎，毕竟“删库跑路”是犯法的。</p><blockquote><p>DROP DATABASE 数据库名称;</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DROP</span> DATABASE mydb;<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.04</span> sec)<br></code></pre></td></tr></table></figure><p>同样，在删除数据库时可以使用<code>IF EXISTS</code>来防止因为删除的数据库不存在而报错。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DROP</span> DATABASE IF <span class="hljs-keyword">EXISTS</span> mydb;<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected, <span class="hljs-number">1</span> warning (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h2 id="数据表操作"><a href="#数据表操作" class="headerlink" title="数据表操作"></a>数据表操作</h2><h3 id="创建数据表"><a href="#创建数据表" class="headerlink" title="创建数据表"></a>创建数据表</h3><p>创建数据表指的是在已存在的数据库中建立新表。基本语法格式如下。</p><blockquote><p>CREATE [TEMPORARY] TABLE [IF NOT EXISTS] 表名</p><p>(字段名 字段类型 [字段属性]…) [表选项]</p></blockquote><ul><li><p>上述语法中，可选项<code>TEMPORARY</code>表示临时表，仅在当前会话中可见，并且在会话关闭时自动删除。</p></li><li><p>“字段名”指的是数据表的列名；</p></li><li><p>“字段类型”设置字段中保存的数据类型，如时间日期类型等；</p></li><li><p>可选项“字段属性”指的是字段的某些特殊约束条件。</p></li><li><p>可选的“表选项”用于设置表的相关特性，如存储引擎（ENGINE）、字符集（CHHARSET）和校对集（COLLATE）。</p></li></ul><figure class="highlight sql"><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><code class="hljs sql"># <span class="hljs-number">1.</span> 创建mydb数据库<br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">CREATE</span> DATABASE mydb;<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.00</span> sec)<br># <span class="hljs-number">2.</span> 选择mydb数据库<br>mysql<span class="hljs-operator">&gt;</span> USE mydb;<br>Database changed<br># <span class="hljs-number">3.</span> 创建goods数据表<br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> goods(<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> id <span class="hljs-type">INT</span> COMMENT <span class="hljs-string">&#x27;编号&#x27;</span>,<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> name <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">32</span>) COMMENT <span class="hljs-string">&#x27;商品名&#x27;</span>,<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> price <span class="hljs-type">INT</span> COMMENT <span class="hljs-string">&#x27;价格&#x27;</span>,<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> description <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">255</span>) COMMENT <span class="hljs-string">&#x27;商品描述&#x27;</span><br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> );<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.24</span> sec)<br></code></pre></td></tr></table></figure><p>上述SQL语句中，<code>INT</code>用于设置字段数据类型是整形；<code>VARCHAR(L)</code>表示可变长度的字符串，L表示字符数，如VARCHAR(32)表示可变的字符数是32；<code>COMMENT</code>用于在创建表时添加注释内容，并将其保存到表结构中。</p><h3 id="查看数据表"><a href="#查看数据表" class="headerlink" title="查看数据表"></a>查看数据表</h3><h4 id="查看数据表-1"><a href="#查看数据表-1" class="headerlink" title="查看数据表"></a>查看数据表</h4><p>选择数据库后，可以通过MySQL提供的SQL语句进行查看，基本语法格式如下。</p><blockquote><p>SHOW TABLES [LIKE 匹配模式];</p></blockquote><p>上述语法中，若不添加可选项“<code>LIKE匹配模式</code>”，表示查看当前数据库中的所有数据表。其中匹配模式符有两种，分别为<code>“%”和“_”</code>。前者表示匹配一个或多个字符，代表任意长度的字符串，长度也可以为0，后者仅可以匹配一个字符。</p><p>先在数据库中一张新表new_goods。</p><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> new_goods(<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> id <span class="hljs-type">INT</span> COMMENT <span class="hljs-string">&#x27;编号&#x27;</span>,<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> name <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">32</span>) COMMENT <span class="hljs-string">&#x27;商品名&#x27;</span>,<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> price <span class="hljs-type">INT</span> COMMENT <span class="hljs-string">&#x27;价格&#x27;</span>,<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> description <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">255</span>) COMMENT <span class="hljs-string">&#x27;商品描述&#x27;</span><br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> );<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.33</span> sec)<br></code></pre></td></tr></table></figure><p>分别查看mydb数据库中的所有数据表和名称中含有new的数据表。</p><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SHOW</span> TABLES;<br><span class="hljs-operator">+</span><span class="hljs-comment">----------------+</span><br><span class="hljs-operator">|</span> Tables_in_mydb <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">----------------+</span><br><span class="hljs-operator">|</span> goods          <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> new_goods      <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">----------------+</span><br><span class="hljs-number">2</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SHOW</span> TABLES <span class="hljs-keyword">LIKE</span> <span class="hljs-string">&#x27;%new%&#x27;</span>;<br><span class="hljs-operator">+</span><span class="hljs-comment">------------------------+</span><br><span class="hljs-operator">|</span> Tables_in_mydb (<span class="hljs-operator">%</span><span class="hljs-keyword">new</span><span class="hljs-operator">%</span>) <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------------------------+</span><br><span class="hljs-operator">|</span> new_goods              <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------------------------+</span><br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h4 id="查看数据表的相关信息"><a href="#查看数据表的相关信息" class="headerlink" title="查看数据表的相关信息"></a>查看数据表的相关信息</h4><p>利用MySQL提供的SQL语句查看数据表的相关信息，如数据表的名称、存储引擎、创建时间等，基本语法格式如下。</p><blockquote><p>SHOW TABLE STATUS [FROM 数据库名] [LIKE匹配模式];</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SHOW</span> <span class="hljs-keyword">TABLE</span> STATUS <span class="hljs-keyword">FROM</span> mydb <span class="hljs-keyword">LIKE</span> <span class="hljs-string">&#x27;%new%&#x27;</span>\G               <br><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span> <span class="hljs-number">1.</span> <span class="hljs-type">row</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span>  <br>           Name: new_goods                                      <br>         Engine: InnoDB                                         <br>        Version: <span class="hljs-number">10</span>                                             <br>     Row_format: <span class="hljs-keyword">Dynamic</span>                                        <br>           <span class="hljs-keyword">Rows</span>: <span class="hljs-number">0</span>                                              <br> Avg_row_length: <span class="hljs-number">0</span>                                              <br>    Data_length: <span class="hljs-number">16384</span>                                          <br>Max_data_length: <span class="hljs-number">0</span>                                              <br>   Index_length: <span class="hljs-number">0</span>                                              <br>      Data_free: <span class="hljs-number">0</span>                                              <br> Auto_increment: <span class="hljs-keyword">NULL</span>                                           <br>    Create_time: <span class="hljs-number">2025</span><span class="hljs-number">-04</span><span class="hljs-number">-30</span> <span class="hljs-number">13</span>:<span class="hljs-number">14</span>:<span class="hljs-number">37</span>                            <br>    Update_time: <span class="hljs-keyword">NULL</span>                                           <br>     Check_time: <span class="hljs-keyword">NULL</span>                                           <br>      <span class="hljs-keyword">Collation</span>: latin1_swedish_ci                              <br>       Checksum: <span class="hljs-keyword">NULL</span>                                           <br> Create_options:                                                <br>        Comment:                                                <br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)                                         <br></code></pre></td></tr></table></figure><p>上述SQL语句中，”\G”是MySQL客户端可以使用的结束符中的一种，用于将显示结果纵向排列，适合字段非常多的情况。</p><h3 id="修改数据表"><a href="#修改数据表" class="headerlink" title="修改数据表"></a>修改数据表</h3><p>在实际开发时，若创建的数据表不符合当前项目的开发要求时，可以通过修改数据表来实现。如修改数据表的名称和表选项。</p><h4 id="修改数据表名称"><a href="#修改数据表名称" class="headerlink" title="修改数据表名称"></a>修改数据表名称</h4><p>在MySQL中，提供了两种修改数据表名称的方式，基本语法格式如下。</p><blockquote><p>#语法格式1</p><p>ALTER TABLE 旧表名 RENAME [TO|AS] 新表名;</p><p>#语法格式2</p><p>RENAME TABLE 旧表名1 TO新表名1[,旧表名2 TO新表名2]…;</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> RENAME <span class="hljs-keyword">TABLE</span> new_goods <span class="hljs-keyword">TO</span> my_goods;<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.09</span> sec)<br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SHOW</span> TABLES;<br><span class="hljs-operator">+</span><span class="hljs-comment">----------------+</span><br><span class="hljs-operator">|</span> Tables_in_mydb <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">----------------+</span><br><span class="hljs-operator">|</span> goods          <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> my_goods       <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">----------------+</span><br><span class="hljs-number">3</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h4 id="修改表选项"><a href="#修改表选项" class="headerlink" title="修改表选项"></a>修改表选项</h4><p>数据表中的表选项字符集、存储引擎以及校对集也可以通过<code>ALTER TABLE</code>修改，基本语法格式如下。</p><blockquote><p>ALTER TABLE 表名 表选项 [&#x3D;] 值;</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> my_goods CHARSET <span class="hljs-operator">=</span> utf8;<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.06</span> sec)<br>Records: <span class="hljs-number">0</span>  Duplicates: <span class="hljs-number">0</span>  Warnings: <span class="hljs-number">0</span><br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SHOW</span> <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> my_goods \G<br><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span> <span class="hljs-number">1.</span> <span class="hljs-type">row</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><br>       <span class="hljs-keyword">Table</span>: my_goods<br><span class="hljs-keyword">Create</span> <span class="hljs-keyword">Table</span>: <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> `my_goods` (<br>  `id` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;编号&#x27;</span>,<br>  `name` <span class="hljs-type">varchar</span>(<span class="hljs-number">32</span>) <span class="hljs-type">CHARACTER</span> <span class="hljs-keyword">SET</span> latin1 <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;商品名&#x27;</span>,<br>  `price` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;价格&#x27;</span>,<br>  `description` <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-type">CHARACTER</span> <span class="hljs-keyword">SET</span> latin1 <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;商品描述&#x27;</span><br>) ENGINE<span class="hljs-operator">=</span>InnoDB <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8<br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h3 id="查看表结构"><a href="#查看表结构" class="headerlink" title="查看表结构"></a>查看表结构</h3><p>MySQL提供的<code>DESCRIBE</code>语句可以查看数据表中所有字段或指定字段的信息，包括字段名、字段类型等。其中，<code>DESCRIBE</code>语句可以简写成<code>DESC</code>。基本语法格式如下。</p><blockquote><p>#语法格式1：查看所有字段的信息</p><p>{DESCRIBE | DESC } 数据表名；</p><p>#语法格式2：查看指定字段的信息</p><p>{DESCRIBE | DESC } 数据表名 字段名；</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DESC</span> my_goods;<br><span class="hljs-operator">+</span><span class="hljs-comment">-------------+--------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> Field       <span class="hljs-operator">|</span> Type         <span class="hljs-operator">|</span> <span class="hljs-keyword">Null</span> <span class="hljs-operator">|</span> Key <span class="hljs-operator">|</span> <span class="hljs-keyword">Default</span> <span class="hljs-operator">|</span> Extra <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------------+--------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> id          <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> name        <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">32</span>)  <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> price       <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> description <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------------+--------------+------+-----+---------+-------+</span><br><span class="hljs-number">4</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DESC</span> my_goods name;<br><span class="hljs-operator">+</span><span class="hljs-comment">-------+-------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> Field <span class="hljs-operator">|</span> Type        <span class="hljs-operator">|</span> <span class="hljs-keyword">Null</span> <span class="hljs-operator">|</span> Key <span class="hljs-operator">|</span> <span class="hljs-keyword">Default</span> <span class="hljs-operator">|</span> Extra <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+-------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> name  <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">32</span>) <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+-------------+------+-----+---------+-------+</span><br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><p>其中，<code>Field</code>表示字段名，<code>Type</code>表示字段的数据类型，<code>Null</code>表示该字段是否可以为空，<code>Key</code>表示该字段是否已设置了索引，<code>Default</code>表示该字段是否有默认值，<code>Extra</code>表示获取到的与该字段相关的附件信息。</p><h4 id="查看数据表的创建语句"><a href="#查看数据表的创建语句" class="headerlink" title="查看数据表的创建语句"></a>查看数据表的创建语句</h4><p>若想要查看创建数据表的具体SQL语句及表的字符编码，可以使用以下SQL语句，基本语法格式如下。</p><blockquote><p>SHOW CREATE TABLE 表名;</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SHOW</span> <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> my_goods \G<br><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span> <span class="hljs-number">1.</span> <span class="hljs-type">row</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><span class="hljs-operator">*</span><br>       <span class="hljs-keyword">Table</span>: my_goods<br><span class="hljs-keyword">Create</span> <span class="hljs-keyword">Table</span>: <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> `my_goods` (<br>  `id` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;编号&#x27;</span>,<br>  `name` <span class="hljs-type">varchar</span>(<span class="hljs-number">32</span>) <span class="hljs-type">CHARACTER</span> <span class="hljs-keyword">SET</span> latin1 <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;商品名&#x27;</span>,<br>  `price` <span class="hljs-type">int</span>(<span class="hljs-number">11</span>) <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;价格&#x27;</span>,<br>  `description` <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-type">CHARACTER</span> <span class="hljs-keyword">SET</span> latin1 <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;商品描述&#x27;</span><br>) ENGINE<span class="hljs-operator">=</span>InnoDB <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8<br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h4 id="查看数据表结构"><a href="#查看数据表结构" class="headerlink" title="查看数据表结构"></a>查看数据表结构</h4><p>MySQL数据库中的<code>SHOW COLUMNS</code>语句也可以查看表结构，基本语法格式如下。</p><blockquote><p>#语法格式1</p><p>SHOW [FULL] COLUMNS FROM 数据表名 [FROM数据库名]；</p><p>#语法格式2</p><p>SHOW [FULL] COLUMNS FROM 数据库名.数据表名；</p></blockquote><p>上述语法格式中，可选项<code>FULL</code>表示显示详细内容，在不添加的情况下查询结果与<code>DESC</code>的结果相同；在添加<code>FULL</code>选项时此语句不仅可以查看到<code>DESC</code>语句查看的信息，还可以查看到字段的权限、<code>COMMENT</code>字段的注释信息等。</p><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SHOW</span> <span class="hljs-keyword">FULL</span> COLUMNS <span class="hljs-keyword">FROM</span> my_goods;<br><span class="hljs-operator">+</span><span class="hljs-comment">-------------+--------------+-------------------+------+-----+</span><br><span class="hljs-operator">|</span> Field       <span class="hljs-operator">|</span> Type         <span class="hljs-operator">|</span> <span class="hljs-keyword">Collation</span>         <span class="hljs-operator">|</span> <span class="hljs-keyword">Null</span> <span class="hljs-operator">|</span> Key <span class="hljs-operator">|</span> <br><span class="hljs-operator">+</span><span class="hljs-comment">-------------+--------------+-------------------+------+-----+</span><br><span class="hljs-operator">|</span> id          <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>              <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> name        <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">32</span>)  <span class="hljs-operator">|</span> latin1_swedish_ci <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <br><span class="hljs-operator">|</span> price       <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>              <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <br><span class="hljs-operator">|</span> description <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-operator">|</span> latin1_swedish_ci <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <br><span class="hljs-operator">+</span><span class="hljs-comment">-------------+--------------+-------------------+------+-----+</span><br><span class="hljs-operator">+</span><span class="hljs-comment">---------+-------+---------------------------------+----------+</span><br><span class="hljs-operator">|</span> <span class="hljs-keyword">Default</span> <span class="hljs-operator">|</span> Extra <span class="hljs-operator">|</span> Privileges                      <span class="hljs-operator">|</span> Comment  <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">---------+-------+---------------------------------+----------+</span><br><span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span> <span class="hljs-keyword">select</span>,<span class="hljs-keyword">insert</span>,<span class="hljs-keyword">update</span>,<span class="hljs-keyword">references</span> <span class="hljs-operator">|</span> 编号      <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span> <span class="hljs-keyword">select</span>,<span class="hljs-keyword">insert</span>,<span class="hljs-keyword">update</span>,<span class="hljs-keyword">references</span> <span class="hljs-operator">|</span> 商品名    <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span> <span class="hljs-keyword">select</span>,<span class="hljs-keyword">insert</span>,<span class="hljs-keyword">update</span>,<span class="hljs-keyword">references</span> <span class="hljs-operator">|</span> 价格      <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span> <span class="hljs-keyword">select</span>,<span class="hljs-keyword">insert</span>,<span class="hljs-keyword">update</span>,<span class="hljs-keyword">references</span> <span class="hljs-operator">|</span> 商品描述  <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">---------+-------+---------------------------------+----------+</span><br><span class="hljs-number">4</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h3 id="修改表结构"><a href="#修改表结构" class="headerlink" title="修改表结构"></a>修改表结构</h3><h4 id="修改字段名"><a href="#修改字段名" class="headerlink" title="修改字段名"></a>修改字段名</h4><p>在MySQL中仅修改数据表中的字段名称，使用<code>CHANGE</code>实现，基本语法格式如下。</p><blockquote><p>ALTER TABLE 数据表名 CHANGE [COLUMN] 旧字段名 新字段名 字段类型 [字段属性];</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> my_goods CHANGE description des <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">255</span>);<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.49</span> sec)<br>Records: <span class="hljs-number">0</span>  Duplicates: <span class="hljs-number">0</span>  Warnings: <span class="hljs-number">0</span><br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DESC</span> my_goods;<br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> Field <span class="hljs-operator">|</span> Type         <span class="hljs-operator">|</span> <span class="hljs-keyword">Null</span> <span class="hljs-operator">|</span> Key <span class="hljs-operator">|</span> <span class="hljs-keyword">Default</span> <span class="hljs-operator">|</span> Extra <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> id    <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> name  <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">32</span>)  <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> price <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> des   <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-number">4</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h4 id="修改字段类型"><a href="#修改字段类型" class="headerlink" title="修改字段类型"></a>修改字段类型</h4><p>在MySQL中仅修改数据表中的字段类型，通常使用<code>MODIFY</code>实现，基本语法格式如下。</p><blockquote><p>ALTER TABLE 数据表名 MODIFY [COLUMN] 字段名 新类型 [字段属性];</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> my_goods MODIFY des <span class="hljs-type">CHAR</span>(<span class="hljs-number">255</span>);<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.55</span> sec)<br>Records: <span class="hljs-number">0</span>  Duplicates: <span class="hljs-number">0</span>  Warnings: <span class="hljs-number">0</span><br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DESC</span> my_goods;<br><span class="hljs-operator">+</span><span class="hljs-comment">-------+-------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> Field <span class="hljs-operator">|</span> Type        <span class="hljs-operator">|</span> <span class="hljs-keyword">Null</span> <span class="hljs-operator">|</span> Key <span class="hljs-operator">|</span> <span class="hljs-keyword">Default</span> <span class="hljs-operator">|</span> Extra <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+-------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> id    <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)     <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> name  <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">32</span>) <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> price <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)     <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> des   <span class="hljs-operator">|</span> <span class="hljs-type">char</span>(<span class="hljs-number">255</span>)   <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+-------------+------+-----+---------+-------+</span><br><span class="hljs-number">4</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h4 id="修改字段的位置"><a href="#修改字段的位置" class="headerlink" title="修改字段的位置"></a>修改字段的位置</h4><p>数据表在创建时，字段编写的先后顺序就是其在数据库中存储的顺序，若需要调整某个字段的位置，也可以使用<code>MODIFY</code>实现，基本语法格式如下。</p><blockquote><p>ALTER TABLE 数据表名</p><p>MODIFY [COLUMN] 字段名1 数据类型 [字段属性] [FIRST | AFTER 字段名2];</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> my_goods MODIFY des <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">255</span>) AFTER name;<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.52</span> sec)<br>Records: <span class="hljs-number">0</span>  Duplicates: <span class="hljs-number">0</span>  Warnings: <span class="hljs-number">0</span><br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DESC</span> my_goods;<br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> Field <span class="hljs-operator">|</span> Type         <span class="hljs-operator">|</span> <span class="hljs-keyword">Null</span> <span class="hljs-operator">|</span> Key <span class="hljs-operator">|</span> <span class="hljs-keyword">Default</span> <span class="hljs-operator">|</span> Extra <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> id    <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> name  <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">32</span>)  <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> des   <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> price <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-number">4</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.01</span> sec)<br></code></pre></td></tr></table></figure><h4 id="新增字段"><a href="#新增字段" class="headerlink" title="新增字段"></a>新增字段</h4><p>对于已经创建好的数据表，也可以根据业务需求利用<code>ADD</code>新增字段，基本语法格式如下。</p><blockquote><p>#语法格式1：新增一个字段，并可指定其位置</p><p>ADD [COLUMN] 新字段名 字段类型 [FIRST | AFTER 字段名]</p><p>#语法格式2：同时新增多个字段</p><p>ALTER TABLE 数据表名</p><p>ADD [COLUMN] (新字段名1 字段类型1, 新字段名2 字段类型2,…)</p></blockquote><figure class="highlight sql"><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></pre></td><td class="code"><pre><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> my_goods <span class="hljs-keyword">ADD</span> num <span class="hljs-type">INT</span> AFTER name;<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.37</span> sec)<br>Records: <span class="hljs-number">0</span>  Duplicates: <span class="hljs-number">0</span>  Warnings: <span class="hljs-number">0</span><br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DESC</span> my_goods;<br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> Field <span class="hljs-operator">|</span> Type         <span class="hljs-operator">|</span> <span class="hljs-keyword">Null</span> <span class="hljs-operator">|</span> Key <span class="hljs-operator">|</span> <span class="hljs-keyword">Default</span> <span class="hljs-operator">|</span> Extra <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> id    <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> name  <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">32</span>)  <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> num   <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> des   <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> price <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-number">5</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h4 id="删除字段"><a href="#删除字段" class="headerlink" title="删除字段"></a>删除字段</h4><p>MySQL中可以通过<code>DROP</code>完成将某个字段从数据表中删除。基本语法格式如下。</p><blockquote><p>ALTER TABLE 数据表名 DROP [COLUMN] 字段名;</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> my_goods <span class="hljs-keyword">DROP</span> num;<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.42</span> sec)<br>Records: <span class="hljs-number">0</span>  Duplicates: <span class="hljs-number">0</span>  Warnings: <span class="hljs-number">0</span><br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DESC</span> my_goods;<br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> Field <span class="hljs-operator">|</span> Type         <span class="hljs-operator">|</span> <span class="hljs-keyword">Null</span> <span class="hljs-operator">|</span> Key <span class="hljs-operator">|</span> <span class="hljs-keyword">Default</span> <span class="hljs-operator">|</span> Extra <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-operator">|</span> id    <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> name  <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">32</span>)  <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> des   <span class="hljs-operator">|</span> <span class="hljs-type">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> price <span class="hljs-operator">|</span> <span class="hljs-type">int</span>(<span class="hljs-number">11</span>)      <span class="hljs-operator">|</span> YES  <span class="hljs-operator">|</span>     <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>    <span class="hljs-operator">|</span>       <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">-------+--------------+------+-----+---------+-------+</span><br><span class="hljs-number">4</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br><br>mysql<span class="hljs-operator">&gt;</span><br></code></pre></td></tr></table></figure><h3 id="删除数据表"><a href="#删除数据表" class="headerlink" title="删除数据表"></a>删除数据表</h3><p>删除数据表的同时，存储在数据表中的数据都将被删除，基本语法格式如下。</p><blockquote><p>DROP [TEMPORARY] TABLE [IF NOT EXISTS] 数据表1[,数据表2]…;</p></blockquote><h2 id="数据操作"><a href="#数据操作" class="headerlink" title="数据操作"></a>数据操作</h2><p>在写API的时候，前面关于创建库、表的操作，基本上是不太能用得到的。一般都用<code>Navicat Premium</code>去操作，毕竟可视化的操作要比去一行行敲SQL简单容易。</p><p>但是从这里开始，基本就是我们要必须掌握的内容了，因为会实打实的在写代码的过程中用到。</p><h3 id="添加数据"><a href="#添加数据" class="headerlink" title="添加数据"></a>添加数据</h3><p>MySQL中使用INSERT语句向数据表中添加数据。根据操作的不同目的一般可分为两种：</p><h4 id="为所有字段添加数据"><a href="#为所有字段添加数据" class="headerlink" title="为所有字段添加数据"></a>为所有字段添加数据</h4><p>为所有字段插入记录时，可以省略字段名称，严格按照数据表结构（字段的位置）插入对应的值，基本语法格式如下。</p><blockquote><p>INSERT [INTO] 数据表名 {VALUES | VALUE}(值1[,值2]…);</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> goods<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">VALUES</span> (<span class="hljs-number">1</span>,<span class="hljs-string">&#x27;notebook&#x27;</span>,<span class="hljs-number">4998</span>,<span class="hljs-string">&#x27;High cost performance&#x27;</span>);<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.04</span> sec)<br></code></pre></td></tr></table></figure><p>通过上述语句，我们在goods表中添加了一条商品记录，并且在插入时我们严格按照数据与字段顺序对应的规则来操作。</p><p>我们尝试再加一条数据，会发现这次的添加出现了错误。造成这种错误的原因：<strong>通常是创建的数据表未指定字符集，系统会默认为latinl。当插入含有中文的数据时，则会报错。</strong></p><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> goods<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">VALUES</span> (<span class="hljs-number">2</span>,<span class="hljs-string">&#x27;笔记本&#x27;</span>,<span class="hljs-number">9998</span>,<span class="hljs-string">&#x27;续航时间超过10个小时&#x27;</span>);<br>ERROR <span class="hljs-number">1366</span> (HY000): Incorrect string <span class="hljs-keyword">value</span>: <span class="hljs-string">&#x27;\xB1\xCA\xBC\xC7\xB1\xBE&#x27;</span> <span class="hljs-keyword">for</span> <span class="hljs-keyword">column</span> <span class="hljs-string">&#x27;name&#x27;</span> <span class="hljs-keyword">at</span> <span class="hljs-type">row</span> <span class="hljs-number">1</span><br></code></pre></td></tr></table></figure><p>解决方式也有两种，一种是创建表时添加表选项，设置数据表的字符集。</p><blockquote><p>CREATE [TEMPORARY] TABLE [IF NOT EXISTS] 表名</p><p>(字段名 字段类型 [字段属性]…)[DEFAULT]{CHARACTER SET|CHARSET}[&#x3D;] utf8;</p></blockquote><p>另外一种是对于已经添加数据的数据表，可以通过<code>ALERT TABLE...CHANGE/MODIFY</code>完成对表字段字符集的设置。</p><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> goods<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> MODIFY name <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">32</span>) <span class="hljs-type">CHARACTER</span> <span class="hljs-keyword">SET</span> utf8,<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> MODIFY description <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">255</span>) <span class="hljs-type">CHARACTER</span> <span class="hljs-keyword">SET</span> utf8;<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.57</span> sec)<br>Records: <span class="hljs-number">1</span>  Duplicates: <span class="hljs-number">0</span>  Warnings: <span class="hljs-number">0</span><br></code></pre></td></tr></table></figure><p>之后我们再次插入含有中文的数据，已经成功了。</p><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> goods<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">VALUES</span> (<span class="hljs-number">2</span>,<span class="hljs-string">&#x27;笔记本&#x27;</span>,<span class="hljs-number">9998</span>,<span class="hljs-string">&#x27;续航时间超过10个小时&#x27;</span>);<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.06</span> sec)<br></code></pre></td></tr></table></figure><h4 id="为部分字段添加数据"><a href="#为部分字段添加数据" class="headerlink" title="为部分字段添加数据"></a>为部分字段添加数据</h4><p>为部分字段添加数据的基本格式如下。</p><blockquote><p>INSERT [INTO] 数据表名 (字段名1[,字段名2]…) {VALUES|VALUE}(值1[,值2]…);</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> goods (id,name) <span class="hljs-keyword">VALUE</span>(<span class="hljs-number">3</span>, <span class="hljs-string">&#x27;Mobile phone&#x27;</span>);<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.05</span> sec)<br></code></pre></td></tr></table></figure><p>除此之外，MySQL还提供了另外一种使用<code>INSERT</code>语句来为指定字段添加数据的方式。</p><blockquote><p>INSERT [INTO] 数据表名 SET 字段名1&#x3D;值1[, 字段名2&#x3D;值2]…;</p></blockquote><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> goods <span class="hljs-keyword">SET</span> id <span class="hljs-operator">=</span> <span class="hljs-number">4</span>, name <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;Camera&#x27;</span>;<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.05</span> sec)<br></code></pre></td></tr></table></figure><h4 id="一次添加多行数据"><a href="#一次添加多行数据" class="headerlink" title="一次添加多行数据"></a>一次添加多行数据</h4><p>在实际开发中，向一张数据表中同时插入多条记录时，我们肯定不能重复的写上面的<code>INSERT</code>指令，这么玩自己迟早得疯。因此，我们可以使用MySQL提供的另外一种插入数据的方式。基本语法格式如下。</p><blockquote><p>INSERT [INTO] 数据表名 [(字段列表)] {VALUES|VALUE}(值列表[,值列表]…);</p></blockquote><p>当字段列表省略时，要严格按照数据表创建的的字段顺序插入，</p><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> goods <span class="hljs-keyword">VALUES</span><br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> (<span class="hljs-number">5</span>, <span class="hljs-string">&#x27;keyBoard&#x27;</span>, <span class="hljs-number">599</span>, <span class="hljs-keyword">NULL</span>),<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> (<span class="hljs-number">6</span>, <span class="hljs-string">&#x27;radio&#x27;</span>, <span class="hljs-number">299</span>, <span class="hljs-keyword">NULL</span>);<br>Query OK, <span class="hljs-number">2</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.04</span> sec)<br>Records: <span class="hljs-number">2</span>  Duplicates: <span class="hljs-number">0</span>  Warnings: <span class="hljs-number">0</span><br></code></pre></td></tr></table></figure><p>需要注意的是，在一次添加多行数据时，若一条数据插入失败，则整个插入语句都会失败。</p><h3 id="查询数据"><a href="#查询数据" class="headerlink" title="查询数据"></a>查询数据</h3><h4 id="查询表中全部数据"><a href="#查询表中全部数据" class="headerlink" title="查询表中全部数据"></a>查询表中全部数据</h4><p>查询数据表中所有字段的数据，可以使用星号“*”通配符代替数据表中的所有字段名，基本语法格式如下。</p><blockquote><p>SELECT * FROM 数据表名;</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> goods;<br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------------+-------+-----------------------+</span><br><span class="hljs-operator">|</span> id   <span class="hljs-operator">|</span> name         <span class="hljs-operator">|</span> price <span class="hljs-operator">|</span> description           <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------------+-------+-----------------------+</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">1</span> <span class="hljs-operator">|</span> notebook     <span class="hljs-operator">|</span>  <span class="hljs-number">4998</span> <span class="hljs-operator">|</span> High cost performance <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">2</span> <span class="hljs-operator">|</span> 笔记本       <span class="hljs-operator">|</span>  <span class="hljs-number">9998</span> <span class="hljs-operator">|</span> 续航时间超过<span class="hljs-number">10</span>个小时  <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">3</span> <span class="hljs-operator">|</span> Mobile phone <span class="hljs-operator">|</span>  <span class="hljs-keyword">NULL</span> <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>                  <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">4</span> <span class="hljs-operator">|</span> Camera       <span class="hljs-operator">|</span>  <span class="hljs-keyword">NULL</span> <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>                  <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">5</span> <span class="hljs-operator">|</span> keyBoard     <span class="hljs-operator">|</span>   <span class="hljs-number">599</span> <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>                  <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">6</span> <span class="hljs-operator">|</span> radio        <span class="hljs-operator">|</span>   <span class="hljs-number">299</span> <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>                  <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------------+-------+-----------------------+</span><br><span class="hljs-number">6</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h4 id="查询表中的部分字段"><a href="#查询表中的部分字段" class="headerlink" title="查询表中的部分字段"></a>查询表中的部分字段</h4><p>查询数据时，可在<code>SELECT</code>语句的字段列表中指定要查询的字段。基本语法格式如下。</p><blockquote><p>SELECT {字段名1,字段名2,字段名3,…} FROM 数据表名;</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SELECT</span> id,name <span class="hljs-keyword">FROM</span> goods;<br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------------+</span><br><span class="hljs-operator">|</span> id   <span class="hljs-operator">|</span> name         <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------------+</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">1</span> <span class="hljs-operator">|</span> notebook     <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">2</span> <span class="hljs-operator">|</span> 笔记本       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">3</span> <span class="hljs-operator">|</span> Mobile phone <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">4</span> <span class="hljs-operator">|</span> Camera       <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">5</span> <span class="hljs-operator">|</span> keyBoard     <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">6</span> <span class="hljs-operator">|</span> radio        <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------------+</span><br><span class="hljs-number">6</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h4 id="简单条件查询数据"><a href="#简单条件查询数据" class="headerlink" title="简单条件查询数据"></a>简单条件查询数据</h4><p>在查询数据时，若想要查询出符合条件的相关数据记录时，可以使用<code>WHERE</code>实现。基本语法格式如下。</p><blockquote><p>SELECT * |{字段名1,字段名2,字段名3,…} FROM 数据表名 WHERE 字段名 &#x3D; 值；</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> goods <span class="hljs-keyword">WHERE</span> id <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br><span class="hljs-operator">+</span><span class="hljs-comment">------+----------+-------+-----------------------+</span><br><span class="hljs-operator">|</span> id   <span class="hljs-operator">|</span> name     <span class="hljs-operator">|</span> price <span class="hljs-operator">|</span> description           <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+----------+-------+-----------------------+</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">1</span> <span class="hljs-operator">|</span> notebook <span class="hljs-operator">|</span>  <span class="hljs-number">4998</span> <span class="hljs-operator">|</span> High cost performance <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+----------+-------+-----------------------+</span><br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h3 id="修改数据"><a href="#修改数据" class="headerlink" title="修改数据"></a>修改数据</h3><p>MySQL提供了<code>UPDATE</code>语句修改数据。基本语法格式如下。</p><blockquote><p>UPDATE 数据表名 SET 字段名1 &#x3D; 值1 [,字段名2 &#x3D; 值2,…] [WHERE 条件表达式]</p></blockquote><p>上述语法中，若实际使用时没有添加<code>WHERE</code>条件，那么表中所有对应的字段都会被修改成统一的值，因此在修改数据时，请谨慎操作。</p><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">UPDATE</span> goods<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SET</span> price <span class="hljs-operator">=</span> <span class="hljs-number">5899</span><br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> <span class="hljs-keyword">WHERE</span> id <span class="hljs-operator">=</span> <span class="hljs-number">2</span>;<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.03</span> sec)<br><span class="hljs-keyword">Rows</span> matched: <span class="hljs-number">1</span>  Changed: <span class="hljs-number">1</span>  Warnings: <span class="hljs-number">0</span><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> goods <span class="hljs-keyword">WHERE</span> id <span class="hljs-operator">=</span> <span class="hljs-number">2</span>;<br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------+-------+----------------------+</span><br><span class="hljs-operator">|</span> id   <span class="hljs-operator">|</span> name   <span class="hljs-operator">|</span> price <span class="hljs-operator">|</span> description          <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------+-------+----------------------+</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">2</span> <span class="hljs-operator">|</span> 笔记本 <span class="hljs-operator">|</span>  <span class="hljs-number">5899</span> <span class="hljs-operator">|</span> 续航时间超过<span class="hljs-number">10</span>个小时 <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------+-------+----------------------+</span><br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><h3 id="删除数据"><a href="#删除数据" class="headerlink" title="删除数据"></a>删除数据</h3><p>删除数据是指对表中存在的记录进行删除。MySQL中使用<code>DELETE</code>语句删除表中的记录，基本语法格式如下。</p><blockquote><p>DELETE FROM 数据表名 [WHERE 条件表达式];</p></blockquote><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DELETE</span> <span class="hljs-keyword">FROM</span> goods <span class="hljs-keyword">WHERE</span> id <span class="hljs-operator">=</span> <span class="hljs-number">3</span>;<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.06</span> sec)<br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> goods;<br><span class="hljs-operator">+</span><span class="hljs-comment">------+----------+-------+-----------------------+</span><br><span class="hljs-operator">|</span> id   <span class="hljs-operator">|</span> name     <span class="hljs-operator">|</span> price <span class="hljs-operator">|</span> description           <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+----------+-------+-----------------------+</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">1</span> <span class="hljs-operator">|</span> notebook <span class="hljs-operator">|</span>  <span class="hljs-number">4998</span> <span class="hljs-operator">|</span> High cost performance <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">2</span> <span class="hljs-operator">|</span> 笔记本   <span class="hljs-operator">|</span>  <span class="hljs-number">5899</span> <span class="hljs-operator">|</span> 续航时间超过<span class="hljs-number">10</span>个小时  <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">4</span> <span class="hljs-operator">|</span> Camera   <span class="hljs-operator">|</span>  <span class="hljs-keyword">NULL</span> <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>                  <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">5</span> <span class="hljs-operator">|</span> keyBoard <span class="hljs-operator">|</span>   <span class="hljs-number">599</span> <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>                  <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">6</span> <span class="hljs-operator">|</span> radio    <span class="hljs-operator">|</span>   <span class="hljs-number">299</span> <span class="hljs-operator">|</span> <span class="hljs-keyword">NULL</span>                  <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+----------+-------+-----------------------+</span><br><span class="hljs-number">5</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><p>上述语法中，若实际使用时没有添加WHERE条件，系统会自动删除该表中的所有记录，因此在操作时，请谨慎操作。</p><h2 id="课后作业"><a href="#课后作业" class="headerlink" title="课后作业"></a>课后作业</h2><ol><li><p>mydb数据库中创建一张电子杂志订阅表(subscribe)。</p></li><li><p>电子杂志订阅表中要包含4个字段，分别为编号(id)、订阅邮件的邮箱地址(email)、用户是否确认订阅(status,使用数字表示，1表示已确认，0表示未确认)、邮箱确认的验证码(code)。</p></li><li><p>为电子杂志订阅表添加5条测试数据，如下表。</p><table><thead><tr><th>编号</th><th>邮箱地址</th><th>是否确认的状态</th><th>邮箱确认验证码</th></tr></thead><tbody><tr><td>1</td><td><a href="mailto:&#116;&#x6f;&#x6d;&#49;&#x32;&#51;&#x40;&#x31;&#x36;&#x33;&#x2e;&#x63;&#x6f;&#109;">&#116;&#x6f;&#x6d;&#49;&#x32;&#51;&#x40;&#x31;&#x36;&#x33;&#x2e;&#x63;&#x6f;&#109;</a></td><td>1</td><td>TRBXPO</td></tr><tr><td>2</td><td><a href="mailto:&#108;&#x75;&#99;&#121;&#x31;&#x32;&#x33;&#64;&#49;&#x36;&#x33;&#46;&#99;&#x6f;&#x6d;">&#108;&#x75;&#99;&#121;&#x31;&#x32;&#x33;&#64;&#49;&#x36;&#x33;&#46;&#99;&#x6f;&#x6d;</a></td><td>1</td><td>LOICPE</td></tr><tr><td>3</td><td><a href="mailto:&#x6c;&#x69;&#108;&#x79;&#x31;&#x32;&#51;&#x40;&#49;&#x36;&#x33;&#46;&#x63;&#x6f;&#109;">&#x6c;&#x69;&#108;&#x79;&#x31;&#x32;&#51;&#x40;&#49;&#x36;&#x33;&#46;&#x63;&#x6f;&#109;</a></td><td>0</td><td>JIXDAM</td></tr><tr><td>4</td><td><a href="mailto:&#x6a;&#105;&#x6d;&#109;&#x79;&#49;&#x32;&#x33;&#x40;&#49;&#54;&#51;&#46;&#99;&#111;&#x6d;">&#x6a;&#105;&#x6d;&#109;&#x79;&#49;&#x32;&#x33;&#x40;&#49;&#54;&#51;&#46;&#99;&#111;&#x6d;</a></td><td>0</td><td>QKOLPH</td></tr><tr><td>5</td><td><a href="mailto:&#x6a;&#111;&#121;&#x31;&#50;&#x33;&#x40;&#x31;&#54;&#x33;&#46;&#99;&#x6f;&#109;">&#x6a;&#111;&#121;&#x31;&#50;&#x33;&#x40;&#x31;&#54;&#x33;&#46;&#99;&#x6f;&#109;</a></td><td>1</td><td>JSMWNL</td></tr></tbody></table></li><li><p>查看已经通过邮箱确认的电子杂志订阅信息。</p></li><li><p>将编号等于4的订阅确认状态设置为“已确认”。</p></li><li><p>删除编号等于5的电子杂志订阅信息。</p></li></ol><figure class="highlight sql"><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><code class="hljs sql">mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> subscribe (<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> id <span class="hljs-type">INT</span> COMMENT <span class="hljs-string">&#x27;编号&#x27;</span>,<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> email <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">60</span>) COMMENT <span class="hljs-string">&#x27;邮件订阅的邮箱地址&#x27;</span>,<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> status <span class="hljs-type">INT</span> COMMENT <span class="hljs-string">&#x27;是否确认,0未确认,1已确认&#x27;</span>,<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> code <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">10</span>) COMMENT <span class="hljs-string">&#x27;邮箱确认的验证码&#x27;</span><br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> ) <span class="hljs-keyword">DEFAULT</span> CHARSET <span class="hljs-operator">=</span> utf8;<br>Query OK, <span class="hljs-number">0</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.22</span> sec)<br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> subscribe <span class="hljs-keyword">VALUES</span><br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> (<span class="hljs-number">1</span>, <span class="hljs-string">&#x27;tom123@163.com&#x27;</span>, <span class="hljs-number">1</span>, <span class="hljs-string">&#x27;TRBXPO&#x27;</span>),<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> (<span class="hljs-number">2</span>, <span class="hljs-string">&#x27;lucy123@163.com&#x27;</span>, <span class="hljs-number">1</span>, <span class="hljs-string">&#x27;LOICPE&#x27;</span>),<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> (<span class="hljs-number">3</span>, <span class="hljs-string">&#x27;lily123@163.com&#x27;</span>, <span class="hljs-number">0</span>, <span class="hljs-string">&#x27;JIXDAM&#x27;</span>),<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> (<span class="hljs-number">4</span>, <span class="hljs-string">&#x27;jimmy123@163.com&#x27;</span>, <span class="hljs-number">0</span>, <span class="hljs-string">&#x27;QKOLPH&#x27;</span>),<br>    <span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span> (<span class="hljs-number">5</span>, <span class="hljs-string">&#x27;joy123@163.com&#x27;</span>, <span class="hljs-number">1</span>, <span class="hljs-string">&#x27;JSMWNL&#x27;</span>);<br>Query OK, <span class="hljs-number">5</span> <span class="hljs-keyword">rows</span> affected (<span class="hljs-number">0.15</span> sec)<br>Records: <span class="hljs-number">5</span>  Duplicates: <span class="hljs-number">0</span>  Warnings: <span class="hljs-number">0</span><br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> subscribe <span class="hljs-keyword">WHERE</span> status <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;<br><span class="hljs-operator">+</span><span class="hljs-comment">------+-----------------+--------+--------+</span><br><span class="hljs-operator">|</span> id   <span class="hljs-operator">|</span> email           <span class="hljs-operator">|</span> status <span class="hljs-operator">|</span> code   <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+-----------------+--------+--------+</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">1</span> <span class="hljs-operator">|</span> tom123<span class="hljs-variable">@163</span>.com  <span class="hljs-operator">|</span>      <span class="hljs-number">1</span> <span class="hljs-operator">|</span> TRBXPO <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">2</span> <span class="hljs-operator">|</span> lucy123<span class="hljs-variable">@163</span>.com <span class="hljs-operator">|</span>      <span class="hljs-number">1</span> <span class="hljs-operator">|</span> LOICPE <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">5</span> <span class="hljs-operator">|</span> joy123<span class="hljs-variable">@163</span>.com  <span class="hljs-operator">|</span>      <span class="hljs-number">1</span> <span class="hljs-operator">|</span> JSMWNL <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+-----------------+--------+--------+</span><br><span class="hljs-number">3</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">UPDATE</span> subscribe <span class="hljs-keyword">SET</span> status <span class="hljs-operator">=</span> <span class="hljs-number">1</span> <span class="hljs-keyword">WHERE</span> id <span class="hljs-operator">=</span> <span class="hljs-number">4</span>;<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.05</span> sec)<br><span class="hljs-keyword">Rows</span> matched: <span class="hljs-number">1</span>  Changed: <span class="hljs-number">1</span>  Warnings: <span class="hljs-number">0</span><br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> subscribe <span class="hljs-keyword">WHERE</span> id <span class="hljs-operator">=</span> <span class="hljs-number">4</span>;<br><span class="hljs-operator">+</span><span class="hljs-comment">------+------------------+--------+--------+</span><br><span class="hljs-operator">|</span> id   <span class="hljs-operator">|</span> email            <span class="hljs-operator">|</span> status <span class="hljs-operator">|</span> code   <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+------------------+--------+--------+</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">4</span> <span class="hljs-operator">|</span> jimmy123<span class="hljs-variable">@163</span>.com <span class="hljs-operator">|</span>      <span class="hljs-number">1</span> <span class="hljs-operator">|</span> QKOLPH <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+------------------+--------+--------+</span><br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">DELETE</span> <span class="hljs-keyword">FROM</span> subscribe <span class="hljs-keyword">WHERE</span> id <span class="hljs-operator">=</span> <span class="hljs-number">5</span>;<br>Query OK, <span class="hljs-number">1</span> <span class="hljs-type">row</span> affected (<span class="hljs-number">0.06</span> sec)<br><br>mysql<span class="hljs-operator">&gt;</span> <span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> subscribe;<br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------------------+--------+--------+</span><br><span class="hljs-operator">|</span> id   <span class="hljs-operator">|</span> email              <span class="hljs-operator">|</span> status <span class="hljs-operator">|</span> code   <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------------------+--------+--------+</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">1</span> <span class="hljs-operator">|</span>  tom123<span class="hljs-variable">@163</span>.com    <span class="hljs-operator">|</span>      <span class="hljs-number">1</span> <span class="hljs-operator">|</span> TRBXPO <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">2</span> <span class="hljs-operator">|</span>  lucy123<span class="hljs-variable">@163</span>.com   <span class="hljs-operator">|</span>      <span class="hljs-number">1</span> <span class="hljs-operator">|</span> LOICPE <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">3</span> <span class="hljs-operator">|</span>  lily123<span class="hljs-variable">@163</span>.com   <span class="hljs-operator">|</span>      <span class="hljs-number">0</span> <span class="hljs-operator">|</span> JIXDAM <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span>    <span class="hljs-number">4</span> <span class="hljs-operator">|</span>  jimmy123<span class="hljs-variable">@163</span>.com  <span class="hljs-operator">|</span>      <span class="hljs-number">1</span> <span class="hljs-operator">|</span> QKOLPH <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------+--------------------+--------+--------+</span><br><span class="hljs-number">4</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.01</span> sec)<br></code></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>目前来说，数据库的相关操作都可以通过<code>Navicat Premium</code>等相关工具直接通过UI界面生成，所以重点在于熟练掌握数据的相关操作，但总体还是要根据书中的例子实际去敲一遍代码，强化对MySQL语句的语感。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;这个系列的笔记主要是读《MySQL数据库原理、设计与应用》一书过程中，整理的读书笔记和书中的练习作业。主要目的是解决因为MySQL数据库使用不熟练，导致在利用Node.js编写后端API时，知道逻辑却不知道对应的SQL语句怎么写的问题。&lt;/p&gt;
&lt;p&gt;这篇笔记的内容对应书中</summary>
      
    
    
    
    <category term="后端探索" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/"/>
    
    
    <category term="MySQL" scheme="https://www.xdxmblog.cn/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>Node + Express + MySQL 开发RESULT API(四)权限+日志</title>
    <link href="https://www.xdxmblog.cn/posts/9650.html"/>
    <id>https://www.xdxmblog.cn/posts/9650.html</id>
    <published>2025-04-26T08:30:36.000Z</published>
    <updated>2025-04-26T08:30:36.000Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇笔记我们实现了接口的参数校验功能、全局中间件的开发和错误处理、以及环境变量的配置。这个小项目已经趋于完善，这篇笔记主要实现接口的权限校验以及日志的接入和上传。最终达到麻雀虽小，五脏俱全的效果。</p><span id="more"></span><h2 id="权限认证"><a href="#权限认证" class="headerlink" title="权限认证"></a>权限认证</h2><h3 id="了解Token"><a href="#了解Token" class="headerlink" title="了解Token"></a>了解Token</h3><p>Token表示令牌，也就是用户的登录凭证。我们做前端开发的时候，几乎可以说每个项目都会用到<code>Token</code>，并且需要把它存到<code>sessionStorage</code>里面，每次向后端请求接口的时候，都要把token放到请求头里，否则接口就会返回401。</p><p>token具有以下几个优点，这也使得它成为了目前主流的权限认证方式。</p><ul><li>支持跨域访问：默认情况下cookie是无法跨域的。而token一般是放到请求头中，所以跨域后不存在信息丢失。</li><li>无状态：token机制在服务端不需要存储session信息，可以减轻服务端压力。</li><li>适用于移动端：APP端使用token认证会简单便捷。</li><li>无需考虑CSRF：由于不依赖cookie，所以使用token认证不会发CSRF，所以无需考虑CSRF防御。</li></ul><h3 id="了解JWT"><a href="#了解JWT" class="headerlink" title="了解JWT"></a>了解JWT</h3><p><code>JWT</code>是<code>token</code>的一种具体实现方式，其全称是<code>JSON Web Token</code>。<code>JWT</code>由以下几方面组成：</p><ul><li>header(标头)：包含有关JWT编码方式的信息，例如用于签署令牌的算法。</li><li>payload（有效负载）：包含声明（关于用户和附加元数据的声明）分为：注册声明、公共声明、私人声明。</li><li>signature（签名）：用于验证JWT的发送者是否是其声称的身份，并确保消息在此发送过程中没有被更改。</li></ul><h4 id="安装JWT"><a href="#安装JWT" class="headerlink" title="安装JWT"></a>安装JWT</h4><p>我们需要在项目中安装依赖：<code>jsonwebtoken</code>，它可以帮助我们在项目中生成token。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm i jsonwebtoken express-jwt<br></code></pre></td></tr></table></figure><h4 id="使用JWT生成Token"><a href="#使用JWT生成Token" class="headerlink" title="使用JWT生成Token"></a>使用JWT生成Token</h4><p>使用JWT生成token之前，我们需要在<code>.env</code>文件里添加一个配置，并将其加入<code>config</code>配置文件。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs env"># .env<br>JWT_SECRETKEY = &#x27;!@#$%^&amp;*()&#x27;  # jwt密钥，随便定义一个，主要用于解密token<br></code></pre></td></tr></table></figure><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;dotenv&#x27;</span>)<br>dotenv.<span class="hljs-title function_">config</span>()<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = &#123;<br>  ...,<br>  <span class="hljs-attr">jwtSecretkey</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">JWT_SECRETKEY</span>,<br>&#125;<br></code></pre></td></tr></table></figure><p>打开<code>utils.js</code>文件，编写以下代码，用于生成token。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;jsonwebtoken&#x27;</span>)<br><br><span class="hljs-comment">// 生成token</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">generateToken</span> = <span class="hljs-function">(<span class="hljs-params">user, isRefresh = <span class="hljs-literal">false</span></span>) =&gt;</span> &#123;<br>  <span class="hljs-comment">// jwt.sign接受3个参数，要加密的数据，jwt密钥，options</span><br>  <span class="hljs-keyword">const</span> token = jwt.<span class="hljs-title function_">sign</span>(&#123; user &#125;, config.<span class="hljs-property">jwtSecretkey</span>, &#123; <span class="hljs-attr">expiresIn</span>: isRefresh ? <span class="hljs-string">&#x27;7d&#x27;</span> : <span class="hljs-string">&#x27;1h&#x27;</span> &#125;) <span class="hljs-comment">// 有效期配置</span><br>  <span class="hljs-keyword">return</span> <span class="hljs-string">`Bearer <span class="hljs-subst">$&#123;token&#125;</span>`</span><br>&#125;<br></code></pre></td></tr></table></figure><p>在这个方法中，我们预留一个<code>isRefresh</code>字段，用于后面生成<code>refreshToken</code>。</p><h3 id="编写登录接口"><a href="#编写登录接口" class="headerlink" title="编写登录接口"></a>编写登录接口</h3><p>通常我们会在登录的时候生成token并将其一起返回给前端，让前端存储token，并在调用接口时将token放在请求头中发给后端。</p><h4 id="新增登录路由"><a href="#新增登录路由" class="headerlink" title="新增登录路由"></a>新增登录路由</h4><p>接下来我们编写一个登录接口，打开<code>user.routes.js</code>，新增登录路由。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-comment">// 邮箱登录</span><br>router.<span class="hljs-title function_">post</span>(<span class="hljs-string">&#x27;/login&#x27;</span>, validator.<span class="hljs-title function_">body</span>(userSchema.<span class="hljs-property">login</span>), <span class="hljs-title function_">asyncHandler</span>(userController.<span class="hljs-property">login</span>))<br><br>...<br></code></pre></td></tr></table></figure><p>登录接口我们选择使用邮箱+验证码的方式登录，也刚好可以利用上上一篇笔记中的发生验证码接口。</p><h4 id="编写登录校验"><a href="#编写登录校验" class="headerlink" title="编写登录校验"></a>编写登录校验</h4><p>打开<code>user.schema.js</code>文件，增加登录接口需要校验的参数。由于之前在写注册接口时已经定义过<code>email、code</code>参数的校验规则，这里我们直接暴露即可。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-built_in">exports</span>.<span class="hljs-property">login</span> = <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">object</span>(&#123; email, code &#125;)<br></code></pre></td></tr></table></figure><h4 id="编写登录逻辑"><a href="#编写登录逻辑" class="headerlink" title="编写登录逻辑"></a>编写登录逻辑</h4><p>打开<code>user.controller.js</code>文件，编写以下代码。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-built_in">exports</span>.<span class="hljs-property">login</span> = <span class="hljs-keyword">async</span> (req, res) =&gt; &#123;<br>  <span class="hljs-keyword">if</span> (!req.<span class="hljs-property">body</span>) <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">400</span>, <span class="hljs-string">&#x27;缺少必要参数&#x27;</span>)<br>  <span class="hljs-keyword">const</span> &#123; email, code &#125; = req.<span class="hljs-property">body</span><br>  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title class_">User</span>.<span class="hljs-title function_">login</span>(email, code)<br>  res.<span class="hljs-title function_">success</span>(result, <span class="hljs-string">&#x27;登录成功&#x27;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><p>打开<code>user.model.js</code>文件，开始编写登录接口的核心逻辑。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 邮箱登录</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">login</span> = <span class="hljs-keyword">async</span> (email, code) =&gt; &#123;<br>  <span class="hljs-comment">// 查询邮箱是否已被注册</span><br>  <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">findUser</span>(<span class="hljs-string">&#x27;email&#x27;</span>, email)<br>  <span class="hljs-keyword">if</span> (users.<span class="hljs-property">length</span> == <span class="hljs-number">0</span>) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">400</span>, <span class="hljs-string">&#x27;该用户不存在&#x27;</span>)<br>  &#125;<br>  <span class="hljs-comment">// 校验验证码</span><br>  <span class="hljs-keyword">const</span> isLogin = <span class="hljs-literal">true</span><br>  <span class="hljs-keyword">const</span> isTrue = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">verifyCode</span>(email, code, isLogin)<br>  <span class="hljs-keyword">if</span> (!isTrue) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">400</span>, <span class="hljs-string">&#x27;验证码错误或已过期&#x27;</span>)<br>  &#125;<br>  <span class="hljs-keyword">const</span> accessToken = utils.<span class="hljs-title function_">generateToken</span>(&#123; <span class="hljs-attr">email</span>: users[<span class="hljs-number">0</span>].<span class="hljs-property">email</span>, <span class="hljs-attr">id</span>: users[<span class="hljs-number">0</span>].<span class="hljs-property">id</span> &#125;)<br>  <span class="hljs-keyword">return</span> &#123; ...users[<span class="hljs-number">0</span>], accessToken &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h4 id="优化通用方法"><a href="#优化通用方法" class="headerlink" title="优化通用方法"></a>优化通用方法</h4><p>登录过程与注册过程差不多，这里我们优化一下<code>verifyCode</code>方法，使其入参更加语义化。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 校验验证码</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">verifyCode</span> = <span class="hljs-keyword">async</span> (email, code, isLogin = <span class="hljs-literal">false</span>) =&gt; &#123;<br>  <span class="hljs-keyword">let</span> sql = <span class="hljs-string">`select * from <span class="hljs-subst">$&#123;isLogin ? <span class="hljs-string">&#x27;user&#x27;</span> : <span class="hljs-string">&#x27;temp_user_code&#x27;</span>&#125;</span> where email = &#x27;<span class="hljs-subst">$&#123;email&#125;</span>&#x27; and expires_at &gt; NOW()`</span><br>  <span class="hljs-keyword">const</span> [rows] = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(sql, [email])<br>...<br>&#125;<br></code></pre></td></tr></table></figure><p>同时我们还利用到了之前写的查询用户的方法，但是我们返回用户的信息并不需要全部字段，所以我们需要改造一下，过滤掉我们不希望展示给前端的数据。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 查询用户</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">findUser</span> = <span class="hljs-keyword">async</span> (findType, data) =&gt; &#123;<br>  <span class="hljs-comment">// 仅返回id,openid,email,typeId，并根据typeId查询typeName一并返回</span><br>  <span class="hljs-keyword">let</span> sql = <span class="hljs-string">`select u.id, u.openid, u.email, u.type_id as typeId, t.name as typeName from user u join user_type t on u.type_id = t.id where <span class="hljs-subst">$&#123;findType&#125;</span> = ?`</span><br>  <span class="hljs-keyword">const</span> [rows] = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(sql, [data])<br>  <span class="hljs-keyword">return</span> rows<br>&#125;<br></code></pre></td></tr></table></figure><p>调用登录接口，输入邮箱和验证码，观察返回结果。我们可以看到token已经成功返回了。</p><figure class="highlight json"><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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;code&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">200</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;message&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;登录成功&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;data&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>        <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">16</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-attr">&quot;openid&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">null</span></span><span class="hljs-punctuation">,</span><br>        <span class="hljs-attr">&quot;email&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;416681736@qq.com&quot;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-attr">&quot;typeId&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-attr">&quot;typeName&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;妈妈&quot;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-attr">&quot;token&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImVtYWlsIjoiNDE2NjgxNzM2QHFxLmNvbSIsImlkIjoxNn0sImlhdCI6MTc0NTA0MzgyOSwiZXhwIjoxNzQ1MDUxMDI5fQ.2IpeNR6BFa0GprP-ahhlffPUg6ft_RnqhtuTDlSXA5o&quot;</span><br>    <span class="hljs-punctuation">&#125;</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><h3 id="校验Token"><a href="#校验Token" class="headerlink" title="校验Token"></a>校验Token</h3><p>校验token通常有两种方式，一种是使用现成的express中间件，另一种是自己开发一个中间件。下面将两种方法都介绍一下,任选其中一个使用即可。</p><h4 id="一-开发authTokenMiddleware中间件"><a href="#一-开发authTokenMiddleware中间件" class="headerlink" title="一. 开发authTokenMiddleware中间件"></a>一. 开发authTokenMiddleware中间件</h4><p>在<code>middlewares</code>文件夹下新建<code>authTokenMiddleware.js</code>文件，编写校验token的代码。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;jsonwebtoken&#x27;</span>)<br><span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;../config&#x27;</span>)<br><span class="hljs-keyword">const</span> createError = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;http-errors&#x27;</span>)<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> &#123;<br>  <span class="hljs-comment">// 定义无需验证token的路由</span><br>  <span class="hljs-keyword">const</span> unlessPath = [<span class="hljs-string">&#x27;/api/user/login&#x27;</span>, <span class="hljs-string">&#x27;/api/user/register&#x27;</span>, <span class="hljs-string">&#x27;/api/user/sendEmaliCode&#x27;</span>]<br>  <span class="hljs-comment">// 遇到无需token的接口，直接放行</span><br>  <span class="hljs-keyword">if</span> (unlessPath.<span class="hljs-title function_">includes</span>(req.<span class="hljs-property">path</span>)) &#123;<br>    <span class="hljs-keyword">return</span> <span class="hljs-title function_">next</span>()<br>  &#125;<br><br>  <span class="hljs-comment">// 获取请求头中的accessToken</span><br>  <span class="hljs-keyword">let</span> accessToken<br>  <span class="hljs-keyword">if</span> (req.<span class="hljs-property">headers</span> &amp;&amp; req.<span class="hljs-property">headers</span>.<span class="hljs-property">authorization</span>) &#123;<br>    <span class="hljs-comment">// 给前端返回token时我们拼了Bearer ，这里需要处理</span><br>    <span class="hljs-keyword">const</span> parts = req.<span class="hljs-property">headers</span>.<span class="hljs-property">authorization</span>.<span class="hljs-title function_">split</span>(<span class="hljs-string">&#x27; &#x27;</span>)<br>    <span class="hljs-keyword">if</span> (parts.<span class="hljs-property">length</span> == <span class="hljs-number">2</span> &amp;&amp; <span class="hljs-regexp">/^Bearer$/i</span>.<span class="hljs-title function_">test</span>(parts[<span class="hljs-number">0</span>])) &#123;<br>      accessToken = parts[<span class="hljs-number">1</span>]<br>      <span class="hljs-comment">// 验证accessToken</span><br>      <span class="hljs-keyword">try</span> &#123;<br>        jwt.<span class="hljs-title function_">verify</span>(accessToken, config.<span class="hljs-property">jwtSecretkey</span>)<br>        <span class="hljs-keyword">return</span> <span class="hljs-title function_">next</span>() <span class="hljs-comment">// 校验token成功，放行</span><br>      &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>        <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">401</span>, <span class="hljs-string">&#x27;无效或过期的token&#x27;</span>) <span class="hljs-comment">// 校验失败，抛出异常</span><br>      &#125;<br>    &#125;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">401</span>, <span class="hljs-string">&#x27;无效或过期的token&#x27;</span>) <span class="hljs-comment">// token 格式校验失败</span><br>  &#125;<br>  <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">401</span>, <span class="hljs-string">&#x27;无效或过期的token&#x27;</span>) <span class="hljs-comment">// 没有携带token，抛出异常</span><br>&#125;<br></code></pre></td></tr></table></figure><p>在<code>app.js</code>文件中，将刚才写好的中间件进行注册，注意中间件的执行顺序，要放在路由之前。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-keyword">const</span> authTokenMiddleware = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./middlewares/authTokenMiddleware&#x27;</span>)<br>...<br><br>app.<span class="hljs-title function_">use</span>(authTokenMiddleware)<br><span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./routes&#x27;</span>)(app)<br><br>...<br></code></pre></td></tr></table></figure><h4 id="二-使用express-jwt中间件"><a href="#二-使用express-jwt中间件" class="headerlink" title="二. 使用express-jwt中间件"></a>二. 使用express-jwt中间件</h4><p><code>express-jwt</code>是一个自动校验<code>jwt</code>的中间件，使用之前需要先安装依赖。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm i express-jwt<br></code></pre></td></tr></table></figure><p>打开<code>utils.js</code>文件，编写以下代码。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-keyword">const</span> &#123; expressjwt &#125; = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;express-jwt&#x27;</span>)<br>...<br><br><span class="hljs-comment">// 校验token</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">jwtAuth</span> = <span class="hljs-title function_">expressjwt</span>(&#123;<br>  <span class="hljs-attr">secret</span>: config.<span class="hljs-property">jwtSecretkey</span>,<br>  <span class="hljs-attr">algorithms</span>: [<span class="hljs-string">&#x27;HS256&#x27;</span>],<br>&#125;).<span class="hljs-title function_">unless</span>(&#123;<br>  <span class="hljs-attr">path</span>: [<span class="hljs-string">&#x27;/api/user/login&#x27;</span>, <span class="hljs-string">&#x27;/api/user/register&#x27;</span>, <span class="hljs-string">&#x27;/api/user/sendEmaliCode&#x27;</span>],<br>&#125;)<br></code></pre></td></tr></table></figure><p>打开<code>errorMiddleware.js</code>，在全局错误中间件里添加错误处理。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-function">(<span class="hljs-params">err, req, res, next</span>) =&gt;</span> &#123;<br> ...<br> <span class="hljs-comment">// 处理token验证错误</span><br> <span class="hljs-keyword">if</span> (err.<span class="hljs-property">name</span> === <span class="hljs-string">&#x27;UnauthorizedError&#x27;</span>) &#123;<br>   err = <span class="hljs-title function_">createError</span>(<span class="hljs-number">401</span>, <span class="hljs-string">&#x27;无效或过期的token&#x27;</span>)<br> &#125;<br> <br> ...<br>&#125;<br></code></pre></td></tr></table></figure><p>在<code>app.js</code>中注册中间件。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-keyword">const</span> utils = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./utils&#x27;</span>)<br>...<br><br>app.<span class="hljs-title function_">use</span>(utils.<span class="hljs-property">jwtAuth</span>)<br><span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./routes&#x27;</span>)(app)<br><br>...<br></code></pre></td></tr></table></figure><p>不管用上述哪种方式，现在我们调用之前写的<code>/api/user/type</code>接口，就会发现已经因为没有token被拦住了。</p><figure class="highlight json"><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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;code&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">401</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;message&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;无效或过期的token&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;data&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">null</span></span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><p>我们在请求头中添加<code>Authorization</code>字段，将登录成功后返回的<code>token</code>值附加到上述接口的请求头中，再次调用接口，会发现已经能正常返回数据了。</p><h3 id="使用双Token无感刷新"><a href="#使用双Token无感刷新" class="headerlink" title="使用双Token无感刷新"></a>使用双Token无感刷新</h3><p>目前的使用场景中，<code>accessToken</code>的有效期通常比较短，大概也就十几分钟到1小时不等。这么做的原因主要是降低token被盗用的风险。但单token的场景也有一个弊端，就是因为失效快而需要频繁登录。</p><p>而一个产品能否长久的存活最重要的一个原因就是用户体验，所以自然而然的就进化到了双Token方案。这种方案的操作步骤如下：</p><ol><li>生成<code>accessToken</code>和<code>refreshToken</code>，其中<code>accessToken</code>不变，依旧是较短时效。而<code>refreshToken</code>通常具有较长的有效期（几天到几个月）。</li><li><code>accessToken</code>依旧用于访问受保护的资源，当<code>accessToken</code>过期时，访问<code>refresh</code>接口，利用<code>refreshToken</code>重新生成新的<code>accessToken</code>返回到客户端。</li><li>客户端拿着新的<code>accessToken</code>重新访问受保护的资源，实现token的无感刷新，提升用户的体验。</li></ol><p>下面我们就来改造代码，将项目改造成双Token实现无感刷新。</p><p>打开<code>utils.js</code>，修改生成token的方法，通过添加字段来区别<code>accessToken</code>和<code>reFreshToken</code>，这么做的原因是为了防止拿<code>accessToken</code>来换取新的<code>accessToken</code>。</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript">... <br><span class="hljs-comment">// 生成token</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">generateToken</span> = <span class="hljs-function">(<span class="hljs-params">user, isRefresh = <span class="hljs-literal">false</span></span>) =&gt;</span> &#123;<br>  <span class="hljs-keyword">const</span> expiresIn = isRefresh ? <span class="hljs-string">&#x27;7d&#x27;</span> : <span class="hljs-string">&#x27;2h&#x27;</span><br>  <span class="hljs-keyword">const</span> tokenType = isRefresh ? <span class="hljs-string">&#x27;refresh&#x27;</span> : <span class="hljs-string">&#x27;access&#x27;</span><br>  <span class="hljs-keyword">const</span> token = jwt.<span class="hljs-title function_">sign</span>(&#123; user, tokenType &#125;, config.<span class="hljs-property">jwtSecretkey</span>, &#123; expiresIn &#125;) <span class="hljs-comment">// 有效期配置</span><br>  <span class="hljs-keyword">return</span> <span class="hljs-string">`Bearer <span class="hljs-subst">$&#123;token&#125;</span>`</span><br>&#125;<br><br><br><span class="hljs-comment">// 校验refreshToken</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">authRefreshToken</span> = <span class="hljs-function"><span class="hljs-params">refreshToken</span> =&gt;</span> &#123;<br>  <span class="hljs-keyword">let</span> token<br>  <span class="hljs-keyword">const</span> parts = refreshToken.<span class="hljs-title function_">split</span>(<span class="hljs-string">&#x27; &#x27;</span>)<br>  <span class="hljs-keyword">if</span> (parts.<span class="hljs-property">length</span> == <span class="hljs-number">2</span> &amp;&amp; <span class="hljs-regexp">/^Bearer$/i</span>.<span class="hljs-title function_">test</span>(parts[<span class="hljs-number">0</span>])) &#123;<br>    token = parts[<span class="hljs-number">1</span>]<br><br>    <span class="hljs-keyword">try</span> &#123;<br>      <span class="hljs-keyword">const</span> result = jwt.<span class="hljs-title function_">verify</span>(token, config.<span class="hljs-property">jwtSecretkey</span>)<br>      <span class="hljs-keyword">return</span> result<br>    &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span><br>    &#125;<br>  &#125;<br>  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span><br>&#125;<br></code></pre></td></tr></table></figure><p>打开<code>user.routes.js</code>，添加刷新token的路由，注意要把这个路由添加到<code>unlessPath</code>的白名单里。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-comment">// 刷新Token</span><br>router.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;/refresh&#x27;</span>, <span class="hljs-title function_">asyncHandler</span>(userController.<span class="hljs-property">refresh</span>))<br>...<br></code></pre></td></tr></table></figure><p>打开<code>user.controller.js</code>，添加对应的controller方法。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-comment">// 刷新token</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">refresh</span> = <span class="hljs-keyword">async</span> (req, res) =&gt; &#123;<br>  <span class="hljs-keyword">const</span> refreshToken = req.<span class="hljs-property">headers</span>.<span class="hljs-property">refreshtoken</span><br>  <span class="hljs-keyword">if</span> (!refreshToken) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">403</span>, <span class="hljs-string">&#x27;Forbidden&#x27;</span>)<br>  &#125;<br>  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title class_">User</span>.<span class="hljs-title function_">refresh</span>()<br>  res.<span class="hljs-title function_">success</span>(result)<br>&#125;<br>...<br></code></pre></td></tr></table></figure><p>打开<code>user.modele.js</code>，编写刷新token的方法，并对之前的login方法进行改造，返回双token。</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 刷新token</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">refresh</span> = <span class="hljs-keyword">async</span> refreshToken =&gt; &#123;<br>  <span class="hljs-keyword">const</span> jwtResult = utils.<span class="hljs-title function_">authRefreshToken</span>(refreshToken)<br>  <span class="hljs-keyword">if</span> (!jwtResult) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">403</span>, <span class="hljs-string">&#x27;Forbidden&#x27;</span>)<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-comment">// 防止拿accessToken换取accessToken</span><br>    <span class="hljs-keyword">if</span> (jwtResult.<span class="hljs-property">tokenType</span> != <span class="hljs-string">&#x27;refresh&#x27;</span>) &#123;<br>      <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">403</span>, <span class="hljs-string">&#x27;Forbidden&#x27;</span>)<br>    &#125;<br>    <span class="hljs-comment">// 生成新的accessToken</span><br>    <span class="hljs-keyword">const</span> accessToken = utils.<span class="hljs-title function_">generateToken</span>(jwtResult.<span class="hljs-property">user</span>)<br>    <span class="hljs-keyword">return</span> accessToken<br>  &#125;<br>&#125;<br><br><span class="hljs-comment">// 邮箱登录</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">login</span> = <span class="hljs-keyword">async</span> (email, code) =&gt; &#123;<br>  <span class="hljs-comment">// 查询邮箱是否已被注册</span><br>  <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">findUser</span>(<span class="hljs-string">&#x27;email&#x27;</span>, email)<br>  <span class="hljs-keyword">if</span> (users.<span class="hljs-property">length</span> == <span class="hljs-number">0</span>) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">400</span>, <span class="hljs-string">&#x27;该用户不存在&#x27;</span>)<br>  &#125;<br>  <span class="hljs-comment">// 校验验证码</span><br>  <span class="hljs-keyword">const</span> isLogin = <span class="hljs-literal">true</span><br>  <span class="hljs-keyword">const</span> isTrue = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">verifyCode</span>(email, code, isLogin)<br>  <span class="hljs-keyword">if</span> (!isTrue) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">400</span>, <span class="hljs-string">&#x27;验证码错误或已过期&#x27;</span>)<br>  &#125;<br>  <span class="hljs-keyword">const</span> isRefresh = <span class="hljs-literal">true</span><br>  <span class="hljs-keyword">const</span> accessToken = utils.<span class="hljs-title function_">generateToken</span>(&#123; <span class="hljs-attr">email</span>: users[<span class="hljs-number">0</span>].<span class="hljs-property">email</span>, <span class="hljs-attr">id</span>: users[<span class="hljs-number">0</span>].<span class="hljs-property">id</span> &#125;)<br>  <span class="hljs-keyword">const</span> refreshToken = utils.<span class="hljs-title function_">generateToken</span>(&#123; <span class="hljs-attr">email</span>: users[<span class="hljs-number">0</span>].<span class="hljs-property">email</span>, <span class="hljs-attr">id</span>: users[<span class="hljs-number">0</span>].<span class="hljs-property">id</span> &#125;, isRefresh)<br><br>  <span class="hljs-keyword">return</span> &#123; ...users[<span class="hljs-number">0</span>], accessToken, refreshToken &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>至此，Token权限校验的部分就开发完了，下面开始开发日志部分。</p><h2 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h2><p>不管是前端开发还是后端开发，日志管理都是追踪错误和监控系统性能的关键。通过日志管理，我们能够更好的维护和问题排查。这里我们选用社区中使用广泛的<code>winston</code>来实现项目的日志管理。</p><h3 id="安装winston、winston-daily-rotate-file"><a href="#安装winston、winston-daily-rotate-file" class="headerlink" title="安装winston、winston-daily-rotate-file"></a>安装winston、winston-daily-rotate-file</h3><p>其中<code>winston-daily-rotate-file</code></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript">npm i winston winston-daily-rotate-file<br></code></pre></td></tr></table></figure><h3 id="使用winston生成日志"><a href="#使用winston生成日志" class="headerlink" title="使用winston生成日志"></a>使用winston生成日志</h3><p>通过<code>winston.createLogger()</code>方法，我们可以创建自己的<code>logger</code>日志器。</p><p>该方法接受以下参数呢：</p><table><thead><tr><th>名称</th><th>默认</th><th>描述</th></tr></thead><tbody><tr><td><code>level</code></td><td><code>&#39;info&#39;</code></td><td>仅当<code>info.level</code>小于或等于此级别时才记录</td></tr><tr><td><code>levels</code></td><td><code>winston.config.npm.levels</code></td><td>表示日志优先级的级别（和颜色）</td></tr><tr><td><code>format</code></td><td><code>winston.format.json</code></td><td><code>info</code>消息的格式</td></tr><tr><td><code>transports</code></td><td><code>[]</code>（无传输）</td><td><code>info</code>消息的日志记录目标集</td></tr><tr><td><code>exitOnError</code></td><td><code>true</code></td><td>如果为<code>false</code>，则处理的异常不会导致<code>process.exit</code></td></tr><tr><td><code>silent</code></td><td><code>fasle</code></td><td>如果为true，则所有日志都将被抑制</td></tr></tbody></table><p>在<code>scr</code>目录下新建<code>logger</code>文件夹，并创建<code>index.js</code>文件，编写以下代码：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> winston = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;winston&#x27;</span>)<br><span class="hljs-keyword">const</span> <span class="hljs-title class_">DailyRotateFile</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;winston-daily-rotate-file&#x27;</span>)<br><br><span class="hljs-keyword">const</span> &#123; combine, timestamp, printf, colorize, label &#125; = winston.<span class="hljs-property">format</span><br><span class="hljs-keyword">const</span> env = process.<span class="hljs-property">env</span>.<span class="hljs-property">NODE_ENV</span> || <span class="hljs-string">&#x27;development&#x27;</span><br><br><span class="hljs-comment">// 自定义日志格式</span><br><span class="hljs-keyword">const</span> logFormat = <span class="hljs-title function_">printf</span>(<span class="hljs-function"><span class="hljs-params">info</span> =&gt;</span> &#123;<br>  <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">$&#123;info.timestamp&#125;</span> [<span class="hljs-subst">$&#123;info.level&#125;</span>]: <span class="hljs-subst">$&#123;info.message&#125;</span>`</span><br>&#125;)<br><br><span class="hljs-comment">// 创建Logger实例</span><br><span class="hljs-keyword">const</span> logger = winston.<span class="hljs-title function_">createLogger</span>(&#123;<br>  <span class="hljs-comment">// 设置日志级别（error, warn, info, verbose, debug, silly）</span><br>  <span class="hljs-attr">level</span>: env === <span class="hljs-string">&#x27;production&#x27;</span> ? <span class="hljs-string">&#x27;info&#x27;</span> : <span class="hljs-string">&#x27;debug&#x27;</span>,<br>  <span class="hljs-attr">format</span>: <span class="hljs-title function_">combine</span>(<br>    <span class="hljs-title function_">timestamp</span>(&#123; <span class="hljs-attr">format</span>: <span class="hljs-string">&#x27;YYYY-MM-DD HH:mm:ss&#x27;</span> &#125;), <span class="hljs-comment">// 添加时间戳</span><br>    logFormat, <span class="hljs-comment">// 应用自定义格式</span><br>  ),<br>  <span class="hljs-attr">transports</span>: [<br>    <span class="hljs-comment">// 控制台输出（带颜色）</span><br>    <span class="hljs-keyword">new</span> winston.<span class="hljs-property">transports</span>.<span class="hljs-title class_">Console</span>(&#123;<br>      <span class="hljs-attr">format</span>: <span class="hljs-title function_">combine</span>(<span class="hljs-title function_">colorize</span>(&#123; <span class="hljs-attr">all</span>: <span class="hljs-literal">true</span> &#125;), logFormat),<br>    &#125;),<br>    <span class="hljs-comment">// 按日分割文件</span><br>    <span class="hljs-keyword">new</span> <span class="hljs-title class_">DailyRotateFile</span>(&#123;<br>      <span class="hljs-attr">dirname</span>: <span class="hljs-string">&#x27;logs&#x27;</span>, <span class="hljs-comment">// 日志目录</span><br>      <span class="hljs-attr">filename</span>: <span class="hljs-string">&#x27;%DATE%.log&#x27;</span>, <span class="hljs-comment">// 文件名（%DATE% 自动替换为日期）</span><br>      <span class="hljs-attr">datePattern</span>: <span class="hljs-string">&#x27;YYYY-MM-DD&#x27;</span>, <span class="hljs-comment">// 日期格式</span><br>      <span class="hljs-attr">zippedArchive</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// 压缩旧日志</span><br>      <span class="hljs-attr">maxSize</span>: <span class="hljs-string">&#x27;20m&#x27;</span>, <span class="hljs-comment">// 单个文件最大大小</span><br>      <span class="hljs-attr">maxFiles</span>: <span class="hljs-string">&#x27;7d&#x27;</span>, <span class="hljs-comment">// 保留最近7天的日志</span><br>    &#125;),<br>  ],<br>  <span class="hljs-comment">// 异常处理（可选）</span><br>  <span class="hljs-attr">exceptionHandlers</span>: [<span class="hljs-keyword">new</span> winston.<span class="hljs-property">transports</span>.<span class="hljs-title class_">File</span>(&#123; <span class="hljs-attr">filename</span>: <span class="hljs-string">&#x27;logs/exceptions.log&#x27;</span> &#125;)],<br>  <span class="hljs-comment">// Promise拒绝处理（可选）</span><br>  <span class="hljs-attr">rejectionHandlers</span>: [<span class="hljs-keyword">new</span> winston.<span class="hljs-property">transports</span>.<span class="hljs-title class_">File</span>(&#123; <span class="hljs-attr">filename</span>: <span class="hljs-string">&#x27;logs/rejections.log&#x27;</span> &#125;)],<br>&#125;)<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = logger<br></code></pre></td></tr></table></figure><h3 id="开发loggerMiddleware中间件"><a href="#开发loggerMiddleware中间件" class="headerlink" title="开发loggerMiddleware中间件"></a>开发loggerMiddleware中间件</h3><p>在<code>middlewares</code>目录下创建<code>loggerMiddleware.js</code>文件，编写以下代码：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> logger = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;../logger&#x27;</span>)<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> &#123;<br>  <span class="hljs-keyword">const</span> start = <span class="hljs-title class_">Date</span>.<span class="hljs-title function_">now</span>()<br><br>  res.<span class="hljs-title function_">on</span>(<span class="hljs-string">&#x27;finish&#x27;</span>, <span class="hljs-function">() =&gt;</span> &#123;<br>    <span class="hljs-keyword">const</span> duration = <span class="hljs-title class_">Date</span>.<span class="hljs-title function_">now</span>() - start<br>    logger.<span class="hljs-title function_">info</span>(<span class="hljs-string">`<span class="hljs-subst">$&#123;req.method&#125;</span> <span class="hljs-subst">$&#123;req.originalUrl&#125;</span> - <span class="hljs-subst">$&#123;res.statusCode&#125;</span> (<span class="hljs-subst">$&#123;duration&#125;</span>ms)`</span>)<br>  &#125;)<br><br>  <span class="hljs-title function_">next</span>()<br>&#125;<br></code></pre></td></tr></table></figure><p>我们在每个请求完成的时候，记录了一条日志，包含了该条请求的主要信息。例如<code>GET /api/user/type - 401 (14ms)</code>，配合下面的错误日志，可以快速定位到是哪个接口报错，从而方便我们排查问题。</p><h3 id="记录错误日志"><a href="#记录错误日志" class="headerlink" title="记录错误日志"></a>记录错误日志</h3><p>在<code>errorMiddleware.js</code>文件中，编写以下代码：</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-keyword">const</span> logger = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;../logger&#x27;</span>)<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-function">(<span class="hljs-params">err, req, res, next</span>) =&gt;</span> &#123;<br>...<br>  <span class="hljs-keyword">if</span> (!createError.<span class="hljs-title function_">isHttpError</span>(err)) &#123;<br>    err = <span class="hljs-title function_">createError</span>(<span class="hljs-number">500</span>, <span class="hljs-string">&#x27;服务器内部错误，请稍后重试&#x27;</span>)<br>  &#125;<br>  <span class="hljs-comment">// 记录错误到日志</span><br>  logger.<span class="hljs-title function_">error</span>(<span class="hljs-string">`Error：&#x27; <span class="hljs-subst">$&#123;err.message&#125;</span>`</span>)<br>  ...<br>&#125;<br><br></code></pre></td></tr></table></figure><h3 id="在路由或服务中使用logger"><a href="#在路由或服务中使用logger" class="headerlink" title="在路由或服务中使用logger"></a>在路由或服务中使用logger</h3><p>以发送验证码为例，我们可以在任何路由或者服务中直接调用logger来记录日志。</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> logger = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;../logger&#x27;</span>)<br><br><span class="hljs-comment">// 发送邮箱验证码</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">sendEmailCode</span> = <span class="hljs-keyword">async</span> email =&gt; &#123;<br>  logger.<span class="hljs-title function_">debug</span>(<span class="hljs-string">&#x27;Fetching data from database...&#x27;</span>);<br>  <span class="hljs-comment">// 生成验证码，过期时间</span><br>  <span class="hljs-keyword">const</span> &#123; code, expiresAt &#125; = utils.<span class="hljs-title function_">generateCode</span>()<br>  <span class="hljs-comment">// 查询当前邮箱是否被注册</span><br>  <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">findUser</span>(<span class="hljs-string">&#x27;email&#x27;</span>, email)<br><br>  <span class="hljs-keyword">if</span> (users &amp;&amp; users.<span class="hljs-property">length</span> &gt; <span class="hljs-number">0</span>) &#123;<br>    <span class="hljs-comment">// 保存登录验证码</span><br>    <span class="hljs-keyword">let</span> updateSQL = <span class="hljs-string">`UPDATE user SET code=?, expires_at=? WHERE email=&#x27;<span class="hljs-subst">$&#123;email&#125;</span>&#x27;`</span><br>    <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(updateSQL, [code, expiresAt, email])<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-comment">// 每次发送之前先清空之前保存的临时验证码</span><br>    <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">deleteCode</span>(email)<br>    <span class="hljs-comment">// 保存注册验证码</span><br>    <span class="hljs-keyword">let</span> saveSQL = <span class="hljs-string">`INSERT into temp_user_code(email, code, expires_at) VALUES(?,?,?)`</span><br>    <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(saveSQL, [email, code, expiresAt])<br>  &#125;<br>  <br>  <span class="hljs-comment">// 发送邮件</span><br>  <span class="hljs-keyword">const</span> mailOptin = utils.<span class="hljs-title function_">generateMailOptions</span>(email, code, expiresAt)<br>  <span class="hljs-keyword">const</span> transporter = utils.<span class="hljs-title function_">transporter</span>()<br>  <span class="hljs-keyword">await</span> transporter.<span class="hljs-title function_">sendMail</span>(mailOptin)<br>  logger.<span class="hljs-title function_">info</span>(<span class="hljs-string">&#x27;Database info: %s&#x27;</span>, <span class="hljs-string">&#x27;验证码发送成功&#x27;</span>);<br>  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span><br>&#125;<br></code></pre></td></tr></table></figure><h3 id="加载日志中间件"><a href="#加载日志中间件" class="headerlink" title="加载日志中间件"></a>加载日志中间件</h3><p>打开<code>app.js</code>，编写以下代码：</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-keyword">const</span> loggerMiddleware = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./middlewares/loggerMiddleware&#x27;</span>)<br><br>...<br><span class="hljs-comment">// 加载日志中间件</span><br>app.<span class="hljs-title function_">use</span>(loggerMiddleware)<br>app.<span class="hljs-title function_">use</span>(responseMiddleware)<br>...<br></code></pre></td></tr></table></figure><h3 id="测试日志是否生成"><a href="#测试日志是否生成" class="headerlink" title="测试日志是否生成"></a>测试日志是否生成</h3><p>随便调用一个接口，将请求头的token去掉，返回401后观察项目根目录是否生成logs文件夹和日志文件。还可以通过终端查看是否打印出日志：</p><figure class="highlight bash"><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><code class="hljs bash">2025-04-25 11:01:49 [info]: GET /api/user/type - 401 (2ms)<br>2025-04-25 11:01:49 [error]: Error：<span class="hljs-string">&#x27; Forbidden</span><br><span class="hljs-string">2025-04-25 11:01:49 [info]: GET /api/user/refresh - 403 (1ms)</span><br></code></pre></td></tr></table></figure><h3 id="日志分割配置"><a href="#日志分割配置" class="headerlink" title="日志分割配置"></a>日志分割配置</h3><table><thead><tr><th>参数</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td>dirname</td><td>string</td><td>日志文件存储在项目根目录的哪个文件夹</td></tr><tr><td>filename</td><td>string</td><td>每日生成的文件名格式</td></tr><tr><td>datePattern</td><td>string</td><td>按什么格式分割，按日，按小时（<code>YYYY-MM-DD, YYYY-MM-DD-HH</code>）</td></tr><tr><td>zippedArchive</td><td>boolean</td><td>旧日志是否自动压缩为 <code>.gz</code> 文件以节省空间</td></tr><tr><td>maxSize</td><td>string</td><td>单个日志文件超过XX体积时触发分割（即使未到日期）</td></tr><tr><td>maxFiles</td><td>string</td><td>自动删除XX天前的日志文件</td></tr></tbody></table><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> <span class="hljs-title class_">DailyRotateFile</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;winston-daily-rotate-file&#x27;</span>)<br><br><span class="hljs-keyword">new</span> <span class="hljs-title class_">DailyRotateFile</span>(&#123;<br>  <span class="hljs-attr">dirname</span>: <span class="hljs-string">&#x27;logs&#x27;</span>, <br>  <span class="hljs-attr">filename</span>: <span class="hljs-string">&#x27;app-%DATE%.log&#x27;</span>,<br>  <span class="hljs-attr">datePattern</span>: <span class="hljs-string">&#x27;YYYY-MM-DD&#x27;</span>, <br>  <span class="hljs-attr">zippedArchive</span>: <span class="hljs-literal">true</span>, <br>  <span class="hljs-attr">maxSize</span>: <span class="hljs-string">&#x27;20m&#x27;</span>, <br>  <span class="hljs-attr">maxFiles</span>: <span class="hljs-string">&#x27;7d&#x27;</span>,<br>&#125;)<br></code></pre></td></tr></table></figure><h3 id="日志文件结构"><a href="#日志文件结构" class="headerlink" title="日志文件结构"></a>日志文件结构</h3><figure class="highlight bash"><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><code class="hljs bash">-logs<br>  -2025-04-25.<span class="hljs-built_in">log</span>     <span class="hljs-comment"># 当日日志</span><br>  -2025-04-25.log.gz  <span class="hljs-comment"># 压缩的旧日志</span><br>  -exceptions.log         <span class="hljs-comment"># 异常日志</span><br>  -rejections.log         <span class="hljs-comment"># Promise拒绝日志</span><br></code></pre></td></tr></table></figure><h3 id="高级配置（可选）"><a href="#高级配置（可选）" class="headerlink" title="高级配置（可选）"></a>高级配置（可选）</h3><p>一. 多级别日志分割</p><p>为不同级别（如 <code>error</code> 和 <code>info</code>）创建独立文件：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-attr">transports</span>: [<br>  <span class="hljs-keyword">new</span> winston.<span class="hljs-property">transports</span>.<span class="hljs-title class_">DailyRotateFile</span>(&#123;<br>    <span class="hljs-attr">level</span>: <span class="hljs-string">&#x27;error&#x27;</span>, <span class="hljs-comment">// 仅记录 error 级别</span><br>    <span class="hljs-attr">filename</span>: <span class="hljs-string">&#x27;error-%DATE%.log&#x27;</span>,<br>    <span class="hljs-comment">// 其他配置同上</span><br>  &#125;),<br>  <span class="hljs-keyword">new</span> winston.<span class="hljs-property">transports</span>.<span class="hljs-title class_">DailyRotateFile</span>(&#123;<br>    <span class="hljs-attr">level</span>: <span class="hljs-string">&#x27;info&#x27;</span>, <span class="hljs-comment">// 仅记录 info 级别</span><br>    <span class="hljs-attr">filename</span>: <span class="hljs-string">&#x27;info-%DATE%.log&#x27;</span>,<br>    <span class="hljs-comment">// 其他配置同上</span><br>  &#125;)<br>]<br></code></pre></td></tr></table></figure><p>二. 自定义日志格式</p><p>添加更多上下文信息（如请求 IP）：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> logFormat = <span class="hljs-title function_">printf</span>(<span class="hljs-function">(<span class="hljs-params">&#123; level, message, timestamp, meta &#125;</span>) =&gt;</span> &#123;<br>  <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">$&#123;timestamp&#125;</span> [<span class="hljs-subst">$&#123;level&#125;</span>] <span class="hljs-subst">$&#123;meta?.ip || <span class="hljs-string">&#x27;-&#x27;</span>&#125;</span> - <span class="hljs-subst">$&#123;message&#125;</span>`</span>;<br>&#125;);<br><br><span class="hljs-comment">// 在中间件中传递元数据</span><br>logger.<span class="hljs-title function_">info</span>(<span class="hljs-string">&#x27;Request received&#x27;</span>, &#123; <span class="hljs-attr">ip</span>: req.<span class="hljs-property">ip</span> &#125;);<br></code></pre></td></tr></table></figure><p>更多配置可以参考<a href="https://winston.nodejs.cn/docs/"><strong>winston开发文档</strong></a>。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>到这里，这个小项目的所有配置基本上就已经差不多都搭建完了，从第一篇笔记的安装Express开始，我们一步一步的完成了：</p><ol><li>路由的配置与开发，数据库的配置与连接，编写接口。</li><li>实现邮箱+验证码注册，配置全局<code>config、utils.js</code>。</li><li>实现路由参数校验、完成全局错误中间件、响应中间件、异步异常处理中间件的开发。</li><li>完成环境变量的配置，完成Token开发，实现路由鉴权，无感刷新token，完成日志记录。</li></ol><p>整个开发过程对我来说是一次很不错的旅程，就目前来说，这些东西已经足够支撑一些小型项目的运行了。但细心的朋友也会发现，在与数据库的交互过程中，我几乎是全部使用了原生的SQL语句，没有用任何的ORM库。</p><p>这块的话其实也是我有意为之，主要还是强化一下自己的SQL语句和用法，因为很多语句都是一遍翻书一边写的。再一个就是目前的ORM库我也只是很久以前用过<code>Sequelize</code>，但当时的体验并不友好，所有这次也不给自己增加太大的难度。</p><p>后续我应该会把整个项目的框架整理一下，做个脚手架，方便大家能够快速搭建，省去一步一步操作的麻烦。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;上一篇笔记我们实现了接口的参数校验功能、全局中间件的开发和错误处理、以及环境变量的配置。这个小项目已经趋于完善，这篇笔记主要实现接口的权限校验以及日志的接入和上传。最终达到麻雀虽小，五脏俱全的效果。&lt;/p&gt;</summary>
    
    
    
    <category term="后端探索" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/"/>
    
    <category term="Node" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/Node/"/>
    
    
    <category term="Node" scheme="https://www.xdxmblog.cn/tags/Node/"/>
    
    <category term="Express" scheme="https://www.xdxmblog.cn/tags/Express/"/>
    
  </entry>
  
  <entry>
    <title>Node + Express + MySQL 开发RESULT API(三)配置优化</title>
    <link href="https://www.xdxmblog.cn/posts/57037.html"/>
    <id>https://www.xdxmblog.cn/posts/57037.html</id>
    <published>2025-04-16T17:03:45.000Z</published>
    <updated>2025-04-16T17:03:45.000Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇笔记我们实现了邮箱+验证码注册的功能，并且在小结的时候也发现了几个问题。如果这些问题放到项目最后在解决，会存在代码改动量过大，容易出错。所以这篇笔记主要针对这几个问题进行优化、解决，使得项目更加健康。</p><span id="more"></span><h2 id="参数校验"><a href="#参数校验" class="headerlink" title="参数校验"></a>参数校验</h2><p>以上一篇笔记中的代码为例，我们仅仅判断3个入参是否为空就已经写了一堆<code>if...else if...else</code>来判断，那如果加上参数类型，或者入参更多一些，恐怕这种方式就难以为继了。</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 通过邮箱注册用户</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">register</span> = <span class="hljs-keyword">async</span> (req, res, next) =&gt; &#123;<br>  <span class="hljs-keyword">const</span> &#123; email, code, type_id &#125; = req.<span class="hljs-property">body</span><br>  <span class="hljs-keyword">if</span> (!email || !code) &#123;<br>    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: -<span class="hljs-number">1</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;邮箱或验证码不能为空&#x27;</span>, <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span> &#125;)<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (!type_id) &#123;<br>    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: -<span class="hljs-number">1</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;请选择身份&#x27;</span>, <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span> &#125;)<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (type_id &amp;&amp; (type_id &lt; <span class="hljs-number">1</span> || type_id &gt; <span class="hljs-number">7</span>)) &#123;<br>    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: -<span class="hljs-number">1</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;身份值有误&#x27;</span>, <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span> &#125;)<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title class_">User</span>.<span class="hljs-title function_">register</span>(email, code, type_id)<br>      res.<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: <span class="hljs-number">200</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;注册成功&#x27;</span>, <span class="hljs-attr">data</span>: result &#125;)<br>    &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>      res.<span class="hljs-title function_">status</span>(error.<span class="hljs-property">status</span> || <span class="hljs-number">500</span>).<span class="hljs-title function_">send</span>(&#123;<br>        <span class="hljs-attr">code</span>: -<span class="hljs-number">1</span>,<br>        <span class="hljs-attr">message</span>: error.<span class="hljs-property">message</span> || <span class="hljs-string">&#x27;服务器内部错误&#x27;</span>,<br>        <span class="hljs-attr">data</span>: error.<span class="hljs-property">data</span> || <span class="hljs-literal">null</span>,<br>      &#125;)<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>那有没有什么方式可以不用写一堆判断来校验参数呢？答案是有的，那就是使用<strong>Joi和express-joi-validation</strong>。</p><p><strong>Joi</strong>是一个强大的对象模式描述语言和验证器，而<strong>express-joi-validation</strong>是Joi的一个中间件，用于将Joi的验证功能集成到Express应用中。</p><h3 id="安装Joi"><a href="#安装Joi" class="headerlink" title="安装Joi"></a>安装Joi</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm i joi<br></code></pre></td></tr></table></figure><h3 id="使用Joi进行数据验证"><a href="#使用Joi进行数据验证" class="headerlink" title="使用Joi进行数据验证"></a>使用Joi进行数据验证</h3><p>以上述代码为例进行改造，我们通过创建一个Schema对象，来决定每个参数的类型，是否为必填项，是否为某个特定的格式等。然后通过<code>validate</code>方法去进行校验。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> <span class="hljs-title class_">Joi</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;joi&#x27;</span>);<br><br><span class="hljs-comment">// 通过邮箱注册用户</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">register</span> = <span class="hljs-keyword">async</span> (req, res, next) =&gt; &#123;<br>  <span class="hljs-keyword">const</span> userSchema = <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">object</span>(&#123;<br>    <span class="hljs-attr">email</span>: <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">string</span>().<span class="hljs-title function_">email</span>().required(), <span class="hljs-comment">// 字符串类型，email格式，必填项</span><br>    <span class="hljs-attr">code</span>: <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">string</span>().required(), <span class="hljs-comment">// 字符串类型，必填项</span><br>    <span class="hljs-attr">type_id</span>: <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">number</span>().<span class="hljs-title function_">integer</span>().<span class="hljs-title function_">valid</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>).required(), <span class="hljs-comment">// 数字类型，整数，必须为1-7的其中一个，必填项</span><br>  &#125;)<br>  <span class="hljs-keyword">const</span> &#123; error, value &#125; = userSchema.<span class="hljs-title function_">validate</span>(req.<span class="hljs-property">body</span>)<br><br>  <span class="hljs-keyword">if</span> (error) &#123; <span class="hljs-comment">// 验证错误，抛出异常</span><br>    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: <span class="hljs-number">400</span>, <span class="hljs-attr">message</span>: error.<span class="hljs-property">details</span>[<span class="hljs-number">0</span>].<span class="hljs-property">message</span>, <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span> &#125;)<br>  &#125; <span class="hljs-keyword">else</span> &#123;  <span class="hljs-comment">// 验证通过</span><br>    <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(value) <br>    <span class="hljs-keyword">try</span> &#123;<br>      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title class_">User</span>.<span class="hljs-title function_">register</span>(email, code, type_id)<br>      res.<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: <span class="hljs-number">200</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;注册成功&#x27;</span>, <span class="hljs-attr">data</span>: result &#125;)<br>    &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>      res.<span class="hljs-title function_">status</span>(<span class="hljs-number">500</span>).<span class="hljs-title function_">send</span>(&#123;<br>        <span class="hljs-attr">code</span>: <span class="hljs-number">500</span>,<br>        <span class="hljs-attr">message</span>: error || <span class="hljs-string">&#x27;服务器内部错误&#x27;</span>,<br>        <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span>,<br>      &#125;)<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="Joi核心验证规则速查表"><a href="#Joi核心验证规则速查表" class="headerlink" title="Joi核心验证规则速查表"></a>Joi核心验证规则速查表</h3><p>下面列举一些Joi的验证规则，方便快速上手。更全的规则请查看<a href="https://joi.dev/api/?v=17.13.3"><strong>《Joi API v17.13.3》</strong></a></p><h4 id="通用规则"><a href="#通用规则" class="headerlink" title="通用规则"></a>通用规则</h4><table><thead><tr><th>规则类型</th><th>示例代码</th><th>说明</th></tr></thead><tbody><tr><td><strong>必填项</strong></td><td><code>.required()</code></td><td>强制字段存在</td></tr><tr><td><strong>类型验证</strong></td><td><code>.string() .number() .boolean()</code></td><td>基础类型验证</td></tr><tr><td><strong>范围限制</strong></td><td><code>.min(5) .max(100)</code></td><td>数值&#x2F;字符串长度限制</td></tr><tr><td><strong>正则验证</strong></td><td><code>.pattern(/^1[3-9]\d&#123;9&#125;$/)</code></td><td>正则表达式匹配</td></tr><tr><td><strong>枚举值</strong></td><td><code>.valid(&#39;admin&#39;, &#39;user&#39;)</code></td><td>限定允许的枚举值</td></tr><tr><td><strong>默认值</strong></td><td><code>.default(&#39;guest&#39;)</code></td><td>未传值时自动填充默认值</td></tr></tbody></table><h4 id="字符串专用"><a href="#字符串专用" class="headerlink" title="字符串专用"></a>字符串专用</h4><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">string</span>()<br>.<span class="hljs-title function_">alphanum</span>() <span class="hljs-comment">// 仅允许字母数字</span><br>  .<span class="hljs-title function_">trim</span>()             <span class="hljs-comment">// 自动去除两端空格</span><br>  .<span class="hljs-title function_">lowercase</span>()        <span class="hljs-comment">// 转换为小写</span><br>  .<span class="hljs-title function_">uppercase</span>()        <span class="hljs-comment">// 转换为大写</span><br>  .<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/ /g</span>, <span class="hljs-string">&#x27;_&#x27;</span>) <span class="hljs-comment">// 替换字符</span><br></code></pre></td></tr></table></figure><h4 id="数字专用"><a href="#数字专用" class="headerlink" title="数字专用"></a>数字专用</h4><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">number</span>()<br>  .<span class="hljs-title function_">integer</span>()          <span class="hljs-comment">// 必须为整数</span><br>  .<span class="hljs-title function_">positive</span>()         <span class="hljs-comment">// 正数</span><br>  .<span class="hljs-title function_">precision</span>(<span class="hljs-number">2</span>)       <span class="hljs-comment">// 小数点后保留2位</span><br></code></pre></td></tr></table></figure><h4 id="复杂结构"><a href="#复杂结构" class="headerlink" title="复杂结构"></a>复杂结构</h4><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 数组验证</span><br><span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">array</span>().<span class="hljs-title function_">items</span>(<span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">string</span>().<span class="hljs-title function_">valid</span>(<span class="hljs-string">&#x27;A&#x27;</span>, <span class="hljs-string">&#x27;B&#x27;</span>, <span class="hljs-string">&#x27;C&#x27;</span>))<br><br><span class="hljs-comment">// 对象嵌套</span><br><span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">object</span>(&#123;<br>  <span class="hljs-attr">address</span>: <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">object</span>(&#123;<br>    <span class="hljs-attr">city</span>: <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">string</span>(),<br>    <span class="hljs-attr">street</span>: <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">string</span>()<br>  &#125;)<br>&#125;)<br><br><span class="hljs-comment">// 条件验证</span><br><span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">when</span>(<span class="hljs-string">&#x27;role&#x27;</span>, &#123;<br>  <span class="hljs-attr">is</span>: <span class="hljs-string">&#x27;admin&#x27;</span>,<br>  <span class="hljs-attr">then</span>: <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">object</span>(&#123; <span class="hljs-attr">accessLevel</span>: <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">number</span>().<span class="hljs-title function_">min</span>(<span class="hljs-number">3</span>) &#125;)<br>&#125;)<br></code></pre></td></tr></table></figure><p>看上去比我们之前写很多<code>if...else</code>校验要好了很多，那能不能继续优化呢？比如在路由层面？答案是当然可以，利用<code>express-joi-validation</code>中间件就可以实现。</p><h3 id="安装express-joi-validation"><a href="#安装express-joi-validation" class="headerlink" title="安装express-joi-validation"></a>安装express-joi-validation</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm i express-joi-validation<br></code></pre></td></tr></table></figure><p>在<code>src</code>目录下创建<code>schemas</code>文件，用来存放接口所需要校验的字段和规则。并在该目录下创建<code>user.schemas.js</code>文件。</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">const</span> <span class="hljs-title class_">Joi</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;joi&#x27;</span>)<br><br><span class="hljs-keyword">const</span> email = <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">string</span>().<span class="hljs-title function_">email</span>().required().<span class="hljs-title function_">messages</span>(&#123;<br>  <span class="hljs-string">&#x27;any.required&#x27;</span>: <span class="hljs-string">&#x27;缺少email&#x27;</span>,<br>  <span class="hljs-string">&#x27;string.email&#x27;</span>: <span class="hljs-string">&#x27;email格式不正确&#x27;</span>,<br>&#125;)<br><br><span class="hljs-keyword">const</span> code = <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">string</span>().required().<span class="hljs-title function_">messages</span>(&#123;<br>  <span class="hljs-string">&#x27;any.required&#x27;</span>: <span class="hljs-string">&#x27;缺少code&#x27;</span>,<br>&#125;)<br><br><span class="hljs-keyword">const</span> type_id = <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">number</span>().<span class="hljs-title function_">integer</span>().required().<span class="hljs-title function_">valid</span>(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>, <span class="hljs-number">7</span>).<span class="hljs-title function_">messages</span>(&#123;<br>  <span class="hljs-string">&#x27;any.required&#x27;</span>: <span class="hljs-string">&#x27;缺少type_id&#x27;</span>,<br>  <span class="hljs-string">&#x27;any.only&#x27;</span>: <span class="hljs-string">&#x27;type_id校验失败&#x27;</span>,<br>&#125;)<br><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">registerSchema</span> = <span class="hljs-title class_">Joi</span>.<span class="hljs-title function_">object</span>(&#123; email, code, type_id &#125;)<br></code></pre></td></tr></table></figure><p>修改<code>user.routes.js</code>文件，利用中间件将校验放到路由上。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-keyword">const</span> validator = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;express-joi-validation&#x27;</span>).<span class="hljs-title function_">createValidator</span>(&#123; <span class="hljs-attr">passError</span>: <span class="hljs-literal">true</span> &#125;)<br><span class="hljs-keyword">const</span> userSchema = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;../schemas/user.schema&#x27;</span>)<br><br>router.<span class="hljs-title function_">post</span>(<span class="hljs-string">&#x27;/register&#x27;</span>, validator.<span class="hljs-title function_">body</span>(userSchema.<span class="hljs-property">registerSchema</span>), userController.<span class="hljs-property">register</span>)<br></code></pre></td></tr></table></figure><p><code>express-joi-validation</code>可以针对以下几种情况来进行校验。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> validator = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;express-joi-validation&#x27;</span>).<span class="hljs-title function_">createValidator</span>(&#123; <span class="hljs-attr">passError</span>: <span class="hljs-literal">true</span> &#125;)<br><br>validator.<span class="hljs-title function_">query</span>(options)<br>validator.<span class="hljs-title function_">body</span>(options)<br>validator.<span class="hljs-title function_">headers</span>(options)<br>validator.<span class="hljs-title function_">params</span>(options)<br>validator.<span class="hljs-title function_">response</span>(options)<br>validator.<span class="hljs-title function_">fields</span>(options)<br></code></pre></td></tr></table></figure><p>修改<code>user.controller.js</code>文件，精简后的代码如下。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 通过邮箱注册用户</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">register</span> = <span class="hljs-keyword">async</span> (req, res, next) =&gt; &#123;<br>  <span class="hljs-keyword">if</span> (!req.<span class="hljs-property">body</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">&#x27;参数不能为空&#x27;</span>)<br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">const</span> &#123; email, code, type_id &#125; = req.<span class="hljs-property">body</span><br>    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title class_">User</span>.<span class="hljs-title function_">register</span>(email, code, type_id)<br>    res.<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: <span class="hljs-number">200</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;注册成功&#x27;</span>, <span class="hljs-attr">data</span>: result &#125;)<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-title function_">next</span>(error)<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>虽然代码精简了，但是我们调用接口发现，如果传入错误的参数，express默认会返回一个html文件来展示error。这显然不是我们想要的，所以我们需要一个全局的错误处理中间件。</p><h2 id="开发中间件"><a href="#开发中间件" class="headerlink" title="开发中间件"></a>开发中间件</h2><h3 id="全局错误中间件"><a href="#全局错误中间件" class="headerlink" title="全局错误中间件"></a>全局错误中间件</h3><p>在<code>src</code>目录下新建<code>middlewares</code>文件夹，用来存放我们所有的中间件。在该文件夹下创建<code>errorMiddleware.js</code>文件，这个就是我们的全局错误中间件。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-function">(<span class="hljs-params">err, req, res, next</span>) =&gt;</span> &#123;<br>  <span class="hljs-comment">// 处理Joi验证错误</span><br>  <span class="hljs-keyword">if</span> (err.<span class="hljs-property">error</span> &amp;&amp; err.<span class="hljs-property">error</span>.<span class="hljs-property">name</span> &amp;&amp; err.<span class="hljs-property">error</span>.<span class="hljs-property">name</span> === <span class="hljs-string">&#x27;ValidationError&#x27;</span>) &#123;<br>    <span class="hljs-keyword">const</span> details = err.<span class="hljs-property">error</span>.<span class="hljs-property">details</span>.<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">d</span> =&gt;</span> (&#123;<br>      <span class="hljs-attr">message</span>: d.<span class="hljs-property">message</span>.<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/&quot;/g</span>, <span class="hljs-string">&#x27;&#x27;</span>),<br>    &#125;))<br><br>    <span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(err.<span class="hljs-property">status</span> || <span class="hljs-number">400</span>).<span class="hljs-title function_">json</span>(&#123;<br>      <span class="hljs-attr">code</span>: <span class="hljs-number">400</span>,<br>      <span class="hljs-attr">message</span>: details &amp;&amp; details.<span class="hljs-property">length</span> &gt; <span class="hljs-number">0</span> ? details[<span class="hljs-number">0</span>].<span class="hljs-property">message</span> : <span class="hljs-string">&#x27;参数校验失败！&#x27;</span>,<br>      <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span>,<br>    &#125;)<br>  &#125;<br>  <span class="hljs-comment">// 其他错误</span><br>  <span class="hljs-keyword">const</span> errorMsg = err <span class="hljs-keyword">instanceof</span> <span class="hljs-title class_">Error</span> ? err.<span class="hljs-property">message</span> : err<br>  <span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(err.<span class="hljs-property">status</span> || <span class="hljs-number">500</span>).<span class="hljs-title function_">json</span>(&#123;<br>    <span class="hljs-attr">code</span>: <span class="hljs-number">500</span>,<br>    <span class="hljs-attr">message</span>: errorMsg,<br>    <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span>,<br>  &#125;)<br>&#125;<br></code></pre></td></tr></table></figure><p>目前我们针对Joi的错误进行了特殊处理，使其更加贴近于日常开发的格式。并且对其他错误进行了一个暂时的兜底。后续做其他功能时会继续完善该中间件。</p><p>打开<code>app.js</code>，将刚才写好的中间件进行注册。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-keyword">const</span> errorMiddleware = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./middlewares/errorMiddleware&#x27;</span>)<br><br><span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./routes&#x27;</span>)(app) <span class="hljs-comment">// 注册路由</span><br>app.<span class="hljs-title function_">use</span>(errorMiddleware) <span class="hljs-comment">// 错误中间件一定要放到路由后面注册才能正常捕捉</span><br></code></pre></td></tr></table></figure><p>但有时候，我们可能会需要在<code>model</code>文件中处理一些异常，但这些异常又不一定全部都要返回500状态码。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-built_in">exports</span>.<span class="hljs-property">register</span> = <span class="hljs-keyword">async</span> (email, code, type_id) =&gt; &#123;<br>  <span class="hljs-keyword">try</span> &#123;<br>    ...<br>    <span class="hljs-keyword">if</span> (users.<span class="hljs-property">length</span> &gt; <span class="hljs-number">0</span>) &#123;<br>      <span class="hljs-comment">// 这里应该返回400状态</span><br>    <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;该邮箱已被注册&#x27;</span><br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>     ...<br>      <span class="hljs-keyword">if</span> (!isTrue) &#123;<br>        <span class="hljs-comment">// 同理，这里也应该返回400状态</span><br>        <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;验证码错误或已过期&#x27;</span><br>      &#125;<br>...<br>    &#125;<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-keyword">throw</span> error<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>这时我们需要一个能快速创建不同错误状态的工具，也就是<strong>http-errors</strong>。</p><h3 id="http-errors"><a href="#http-errors" class="headerlink" title="http-errors"></a>http-errors</h3><p><strong>http-errors</strong>是一个Node.js模块，通过简单的API，让您可以方便地创建和扩展与HTTP状态码关联的错误对象。它提供了丰富的构造函数来直接创建不同类型的HTTP错误，并且可以自定义错误信息以及附加属性。</p><h4 id="安装http-errors"><a href="#安装http-errors" class="headerlink" title="安装http-errors"></a>安装http-errors</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">npm i http-errors<br></code></pre></td></tr></table></figure><h4 id="使用http-errors创建HTTP错误"><a href="#使用http-errors创建HTTP错误" class="headerlink" title="使用http-errors创建HTTP错误"></a>使用http-errors创建HTTP错误</h4><p>修改<code>user.model.js</code>，修改上述例子的代码。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> createError = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;http-errors&#x27;</span>)<br><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">register</span> = <span class="hljs-keyword">async</span> (email, code, type_id) =&gt; &#123;<br>  <span class="hljs-keyword">try</span> &#123;<br>    ...<br>    <span class="hljs-keyword">if</span> (users.<span class="hljs-property">length</span> &gt; <span class="hljs-number">0</span>) &#123;<br>      <span class="hljs-comment">// 这里应该返回400状态</span><br>    <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">400</span>, <span class="hljs-string">&#x27;该邮箱已被注册&#x27;</span>)<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>     ...<br>      <span class="hljs-keyword">if</span> (!isTrue) &#123;<br>        <span class="hljs-comment">// 同理，这里也应该返回400状态</span><br>        <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">400</span>, <span class="hljs-string">&#x27;验证码错误或已过期&#x27;</span>)<br>      &#125;<br>...<br>    &#125;<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-keyword">throw</span> error<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>修改<code>errorMiddleware.js</code>，形成统一的错误返回。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> createError = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;http-errors&#x27;</span>)<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-function">(<span class="hljs-params">err, req, res, next</span>) =&gt;</span> &#123;<br>  <span class="hljs-comment">// 处理Joi验证错误</span><br>  <span class="hljs-keyword">if</span> (err.<span class="hljs-property">error</span> &amp;&amp; err.<span class="hljs-property">error</span>.<span class="hljs-property">name</span> &amp;&amp; err.<span class="hljs-property">error</span>.<span class="hljs-property">name</span> === <span class="hljs-string">&#x27;ValidationError&#x27;</span>) &#123;<br>    <span class="hljs-keyword">const</span> details = err.<span class="hljs-property">error</span>.<span class="hljs-property">details</span>.<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">d</span> =&gt;</span> (&#123;<br>      <span class="hljs-attr">message</span>: d.<span class="hljs-property">message</span>.<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/&quot;/g</span>, <span class="hljs-string">&#x27;&#x27;</span>),<br>    &#125;))<br>    <span class="hljs-keyword">const</span> message = details &amp;&amp; details.<span class="hljs-property">length</span> &gt; <span class="hljs-number">0</span> ? details[<span class="hljs-number">0</span>].<span class="hljs-property">message</span> : <span class="hljs-string">&#x27;参数校验失败！&#x27;</span><br>    err = <span class="hljs-title function_">createError</span>(<span class="hljs-number">400</span>, message)<br>  &#125;<br><br>  <span class="hljs-comment">// 对其他异常进行兜底，转换成通用的错误对象</span><br>  <span class="hljs-keyword">if</span> (!createError.<span class="hljs-title function_">isHttpError</span>(err)) &#123;<br>    err = <span class="hljs-title function_">createError</span>(<span class="hljs-number">500</span>, <span class="hljs-string">&#x27;服务器内部错误，请稍后重试&#x27;</span>)<br>  &#125;<br><br>  <span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(err.<span class="hljs-property">status</span>).<span class="hljs-title function_">json</span>(&#123;<br>    <span class="hljs-attr">code</span>: err.<span class="hljs-property">status</span>,<br>    <span class="hljs-attr">message</span>: err.<span class="hljs-property">message</span>,<br>    <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span>,<br>  &#125;)<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="全局响应中间件"><a href="#全局响应中间件" class="headerlink" title="全局响应中间件"></a>全局响应中间件</h3><p>返回成功响应的时候，我们每次都要写如下代码，这个地方能不能简化呢？答案是当然能，我们编写一个中间件来对res对象进行扩展。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript">res.<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: <span class="hljs-number">200</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;注册成功&#x27;</span>, <span class="hljs-attr">data</span>: result &#125;)<br></code></pre></td></tr></table></figure><p>在<code>middlewares</code>文件夹下创建<code>responseMiddleware</code>文件，编写以下代码。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> &#123;<br>  res.<span class="hljs-property">success</span> = <span class="hljs-function">(<span class="hljs-params">data = <span class="hljs-literal">null</span>, message = <span class="hljs-string">&#x27;请求成功&#x27;</span></span>) =&gt;</span> &#123;<br>    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">200</span>).<span class="hljs-title function_">json</span>(&#123;<br>      <span class="hljs-attr">code</span>: <span class="hljs-number">200</span>,<br>      message,<br>      data,<br>    &#125;)<br>  &#125;<br>  <span class="hljs-title function_">next</span>()<br>&#125;<br></code></pre></td></tr></table></figure><p>打开<code>app.js</code>，将刚才写好的中间件进行注册。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-keyword">const</span> responseMiddleware = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./middlewares/responseMiddleware&#x27;</span>)<br><br>app.<span class="hljs-title function_">use</span>(responseMiddleware) <span class="hljs-comment">// 要放到路由前面注册，否则路由里拿不到success方法</span><br><span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./routes&#x27;</span>)(app) <span class="hljs-comment">// 注册路由</span><br></code></pre></td></tr></table></figure><p>修改<code>user.controller.js</code>文件，现在代码看起来清爽多啦！</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 通过邮箱注册用户</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">register</span> = <span class="hljs-keyword">async</span> (req, res, next) =&gt; &#123;<br>  <span class="hljs-keyword">if</span> (!req.<span class="hljs-property">body</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">&#x27;缺少必要参数&#x27;</span>)<br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title class_">User</span>.<span class="hljs-title function_">register</span>(email, code, type_id)<br>    res.<span class="hljs-title function_">success</span>(result, <span class="hljs-string">&#x27;注册成功&#x27;</span>)<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-title function_">next</span>(error)<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="异步异常处理中间件"><a href="#异步异常处理中间件" class="headerlink" title="异步异常处理中间件"></a>异步异常处理中间件</h3><p>但我们的代码还有一个小问题，就是<code>try...catch</code>太多了，原因就是Epxress可以很好的处理同步的异常，因为同步的异常<code>throw err</code>，会自动转化为</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-title function_">next</span>(err)<br></code></pre></td></tr></table></figure><p>但是异步的异常，就必须要手动<code>next(err)</code>，就像上面的代码一样。然而，<code>try...catch</code>并非唯一可以手动<code>next(err)</code>的途径，<code>Promise</code>的<code>catch</code>也行。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 官网提供的示例</span><br>app.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;/&#x27;</span>, <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> &#123;<br>  <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">resolve</span>().<span class="hljs-title function_">then</span>(<span class="hljs-function">() =&gt;</span> &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(<span class="hljs-string">&#x27;BROKEN&#x27;</span>)<br>  &#125;).<span class="hljs-title function_">catch</span>(next) <span class="hljs-comment">// Errors will be passed to Express.</span><br>&#125;)<br></code></pre></td></tr></table></figure><p>在<code>middlewares</code>文件夹下创建<code>asyncMiddleware</code>文件，编写以下代码。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-function"><span class="hljs-params">fn</span> =&gt;</span> <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> &#123;<br>  <span class="hljs-keyword">return</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">resolve</span>(<span class="hljs-title function_">fn</span>(req, res, next)).<span class="hljs-title function_">catch</span>(next)<br>&#125;<br></code></pre></td></tr></table></figure><p>修改<code>user.routes.js</code>文件，在路由层面添加中间件处理。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> asyncHandler = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;../middlewares/asyncMiddleware&#x27;</span>)<br><br><span class="hljs-comment">// 查询用户身份列表</span><br>router.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;/type&#x27;</span>, <span class="hljs-title function_">asyncHandler</span>(userController.<span class="hljs-property">findAllType</span>))<br><span class="hljs-comment">// 注册</span><br>router.<span class="hljs-title function_">post</span>(<span class="hljs-string">&#x27;/register&#x27;</span>, validator.<span class="hljs-title function_">body</span>(userSchema.<span class="hljs-property">register</span>), <span class="hljs-title function_">asyncHandler</span>(userController.<span class="hljs-property">register</span>))<br><span class="hljs-comment">// 发送验证</span><br>router.<span class="hljs-title function_">post</span>(<span class="hljs-string">&#x27;/sendEmaliCode&#x27;</span>, validator.<span class="hljs-title function_">body</span>(userSchema.<span class="hljs-property">sendEmailCode</span>), <span class="hljs-title function_">asyncHandler</span>(userController.<span class="hljs-property">sendEmailCode</span>))<br></code></pre></td></tr></table></figure><p>好了，可以愉快的把<code>controller</code>和<code>model</code>文件里的<code>try...catch</code>统统干掉啦！</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 通过邮箱注册用户</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">register</span> = <span class="hljs-keyword">async</span> (req, res, next) =&gt; &#123;<br>  <span class="hljs-keyword">if</span> (!req.<span class="hljs-property">body</span>) <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;缺少必要参数&#x27;</span><br>  <span class="hljs-keyword">const</span> &#123; email, code, type_id &#125; = req.<span class="hljs-property">body</span><br>  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title class_">User</span>.<span class="hljs-title function_">register</span>(email, code, type_id)<br>  res.<span class="hljs-title function_">success</span>(result, <span class="hljs-string">&#x27;注册成功&#x27;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 注册用户</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">register</span> = <span class="hljs-keyword">async</span> (email, code, type_id) =&gt; &#123;<br>  <span class="hljs-comment">// 查询邮箱是否已被注册</span><br>  <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">findUser</span>(<span class="hljs-string">&#x27;email&#x27;</span>, email)<br>  <span class="hljs-keyword">if</span> (users.<span class="hljs-property">length</span> &gt; <span class="hljs-number">0</span>) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">400</span>, <span class="hljs-string">&#x27;该邮箱已被注册&#x27;</span>)<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-keyword">const</span> isTrue = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">verifyCode</span>(email, code)<br><br>    <span class="hljs-keyword">if</span> (!isTrue) &#123;<br>      <span class="hljs-keyword">throw</span> <span class="hljs-title function_">createError</span>(<span class="hljs-number">400</span>, <span class="hljs-string">&#x27;验证码错误或已过期&#x27;</span>)<br>    &#125;<br><br>    <span class="hljs-keyword">let</span> createSQL = <span class="hljs-string">&#x27;insert into user (email, type_id) value(?,?)&#x27;</span><br>    <span class="hljs-keyword">const</span> [results] = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(createSQL, [email, type_id])<br>    <span class="hljs-keyword">if</span> (results.<span class="hljs-property">affectedRows</span> === <span class="hljs-number">1</span>) &#123;<br>      <span class="hljs-comment">// 注册成功，删除临时保存的验证码</span><br>      <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">deleteCode</span>(email)<br>      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span><br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="环境变量配置"><a href="#环境变量配置" class="headerlink" title="环境变量配置"></a>环境变量配置</h2><p>上一篇笔记也提到了目前很多敏感数据在config文件下明文存储，对于项目不是很友好。所以我们需要一个管理环境变量的工具，帮助我们管理开发、测试、生产的配置。</p><p>上一篇笔记也提到了目前很多敏感数据在<code>config</code>文件下明文存储，对于项目不是很友好。所以我们需要一个管理环境变量的工具，帮助我们管理开发、测试、生产的配置。</p><h3 id="安装dotenv"><a href="#安装dotenv" class="headerlink" title="安装dotenv"></a>安装dotenv</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm i dotenv<br></code></pre></td></tr></table></figure><h3 id="创建-env文件"><a href="#创建-env文件" class="headerlink" title="创建.env文件"></a>创建.env文件</h3><p>在根目录创建<code>.env</code>文件，将<code>config</code>文件下的配置写入。</p><figure class="highlight plaintext"><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><code class="hljs env">#DB_CONFIG<br>DB_HOST= &quot;127.0.0.1&quot; #数据库地址<br>DB_PORT= &quot;3306&quot; #数据库端口<br>DB_USER= &quot;root&quot; #数据库用户名<br>DB_PASSWORD= &quot;123456&quot; #数据库密码<br>DB_DATABASE= &quot;test&quot; #数据库名称<br><br>#EMAIL_CONFIG<br>EMAIL_HOST= &quot;smtp.163.com&quot; #SMTP服务器地址<br>EMAIL_PORT= 465 #端口号<br>EMAIL_SECURE= true #使用SSL<br>EMAIL_USER=&quot;abc@163.com&quot; #发件人邮箱<br>EMAIL_PASS= &quot;xxxxxx&quot; #邮箱授权码<br></code></pre></td></tr></table></figure><h3 id="引入dotenv"><a href="#引入dotenv" class="headerlink" title="引入dotenv"></a>引入dotenv</h3><p>修改<code>config</code>配置文件，引入<code>dotenv</code>，<code>dotenv</code>会将<code>.env</code>文件内的变量都挂载到<code>process.env</code>上面。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;dotenv&#x27;</span>)<br>dotenv.<span class="hljs-title function_">config</span>()<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = &#123;<br>  <span class="hljs-attr">dbConfig</span>: &#123;<br>    <span class="hljs-attr">host</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_HOST</span>,<br>    <span class="hljs-attr">port</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_PORT</span>,<br>    <span class="hljs-attr">user</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_USER</span>,<br>    <span class="hljs-attr">password</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_PASSWORD</span>,<br>    <span class="hljs-attr">database</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_DATABASE</span>,<br>  &#125;,<br>  <span class="hljs-attr">emailConfig</span>: &#123;<br>    <span class="hljs-attr">host</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">EMAIL_HOST</span>,<br>    <span class="hljs-attr">port</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">EMAIL_PORT</span>,<br>    <span class="hljs-attr">secure</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">EMAIL_SECURE</span>,<br>    <span class="hljs-attr">auth</span>: &#123;<br>      <span class="hljs-attr">user</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">EMAIL_USER</span>,<br>      <span class="hljs-attr">pass</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">EMAIL_PASS</span>,<br>    &#125;,<br>  &#125;,<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="确保-env不被提交"><a href="#确保-env不被提交" class="headerlink" title="确保.env不被提交"></a>确保.env不被提交</h3><p>在<code>.gitignore</code> 文件中添加<code>.env</code>文件，以确保它不会被提交到版本控制系统当中。</p><figure class="highlight plaintext"><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><code class="hljs gitignore">/node_modules<br>npm-debug.log<br>package-lock.json<br>.env<br></code></pre></td></tr></table></figure><h3 id="在不同的环境中设置环境变量"><a href="#在不同的环境中设置环境变量" class="headerlink" title="在不同的环境中设置环境变量"></a>在不同的环境中设置环境变量</h3><ul><li><strong>开发环境</strong>：通常 <code>.env</code> 文件会包含开发环境的配置。</li><li><strong>测试环境</strong>：可以创建一个 <code>.env.test</code> 文件，并在测试脚本中加载它，或者通过命令行设置环境变量。</li><li><strong>生产环境</strong>：环境变量通常通过操作系统的环境变量设置，或者通过像 Heroku、AWS 等平台的环境变量管理工具来管理。</li></ul><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>这篇笔记没有加新的功能，主要是针对上一篇笔记末尾总结的三个问题进行优化和改良。同时也学习了中间件的开发与使用，并且通过使用社区成熟的库来减少重复造轮子的行为。希望这篇笔记能帮到同样在用<code>Node+Express</code>学习接口开发的你吧！</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;上一篇笔记我们实现了邮箱+验证码注册的功能，并且在小结的时候也发现了几个问题。如果这些问题放到项目最后在解决，会存在代码改动量过大，容易出错。所以这篇笔记主要针对这几个问题进行优化、解决，使得项目更加健康。&lt;/p&gt;</summary>
    
    
    
    <category term="后端探索" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/"/>
    
    <category term="Node" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/Node/"/>
    
    
    <category term="Node" scheme="https://www.xdxmblog.cn/tags/Node/"/>
    
    <category term="Express" scheme="https://www.xdxmblog.cn/tags/Express/"/>
    
  </entry>
  
  <entry>
    <title>解决群晖Docker安装MySQL时区问题</title>
    <link href="https://www.xdxmblog.cn/posts/58263.html"/>
    <id>https://www.xdxmblog.cn/posts/58263.html</id>
    <published>2025-04-15T02:34:30.000Z</published>
    <updated>2025-04-15T02:34:30.000Z</updated>
    
    <content type="html"><![CDATA[<p>前段时间在群晖NAS的Docker里安装了MySQL，方便自己折腾result API开发，最近在开发时遇到了一个问题，就是数据库的时间跟北京时间相差8小时，会导致时间判断出错，这里记录一下解决办法，供遇到同样问题的同学参考。</p><span id="more"></span><h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>在做邮箱+验证码登录的功能时，我们将<code>javascript</code>生成的验证码和过期时间存到了数据库的表中。然而在利用过期时间做查询校验时，却发现不管验证码过没过期，都查不到任何数据。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> sql = <span class="hljs-string">`select * from <span class="hljs-subst">$&#123;type == <span class="hljs-string">&#x27;register&#x27;</span> ? <span class="hljs-string">&#x27;temp_user_code&#x27;</span> : <span class="hljs-string">&#x27;user&#x27;</span>&#125;</span> where email = &#x27;<span class="hljs-subst">$&#123;email&#125;</span>&#x27; and expires_at &gt; NOW()`</span><br><span class="hljs-keyword">const</span> [rows] = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(sql, [email])<br></code></pre></td></tr></table></figure><h2 id="排查过程"><a href="#排查过程" class="headerlink" title="排查过程"></a>排查过程</h2><p>一开始以为是生成验证码和过期时间的函数有问题，但是不管是<code>console.log()</code>，还是查看数据库表的存储，过期时间确实没啥问题。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 生成验证码</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">generateCode</span> = <span class="hljs-function">() =&gt;</span> &#123;<br>  <span class="hljs-keyword">const</span> code = randomstring.<span class="hljs-title function_">generate</span>(&#123;<br>    <span class="hljs-attr">length</span>: <span class="hljs-number">6</span>, <span class="hljs-comment">// 验证码长度</span><br>    <span class="hljs-attr">charset</span>: <span class="hljs-string">&#x27;numeric&#x27;</span>, <span class="hljs-comment">//可以选择 &#x27;alphabetic&#x27;, &#x27;numeric&#x27;, &#x27;hexadecimal&#x27;, &#x27;binary&#x27; 或者自定义字符集</span><br>  &#125;)<br>  <span class="hljs-keyword">const</span> expiresAt = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(<span class="hljs-title class_">Date</span>.<span class="hljs-title function_">now</span>() + <span class="hljs-number">5</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>) <span class="hljs-comment">// 验证码有效期为5分钟</span><br>  <span class="hljs-keyword">return</span> &#123; code, expiresAt &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>于是我将怀疑对象转向了MySQL的<code>NOW()</code>方法，果断进入Docker容器，输入命令来验证我的猜想。</p><blockquote><p>进入容器的方法：打开群晖Docker,点击<strong>容器——MySQL——详情——终端机</strong>,进入容器内部，输入用户名和密码，登录MySQL数据。</p></blockquote><p>进入容器登录以后，输入<code>select now();</code>，查看打印的时间，通过打印结果观察，果然是数据库的时间不对。</p><figure class="highlight sql"><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><code class="hljs sql"><span class="hljs-keyword">select</span> now();                                                                    <br><span class="hljs-operator">+</span><span class="hljs-comment">---------------------+                                                                 </span><br><span class="hljs-operator">|</span> now()               <span class="hljs-operator">|</span>                                                                 <br><span class="hljs-operator">+</span><span class="hljs-comment">---------------------+                                                                 </span><br><span class="hljs-operator">|</span> <span class="hljs-number">2025</span><span class="hljs-number">-04</span><span class="hljs-number">-15</span> <span class="hljs-number">03</span>:<span class="hljs-number">00</span>:<span class="hljs-number">31</span> <span class="hljs-operator">|</span>                                                                 <br><span class="hljs-operator">+</span><span class="hljs-comment">---------------------+                                                                 </span><br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec) <br></code></pre></td></tr></table></figure><p>继续输入<code>show variables like &#39;%time_zone%&#39;; </code>来查看MySQL的时区。</p><figure class="highlight sql"><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><code class="hljs sql"><span class="hljs-keyword">show</span> variables <span class="hljs-keyword">like</span> <span class="hljs-string">&#x27;%time_zone%&#x27;</span>;                                               <br><span class="hljs-operator">+</span><span class="hljs-comment">------------------+--------+                                                           </span><br><span class="hljs-operator">|</span> Variable_name    <span class="hljs-operator">|</span> <span class="hljs-keyword">Value</span>  <span class="hljs-operator">|</span>                                                           <br><span class="hljs-operator">+</span><span class="hljs-comment">------------------+--------+                                                           </span><br><span class="hljs-operator">|</span> system_time_zone <span class="hljs-operator">|</span> UTC    <span class="hljs-operator">|</span>                                                           <br><span class="hljs-operator">|</span> time_zone        <span class="hljs-operator">|</span> <span class="hljs-keyword">SYSTEM</span> <span class="hljs-operator">|</span>                                                           <br><span class="hljs-operator">+</span><span class="hljs-comment">------------------+--------+                                                           </span><br><span class="hljs-number">2</span> <span class="hljs-keyword">rows</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec)<br></code></pre></td></tr></table></figure><p>观察可知，<code>time_zone</code>的值是<code>SYSTEM</code>，也就是取了群晖Docker的时区，我们继续查看Docker容器所属的时区。</p><figure class="highlight shell"><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><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">先退出mysql</span><br>mysql &gt; quit;<br><span class="hljs-meta prompt_"># </span><span class="language-bash">退出之后输入 <span class="hljs-built_in">date</span> -R</span><br>date -R                                                                   <br>Tue, 15 Apr 2025 03:05:00 +0000<br></code></pre></td></tr></table></figure><p>至此，真相大白了。</p><p>我们通过上面的排查，可以确定，<strong>问题出在了安装MySQL时未指定时区，而MySQL会默认取Docker容器所属的时区，而Docker容器默认的时区又是+0000</strong>，所以数据库的时间与中国时间相差了8个小时。</p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>既然知道了问题原因，自然而然的就可以列出解决方向：</p><ol><li>修改Docker系统所属的时区。</li><li>修改MySQL所属的时区。</li></ol><h3 id="修改Docker容器（未验证成功）"><a href="#修改Docker容器（未验证成功）" class="headerlink" title="修改Docker容器（未验证成功）"></a>修改Docker容器（未验证成功）</h3><p>网上很多文章都写了这个方案，然后我尝试了，很遗憾失败了。可能是因为我的Docker安装位置是群晖的NAS系统。不管是从上面的终端机还是使用putty连接NAS，输入该命令只会得到<code>docker: command not found</code>。</p><figure class="highlight shell"><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><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">进入容器</span><br>docker exec -it mysql1 /bin/bash<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">下列方法 1~3 中任选其一即可：</span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">1.强制生成 Asia/Shanghai 时区文件软链接</span><br>ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime<br><span class="hljs-meta prompt_"># </span><span class="language-bash">2.Asia/Shanghai 软链接实际指向 PRC 文件，将其复制为 localtime 时区文件</span><br>cp /usr/share/zoneinfo/PRC /etc/localtime<br><span class="hljs-meta prompt_"># </span><span class="language-bash">3.通过 tzselect 命令，可选择 Beijing、HongKong 城市时区</span><br>tzselect <br></code></pre></td></tr></table></figure><p>对Docker比较熟悉的小伙伴遇到这个问题可以尝试一下这个方案是否可行。</p><h3 id="修改MySQL所属的时区（验证成功）"><a href="#修改MySQL所属的时区（验证成功）" class="headerlink" title="修改MySQL所属的时区（验证成功）"></a>修改MySQL所属的时区（验证成功）</h3><p>还是通过终端机登录MySQL，可以用以下命令来临时修改MySQL所属的时区。</p><figure class="highlight sql"><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><code class="hljs sql"># 设置全局会话时区<br><span class="hljs-keyword">set</span> <span class="hljs-keyword">global</span> time_zone <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;+08:00&#x27;</span>;<br><br># 设置当前会话时区<br><span class="hljs-keyword">set</span> session time_zone <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;+08:00&#x27;</span>;<br><br># 设置后查看 Mysql 时区配置属性。<br><span class="hljs-keyword">show</span> variables <span class="hljs-keyword">like</span> <span class="hljs-string">&#x27;%time_zone%&#x27;</span>;<br><span class="hljs-operator">+</span><span class="hljs-comment">------------------+--------+</span><br><span class="hljs-operator">|</span> Variable_name    <span class="hljs-operator">|</span> <span class="hljs-keyword">Value</span>  <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------------------+--------+</span><br><span class="hljs-operator">|</span> system_time_zone <span class="hljs-operator">|</span> UTC    <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> time_zone        <span class="hljs-operator">|</span> <span class="hljs-operator">+</span><span class="hljs-number">08</span>:<span class="hljs-number">00</span> <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------------------+--------+</span><br></code></pre></td></tr></table></figure><p>但因为我们的MySQL安装在了Docker里，一旦Docker遇到意外发生故障或者重启，该配置就会失效，所以我们要想办法将它永久化。</p><p>在另一篇笔记<a href="https://www.xdxmblog.cn/posts/25614.html">《群晖NAS使用指北-Docker安装MySQL》</a>中，为了使数据库的数据永久化，我们在宿主机创建了三个文件夹<code>logs、conf、data</code>来进行映射。</p><p>在电脑中新建一个<code>mycustom.txt</code>文件，写入以下配置：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">[mysqld]<br><span class="hljs-keyword">default</span><span class="hljs-operator">-</span><span class="hljs-type">time</span><span class="hljs-operator">-</span>zone<span class="hljs-operator">=</span><span class="hljs-string">&#x27;+08:00&#x27;</span><br></code></pre></td></tr></table></figure><p>上面的配置会指定MySQL的时区，另存为改文件或者直接重命名改后缀为<code>mycustom.cnf</code>，将该文件放入NAS中我们先前创建的conf文件夹中。</p><p>重新启动MySQL容器，进入终端机，登录数据库，会发现提示一个报错。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">mysql: [Warning] World<span class="hljs-operator">-</span>writable config file <span class="hljs-string">&#x27;/etc/mysql/conf.d/mycustom.cnf</span><br></code></pre></td></tr></table></figure><p>这个报错的意思是：<code>mycustom.cnf</code>这个文件的权限太大了，任何人都可以修改，不太安全，MySQL会忽略其中的配置。导致这个问题的原因是因为<code>conf</code>文件夹的权限我们设置为了任何人都可以读取&#x2F;写入。</p><p>右键该文件夹，点击<strong>属性——权限</strong>，编辑<strong>Everyone</strong>的权限设置为读取，把写入都给勾掉。</p><p>如果权限列表都是灰色的，就点<strong>高级选项——使继承权限显示化</strong>，再继续上述步骤即可。</p><p>修改完后重启MySQL，登录终端机，查看修改效果，发现已经OK了。</p><figure class="highlight sql"><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></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">show</span> variables <span class="hljs-keyword">like</span> <span class="hljs-string">&#x27;%time_zone%&#x27;</span>;<br><span class="hljs-operator">+</span><span class="hljs-comment">------------------+--------+</span><br><span class="hljs-operator">|</span> Variable_name    <span class="hljs-operator">|</span> <span class="hljs-keyword">Value</span>  <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------------------+--------+</span><br><span class="hljs-operator">|</span> system_time_zone <span class="hljs-operator">|</span> UTC    <span class="hljs-operator">|</span><br><span class="hljs-operator">|</span> time_zone        <span class="hljs-operator">|</span> <span class="hljs-operator">+</span><span class="hljs-number">08</span>:<span class="hljs-number">00</span> <span class="hljs-operator">|</span><br><span class="hljs-operator">+</span><span class="hljs-comment">------------------+--------+</span><br><br><span class="hljs-keyword">select</span> now();                                                                    <br><span class="hljs-operator">+</span><span class="hljs-comment">---------------------+                                                                 </span><br><span class="hljs-operator">|</span> now()               <span class="hljs-operator">|</span>                                                                 <br><span class="hljs-operator">+</span><span class="hljs-comment">---------------------+                                                                 </span><br><span class="hljs-operator">|</span> <span class="hljs-number">2025</span><span class="hljs-number">-04</span><span class="hljs-number">-15</span> <span class="hljs-number">11</span>:<span class="hljs-number">00</span>:<span class="hljs-number">31</span> <span class="hljs-operator">|</span>                                                                 <br><span class="hljs-operator">+</span><span class="hljs-comment">---------------------+                                                                 </span><br><span class="hljs-number">1</span> <span class="hljs-type">row</span> <span class="hljs-keyword">in</span> <span class="hljs-keyword">set</span> (<span class="hljs-number">0.00</span> sec) <br></code></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>这篇笔记主要是对Docker中MySQL时区不正确的问题做一个复盘和记录。问题本身并不复杂，但是对于像我这种纯前端同学，初玩后端还不太熟练的话，解决起来也确实需要费一番功能。就像让纯后端同学去调试css样式一样难受，哈哈！</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;前段时间在群晖NAS的Docker里安装了MySQL，方便自己折腾result API开发，最近在开发时遇到了一个问题，就是数据库的时间跟北京时间相差8小时，会导致时间判断出错，这里记录一下解决办法，供遇到同样问题的同学参考。&lt;/p&gt;</summary>
    
    
    
    <category term="折腾小记" scheme="https://www.xdxmblog.cn/categories/%E6%8A%98%E8%85%BE%E5%B0%8F%E8%AE%B0/"/>
    
    <category term="NAS" scheme="https://www.xdxmblog.cn/categories/%E6%8A%98%E8%85%BE%E5%B0%8F%E8%AE%B0/NAS/"/>
    
    
    <category term="MySQL" scheme="https://www.xdxmblog.cn/tags/MySQL/"/>
    
    <category term="NAS" scheme="https://www.xdxmblog.cn/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>Node + Express + MySQL 开发RESULT API(二)邮箱+验证码注册</title>
    <link href="https://www.xdxmblog.cn/posts/60030.html"/>
    <id>https://www.xdxmblog.cn/posts/60030.html</id>
    <published>2025-04-10T06:50:07.000Z</published>
    <updated>2025-04-10T06:50:07.000Z</updated>
    
    <content type="html"><![CDATA[<p>上一篇笔记主要记录了如何快速搭建一个用<code>Node + Express + MySQL</code>开发的RESULT API小项目，并且编写并运行了一个GET接口。这篇笔记会继续完善这个小项目，将API开发中的CURD完整的写出来。</p><span id="more"></span><p>上一篇笔记的最后我们实现了一个查询接口，用来查询用户的身份（分类），这篇笔记我们来实现通过邮件验证码来注册用户。</p><h2 id="创建数据库表"><a href="#创建数据库表" class="headerlink" title="创建数据库表"></a>创建数据库表</h2><p>在数据库中创建两个表<code>user，temp_user_code</code>表，用来存储用户数据和验证码数据，并设计以下结构。</p><table><thead><tr><th>名</th><th>类型</th><th>长度</th><th>小数点</th><th>不是null</th><th>虚拟</th><th>键</th><th>注释</th></tr></thead><tbody><tr><td>id</td><td>int</td><td>10</td><td>0</td><td>√</td><td></td><td>√</td><td>用户id(无符号，自动递增)</td></tr><tr><td>openid</td><td>varchar</td><td>255</td><td>0</td><td></td><td></td><td></td><td>微信openid</td></tr><tr><td>email</td><td>varchar</td><td>15</td><td>0</td><td>√</td><td></td><td></td><td>邮箱（添加UNIQUE索引）</td></tr><tr><td>code</td><td>varchar</td><td>6</td><td>0</td><td></td><td></td><td></td><td>邮箱验证码</td></tr><tr><td>expires_at</td><td>datetime</td><td>0</td><td>0</td><td></td><td></td><td></td><td>验证码过期时间</td></tr><tr><td>type_id</td><td>tinyint</td><td>2</td><td>0</td><td></td><td></td><td></td><td>用户身份id(添加外键指向user_type表中的id字段)</td></tr><tr><td>create_time</td><td>datetime</td><td>0</td><td>0</td><td></td><td></td><td></td><td>创建时间（默认值CURRENT_TIMESTAMP）</td></tr><tr><td>update_time</td><td>datetime</td><td>0</td><td>0</td><td></td><td></td><td></td><td>更新时间（默认值CURRENT_TIMESTAMP，自动更新）</td></tr></tbody></table><table><thead><tr><th>名</th><th>类型</th><th>长度</th><th>小数点</th><th>不是null</th><th>虚拟</th><th>键</th><th>注释</th></tr></thead><tbody><tr><td>id</td><td>int</td><td>10</td><td>0</td><td>√</td><td></td><td>√</td><td>验证码id(无符号，自动递增)</td></tr><tr><td>email</td><td>varchar</td><td>15</td><td>0</td><td></td><td></td><td></td><td>邮箱（添加UNIQUE索引）</td></tr><tr><td>code</td><td>varchar</td><td>6</td><td>0</td><td></td><td></td><td></td><td>邮箱验证码</td></tr><tr><td>expires_at</td><td>datetime</td><td>0</td><td>0</td><td></td><td></td><td></td><td>验证码过期时间</td></tr></tbody></table><h2 id="实现邮箱-验证码注册"><a href="#实现邮箱-验证码注册" class="headerlink" title="实现邮箱+验证码注册"></a>实现邮箱+验证码注册</h2><p>创建好数据库表以后，我们就正式进入接口开发的环节了，这里我们选择使用邮箱+验证码登录的方式来实现用户的创建。</p><h3 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h3><p>安装<code>nodemailer randomstring</code>依赖，其中<code>randomstring</code>库用来生成随机字符串（也就是我们要做的验证码），而<code>nodemailer</code>用来实现发送邮箱验证码。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm i nodemailer randomstring<br></code></pre></td></tr></table></figure><h3 id="编写路由、控制器函数"><a href="#编写路由、控制器函数" class="headerlink" title="编写路由、控制器函数"></a>编写路由、控制器函数</h3><p>安装好依赖以后，我们来编写路由函数，打开<code>user.routes.js</code>，添加以下路由。</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-comment">// 注册</span><br>router.<span class="hljs-title function_">post</span>(<span class="hljs-string">&#x27;/register&#x27;</span>, userController.<span class="hljs-property">register</span>)<br><span class="hljs-comment">// 发送验证</span><br>router.<span class="hljs-title function_">post</span>(<span class="hljs-string">&#x27;/sendEmaliCode&#x27;</span>, userController.<span class="hljs-property">sendEmailCode</span>)<br>...<br></code></pre></td></tr></table></figure><p>接着我们来编写控制函数，打开<code>user.controller.js</code>，添加以下代码。</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 发送验证码</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">sendEmailCode</span> = <span class="hljs-keyword">async</span> (req, res, next) =&gt; &#123;<br>  <span class="hljs-keyword">const</span> &#123; email &#125; = req.<span class="hljs-property">body</span><br>  <span class="hljs-keyword">if</span> (!email) &#123;<br>    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: -<span class="hljs-number">1</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;请提供邮箱地址&#x27;</span>, <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span> &#125;)<br>  &#125;<br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title class_">User</span>.<span class="hljs-title function_">sendEmailCode</span>(email)<br>    res.<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: <span class="hljs-number">200</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;验证码已发送，请查收邮件&#x27;</span>, <span class="hljs-attr">data</span>: result &#125;)<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    res.<span class="hljs-title function_">status</span>(error.<span class="hljs-property">status</span> || <span class="hljs-number">500</span>).<span class="hljs-title function_">send</span>(&#123;<br>      <span class="hljs-attr">code</span>: error.<span class="hljs-property">code</span> || -<span class="hljs-number">1</span>,<br>      <span class="hljs-attr">message</span>: error.<span class="hljs-property">message</span> || <span class="hljs-string">&#x27;服务器内部错误&#x27;</span>,<br>      <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span>,<br>    &#125;)<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>接着我们使用接口调试工具（我这里使用的是<strong>Apifox</strong>)，调用接口，我们发现不管参数email有没有值，接口都会报错，并且在命令也发现打印的<code>email</code>值也是<code>undefined</code>。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_60030_01.webp" alt="接口调试"></p><h3 id="解析req-body入参"><a href="#解析req-body入参" class="headerlink" title="解析req.body入参"></a>解析req.body入参</h3><p>其实这是因为express默认没有解析请求体中的数据导致的，现在我们来对它进行设置，打开<code>app.js</code>，添加以下代码：</p><figure class="highlight javascript"><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><code class="hljs javascript">...<br><span class="hljs-keyword">const</span> app = <span class="hljs-title function_">express</span>()<br><span class="hljs-comment">//添加以下代码</span><br>app.<span class="hljs-title function_">use</span>(express.<span class="hljs-title function_">json</span>()) <span class="hljs-comment">// 解析json入参</span><br>app.<span class="hljs-title function_">use</span>(express.<span class="hljs-title function_">urlencoded</span>(&#123; <span class="hljs-attr">extended</span>: <span class="hljs-literal">false</span> &#125;)) <span class="hljs-comment">// 解析x-www-form-urlencoded入参</span><br><br>...<br></code></pre></td></tr></table></figure><p>再次传入一个空的<code>email</code>，现在能正常返回错误信息了。</p><figure class="highlight json"><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><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;code&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">-1</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;message&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;请提供邮箱地址&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;data&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">null</span></span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><h3 id="配置邮件config、创建工具文件"><a href="#配置邮件config、创建工具文件" class="headerlink" title="配置邮件config、创建工具文件"></a>配置邮件config、创建工具文件</h3><p>打开<code>config.js</code>文件，写入以下配置。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = &#123;<br>  <span class="hljs-attr">dbConfig</span>: &#123;<br>    ...<br>  &#125;,<br>  <span class="hljs-attr">emailConfig</span>: &#123;<br>    <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;smtp.163.com&#x27;</span>, <span class="hljs-comment">// SMTP服务器地址</span><br>    <span class="hljs-attr">port</span>: <span class="hljs-number">465</span>, <span class="hljs-comment">// 端口号</span><br>    <span class="hljs-attr">secure</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// 使用SSL</span><br>    <span class="hljs-attr">auth</span>: &#123;<br>      <span class="hljs-attr">user</span>: <span class="hljs-string">&#x27;youremail@163.com&#x27;</span>, <span class="hljs-comment">// 发件人邮箱</span><br>      <span class="hljs-attr">pass</span>: <span class="hljs-string">&#x27;这里填你的授权码&#x27;</span>, <span class="hljs-comment">// STMP授权码</span><br>    &#125;,<br>  &#125;,<br>&#125;<br></code></pre></td></tr></table></figure><p>在<code>src</code>目录下创建一个<code>utils.js</code>文件，用来开发工具函数,并写入以下代码。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> nodemailer = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;nodemailer&#x27;</span>)<br><span class="hljs-keyword">const</span> randomstring = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;randomstring&#x27;</span>)<br><span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./config&#x27;</span>)<br><br><span class="hljs-comment">// 创建nodemailer实例</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">transporter</span> = <span class="hljs-function">() =&gt;</span> &#123;<br>  <span class="hljs-keyword">return</span> nodemailer.<span class="hljs-title function_">createTransport</span>(&#123;<br>    ...config.<span class="hljs-property">emailConfig</span>,<br>  &#125;)<br>&#125;<br><br><span class="hljs-comment">// 生成验证码</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">generateCode</span> = <span class="hljs-function">() =&gt;</span> &#123;<br>  <span class="hljs-keyword">const</span> code = randomstring.<span class="hljs-title function_">generate</span>(&#123;<br>    <span class="hljs-attr">length</span>: <span class="hljs-number">6</span>, <span class="hljs-comment">// 验证码长度</span><br>    <span class="hljs-attr">charset</span>: <span class="hljs-string">&#x27;numeric&#x27;</span>, <span class="hljs-comment">//可以选择 &#x27;alphabetic&#x27;, &#x27;numeric&#x27;, &#x27;hexadecimal&#x27;, &#x27;binary&#x27; 或者自定义字符集</span><br>  &#125;)<br>  <span class="hljs-keyword">const</span> expiresAt = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(<span class="hljs-title class_">Date</span>.<span class="hljs-title function_">now</span>() + <span class="hljs-number">5</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>) <span class="hljs-comment">// 验证码有效期为5分钟</span><br>  <span class="hljs-keyword">return</span> &#123; code, expiresAt &#125;<br>&#125;<br><br><span class="hljs-comment">// 生成邮件模板</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">generateMailOptions</span> = <span class="hljs-function">(<span class="hljs-params">email, code, expiresAt</span>) =&gt;</span> &#123;<br>  <span class="hljs-keyword">return</span> &#123;<br>    <span class="hljs-attr">from</span>: config.<span class="hljs-property">emailConfig</span>.<span class="hljs-property">auth</span>.<span class="hljs-property">user</span>,<br>    <span class="hljs-attr">to</span>: email,<br>    <span class="hljs-attr">subject</span>: <span class="hljs-string">`您的验证码 <span class="hljs-subst">$&#123;code&#125;</span>`</span>,<br>    <span class="hljs-attr">text</span>: <span class="hljs-string">`您好，您的验证码是：<span class="hljs-subst">$&#123;code&#125;</span>。请不要告诉他人。验证码将在 <span class="hljs-subst">$&#123;expiresAt.toISOString()&#125;</span> 前有效。`</span>,<br>    <span class="hljs-attr">html</span>: <span class="hljs-string">`&lt;b&gt;你的验证码是 <span class="hljs-subst">$&#123;code&#125;</span>，请勿告诉他人。&lt;/b&gt;`</span>, <span class="hljs-comment">// HTML 内容</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="编写发送验证码函数"><a href="#编写发送验证码函数" class="headerlink" title="编写发送验证码函数"></a>编写发送验证码函数</h3><p>打开<code>user.model.js</code>文件，编写发送验证码函数，这段函数的流程主要如下：</p><ol><li>通过<code>utils.generateCode()</code>方法生成验证码和过期时间</li><li>通过辅助函数<code>findUser()</code>查询当前的邮箱是否已被注册，该函数做了通用化处理，可以根据<code>user</code>表的任意字段进行查询。</li><li>根据查询结果判断当前发送的是登录验证码还是注册验证码，将<code>code,expiresAt</code>存入数据库中，以供后续验证比对。</li><li>通过<code>utils.transporter()</code>方法发送邮件。</li></ol><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> utils = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;../utils&#x27;</span>)<br><br>...<br><br><span class="hljs-comment">// 发送邮箱验证码</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">sendEmailCode</span> = <span class="hljs-keyword">async</span> email =&gt; &#123;<br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-comment">// 生成验证码，过期时间</span><br>    <span class="hljs-keyword">const</span> &#123; code, expiresAt &#125; = utils.<span class="hljs-title function_">generateCode</span>()<br>    <span class="hljs-comment">// 查询当前邮箱是否被注册</span><br>    <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">findUser</span>(<span class="hljs-string">&#x27;email&#x27;</span>, email)<br><br>    <span class="hljs-keyword">if</span> (users &amp;&amp; users.<span class="hljs-property">length</span> &gt; <span class="hljs-number">0</span>) &#123;<br>      <span class="hljs-comment">// 保存登录验证码</span><br>      <span class="hljs-keyword">let</span> updateSQL = <span class="hljs-string">`update user set code=?, expires_at=? where email=&#x27;<span class="hljs-subst">$&#123;email&#125;</span>&#x27;`</span><br>      <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(updateSQL, [code, expiresAt, email])<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>      <span class="hljs-comment">// 每次发送之前先清空之前保存的临时验证码</span><br>      <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">deleteCode</span>(email)<br>      <span class="hljs-comment">// 保存注册验证码</span><br>      <span class="hljs-keyword">let</span> saveSQL = <span class="hljs-string">`insert into temp_user_code(email, code, expires_at) values(?,?,?)`</span><br>      <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(saveSQL, [email, code, expiresAt])<br>    &#125;<br>    <span class="hljs-comment">// 发送邮件</span><br>    <span class="hljs-keyword">const</span> mailOptin = utils.<span class="hljs-title function_">generateMailOptions</span>(email, code, expiresAt)<br>    <span class="hljs-keyword">const</span> transporter = utils.<span class="hljs-title function_">transporter</span>()<br>    <span class="hljs-keyword">await</span> transporter.<span class="hljs-title function_">sendMail</span>(mailOptin)<br>    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span><br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;验证码发送失败，请重新尝试&#x27;</span><br>  &#125;<br>&#125;<br><br><span class="hljs-comment">// 查询用户</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">findUser</span> = <span class="hljs-keyword">async</span> (findType, data) =&gt; &#123;<br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">let</span> sql = <span class="hljs-string">`select * from user where <span class="hljs-subst">$&#123;findType&#125;</span> = ?`</span><br>    <span class="hljs-keyword">const</span> [rows] = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(sql, [data])<br>    <span class="hljs-keyword">return</span> rows<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;服务器内部错误，请稍后重试&#x27;</span><br>  &#125;<br>&#125;<br><br><span class="hljs-comment">// 删除验证码</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">deleteCode</span> = <span class="hljs-keyword">async</span> email =&gt; &#123;<br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">let</span> deleteSQL = <span class="hljs-string">`delete from temp_user_code where email=&#x27;<span class="hljs-subst">$&#123;email&#125;</span>&#x27;`</span><br>    <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(deleteSQL, [email])<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;服务器内部错误，请稍后重试&#x27;</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>这时我们再次调用发送验证码的接口，可以观察到接口已经调用成功了，并且邮箱也收到了生成的验证码。</p><figure class="highlight javascript"><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><code class="hljs javascript">&#123;<br>    <span class="hljs-string">&quot;code&quot;</span>: <span class="hljs-number">200</span>,<br>    <span class="hljs-string">&quot;message&quot;</span>: <span class="hljs-string">&quot;验证码已发送，请查收邮件&quot;</span>,<br>    <span class="hljs-string">&quot;data&quot;</span>: <span class="hljs-literal">null</span><br>&#125;<br></code></pre></td></tr></table></figure><h3 id="编写校验验证码函数"><a href="#编写校验验证码函数" class="headerlink" title="编写校验验证码函数"></a>编写校验验证码函数</h3><p>这个过程比较简单，为了方便使用，该辅助函数也做了复用处理，通过<code>type</code>入参来决定校验的是注册code还是登录code。并将比对结果进行返回。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 校验验证码</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">verifyCode</span> = <span class="hljs-keyword">async</span> (email, code, type = <span class="hljs-string">&#x27;register&#x27;</span>) =&gt; &#123;<br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">let</span> sql = <span class="hljs-string">`select * from <span class="hljs-subst">$&#123;</span></span><br><span class="hljs-subst"><span class="hljs-string">      type == <span class="hljs-string">&#x27;register&#x27;</span> ? <span class="hljs-string">&#x27;temp_user_code&#x27;</span> : <span class="hljs-string">&#x27;user&#x27;</span></span></span><br><span class="hljs-subst"><span class="hljs-string">    &#125;</span> where email = &#x27;<span class="hljs-subst">$&#123;email&#125;</span>&#x27; and expires_at &gt; NOW()`</span><br>    <span class="hljs-keyword">const</span> [rows] = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(sql, [email])<br><br>    <span class="hljs-keyword">if</span> (rows.<span class="hljs-property">length</span> === <span class="hljs-number">0</span>) &#123;<br>      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br>    &#125;<br>    <span class="hljs-comment">// 比较验证码</span><br>    <span class="hljs-keyword">const</span> storeCode = rows[<span class="hljs-number">0</span>].<span class="hljs-property">code</span><br>    <span class="hljs-keyword">if</span> (storeCode === code) &#123;<br>      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br>    &#125;<br>    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;服务器内部错误，请稍后重试&#x27;</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="编写注册函数"><a href="#编写注册函数" class="headerlink" title="编写注册函数"></a>编写注册函数</h3><p>在<code>user.controller.js</code>文件内，编写注册控制器函数。</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 通过邮箱注册用户</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">register</span> = <span class="hljs-keyword">async</span> (req, res, next) =&gt; &#123;<br>  <span class="hljs-keyword">const</span> &#123; email, code, type_id &#125; = req.<span class="hljs-property">body</span><br>  <span class="hljs-keyword">if</span> (!email || !code) &#123;<br>    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: -<span class="hljs-number">1</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;邮箱或验证码不能为空&#x27;</span>, <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span> &#125;)<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (!type_id) &#123;<br>    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: -<span class="hljs-number">1</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;请选择身份&#x27;</span>, <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span> &#125;)<br>  &#125; <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (type_id &amp;&amp; (type_id &lt; <span class="hljs-number">1</span> || type_id &gt; <span class="hljs-number">7</span>)) &#123;<br>    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">400</span>).<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: -<span class="hljs-number">1</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;身份值有误&#x27;</span>, <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span> &#125;)<br>  &#125; <span class="hljs-keyword">else</span> &#123;<br>    <span class="hljs-keyword">try</span> &#123;<br>      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title class_">User</span>.<span class="hljs-title function_">register</span>(email, code, type_id)<br>      res.<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: <span class="hljs-number">200</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;注册成功&#x27;</span>, <span class="hljs-attr">data</span>: result &#125;)<br>    &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>      res.<span class="hljs-title function_">status</span>(<span class="hljs-number">500</span>).<span class="hljs-title function_">send</span>(&#123;<br>        <span class="hljs-attr">code</span>: <span class="hljs-number">500</span>,<br>        <span class="hljs-attr">message</span>: error || <span class="hljs-string">&#x27;服务器内部错误&#x27;</span>,<br>        <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span>,<br>      &#125;)<br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>在<code>user.model.js</code>文件内，编写注册函数。这个函数也比较简单，通过查询判断邮箱是否已注册，然后校验验证码，进行用户的创建即可。</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">// 注册用户</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">register</span> = <span class="hljs-keyword">async</span> (email, code, type_id) =&gt; &#123;<br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-comment">// 查询邮箱是否已被注册</span><br>    <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">findUser</span>(<span class="hljs-string">&#x27;email&#x27;</span>, email)<br>    <span class="hljs-keyword">if</span> (users.<span class="hljs-property">length</span> &gt; <span class="hljs-number">0</span>) &#123;<br>      <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;该邮箱已被注册&#x27;</span><br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>      <span class="hljs-keyword">const</span> isTrue = <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">verifyCode</span>(email, code)<br><br>      <span class="hljs-keyword">if</span> (!isTrue) &#123;<br>        <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;验证码错误或已过期&#x27;</span><br>      &#125;<br><br>      <span class="hljs-keyword">let</span> createSQL = <span class="hljs-string">&#x27;insert into user (email, type_id) value(?,?)&#x27;</span><br>      <span class="hljs-keyword">const</span> [results] = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(createSQL, [email, type_id])<br>      <span class="hljs-keyword">if</span> (results.<span class="hljs-property">affectedRows</span> === <span class="hljs-number">1</span>) &#123;<br>        <span class="hljs-comment">// 注册成功，删除存储的验证码</span><br>        <span class="hljs-keyword">await</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">deleteCode</span>(email)<br>        <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span><br>      &#125;<br>    &#125;<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;注册失败，请稍后重试&#x27;</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>通过观察我们可以发现，仅仅实现一个发送验证码的函数，我们就用到了数据的增删改查四种能力。其实这四种sql语句单独使用的话，就对应了前端平时的接口调用<strong>GET、POST、PUT、DELETE</strong>，是不是很简单呢？</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 增加</span><br><span class="hljs-keyword">let</span> saveSQL = <span class="hljs-string">`insert into temp_user_code(email, code, expires_at) values(?,?,?)`</span><br><span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(saveSQL, [email, code, expiresAt])<br><span class="hljs-comment">// 更新</span><br><span class="hljs-keyword">let</span> updateSQL = <span class="hljs-string">`update user set code=?, expires_at=? where email=&#x27;<span class="hljs-subst">$&#123;email&#125;</span>&#x27;`</span><br><span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(updateSQL, [code, expiresAt, email])<br><span class="hljs-comment">// 删除</span><br><span class="hljs-keyword">let</span> deleteSQL = <span class="hljs-string">`delete from temp_user_code where email=&#x27;<span class="hljs-subst">$&#123;email&#125;</span>&#x27;`</span><br><span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(deleteSQL, [email])<br><span class="hljs-comment">// 查询</span><br><span class="hljs-keyword">let</span> sql = <span class="hljs-string">`select * from user where <span class="hljs-subst">$&#123;findType&#125;</span> = ?`</span><br><span class="hljs-keyword">const</span> [rows] = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(sql, [data])<br></code></pre></td></tr></table></figure><p>但是我们观察代码也能发现有几个问题：</p><ol><li>入参校验需要写一大堆if else 很不美观，而且目前只做到了简单校验，对于入参的数据类型等等都没实现。</li><li>错误捕获目前只能靠try catch，而且要重复写很多的<code>res.status().send()</code>，没有进行统一处理。</li><li><code>config</code>文件保存很多敏感配置，比如数据库的用户名密码，邮件的授权码等，这种内容一旦传到github等，有暴露的风险。</li></ol><p>下一篇笔记会针对这些错误进行优化配置，敬请期待吧！</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;上一篇笔记主要记录了如何快速搭建一个用&lt;code&gt;Node + Express + MySQL&lt;/code&gt;开发的RESULT API小项目，并且编写并运行了一个GET接口。这篇笔记会继续完善这个小项目，将API开发中的CURD完整的写出来。&lt;/p&gt;</summary>
    
    
    
    <category term="后端探索" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/"/>
    
    <category term="Node" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/Node/"/>
    
    
    <category term="Node" scheme="https://www.xdxmblog.cn/tags/Node/"/>
    
    <category term="Express" scheme="https://www.xdxmblog.cn/tags/Express/"/>
    
  </entry>
  
  <entry>
    <title>Node + Express + MySQL 开发RESULT API(一)框架搭建</title>
    <link href="https://www.xdxmblog.cn/posts/26719.html"/>
    <id>https://www.xdxmblog.cn/posts/26719.html</id>
    <published>2025-04-01T08:28:40.000Z</published>
    <updated>2025-04-01T08:28:40.000Z</updated>
    
    <content type="html"><![CDATA[<p>最近在用<code>uni-app</code>给娃做一个日常习惯打卡的APP + 小程序，后端接口打算用<code>Node+Express</code>去做，后续部署到Serverless，虽然近几年出了很多Node框架，但思虑再三，还是选择用比较成熟的Express来做，毕竟社区成熟度在这儿摆着。</p><span id="more"></span><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><p>这个系列的文章主要记录开发和后续部署到Serverless的过程。至于如何安装Node和MySQL网上有大量现成的教程，这里不再过多赘述。</p><p>本系列笔记选用Express 5.X版本开发，需要Node.js版本至少为18或更高。</p><h3 id="安装Express"><a href="#安装Express" class="headerlink" title="安装Express"></a>安装Express</h3><p>首先新建一个文件夹，并初始化项目，然后安装Express。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm init -y<br></code></pre></td></tr></table></figure><figure class="highlight shell"><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><code class="hljs shell">Wrote to C:\Users\Administrator\Desktop\express-api\package.json:<br><br>&#123;<br>  &quot;name&quot;: &quot;express-api&quot;,<br>  &quot;version&quot;: &quot;1.0.0&quot;,<br>  &quot;description&quot;: &quot;&quot;,<br>  &quot;main&quot;: &quot;index.js&quot;,<br>  &quot;scripts&quot;: &#123;<br>    &quot;test&quot;: &quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;<br>  &#125;,<br>  &quot;keywords&quot;: [],<br>  &quot;author&quot;: &quot;&quot;,<br>  &quot;license&quot;: &quot;ISC&quot;<br>&#125;<br></code></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm i express<br></code></pre></td></tr></table></figure><figure class="highlight shell"><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><code class="hljs shell">added 66 packages, and audited 67 packages in 4s<br><br>14 packages are looking for funding<br>  run `npm fund` for details<br><br>found 0 vulnerabilities<br><br></code></pre></td></tr></table></figure><h3 id="编写入口文件"><a href="#编写入口文件" class="headerlink" title="编写入口文件"></a>编写入口文件</h3><p>接下来我们在根目录创建<code>src</code>文件夹，并在文件夹下创建<code>app.js</code>文件，也就是主入口文件。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 导入express</span><br><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;express&#x27;</span>)<br><span class="hljs-comment">// 创建express实例</span><br><span class="hljs-keyword">const</span> app = <span class="hljs-title function_">express</span>()<br><br><span class="hljs-comment">// 调用app.listen方法，指定端口号并启动web服务器</span><br>app.<span class="hljs-title function_">listen</span>(<span class="hljs-number">3000</span>, <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) &#123;<br>  <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;server running at http://127.0.0.1:3000&#x27;</span>)<br>&#125;)<br></code></pre></td></tr></table></figure><p>接着我们在命令行输入启动命令</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript">node src/app.<span class="hljs-property">js</span><br></code></pre></td></tr></table></figure><p>这时候在浏览器输入<code>http://127.0.0.1:3000</code>,看到<code>Cannot GET/</code>，说明我们的服务器就启动好了。</p><h2 id="添加路由"><a href="#添加路由" class="headerlink" title="添加路由"></a>添加路由</h2><h3 id="规划目录结构"><a href="#规划目录结构" class="headerlink" title="规划目录结构"></a>规划目录结构</h3><p>在<code>src</code>文件夹下分别创建<code>controllers、models、routes</code>文件夹，具体作用如下：</p><ul><li>controllers：封装与前端进行的交互，如数据校验等。</li><li>models：封装Node.js与数据库的交互逻辑。</li><li>routes:存放路由文件。</li></ul><h3 id="创建路由文件"><a href="#创建路由文件" class="headerlink" title="创建路由文件"></a>创建路由文件</h3><p>在<code>routes</code>目录下创建<code>index.js、user.routes.js</code>文件，具体作用如下：</p><ul><li>index.js：作为路由的入口文件，负责暴露所有的路由模块。</li><li>user.routes.js：用户路由模块，用于存放用户相关的接口声明。</li><li>xxx.routes.js：根据项目需求，存放不同的路由模块。</li></ul><p>在<code>user.routes.js</code>文件里写入以下代码，其中的<code>router.get</code>表示我们要创建一个<code>GET</code>接口，第一个参数表示接口的地址，第二个参数是一个函数，表示访问该接口时执行的方法。具体的req,res等参数和方法，可以查阅express官方文档了解。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;express&#x27;</span>)<br><span class="hljs-comment">// 创建路由对象</span><br><span class="hljs-keyword">const</span> router = express.<span class="hljs-title class_">Router</span>()<br><br><span class="hljs-comment">// 获取用户分类</span><br>router.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;/type&#x27;</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span>&#123;<br>  res.<span class="hljs-title function_">send</span>(<span class="hljs-string">&#x27;ok&#x27;</span>)<br>&#125;)<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = router<br></code></pre></td></tr></table></figure><h3 id="导入并注册写好的路由模块"><a href="#导入并注册写好的路由模块" class="headerlink" title="导入并注册写好的路由模块"></a>导入并注册写好的路由模块</h3><p>在<code>index.js</code>文件里写入以下代码，这部分代码主要用于注册我们写好的路由模块，参数<code>/api/user</code>，表示为用户模块的接口添加统一的前缀，以刚才写的<code>/type</code>接口为例，访问时应该用<code>/api/user/type</code>。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 导入用户路由模块</span><br><span class="hljs-keyword">const</span> userRouter = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./user.routes&#x27;</span>)<br><br><span class="hljs-comment">// 暴露一个方法，用来注册已经写好的路由模块</span><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-function"><span class="hljs-params">app</span> =&gt;</span> &#123;<br>  app.<span class="hljs-title function_">use</span>(<span class="hljs-string">&#x27;/api/user&#x27;</span>, userRouter)<br>&#125;<br></code></pre></td></tr></table></figure><p>回到<code>app.js</code>文件，写入以下代码来将所有的路由进行注册。</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> app = <span class="hljs-title function_">express</span>()<br><span class="hljs-comment">// 添加以下代码</span><br><span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./routes&#x27;</span>)(app)<br></code></pre></td></tr></table></figure><p>此时我们用浏览器访问<code>http://127.0.0.1:3000/api/user/type</code>并不会有任何反应，是因为我们每次修改文件都要重新启动服务器才能够得到最新的代码，那有什么办法可以不用一直手动重启服务器吗？答案是<code>Nodemon</code>。</p><h3 id="自动监听并重启服务器"><a href="#自动监听并重启服务器" class="headerlink" title="自动监听并重启服务器"></a>自动监听并重启服务器</h3><p><code>Nodemon</code>是一个基于Node.js构建的开发工具，专为帮助开发者自动监控项目文件的更改而设计。每当文件发生变更时<code>Nodemon</code>会自动重启Node.js服务器，无需手动停止并重启。这对于提升开发速度、减少人工操作非常有帮助，尤其适用于构建后端服务或API接口时。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">npm i -g nodemon<br></code></pre></td></tr></table></figure><figure class="highlight shell"><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><code class="hljs shell">added 29 packages, and audited 30 packages in 4s<br><br>4 packages are looking for funding<br>  run `npm fund` for details<br><br>found 0 vulnerabilities<br></code></pre></td></tr></table></figure><p>接下来打开<code>package.json</code>文件，进行以下改写：</p><figure class="highlight json"><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><code class="hljs json"><span class="hljs-attr">&quot;scripts&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;test&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;</span><span class="hljs-punctuation">,</span> <br>    <span class="hljs-attr">&quot;start&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-string">&quot;nodemon src/app.js&quot;</span><br>  <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br></code></pre></td></tr></table></figure><p>接着在命令行输入<code>npm start</code>，即可正常使用。</p><figure class="highlight shell"><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><code class="hljs shell">npm start<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">&gt; </span><span class="language-bash">express-api@1.0.0 start</span><br><span class="hljs-meta prompt_">&gt; </span><span class="language-bash">nodemon src/app.js</span><br><br>[nodemon] 3.1.9<br>[nodemon] to restart at any time, enter `rs`<br>[nodemon] watching path(s): *.*<br>[nodemon] watching extensions: js,mjs,cjs,json<br>[nodemon] starting `node src/app.js`<br>server running at http://127.0.0.1:3000<br></code></pre></td></tr></table></figure><p>此时我们再次访问<code>http://127.0.0.1:3000/api/user/type</code>，就能看到返回的ok啦。</p><h2 id="连接数据库"><a href="#连接数据库" class="headerlink" title="连接数据库"></a>连接数据库</h2><h3 id="安装mysql2模块"><a href="#安装mysql2模块" class="headerlink" title="安装mysql2模块"></a>安装mysql2模块</h3><p>在命令行输入以下代码来安装mysql2模块。</p><figure class="highlight shell"><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><code class="hljs shell">npm i mysql2<br><br>added 11 packages, and audited 78 packages in 2s<br><br>15 packages are looking for funding<br>  run `npm fund` for details<br><br>found 0 vulnerabilities<br></code></pre></td></tr></table></figure><p>这里使用<code>mysql2</code>而不使用<code>mysql</code>的主要原因是<code>mysql2</code>增加了很多<code>mysql</code>没有的功能，比如Promise、Prepared Statements、更好的错误处理等。</p><h3 id="数据库配置"><a href="#数据库配置" class="headerlink" title="数据库配置"></a>数据库配置</h3><p>在<code>src</code>目录下新建<code>config</code>文件夹，用于存放配置文件，同时在该文件夹下创建<code>index.js</code>文件，在该文件内添加以下代码：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = &#123;<br>  <span class="hljs-attr">dbConfig</span>: &#123;<br>    <span class="hljs-attr">host</span>: <span class="hljs-string">&#x27;127.0.0.1&#x27;</span>, <span class="hljs-comment">// 数据库地址</span><br>    <span class="hljs-attr">port</span>: <span class="hljs-string">&#x27;8996&#x27;</span>, <span class="hljs-comment">// 端口</span><br>    <span class="hljs-attr">user</span>: <span class="hljs-string">&#x27;root&#x27;</span>, <span class="hljs-comment">// 用户名</span><br>    <span class="hljs-attr">password</span>: <span class="hljs-string">&#x27;123456&#x27;</span>, <span class="hljs-comment">//密码</span><br>    <span class="hljs-attr">database</span>: <span class="hljs-string">&#x27;test&#x27;</span>, <span class="hljs-comment">//数据库名称</span><br>  &#125;,<br>&#125;<br></code></pre></td></tr></table></figure><p>在刚才创建好的<code>models</code>文件夹里新建<code>db.js</code>，并在该文件内添加以下代码：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">// 由于能够更好的使用Promise,这里选择引入promise模块</span><br><span class="hljs-keyword">const</span> mysql = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;mysql2/promise&#x27;</span>)<br><span class="hljs-keyword">const</span> config = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;../config&#x27;</span>)<br><br><span class="hljs-comment">// 建立与mysql数据库的连接池</span><br><span class="hljs-keyword">const</span> pool = mysql.<span class="hljs-title function_">createPool</span>(&#123;<br>  <span class="hljs-attr">host</span>: config.<span class="hljs-property">dbConfig</span>.<span class="hljs-property">host</span>,<br>  <span class="hljs-attr">port</span>: config.<span class="hljs-property">dbConfig</span>.<span class="hljs-property">port</span>,<br>  <span class="hljs-attr">user</span>: config.<span class="hljs-property">dbConfig</span>.<span class="hljs-property">user</span>,<br>  <span class="hljs-attr">password</span>: config.<span class="hljs-property">dbConfig</span>.<span class="hljs-property">password</span>,<br>  <span class="hljs-attr">database</span>: config.<span class="hljs-property">dbConfig</span>.<span class="hljs-property">database</span>,<br>&#125;)<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = pool<br></code></pre></td></tr></table></figure><h3 id="创建本地数据"><a href="#创建本地数据" class="headerlink" title="创建本地数据"></a>创建本地数据</h3><p>打开本地的MySQL数据库，创建一个名为<code>test</code>的数据库，并新建一个<code>user_type</code>的表，写入以下数据：（如何安装MySQL,创建数据库表等内容，由于不是本系列笔记的重点，这里不再赘述，网上找教程即可）</p><table><thead><tr><th>名</th><th>类型</th><th>长度</th><th>小数点</th><th>不是null</th><th>虚拟</th><th>键</th><th>注释</th></tr></thead><tbody><tr><td>id</td><td>tinyint</td><td>2</td><td>0</td><td>√</td><td></td><td>√</td><td>用户身份类型id</td></tr><tr><td>name</td><td>varchar</td><td>2</td><td>0</td><td>√</td><td></td><td></td><td>用户身份类型名称</td></tr><tr><td>create_time</td><td>datetime</td><td>0</td><td>0</td><td></td><td></td><td></td><td>创建时间（默认值CURRENT_TIMESTAMP）</td></tr><tr><td>update_time</td><td>datetime</td><td>0</td><td>0</td><td></td><td></td><td></td><td>更新时间（默认值CURRENT_TIMESTAMP，自动更新）</td></tr></tbody></table><p>创建好表后预先写入以下数据。</p><table><thead><tr><th>id</th><th>name</th><th>create_time</th><th>update_time</th></tr></thead><tbody><tr><td>1</td><td>妈妈</td><td>2025-03-28 10:20:55</td><td>2025-03-28 10:20:55</td></tr><tr><td>2</td><td>爸爸</td><td>2025-03-28 10:20:55</td><td>2025-03-28 10:20:55</td></tr><tr><td>3</td><td>爷爷</td><td>2025-03-28 10:20:55</td><td>2025-03-28 10:20:55</td></tr><tr><td>4</td><td>奶奶</td><td>2025-03-28 10:20:55</td><td>2025-03-28 10:20:55</td></tr><tr><td>5</td><td>姥爷</td><td>2025-03-28 10:20:55</td><td>2025-03-28 10:20:55</td></tr><tr><td>6</td><td>姥姥</td><td>2025-03-28 10:20:55</td><td>2025-03-28 10:20:55</td></tr><tr><td>7</td><td>亲人</td><td>2025-03-28 10:20:55</td><td>2025-03-28 10:20:55</td></tr></tbody></table><h2 id="编写接口"><a href="#编写接口" class="headerlink" title="编写接口"></a>编写接口</h2><p>以上准备工作都做完以后，我们就可以进行正常的接口逻辑开发了。</p><h3 id="与数据库的逻辑"><a href="#与数据库的逻辑" class="headerlink" title="与数据库的逻辑"></a>与数据库的逻辑</h3><p>在<code>models</code>文件夹里新建<code>user.model.js</code>，并写入以下代码：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> pool = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;./db&#x27;</span>)<br><br><span class="hljs-comment">// 查询用户身份列表</span><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">getAllType</span> = <span class="hljs-keyword">async</span> () =&gt; &#123;<br>  <span class="hljs-keyword">let</span> sql = <span class="hljs-string">&#x27;select * from user_type&#x27;</span> <span class="hljs-comment">// 要执行的SQL语句</span><br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">const</span> [rows, fields] = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">query</span>(sql) <span class="hljs-comment">// rows返回查询结果，fields返回该表的字段名数组，一般用不到</span><br>    <span class="hljs-keyword">return</span> rows <span class="hljs-comment">//执行成功，返回数据集</span><br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    <span class="hljs-keyword">throw</span> <span class="hljs-string">&#x27;查询失败，请稍后重试&#x27;</span> <span class="hljs-comment">//执行失败，返回错误信息</span><br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="与前端的逻辑"><a href="#与前端的逻辑" class="headerlink" title="与前端的逻辑"></a>与前端的逻辑</h3><p>在<code>controllers</code>文件夹里新建<code>user.controller.js</code>，并写入以下代码：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> <span class="hljs-title class_">User</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;../models/user.model&#x27;</span>)<br><br><span class="hljs-built_in">exports</span>.<span class="hljs-property">findAllType</span> = <span class="hljs-keyword">async</span> (req, res) =&gt; &#123;<br>  <span class="hljs-keyword">try</span> &#123;<br>    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title class_">User</span>.<span class="hljs-title function_">getAllType</span>()<br>    res.<span class="hljs-title function_">send</span>(&#123; <span class="hljs-attr">code</span>: <span class="hljs-number">200</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">&#x27;成功&#x27;</span>, <span class="hljs-attr">data</span>: result &#125;)<br>  &#125; <span class="hljs-keyword">catch</span> (error) &#123;<br>    res.<span class="hljs-title function_">status</span>(<span class="hljs-number">500</span>).<span class="hljs-title function_">send</span>(&#123;<br>      <span class="hljs-attr">code</span>: <span class="hljs-number">500</span>,<br>      <span class="hljs-attr">message</span>: error || <span class="hljs-string">&#x27;服务器内部错误&#x27;</span>,<br>      <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span><br>    &#125;)<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="将接口逻辑与路由进行挂载"><a href="#将接口逻辑与路由进行挂载" class="headerlink" title="将接口逻辑与路由进行挂载"></a>将接口逻辑与路由进行挂载</h3><p>回到<code>user.routes.js</code>文件，将我们写好的逻辑挂载到路由上：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;express&#x27;</span>)<br><span class="hljs-keyword">const</span> userController = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;../controllers/user.controller&#x27;</span>)<br><span class="hljs-comment">// 创建路由对象</span><br><span class="hljs-keyword">const</span> router = express.<span class="hljs-title class_">Router</span>()<br><br><span class="hljs-comment">// 获取用户分类</span><br>router.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;/type&#x27;</span>, userController.<span class="hljs-property">findAllType</span>)<br><br><span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = router<br></code></pre></td></tr></table></figure><p>现在我们刷新浏览器<code>http://127.0.0.1:3000/api/user/type</code>,就能看到数据库返回的数据啦~</p><figure class="highlight json"><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></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-punctuation">&#123;</span><br>    <span class="hljs-attr">&quot;code&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">200</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;message&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;成功&quot;</span><span class="hljs-punctuation">,</span><br>    <span class="hljs-attr">&quot;data&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br>        <span class="hljs-punctuation">&#123;</span><br>            <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;妈妈&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;create_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;update_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><br>        <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-punctuation">&#123;</span><br>            <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">2</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;爸爸&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;create_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;update_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><br>        <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-punctuation">&#123;</span><br>            <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">3</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;爷爷&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;create_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;update_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><br>        <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-punctuation">&#123;</span><br>            <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">4</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;奶奶&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;create_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;update_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><br>        <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-punctuation">&#123;</span><br>            <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">5</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;姥爷&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;create_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;update_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><br>        <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-punctuation">&#123;</span><br>            <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">6</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;姥姥&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;create_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;update_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><br>        <span class="hljs-punctuation">&#125;</span><span class="hljs-punctuation">,</span><br>        <span class="hljs-punctuation">&#123;</span><br>            <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-number">7</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;name&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;亲人&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;create_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><span class="hljs-punctuation">,</span><br>            <span class="hljs-attr">&quot;update_time&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2025-03-28T02:20:55.000Z&quot;</span><br>        <span class="hljs-punctuation">&#125;</span><br>    <span class="hljs-punctuation">]</span><br><span class="hljs-punctuation">&#125;</span><br></code></pre></td></tr></table></figure><blockquote><p>tips:不过为了方便调试接口，最好能在电脑安装诸如<code>Apifox、Postman</code>等接口调试工具。</p></blockquote><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>这篇笔记主要记录了如何从零开始搭建一个<code>Node.js + Express + MySQL</code>为主的后端接口项目，包括如何规划目录结构，如何连接数据库，不同的处理逻辑应该如何解耦。目前的接口只实现了查的功能，后续的笔记会继续完善该项目，完成增删改，以及跨域、中间件等配置。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近在用&lt;code&gt;uni-app&lt;/code&gt;给娃做一个日常习惯打卡的APP + 小程序，后端接口打算用&lt;code&gt;Node+Express&lt;/code&gt;去做，后续部署到Serverless，虽然近几年出了很多Node框架，但思虑再三，还是选择用比较成熟的Express来做，毕竟社区成熟度在这儿摆着。&lt;/p&gt;</summary>
    
    
    
    <category term="后端探索" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/"/>
    
    <category term="Node" scheme="https://www.xdxmblog.cn/categories/%E5%90%8E%E7%AB%AF%E6%8E%A2%E7%B4%A2/Node/"/>
    
    
    <category term="Node" scheme="https://www.xdxmblog.cn/tags/Node/"/>
    
    <category term="Express" scheme="https://www.xdxmblog.cn/tags/Express/"/>
    
  </entry>
  
  <entry>
    <title>适合儿童的英文原版书阅读、裸听书单</title>
    <link href="https://www.xdxmblog.cn/posts/22628.html"/>
    <id>https://www.xdxmblog.cn/posts/22628.html</id>
    <published>2025-03-28T01:50:40.000Z</published>
    <updated>2025-03-28T01:50:40.000Z</updated>
    
    <content type="html"><![CDATA[<p>整理一下互联网上推荐的英文原版书单，以及蓝思指数中外年级对照表，方便自家娃使用。俗话说得好，鸡娃不如鸡自己。好在娃对英语不反感，对好些英语绘本还挺喜欢的，希望能顺顺利利按照这个路子坚持下去。</p><span id="more"></span><h2 id="蓝思指数"><a href="#蓝思指数" class="headerlink" title="蓝思指数"></a>蓝思指数</h2><h3 id="什么是蓝思值？"><a href="#什么是蓝思值？" class="headerlink" title="什么是蓝思值？"></a>什么是蓝思值？</h3><p>蓝思值（Lexile)是全球广泛使用的英语阅读分级系统，通过算法得出，精准反映个人英语阅读理解能力。</p><h3 id="蓝思值是如何计算的？"><a href="#蓝思值是如何计算的？" class="headerlink" title="蓝思值是如何计算的？"></a>蓝思值是如何计算的？</h3><p>蓝思值通过分析文本中的词汇复杂性和橘子结构来计算出一个数值，通常以“L”后跟数字表示，例如800L。</p><p>蓝思值的计算主要依赖句子长度和词汇频率。数值越大，表明句子长度越长，词汇重复出现的频率也越低，整体阅读难度也就更高。蓝思值的范围为0L-2000L,通常人的阅读能力为0-1700L。</p><h3 id="蓝思值查询"><a href="#蓝思值查询" class="headerlink" title="蓝思值查询"></a>蓝思值查询</h3><ul><li>AD &#x3D; Adult Directed:家长指导读物，适合亲自共读。</li><li>NC &#x3D; Non-Conforming:非常规书籍，语言难度超过了目标读者的阅读能力，适合阅读能力高于平均水平的读者。</li><li>HL &#x3D; High-Low:趣味性高但难度低的书籍，适合较高年级阅读能力较低的学生。</li><li>G  &#x3D; lllustrated Guide:图释类，如百科全书。</li><li>GN &#x3D; Graphic Novel:连环画或漫画。</li><li>NP &#x3D; Non-Prose:非散文性文章，如诗歌、歌词或菜谱，此类文章无法评定蓝思等级。</li><li>BR &#x3D; Beginning Reader:阅读入门者&#x2F;初级读物，蓝思值在0L以下的书籍和阅读者，数值越大，难度越低。</li></ul><h3 id="蓝思值对照表"><a href="#蓝思值对照表" class="headerlink" title="蓝思值对照表"></a>蓝思值对照表</h3><table><thead><tr><th>阅读阶段</th><th>级别</th><th>蓝思值范围</th><th>中国年级</th><th>美国年级</th></tr></thead><tbody><tr><td>阅读萌芽期</td><td>PreA</td><td>BR-0</td><td>学龄前</td><td>Grade K（学龄前）</td></tr><tr><td></td><td>PreB</td><td>1L-150L</td><td>小学1-3年级</td><td>Grade K</td></tr><tr><td>阅读早期</td><td>L1</td><td>151L-300L</td><td>小学4-6年级</td><td>Grade 1（小学1年级）</td></tr><tr><td></td><td>L2</td><td>301L-405L</td><td>初中1年级</td><td>Grade 2（小学2年级）</td></tr><tr><td>阅读发展期</td><td>L3</td><td>406L-510L</td><td>初中2年级</td><td>Grade 3（小学3年级）</td></tr><tr><td></td><td>L4</td><td>511L-625L</td><td>初中3年级</td><td>Grade 4（小学4年级）</td></tr><tr><td>独立阅读早期</td><td>L5</td><td>626L-750L</td><td>高中1年级</td><td>Grade 5（小学5年级）</td></tr><tr><td>独立阅读发展期</td><td>L6</td><td>751L-875L</td><td>高中2-3年级</td><td>Grade 6（小学6年级）</td></tr><tr><td>独立阅读期</td><td>L7</td><td>876L-950L</td><td>大学1年级</td><td>Grade 7（初中1年级）</td></tr><tr><td></td><td>L8</td><td>951L-1300L</td><td>大学2-4年级</td><td>Grade 8-10（初中2-3年级）</td></tr></tbody></table><h2 id="RAZ、牛津阅读树、海尼曼对照表"><a href="#RAZ、牛津阅读树、海尼曼对照表" class="headerlink" title="RAZ、牛津阅读树、海尼曼对照表"></a>RAZ、牛津阅读树、海尼曼对照表</h2><p>有些书家里买了实体书，有些书NAS里存了电子版，还有些自家有的没写进去，表中的大部分书也没看过，但我觉得也不是所有的书都是必读的，后续会根据选择进行精炼一下。</p><table><thead><tr><th>年级</th><th>RAZ</th><th>牛津阅读树</th><th>海尼曼</th><th>Lexile</th><th>是否已读</th></tr></thead><tbody><tr><td>学龄前</td><td>RAZ aa</td><td>- - -</td><td>GK</td><td>BR70L-BR10L</td><td>是</td></tr><tr><td></td><td>A</td><td></td><td></td><td></td><td>是</td></tr><tr><td></td><td>B</td><td>牛1</td><td></td><td>BR40L-160L</td><td>是</td></tr><tr><td></td><td>C</td><td>牛2</td><td></td><td></td><td>是</td></tr><tr><td>小学1-2年级</td><td>D</td><td>牛3</td><td>G1</td><td>160L-310L</td><td></td></tr><tr><td></td><td>E</td><td>牛4</td><td></td><td></td><td></td></tr><tr><td>小学2-4年级</td><td>F</td><td>牛5</td><td></td><td>300L-450L</td><td></td></tr><tr><td></td><td>G</td><td>牛6</td><td></td><td></td><td></td></tr><tr><td>小学5-6年级</td><td>H</td><td></td><td></td><td>430L-530L</td><td></td></tr><tr><td></td><td>I</td><td>牛7</td><td>G2</td><td></td><td></td></tr><tr><td>初中1-2年级</td><td>J</td><td>牛8</td><td></td><td></td><td></td></tr><tr><td></td><td>K</td><td></td><td></td><td>520L-620L</td><td></td></tr><tr><td></td><td>L</td><td>牛9-10</td><td></td><td></td><td></td></tr><tr><td></td><td>M</td><td></td><td></td><td>530L-810L</td><td></td></tr><tr><td></td><td>N</td><td></td><td>- - -</td><td></td><td></td></tr><tr><td>初中3年级</td><td>O</td><td>牛11-12</td><td></td><td>600L-850L</td><td></td></tr><tr><td></td><td>P</td><td></td><td></td><td></td><td></td></tr></tbody></table><h2 id="桥梁书"><a href="#桥梁书" class="headerlink" title="桥梁书"></a>桥梁书</h2><table><thead><tr><th>序号</th><th>书名</th><th>Lexile</th><th>AR</th><th>是否已读</th></tr></thead><tbody><tr><td>1</td><td>Amelia Bedelia《糊涂女佣》</td><td>100L-570L</td><td>1.8-3.2</td><td></td></tr><tr><td>2</td><td>Nate The Great《大侦探内特》</td><td>230L-570L</td><td>2.0-3.2</td><td></td></tr><tr><td>3</td><td>Junie B. Jones《朱妮·琼斯》</td><td>250L-420L</td><td>2.6-3.1</td><td></td></tr><tr><td>4</td><td>Dog man《神探狗狗》</td><td>260L-540L</td><td>2.3-2.6</td><td></td></tr><tr><td>5</td><td>Fly Guy《苍蝇小子》</td><td>270L-470L</td><td>1.3-2.7</td><td>是</td></tr><tr><td>6</td><td>Fancy Nancy《漂亮的南希》</td><td>270L-540L</td><td></td><td></td></tr><tr><td>7</td><td>Winnie the Witch《女巫温妮》</td><td>330L-560L</td><td>2.3-3.0</td><td></td></tr><tr><td>8</td><td>Henry and Mudge《亨利和马奇》</td><td>370L-600L</td><td>2.1-2.9</td><td></td></tr><tr><td>9</td><td>Owl Diaries《猫头鹰日记》</td><td>370L-560L</td><td>2.5-3.1</td><td></td></tr><tr><td>10</td><td>Kung Pow Chicken《宫保鸡丁》</td><td>440L-520L</td><td></td><td></td></tr><tr><td>11</td><td>Mercy Watson《小猪梅西》</td><td>450L-550L</td><td>2.6-3.2</td><td></td></tr><tr><td>12</td><td>Monkey Me《猴子男孩》</td><td>490L-510L</td><td>2.2-2.5</td><td></td></tr><tr><td>13</td><td>Frog and Toad《青蛙和蟾蜍》</td><td>400L-490L</td><td>2.5-2.9</td><td></td></tr></tbody></table><h2 id="初章书"><a href="#初章书" class="headerlink" title="初章书"></a>初章书</h2><table><thead><tr><th>序号</th><th>书名</th><th>Lexile</th><th>AR</th><th>是否已读</th></tr></thead><tbody><tr><td>1</td><td>Marvin Redpost《麻烦精马文》</td><td>290L-460L</td><td>2.7-3.6</td><td></td></tr><tr><td>2</td><td>Roscoe Riley Rules《罗斯科莱利规则》</td><td>349L-490L</td><td>2.7-3.2</td><td></td></tr><tr><td>3</td><td>Horrid Henry《可怕的亨利》</td><td>320L-710L</td><td>3.1-3.8</td><td></td></tr><tr><td>4</td><td>Young Cam Jansen《照相机女孩》</td><td>330L-630L</td><td>3.2-3.9</td><td></td></tr><tr><td>5</td><td>Dragon Masters《神龙斗士》</td><td>360L-580L</td><td>3.1-3.5</td><td></td></tr><tr><td>6</td><td>The Zack Files《扎克档案》</td><td>370L-590L</td><td>2.7-3.9</td><td></td></tr><tr><td>7</td><td>Magic Tree House《神奇树屋系列(前28本)》</td><td>380L-580L</td><td>2.6-3.5</td><td></td></tr><tr><td>8</td><td>Stink Moody《斯丁莫迪》</td><td>380L-600L</td><td>3.0-3.6</td><td></td></tr><tr><td>9</td><td>The Secrets of Droon《地下王国历险》</td><td>380L-670L</td><td>2.9-4.4</td><td></td></tr><tr><td>10</td><td>Jigsaw Jones《小侦探琼斯》</td><td>390L-420L</td><td>2.9-3.1</td><td></td></tr><tr><td>11</td><td>Big Nate《捣蛋王》</td><td>400L-600L</td><td>3.1-3.4</td><td></td></tr><tr><td>12</td><td>Flat Stanley’s Worldwide Adventures《小扁人》</td><td>420L-750L</td><td>3.2-4.0</td><td></td></tr><tr><td>13</td><td>Captain Underpants《内裤队长》</td><td>420L-890L</td><td>2.2-5.4</td><td></td></tr><tr><td>14</td><td>The Boxcar Children《篷车少年》</td><td>430L-800L</td><td>2.9-5.0</td><td></td></tr><tr><td>15</td><td>Wayside School《歪歪小学》</td><td>440L-500L</td><td>3.3-3.4</td><td></td></tr><tr><td>16</td><td>Judy Moody《稀奇古怪小朱迪》</td><td>440L-600L</td><td>3.0-3.7</td><td></td></tr><tr><td>17</td><td>Geronimo Stilton《老鼠记者》</td><td>450L-700L</td><td>3.1-3.8</td><td></td></tr><tr><td>18</td><td>My Weird school《疯狂学校》</td><td>450L-770L</td><td>3.6-4.4</td><td></td></tr><tr><td>19</td><td>Ready,Freedy《弗雷迪》</td><td>460L-660L</td><td>3.1-3.4</td><td></td></tr><tr><td>20</td><td>Andrew Lost《微观世界儿童百科》</td><td>490L-600L</td><td>3.3-4.0</td><td></td></tr><tr><td>21</td><td>A to Z Mysteries《神秘案件》</td><td>490L-660L</td><td>3.2-4.0</td><td></td></tr><tr><td>22</td><td>Calendar Mysteries《日历神秘案件》</td><td>550L-650L</td><td>2.9-3.3</td><td></td></tr></tbody></table><h2 id="中章书"><a href="#中章书" class="headerlink" title="中章书"></a>中章书</h2><table><thead><tr><th>序号</th><th>书名</th><th>Lexile</th><th>AR</th><th>是否已读</th></tr></thead><tbody><tr><td>1</td><td>Merlin Missions《梅林的任务》(神奇树屋第二部，25本)</td><td>400L-700L</td><td>3.7-4.1</td><td></td></tr><tr><td>2</td><td>Goosebumps《鸡皮疙瘩》(7本)</td><td>450L-600L</td><td>3.7</td><td></td></tr><tr><td>3</td><td>Vet Volunteers《兽医志愿者》</td><td>510L-760L</td><td>3.6-4.6</td><td></td></tr><tr><td>4</td><td>The 39 Clues《39 条线索》(11本)</td><td>550L-730L</td><td>4.0-5.8</td><td></td></tr><tr><td>5</td><td>The Spiderwick Chronicles《奇幻精灵事件薄》(5本)</td><td>560L-690L</td><td>3.9-4.4</td><td></td></tr><tr><td>6</td><td>Nancy Drew : Girl Detective《少年侦探南茜•朱尔》(19本)</td><td>560L-820L</td><td>4.0-5.4</td><td></td></tr><tr><td>7</td><td>Percy Jackson and the Olympians《波西•杰克逊第一部》(5本)</td><td>590L-680L</td><td>4.1-5.6</td><td></td></tr><tr><td>8</td><td>Fantastic Mr Fox《了不起的狐狸爸爸》(罗德达尔系列)</td><td>600L</td><td>4.1</td><td></td></tr><tr><td>9</td><td>The Little House《小木屋的故事》(9本)</td><td>600L-700L</td><td></td><td></td></tr><tr><td>10</td><td>Tom Gates《了不起的小盖茨》(10 本，英国的小屁孩日记)</td><td>600L-720L</td><td>3.9-4.4</td><td></td></tr><tr><td>11</td><td>Children of the Red King《赤王的子民》(8本)</td><td>630L-770L</td><td>4.5-5.1</td><td></td></tr><tr><td>12</td><td>The Heroes of Olympus《奥林匹斯英雄》(波西杰克逊第二部)(5本)</td><td>640L-690L</td><td>4.5-5.2</td><td></td></tr><tr><td>13</td><td>Rainbow Magic《彩虹仙子》</td><td>640L-820L</td><td>3.3-3.9</td><td></td></tr><tr><td>14</td><td>Holes《别有洞天》(纽伯瑞金奖)</td><td>660L</td><td>4.6</td><td></td></tr><tr><td>15</td><td>The Famous Five《五伙伴历险记》(21本)</td><td>670L-700L</td><td></td><td></td></tr><tr><td>16</td><td>The Tale of Despereaux《浪漫鼠佩德罗》(纽伯瑞金奖)</td><td>670L</td><td></td><td></td></tr><tr><td>17</td><td>The Underland Chronicles Series《地底王国》(5本)</td><td>680L-730L</td><td>4.7-5.0</td><td></td></tr><tr><td>18</td><td>Charlotte’s Web、The Trumpet of the Swan、Stuart Little</td><td>680L-920L</td><td>4.4-6.0</td><td></td></tr><tr><td>19</td><td>The Miraculous Journey of Edward Tulane《爱德华的奇妙之旅》</td><td>700L</td><td>4.1</td><td></td></tr><tr><td>20</td><td>The Ramona Quimby《永远的雷蒙拉》</td><td>700L-850L</td><td></td><td></td></tr><tr><td>21</td><td>The Little Prince《小王子》</td><td>710L</td><td>5.0</td><td></td></tr><tr><td>22</td><td>The BFG《好心眼的巨人》(罗德达尔系列)</td><td>720L</td><td>4.8</td><td></td></tr><tr><td>23</td><td>The Genius Files《天才档案》(3本)</td><td>720L-750L</td><td>4.8-5.0</td><td></td></tr><tr><td>24</td><td>The land of stories《异世界童话》(6本)</td><td>720L-860L</td><td>5.0-6.1</td><td></td></tr><tr><td>25</td><td>Guardians of Ga,Hoole《猫头鹰王国的守护者》(10本)</td><td>730L-850L</td><td>4.8-5.6</td><td></td></tr><tr><td>26</td><td>A Wrinkle in Time《时间的皱纹》</td><td>740L</td><td></td><td></td></tr><tr><td>27</td><td>The Witches《女巫》(罗德达尔系列)</td><td>740L</td><td>4.7</td><td></td></tr><tr><td>28</td><td>Harriet the Spy《小间谍哈瑞特》</td><td>760L</td><td></td><td></td></tr><tr><td>29</td><td>The Sisters Grimm《格林姐妹》(9本)</td><td>780L-840L</td><td>4.5</td><td></td></tr><tr><td>30</td><td>The Cricket in Time Suare《时代广场的蟋蟀》(纽伯瑞大奖)</td><td>780L</td><td>4.9</td><td></td></tr><tr><td>31</td><td>The Penderwicks《夏天的故事》(6本)</td><td>800L-940L</td><td>4.7-5.6</td><td></td></tr><tr><td>32</td><td>Charlie and the Chocolate Factory《查理和巧克力工厂》(罗德达尔系列)</td><td>810L</td><td>4.8</td><td></td></tr><tr><td>33</td><td>Frindle《我们叫它粉灵豆》</td><td>830L</td><td></td><td></td></tr><tr><td>34</td><td>Allie Finkle’s Rules for Girls《女孩守则》(6本)</td><td>830L-860L</td><td>5.0</td><td></td></tr><tr><td>35</td><td>Matilda《玛蒂尔达》(罗德达尔系列)</td><td>840L</td><td>5.0</td><td></td></tr></tbody></table><h2 id="高章书"><a href="#高章书" class="headerlink" title="高章书"></a>高章书</h2><table><thead><tr><th>序号</th><th>书名</th><th>Lexile</th><th>AR</th><th>是否已读</th></tr></thead><tbody><tr><td>1</td><td>Harry Potter《哈利•波特系列》</td><td>500L-1230L</td><td>3.9-8.8</td><td></td></tr><tr><td>2</td><td>Artemis Fowl《阿特密斯奇幻历险》(8本)</td><td>600L-930L</td><td>5.0-6.6</td><td></td></tr><tr><td>3</td><td>Sammy Keyes《萨米凯斯系列》(10本,破案小说)</td><td>620L-860L</td><td>4.0-5.7</td><td></td></tr><tr><td>4</td><td>Alex Rider《少年 007》(间谍故事)(11本)</td><td>630L-780L</td><td>4.8-5.6</td><td></td></tr><tr><td>5</td><td>The House of the Scorpion《蝎子之屋》(纽伯瑞银奖)</td><td>660L</td><td>5.1</td><td></td></tr><tr><td>6</td><td>Dork Diaries Series《女版小屁孩日记》(13本)</td><td>660L-890L</td><td>4.2-5.4</td><td></td></tr><tr><td>7</td><td>Number the stars《数星星》(纽伯瑞金奖)</td><td>670L</td><td>4.5</td><td></td></tr><tr><td>8</td><td>Divergent Trilogy《分歧者三部曲》</td><td>700L-850L</td><td>4.8-5.8</td><td></td></tr><tr><td>9</td><td>The Inheritance Cycle《遗产四部曲》</td><td>710L-1050L</td><td>5.6-8.1</td><td></td></tr><tr><td>10</td><td>Esperanza Rising《风中玫瑰》(纽伯瑞奖)</td><td>750L</td><td>5.3</td><td></td></tr><tr><td>11</td><td>The Giver《记忆传授人》(纽伯瑞奖)</td><td>760L</td><td>5.3</td><td></td></tr><tr><td>12</td><td>Wolves Of The Beyond《绝境狼王》(5本)</td><td>770L-890L</td><td>5.1-6.2</td><td></td></tr><tr><td>13</td><td>The Indian in the Cupboard《魔柜小奇兵》</td><td>780L</td><td>4.6</td><td></td></tr><tr><td>14</td><td>The Chronicles of Namia《纳尼亚传奇》(7本)</td><td>790L-970L</td><td>2.8-5.9</td><td></td></tr><tr><td>15</td><td>Warriors《猫武士系列》(4 本)</td><td>790L-900L</td><td>5.4-6.3</td><td></td></tr><tr><td>16</td><td>The Dark is Rising sequence《黑暗崛起》(纽伯瑞奖5本)</td><td>800L-930L</td><td>5.3-6.2</td><td></td></tr><tr><td>17</td><td>The Hunger Games《饥饿游戏三部曲》</td><td>800L-820L</td><td>5.3</td><td></td></tr><tr><td>18</td><td>The Lord of the Rings《魔戒三部曲》</td><td>810L-920L</td><td>6.1-6.3</td><td></td></tr><tr><td>19</td><td>Julie of the Wolves《狼群中的朱莉》(纽伯瑞奖)</td><td>860L</td><td>5.8</td><td></td></tr><tr><td>20</td><td>TheWitch of Blackbird Pond《黑鸟水塘的女巫》(纽伯瑞奖)</td><td>850L</td><td>5.7</td><td></td></tr><tr><td>21</td><td>The Mysterious Benedict Society《天才神秘会社》</td><td>890L-900L</td><td>5.6-6.3</td><td></td></tr><tr><td>22</td><td>A Christmas Carol《圣诞颂歌》(狄更斯作品)</td><td>900L</td><td>6.7</td><td></td></tr><tr><td>23</td><td>Dear Mr. Henshaw《亲爱的汉修先生》(纽伯瑞奖)</td><td>910L</td><td>4.9</td><td></td></tr><tr><td>24</td><td>The Alchemist《牧羊少年奇幻之旅》</td><td>910L</td><td>6.4</td><td></td></tr><tr><td>25</td><td>Diary of Wimpy Kid《小屁孩日记》(13本)</td><td>910L-1060L</td><td>5.2-5.8</td><td></td></tr><tr><td>26</td><td>How to Train Your Dragon《驯龙高手》</td><td>910L-1070L</td><td>6.2-6.8</td><td></td></tr><tr><td>27</td><td>Daddy Long Legs《长腿叔叔》</td><td>920L</td><td>6.1</td><td></td></tr><tr><td>28</td><td>Everything on a Waffle《松饼屋的异想世界》(纽伯瑞奖)</td><td>950L</td><td>5.8</td><td></td></tr><tr><td>29</td><td>Hatchet series《手斧男孩》(纽伯瑞奖)(5本)</td><td>960L-1140L</td><td>5.5-5.9</td><td></td></tr><tr><td>30</td><td>The Secret Garden《秘密花园》</td><td>970L</td><td>6.8</td><td></td></tr><tr><td>31</td><td>A Series of Unfortunate Events《雷蒙•斯尼奇的不幸历险》</td><td>980L-1370L</td><td>6.2-7.4</td><td></td></tr><tr><td>32</td><td>Anne of Green Gables《绿山墙的安妮》</td><td>990L</td><td>7.3</td><td></td></tr><tr><td>33</td><td>Island of the blue dolphins《蓝色海豚岛》(纽伯瑞奖)</td><td>1000L</td><td>5.4</td><td></td></tr><tr><td>34</td><td>The Phantom Tollbooth《神奇的收费亭》</td><td>1000L</td><td>6.7</td><td></td></tr><tr><td>35</td><td>The Hobbit《霍比特人》</td><td>1000L</td><td>6.6</td><td></td></tr><tr><td>36</td><td>The Hitchhiker s Guide to the Galaxy《银河系漫游指南》</td><td>1000L</td><td>6.6</td><td></td></tr><tr><td>37</td><td>Black Beayty《黑骏马》</td><td>1010L</td><td></td><td></td></tr><tr><td>38</td><td>The Diary of a Young Girl《安妮日记》</td><td>1020L</td><td>6.5</td><td></td></tr><tr><td>39</td><td>The Mysterious Adventures of Sherlock Holmes《福尔摩斯探案集》(9本)</td><td>1090L</td><td>7.9</td><td></td></tr><tr><td>40</td><td>Around the World in Eighty Days《八十天环游地球》</td><td>1090L</td><td>9.6</td><td></td></tr></tbody></table>]]></content>
    
    
    <summary type="html">&lt;p&gt;整理一下互联网上推荐的英文原版书单，以及蓝思指数中外年级对照表，方便自家娃使用。俗话说得好，鸡娃不如鸡自己。好在娃对英语不反感，对好些英语绘本还挺喜欢的，希望能顺顺利利按照这个路子坚持下去。&lt;/p&gt;</summary>
    
    
    
    <category term="幼苗成长" scheme="https://www.xdxmblog.cn/categories/%E5%B9%BC%E8%8B%97%E6%88%90%E9%95%BF/"/>
    
    
    <category term="英语启蒙" scheme="https://www.xdxmblog.cn/tags/%E8%8B%B1%E8%AF%AD%E5%90%AF%E8%92%99/"/>
    
  </entry>
  
  <entry>
    <title>群晖NAS使用指北-Docker安装MySQL</title>
    <link href="https://www.xdxmblog.cn/posts/25614.html"/>
    <id>https://www.xdxmblog.cn/posts/25614.html</id>
    <published>2025-03-24T09:31:32.000Z</published>
    <updated>2025-03-24T09:31:32.000Z</updated>
    
    <content type="html"><![CDATA[<p>玩 NAS 有一段时间了，最近准备试试在 NAS 上面安装 MySQL 数据库，用来存放一些自己折腾的数据，由于对 Docker 搭建 MySQL 还不是特别熟悉，特通过此笔记进行记录，方便后续查看。</p><span id="more"></span><h2 id="安装教程"><a href="#安装教程" class="headerlink" title="安装教程"></a>安装教程</h2><h3 id="拉取映像"><a href="#拉取映像" class="headerlink" title="拉取映像"></a>拉取映像</h3><p>先使用<code>PuTTY</code>通过 ssh 连接 NAS，登录之后输入以下命令进行拉取 MySQL 的映像。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">docker pull dockerpull.cn/mysql:8.0.4<br></code></pre></td></tr></table></figure><p>如图所示，说明映像已经拉取成功，否则请检查 docker 的镜像是否可用，并替换<code>dockerpull.cn</code>字段重试。或者进行科学上网，使用官方镜像拉取。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_01.jpg" alt="docker拉取映像"></p><h3 id="安装映像"><a href="#安装映像" class="headerlink" title="安装映像"></a>安装映像</h3><p>在 docker 文件夹下创建 mysql 文件夹，并创建 conf、data、logs 三个子文件夹，用于存储配置文件、数据、错误日志。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_02.png" alt="创建文件夹"></p><p>右键我们创建的mysql文件夹，选择属性，选择权限，勾选应用到子文件夹。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_02_01.png" alt="设置文件夹权限"></p><p>双击 mysql 映像并启动，输入容器名称，并点击高级设置，勾选“启用自动重新启动”。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_03.png" alt="启动映像"></p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_04.jpg" alt="输入容器信息"></p><p>切换到存储空间，添加文件夹，选择上面创建的三个文件夹，并填写相应的装在路径。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_05.png" alt="添加文件夹"></p><p>切换到端口设置，填写本地端口（随便写，后面要用到），容器端口和类型默认即可。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_06.png" alt="填写端口"></p><p>切换到环境，添加一个变量<code>MYSQL_ROOT_PASSWORD</code>，值就是你的数据库密码，这一步很重要，否则后续会失败，无法进行连接。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_07.png" alt="配置密码"></p><h3 id="远程配置"><a href="#远程配置" class="headerlink" title="远程配置"></a>远程配置</h3><p>点击 docker 的容器选项，右键 mysql 容器，点击详情。接着点击终端机，点击新增，我们需要通过这种方式与容器进行连接。</p><p>输入以下代码来安装 vim：</p><figure class="highlight shell"><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><code class="hljs shell">apt-get update #同步最近的软件包列表到本地缓存<br>apt-get install net-tools #安装网络管理工具<br>apt-get install vim  #安装vim<br></code></pre></td></tr></table></figure><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_08.jpg" alt="安装vim"></p><p>安装完成之后输入以下命令来登录 mysql：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">mysql -u root -p<br></code></pre></td></tr></table></figure><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_09.jpg" alt="登录mysql"></p><p>输入密码（刚才在环境变量设置的密码）以后，授权用户从任何主机连接到 MySQL 服务器。</p><ul><li>grant：是 MySQL 的关键字，用于执行授权操作，给用户赋予特定的数据库权限。</li><li>all privileges：表示授予所有可用的权限，包括 SELECT、INSERT、UPDATE、DELETE、CREATE、DROP 等操作权限，涵盖了对数据库对象的各种操作能力。</li><li>on：第一个代表数据库名，这里的表示所有数据库；第二个代表表名，这里表示所有表。整体表示对所有数据库中的所有表进行授权。</li><li>to ‘root‘@’%’：root 是要授予权限的用户名，@是分隔符，用于分隔用户名和主机名。%表示允许该用户从任何主机连接到 MySQL 服务器。</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">grant all privileges on *.* to &#x27;root&#x27;@&#x27;%&#x27;;<br></code></pre></td></tr></table></figure><p>接着执行以下命令：</p><p>这一步通过查询资料得知是修改密码，但是不执行的话，用 native 无法连接数据库。待后续了解以后再做补充。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">ALTER USER &#x27;root&#x27;@&#x27;%&#x27; IDENTIFIED WITH mysql_native_password BY &#x27;密码&#x27;;<br></code></pre></td></tr></table></figure><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_25614_10.png" alt="通过native连接"></p><h3 id="常见错误"><a href="#常见错误" class="headerlink" title="常见错误"></a>常见错误</h3><p>Q:挂载的data文件夹一直是空的，容器重启后，数据全部消失。</p><p>A:检查文件的挂载路径是否跟本教程的图片内容一致，网上有的教程配置如下，下面的挂载路径是错的，虽然能正常运行，但数据只会存在容器内。</p><ul><li>&#x2F;logs</li><li>&#x2F;data</li><li>&#x2F;conf</li></ul><p>Q:在终端连接数据库报错：Can ‘t connect to local MySQL server through socket ‘&#x2F;tmp&#x2F;mysql.sock ‘(2) “;</p><p>A：文件夹权限问题，确保按步骤给docker文件夹下创建的mysql文件夹设置了足够的权限且应用于子文件和子文件夹。</p><p>Q:native连接报错：1045 - Access denied for user ‘root‘@’xxx.xxx.xxx.xxx’(using password:YES)</p><p>A:没执行文中远程授权的步骤。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总体来说，用 Docker 安装 MySQL 还是挺方便的，配置项也基本都是图文的方式，接下来准备复习一下 SQL 知识，尝试写一套 API。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;玩 NAS 有一段时间了，最近准备试试在 NAS 上面安装 MySQL 数据库，用来存放一些自己折腾的数据，由于对 Docker 搭建 MySQL 还不是特别熟悉，特通过此笔记进行记录，方便后续查看。&lt;/p&gt;</summary>
    
    
    
    <category term="折腾小记" scheme="https://www.xdxmblog.cn/categories/%E6%8A%98%E8%85%BE%E5%B0%8F%E8%AE%B0/"/>
    
    <category term="NAS" scheme="https://www.xdxmblog.cn/categories/%E6%8A%98%E8%85%BE%E5%B0%8F%E8%AE%B0/NAS/"/>
    
    
    <category term="MySQL" scheme="https://www.xdxmblog.cn/tags/MySQL/"/>
    
    <category term="NAS" scheme="https://www.xdxmblog.cn/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>手把手教你DIY一台属于自己的NAS——硬件篇</title>
    <link href="https://www.xdxmblog.cn/posts/21382.html"/>
    <id>https://www.xdxmblog.cn/posts/21382.html</id>
    <published>2025-03-08T13:50:02.000Z</published>
    <updated>2025-03-08T13:50:02.000Z</updated>
    
    <content type="html"><![CDATA[<p>自己DIY的家庭NAS已经稳定使用一年半了，最近又刷到了很多成品NAS的软广，感觉真的是把很多不懂数码的小白当韭菜割。所以我决定写一个系列性教程，供那些想要DIY但又不知如何操作的小伙伴参考，话不多说，开干！</p><span id="more"></span><h2 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h2><p>想要DIY一个自己的NAS,首先要对NAS有一个基本的了解。本质上，NAS就是一个电脑主机加一个特殊的系统。我们可以通过这套设备来实现在任意时间、任意地点来访问我们存储在这套设备里的内容，比如：相册、电影、电子书。甚至我们可以利用NAS来搭建自己的导航页、博客等等。</p><p>直接把家里的电脑改成NAS这显然是有点大材小用，一方面家里的电脑还有其他用处，改成NAS以后没办法日常办公娱乐，另一方面NAS的使用场景使得我们必须考虑这套设备的功耗来节约电费。</p><p>但我们完全仍然可以利用家里淘汰下来的旧电脑主机配件来作为DIY的原材料。</p><h3 id="定个小目标"><a href="#定个小目标" class="headerlink" title="定个小目标"></a>定个小目标</h3><p>我们希望DIY的这台NAS至少有以下几个功能：</p><ol><li>相册备份以及管理全家人的照片。</li><li>实现家庭影音库。</li><li>实现电子图书馆。</li><li>存储各种资料。</li><li>可以在手机端操作。</li><li>便捷的可拓展性，便于后续实现其他功能。</li></ol><h2 id="硬件配置清单"><a href="#硬件配置清单" class="headerlink" title="硬件配置清单"></a>硬件配置清单</h2><p>先放出我自己DIY的NAS硬件配置清单：</p><table><thead><tr><th>序号</th><th>零件</th><th>型号</th><th>2023年价格</th><th>2025年价格</th><th>购入渠道</th></tr></thead><tbody><tr><td>1</td><td>机箱（全新）</td><td>矩阵Z3 6盘位| Dataline 6盘位| 恒煜 6盘位</td><td>机箱电源打包350元</td><td>350元</td><td>闲鱼</td></tr><tr><td>2</td><td>电源（全新）</td><td>益衡7025b(250W)</td><td>机箱电源打包350元</td><td>80元</td><td>闲鱼</td></tr><tr><td>3</td><td>CPU（二手）</td><td>G4600</td><td>60元</td><td>30元</td><td>闲鱼</td></tr><tr><td>4</td><td>主板（二手）</td><td>技嘉b150m-pio-si</td><td>110元</td><td>95元</td><td>闲鱼</td></tr><tr><td>5</td><td>硬盘（全新）</td><td>海康&amp;西数联名款（WD42PURU-78) 4T</td><td>380元 &amp; 450元</td><td>400元 &amp; 400元</td><td>闲鱼&amp;京东自营</td></tr><tr><td>6</td><td>CPU风扇（全新）</td><td>115X超薄风扇</td><td>22元</td><td>20元</td><td>淘宝</td></tr><tr><td>7</td><td>内存（旧电脑拆卸）</td><td>金士顿DDR4 2400</td><td>60元</td><td>60元</td><td>京东自营</td></tr><tr><td>8</td><td>引导U盘（全新）</td><td>闪迪32G</td><td>15元</td><td>15元</td><td>京东自营</td></tr><tr><td></td><td></td><td><strong>总价</strong></td><td>1447元</td><td>1408元</td><td></td></tr></tbody></table><p>由于我们DIY的目标是普通家庭使用，所以万兆网卡，扩展卡，软路由等极客才折腾的东西不在我们的考虑范围之内。上述清单的硬盘是按照8T的大小配置的，如果用不了这么大的空间，只需要买一块即可。</p><h3 id="机箱"><a href="#机箱" class="headerlink" title="机箱"></a>机箱</h3><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_21382_01.jpg" alt="机箱正反面"></p><p>机箱我是淘了很久，选择了这款当时叫<strong>矩阵Z3 6盘位</strong>现在叫<strong>Dataline 6盘位</strong>或者<strong>恒煜 6盘位</strong>的机箱，选择它的原因有2点：</p><ol><li>这款机箱是6盘位，颜值也还可以，自带2个散热风扇，自带一个小型显示屏，能够显示机箱温度和风扇的转速，而且有一个磁吸的防尘亚克力前挡板。</li><li>这款机箱自带一个全新的益恒7025b电源，而一个全新的电源大概在150-200左右，算下来这个全新的机箱也才150元。</li></ol><p>在2023年，一款全新的6盘位定制机箱，价位基本都在500+以上，像星际蜗牛那种4盘位的又不太能入我的眼。而3D打印的机箱又没有硬盘架，且价格也较高。</p><p>这款机箱也有二手的，价格大概在200+，不过二手机箱后背板是没有像上图一样重新冲压的，只能装指定的某款主板才适配。如果想装其他主板，要自己进行切割才行。</p><p>不过经过两年的发展，现在很多3D打印的机箱卖的也很便宜，有些也挺好看的，这块根据自己的预算和需要选购即可。</p><p><strong>需要注意的点是这款机箱装完配件以后，内部空间比较小，走线需要多费心才行。</strong></p><h3 id="电源"><a href="#电源" class="headerlink" title="电源"></a>电源</h3><p>益恒7025b，可以说是小机箱或者NAS使用的标配了，1U的体积，铜牌80Plus认证，直出线缆，并且最大的亮点是静音。同时这款电源至少能支持8个盘位同时运行，转化率很高，也很省电。</p><h3 id="CPU"><a href="#CPU" class="headerlink" title="CPU"></a>CPU</h3><p>由于我们需要实现家庭影音库，安装独立显卡显然有点大材小用，所以CPU必须选择带核显的才能进行硬解码。考虑到价格，我这里主要选择了<code>G4600</code>和<code>i3 8100</code>两款CPU做对比。</p><p>这两款CPU都是3.60Hz，并且都不能超频，其次两款CPU的核显都是HD630，完全足够我们硬解使用，在单线程方面，两款CPU的分数差不多，差距主要体现在多线程上面，最终我选择了价格相对便宜的G4600。</p><p>有条件的且不太在乎电费的童鞋可以直接上i3 8100，不过买cpu一定问清楚是否带核显。</p><table><thead><tr><th>型号</th><th>G4600</th><th>i3 8100</th></tr></thead><tbody><tr><td>核心对比</td><td>2核心4线程</td><td>4核心4线程</td></tr><tr><td>单线程比较</td><td>2060分（98%）</td><td>2102分（100%）</td></tr><tr><td>多线程比较</td><td>5098分（62%）</td><td>8103分（100%）</td></tr><tr><td>TDP比较</td><td>51W（78%）</td><td>65W（100%）</td></tr></tbody></table><h3 id="主板"><a href="#主板" class="headerlink" title="主板"></a>主板</h3><p>由于NAS的兴起，很多主板的价格也水涨船高，并且6个STAT口的主板价格更是飙升，毕竟一个STAT转接器就要200元！所以最终我选择了<strong>技嘉b150m-pio-si</strong>，ITX板的大小能够完美适配我选择的机箱，并且这块主板自带6STAT口，跟机箱的6盘位也完美搭配。</p><p>不过这块主板需要一点点动手能力，<strong>需要刷BIOS才能支持7代CPU</strong>。选择这款主板可以加钱让卖家刷好bios，或者自己淘宝买一个编码器来线刷。</p><p><strong>特别注意：不要买技嘉b150m-pio-si-R3，跟技嘉b150m-pio-si是完全不同的两个主板！！</strong></p><h3 id="硬盘"><a href="#硬盘" class="headerlink" title="硬盘"></a>硬盘</h3><p>硬盘这里不推荐买二手的企业盘，都是坑！有钱上红盘，没钱就跟着买这种监控盘就OK了。</p><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><ul><li>CPU风扇，直接淘宝买115X超薄风扇，高度在28mm（2.8cm)以内都可以，最好选择铜芯的。</li><li>内存，金士顿DDR4 2400可以兼容，<strong>先锋DDR4 2400 不兼容！</strong>其他品牌未测试。</li><li>引导U盘主要用来安装NAS系统，这里8G就够用，我买的32G。</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>全部成本大概1500以内，就可以DIY一套带硬解，容量8T的NAS，如果只需要4T的容量，那整套硬件下来也才1000左右，还是极具性价比的。不过对于缺乏一定的动手能力的童鞋，最好是买品牌的成品NAS，当然价格就会高不少了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;自己DIY的家庭NAS已经稳定使用一年半了，最近又刷到了很多成品NAS的软广，感觉真的是把很多不懂数码的小白当韭菜割。所以我决定写一个系列性教程，供那些想要DIY但又不知如何操作的小伙伴参考，话不多说，开干！&lt;/p&gt;</summary>
    
    
    
    <category term="折腾小记" scheme="https://www.xdxmblog.cn/categories/%E6%8A%98%E8%85%BE%E5%B0%8F%E8%AE%B0/"/>
    
    <category term="NAS" scheme="https://www.xdxmblog.cn/categories/%E6%8A%98%E8%85%BE%E5%B0%8F%E8%AE%B0/NAS/"/>
    
    
    <category term="NAS" scheme="https://www.xdxmblog.cn/tags/NAS/"/>
    
  </entry>
  
  <entry>
    <title>慢读《魔兽世界·部落的崛起》</title>
    <link href="https://www.xdxmblog.cn/posts/10344.html"/>
    <id>https://www.xdxmblog.cn/posts/10344.html</id>
    <published>2025-02-24T06:04:28.000Z</published>
    <updated>2025-02-24T06:04:28.000Z</updated>
    
    <content type="html"><![CDATA[<p>权力、野心、堕落与救赎；文明的碰撞；个体与传统权威下的认知困境。花了四个晚上，一边看一边做笔记，终于看完了这本小说。小说通过霜狼氏族的酋长杜隆坦的成长经历为线索，讲述了兽人一族是如何在古尔丹的带领下走向腐化与堕落，最终沦为战争的机器：部落。</p><span id="more"></span><p><strong>权力与堕落</strong></p><p>“他的允诺十分诱人，令人激动，使人心生向往”面对萨格拉斯的允诺，艾瑞达的三位领袖做出了截然不同的选择：阿克蒙德和基尔加丹选择了宣誓效忠，得到了无尽的力量，可也变成了扭曲的怪物“曼阿瑞”。维纶选择了谨慎拒绝，拯救了部分艾瑞达人，却也因此遭到昔日好友基尔加丹的追杀，不得不在纳鲁的帮助下开启了逃亡之旅。</p><p><strong>文明与冲突</strong></p><p>兽人因为氏族隔阂一直以松散的氏族个体存在。在德莱尼人（维纶和他所拯救的人民）逃亡至此并定居的两百年间，两个种族保持着谨慎和微妙的平衡。</p><p>一次科什哈格节上，两个年幼的兽人——霜狼氏族的杜隆坦和黑石氏族的奥格瑞姆建立了兽人千百年来第一段跨越氏族的友情。</p><p>年幼的二人因为一次意外探险被德莱尼人瑞斯塔兰所救。在泰尔莫城，二人与维纶相遇，两种文明进行了短暂的交融，而这一经历也为后来的冲突埋下伏笔。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_10344_01.png" alt="部落的崛起人物势力图"></p><p><strong>野心与背叛</strong></p><p>“仇恨是强大的。仇恨可能是永无止境的。仇恨是可以被操纵利用的。同样，仇恨是可以被创造出来的。”在基尔加丹的诱惑下，耐奥祖以先祖之魂的名义编制谎言，配合古尔丹“自贬式表演”煽动群体情绪，两者利用兽人对荣誉的执念，将松散的兽人氏族进行统一。<strong>这种操纵也揭示了权力建构的共性：神话叙事与群体心理的精准把控。</strong></p><p>而在这场战争的发起过程中，<strong>兽人将德莱尼的善意误解为侵略预谋，也体现了文明碰撞中“他者恐惧”的普遍性，折射出人类历史上种族矛盾的典型模式。</strong></p><p>耐奥祖将德莱尼污名化为“灾厄源头”的手段，可谓与现实中极端民族主义的煽动策略如出一辙：<strong>当群体认同建立在对外敌的想象性仇恨上时，社会将滑向非理性深渊。</strong></p><p><strong>救赎、个体良知与集体狂热的对抗</strong></p><p>“我们都在某些方面有所欠缺，这与我们的种族无关，有时弱点其实是一种尚未表现出来的力量，有时弱点则会导致我们彻底毁灭，而有的时候两者皆有。智者了解他的缺点，而且会从中吸取教训；而愚蠢的人则会任由他的弱点控制并毁灭他。”</p><p>霜狼氏族酋长杜隆坦与其他少数人对这场战争的正当性进行了质疑，但最终被“大多数人的决定”所压制。</p><p>书中通过杜隆坦的内心独白——“没有资格质疑先祖的眼光”，刻画出了<strong>个体在传统权威下的认知困境</strong>。虽然身为霜狼氏族的酋长，但在大环境的裹挟下，也不得不做出有违内心的决定，这也直接导致泰尔莫城被屠戮一空，曾经的救命恩人瑞斯塔兰战死在自己面前。</p><p>同样的场景也出现耐奥祖身上，当得知古尔丹要求部落所有人喝下恶魔之血成为恶魔的奴隶时，他写了一封密信交给了杜隆坦，试图对自己之前的行为进行救赎。最终，杜隆坦所领导的霜狼氏族拒绝喝下恶魔之血，在古尔丹与麦迪文打开通往艾泽拉斯的传送门后，霜狼氏族被流放到了奥特兰克山脉。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;权力、野心、堕落与救赎；文明的碰撞；个体与传统权威下的认知困境。花了四个晚上，一边看一边做笔记，终于看完了这本小说。小说通过霜狼氏族的酋长杜隆坦的成长经历为线索，讲述了兽人一族是如何在古尔丹的带领下走向腐化与堕落，最终沦为战争的机器：部落。&lt;/p&gt;</summary>
    
    
    
    <category term="慢读" scheme="https://www.xdxmblog.cn/categories/%E6%85%A2%E8%AF%BB/"/>
    
    
    <category term="阅读" scheme="https://www.xdxmblog.cn/tags/%E9%98%85%E8%AF%BB/"/>
    
    <category term="慢生活" scheme="https://www.xdxmblog.cn/tags/%E6%85%A2%E7%94%9F%E6%B4%BB/"/>
    
  </entry>
  
  <entry>
    <title>Hexo + Butterfly 实现根据作者进行文章分类查询</title>
    <link href="https://www.xdxmblog.cn/posts/21905.html"/>
    <id>https://www.xdxmblog.cn/posts/21905.html</id>
    <published>2024-09-07T18:28:40.000Z</published>
    <updated>2024-09-07T18:28:40.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>由于hexo自身并未实现多作者，包括butterfly主题也没有相关方面的设置。所以一直也无法根据作者进行文章分类。</p><p>今天抽空看了看hexo的插件开发教程，简单写了一个插件，搭配任意主题都可以实现根据作者进行文章分类查询（多人博客）效果。</p><span id="more"></span><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>整个安装步骤分为三步：1.插件安装、2.主题文件修改、3.指定作者。</p><h3 id="插件安装"><a href="#插件安装" class="headerlink" title="插件安装"></a>插件安装</h3><p>在博客根目录打开<code>git bash</code>，输入以下命令安装<code>hexo-generator-author3</code>插件。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">npm install hexo-generator-author3 --save<br></code></pre></td></tr></table></figure><p>然后在博客的<code>source</code>目录下新建<code>authors</code>文件夹，进入<code>authors</code>文件，新建<code>index.md</code>文件并输入以下内容：</p><figure class="highlight markdown"><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><code class="hljs markdown">---<br>title: 作者<br>date: 2024-09-08 02:18:32<br>type: &#x27;authors&#x27;<br><span class="hljs-section">comments: false</span><br><span class="hljs-section">---</span><br></code></pre></td></tr></table></figure><h3 id="主题文件修改"><a href="#主题文件修改" class="headerlink" title="主题文件修改"></a>主题文件修改</h3><p>安装好插件以后，需要在对应的主题文件下面创建模板，便于根据模板生成对应的文件。</p><p>以butterfly主题为例：</p><p>在主题<code>themes\butterfly\scripts\helpers\</code>文件夹下，修改<code>page.js</code>文件，在文件末尾添加以下内容：</p><figure class="highlight javascript"><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><code class="hljs javascript">hexo.<span class="hljs-property">extend</span>.<span class="hljs-property">helper</span>.<span class="hljs-title function_">register</span>(<span class="hljs-string">&#x27;authors&#x27;</span>, <span class="hljs-keyword">function</span> (<span class="hljs-params">source = []</span>) &#123;<br>  <span class="hljs-keyword">let</span> result = <span class="hljs-string">&#x27;&#x27;</span><br>  <span class="hljs-keyword">let</span> authors = []<br>  <span class="hljs-keyword">if</span> (source.<span class="hljs-property">length</span>) &#123;<br>    source.<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">post</span> =&gt;</span> &#123;<br>      <span class="hljs-keyword">if</span> (post.<span class="hljs-property">author</span> &amp;&amp; !authors.<span class="hljs-title function_">includes</span>(post.<span class="hljs-property">author</span>)) &#123;<br>        authors.<span class="hljs-title function_">push</span>(post.<span class="hljs-property">author</span>)<br>      &#125;<br>    &#125;)<br>  &#125;<br>  <span class="hljs-keyword">if</span> (authors.<span class="hljs-property">length</span>) &#123;<br>    result += <span class="hljs-string">&#x27;&lt;ul class=&quot;category-list&quot;&gt;&#x27;</span><br>    authors.<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">author</span> =&gt;</span> &#123;<br>      <span class="hljs-keyword">let</span> count = source.<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">post</span> =&gt;</span> post.<span class="hljs-property">author</span> == author).<span class="hljs-property">length</span><br>      result += <span class="hljs-string">`&lt;li class=&quot;category-list-item&quot;&gt;&lt;a class=&quot;category-list-link&quot; href=&quot;/authors/<span class="hljs-subst">$&#123;author&#125;</span>/&quot;&gt;<span class="hljs-subst">$&#123;author&#125;</span>&lt;/a&gt;&lt;span class=&quot;category-list-count&quot;&gt;<span class="hljs-subst">$&#123;count&#125;</span>&lt;/span&gt;&lt;/li&gt;`</span><br>    &#125;)<br>  &#125;<br>  <span class="hljs-keyword">return</span> result<br>&#125;)<br></code></pre></td></tr></table></figure><p>在主题<code>themes\butterfly\layout\</code>文件夹下，修改<code>page.pug</code>文件，添加以下内容：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">extends</span> includes/layout.<span class="hljs-property">pug</span><br><br>block content<br>  #page<br>    <span class="hljs-keyword">if</span> top_img === <span class="hljs-literal">false</span><br>      h1.<span class="hljs-property">page</span>-title= page.<span class="hljs-property">title</span><br><br>    <span class="hljs-keyword">case</span> page.<span class="hljs-property">type</span><br>      when <span class="hljs-string">&#x27;tags&#x27;</span><br>        include includes/page/tags.<span class="hljs-property">pug</span><br>      when <span class="hljs-string">&#x27;link&#x27;</span><br>        include includes/page/flink.<span class="hljs-property">pug</span><br>      when <span class="hljs-string">&#x27;categories&#x27;</span><br>        include includes/page/categories.<span class="hljs-property">pug</span><br>      <span class="hljs-comment">//- 添加下面这句</span><br>      when <span class="hljs-string">&#x27;authors&#x27;</span><br>        include includes/page/authors.<span class="hljs-property">pug</span><br>      <span class="hljs-comment">//- 添加上面这句</span><br>      <span class="hljs-keyword">default</span><br>        include includes/page/<span class="hljs-keyword">default</span>-page.<span class="hljs-property">pug</span><br><br>    <span class="hljs-keyword">if</span> page.<span class="hljs-property">comments</span> !== <span class="hljs-literal">false</span> &amp;&amp; theme.<span class="hljs-property">comments</span> &amp;&amp; theme.<span class="hljs-property">comments</span>.<span class="hljs-property">use</span><br>      - <span class="hljs-keyword">var</span> commentsJsLoad = <span class="hljs-literal">true</span><br>      !=<span class="hljs-title function_">partial</span>(<span class="hljs-string">&#x27;includes/third-party/comments/index&#x27;</span>, &#123;&#125;, &#123;<span class="hljs-attr">cache</span>: <span class="hljs-literal">true</span>&#125;)<br></code></pre></td></tr></table></figure><p>在主题<code>themes\butterfly\layout\includes\page\</code>文件夹下，新建<code>authors.pug</code>文件并输入以下内容：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs javascript">.<span class="hljs-property">authors</span>-list<br>  !=<span class="hljs-title function_">authors</span>(site.<span class="hljs-property">posts</span>)<br></code></pre></td></tr></table></figure><p>在主题<code>themes\butterfly\source\css\_page\</code>文件夹下，修改<code>categories.styl</code>文件，第1行修改为以下内容：</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs css"><span class="hljs-selector-class">.category-lists</span>,<span class="hljs-selector-class">.authors-list</span><br></code></pre></td></tr></table></figure><h3 id="指定作者"><a href="#指定作者" class="headerlink" title="指定作者"></a>指定作者</h3><p>这一步就比较简单了，我们只需要在文章的信息里面，添加<code>author</code>字段，即可为该文章指定作者：</p><figure class="highlight markdown"><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><code class="hljs markdown">---<br>title: JavaScript this到底指向谁<br>author: 小呆<br>abbrlink: 45186<br>tags:<br><span class="hljs-bullet">  -</span> 前端面试<br>categories: JavaScript<br>date: 2023-04-07 17:36:44<br><span class="hljs-section">updated: 2023-04-07 17:36:44</span><br><span class="hljs-section">---</span><br></code></pre></td></tr></table></figure><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><p>在博客目录下，运行以下命令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">hexo clean &amp; hexo g &amp; hexo s<br></code></pre></td></tr></table></figure><p>打开<code>localhost:4000/authors/</code>，就能查看到效果了。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_21905_01.png" alt="效果图1"></p><p>点击不同的作者，就能查看到该作者下的全部文章，不同的主题展示效果有差异，非butterfly主题自行写对应的模板及css即可。</p><p><strong>PS:插件会默认根据<code>[&#39;author&#39;,&#39;archive&#39;,&#39;index&#39;]</code>的顺序查找对应模板，下图为<code>archive</code>的效果。</strong></p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_21905_02.png" alt="效果图2"></p><h2 id="美化"><a href="#美化" class="headerlink" title="美化"></a>美化</h2><p>同样以butterfly主题为例：</p><p>在主题<code>themes\butterfly\layout\</code>文件夹下，新建<code>author.pug</code>文件，并添加以下内容：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">extends</span> includes/layout.<span class="hljs-property">pug</span><br><br>block content<br>  <span class="hljs-keyword">if</span> theme.<span class="hljs-property">author_ui</span> == <span class="hljs-string">&#x27;index&#x27;</span><br>    include ./includes/mixins/post-ui.<span class="hljs-property">pug</span><br>    #recent-posts.<span class="hljs-property">recent</span>-posts.<span class="hljs-property">authors_ui</span><br>      +postUI<br>      include includes/pagination.<span class="hljs-property">pug</span><br>  <span class="hljs-keyword">else</span><br>    include ./includes/mixins/article-sort.<span class="hljs-property">pug</span><br>    #author<br>      .<span class="hljs-property">article</span>-sort-title= <span class="hljs-title function_">_p</span>(<span class="hljs-string">&#x27;page.authors&#x27;</span>) + <span class="hljs-string">&#x27; - &#x27;</span> + page.<span class="hljs-property">author</span><br>      +<span class="hljs-title function_">articleSort</span>(page.<span class="hljs-property">posts</span>)<br>      include includes/pagination.<span class="hljs-property">pug</span><br></code></pre></td></tr></table></figure><p>在主题<code>themes\butterfly\layout\includes\header\</code>文件夹下，修改<code>index.pug</code>文件，第22行添加以下内容：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">var</span> site_title = page.<span class="hljs-property">title</span> || page.<span class="hljs-property">tag</span> || page.<span class="hljs-property">category</span> || page.<span class="hljs-property">author</span> || config.<span class="hljs-property">title</span><br></code></pre></td></tr></table></figure><p>在主题<code>themes\butterfly\languages\</code>文件夹下，修改每个文件，添加对应的多语言文字：</p><figure class="highlight yaml"><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><code class="hljs yaml"><span class="hljs-comment"># default.yml  en.yml</span><br><span class="hljs-attr">page:</span><br>  <span class="hljs-attr">articles:</span> <span class="hljs-string">Articles</span><br>  <span class="hljs-attr">tag:</span> <span class="hljs-string">Tag</span><br>  <span class="hljs-attr">category:</span> <span class="hljs-string">Category</span><br>  <span class="hljs-attr">archives:</span> <span class="hljs-string">Archives</span><br>  <span class="hljs-attr">authors:</span> <span class="hljs-string">Authors</span><br><span class="hljs-comment"># zh-CN.yml</span><br><span class="hljs-attr">page:</span><br>  <span class="hljs-attr">articles:</span> <span class="hljs-string">文章总览</span><br>  <span class="hljs-attr">tag:</span> <span class="hljs-string">标签</span><br>  <span class="hljs-attr">category:</span> <span class="hljs-string">分类</span><br>  <span class="hljs-attr">archives:</span> <span class="hljs-string">归档</span><br>  <span class="hljs-attr">authors:</span> <span class="hljs-string">作者</span><br><span class="hljs-comment"># zh-TW.yml</span><br><span class="hljs-attr">page:</span><br>  <span class="hljs-attr">articles:</span> <span class="hljs-string">文章總覽</span><br>  <span class="hljs-attr">tag:</span> <span class="hljs-string">標籤</span><br>  <span class="hljs-attr">category:</span> <span class="hljs-string">分類</span><br>  <span class="hljs-attr">archives:</span> <span class="hljs-string">歸檔</span><br>  <span class="hljs-attr">authors:</span> <span class="hljs-string">作者</span><br></code></pre></td></tr></table></figure><p>重新执行测试命令，查看美化后的效果：</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_21905_03.png" alt="效果图3"></p><p>在主题配置文件<code>_config.butterfly.yml</code>中，还可以添加<code>author_ui</code>字段来决定是否使用主页模板来展示作者文章页。</p><p>若需使用主页模板，请使用<code>author_ui: index</code></p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">author_ui:</span> <span class="hljs-comment"># 留空或 index</span><br></code></pre></td></tr></table></figure><p>最后我们在文章页内添加作者的快捷跳转入口：</p><p>修改主题<code>themes\butterfly\layout\includes\header</code>文件夹下，<code>post-info.pug</code>文件，在第28行加入以下代码：</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-keyword">if</span> (page.<span class="hljs-property">author</span>)<br>        span.<span class="hljs-property">post</span>-meta-author<br>          span.<span class="hljs-property">post</span>-meta-separator |<br>          i.<span class="hljs-property">far</span>.<span class="hljs-property">fa</span>-solid.<span class="hljs-property">fa</span>-user-pen.<span class="hljs-property">post</span>-meta-icon<br>          span.<span class="hljs-property">post</span>-meta-label= <span class="hljs-title function_">_p</span>(<span class="hljs-string">&#x27;post.copyright.author&#x27;</span>)<br>          a.<span class="hljs-property">post</span>-meta-<span class="hljs-title function_">categories</span>(href=<span class="hljs-string">&quot;/authors&quot;</span> + <span class="hljs-title function_">url_for</span>(page.<span class="hljs-property">author</span>))= page.<span class="hljs-property">author</span><br></code></pre></td></tr></table></figure><p>修改主题<code>themes\butterfly\layout\includes\post</code>文件夹下，<code>post-copyright.pug</code>文件，修改第3行代码：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> authorHref = page.<span class="hljs-property">copyright_author_href</span> || <span class="hljs-string">&#x27;/authors/&#x27;</span> + author || theme.<span class="hljs-property">post_copyright</span>.<span class="hljs-property">author_href</span> || config.<span class="hljs-property">url</span><br></code></pre></td></tr></table></figure><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_21905_04.png" alt="效果图4"></p><p>这样就可以通过文章顶部的小标识直接跳转到作者栏目下。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>总体实现步骤不难，主要是美化过程中主题文件比较分散，需要美化的小点比较多。但最终实现了多作者的展示效果，第一次写hexo的插件，感觉还是挺简单的。</p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;由于hexo自身并未实现多作者，包括butterfly主题也没有相关方面的设置。所以一直也无法根据作者进行文章分类。&lt;/p&gt;
&lt;p&gt;今天抽空看了看hexo的插件开发教程，简单写了一个插件，搭配任意主题都可以实现根据作者进行文章分类查询（多人博客）效果。&lt;/p&gt;</summary>
    
    
    
    <category term="前端积累" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/"/>
    
    <category term="Hexo" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/Hexo/"/>
    
    
    <category term="Hexo" scheme="https://www.xdxmblog.cn/tags/Hexo/"/>
    
  </entry>
  
  <entry>
    <title>Kindle 换美区超详细教程</title>
    <link href="https://www.xdxmblog.cn/posts/29034.html"/>
    <id>https://www.xdxmblog.cn/posts/29034.html</id>
    <published>2024-06-27T01:50:40.000Z</published>
    <updated>2024-06-27T01:50:40.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>根据亚马逊官方消息，kindle中国运营在2024年6月30日将全面停止运营！</p><ol><li>6月30日后，将无法继续从应用商店下载Kindle APP，以后kindle APP仅能阅读已经下载的电子书。</li><li>云端服务也将全面停止所有云端内容必须在6月30日之前下载，云端内容包含电子书及个人文档。</li><li>Send to Kindle也将于6月30日关闭，包含浏览器传送、邮箱传送等所有无线传输路径，以后传输只能数据线传输。</li></ol><p>但是，这能难倒我们国人吗？既然中国区停了，那我们换个正常运营的区是不是就行了！</p><p>于是在即将停服的前3天，通过查询资料，加上自己亲自动手，Kindle换美区成功，完美迁移，继续邮箱传书！</p><span id="more"></span><h2 id="换区教程"><a href="#换区教程" class="headerlink" title="换区教程"></a>换区教程</h2><h3 id="第一步、备份当前Kindle图书数据"><a href="#第一步、备份当前Kindle图书数据" class="headerlink" title="第一步、备份当前Kindle图书数据"></a>第一步、备份当前Kindle图书数据</h3><ol><li>用数据线把kindle和电脑连接，打开我的电脑，找到<code>Kindle</code>磁盘（连接数据线时要解锁Kindle)。</li><li>找到<code>documents</code>文件夹，直接将整个文件夹复制一份到电脑桌面。（这个文件夹包含了电子书、浏览记录、摘要笔记等内容）</li></ol><h3 id="第二步、注册亚马逊美区账号"><a href="#第二步、注册亚马逊美区账号" class="headerlink" title="第二步、注册亚马逊美区账号"></a>第二步、注册亚马逊美区账号</h3><ol><li>首先进入亚马逊美区（注意结尾是.com不是.cn）<a href="https://www.amazon.com/">https://www.amazon.com/</a></li><li>注册美区账号（注意换个邮箱，不要用亚区的邮箱注册，163，QQ等常用邮箱都可以。</li><li>注册完成后登录，点击内容和设备，这时都是空的，不用管。</li></ol><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_29034_01.webp" alt="注册亚马逊美区账号"></p><h3 id="第三步、注销亚区账号，登录美区账号"><a href="#第三步、注销亚区账号，登录美区账号" class="headerlink" title="第三步、注销亚区账号，登录美区账号"></a>第三步、注销亚区账号，登录美区账号</h3><p>这一步有两种方式，两种方式的操作只有一个区别就是是否重置设备。（初步推测是有些人美区和亚区用了同一个邮箱注册，当用同一个邮箱时，Kindle无法识别出是亚区账号还是美区账号，导致换区不成功）</p><p>我因为亚区和美区用了2个不同的账号，所以第一次没有重置，亲测也换区成功了。然后本着好奇的心态，又试了一遍重置的流程，也换区成功！</p><p>第一种（无需重置法）：</p><ol><li>打开Kindle，点击右上角三个点，选择设置，您的账户，注销设备。</li><li>注销结束后，在kindle选择登录新账户，这里用第二步注册好的美区账号登录。</li><li>进入到设置，您的账户，查看【发送至Kindle】电子邮件地址是否变为<code>.com</code>结尾，进入主页查看商城是否推荐一些英文书籍。都有则换区成功！</li></ol><p>第二种（需重置法）：</p><ol><li><p>打开Kindle，点击右上角三个点，选择设置，您的账户，注销设备。</p></li><li><p>在您的账户这个选项页面中，点击右上角三个点，选择重置。</p></li><li><p>等设备重启后，会进入选择语言界面，选择English，下一步选择United States。</p></li><li><p>如果进入set up your kindle 页面，选择set up on this kindle，没有忽视（我没有这一步）。</p></li><li><p>然后会让你连WIFI，连好之后登录刚刚注册好的美区账号。</p></li><li><p>如果进入verify Account页面，打开电脑浏览器，输入第一个网址:<code>www.amazon.com/code</code>（没登陆的话登录一下，会提示注册您的设备，输入代码）。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_29034_02.webp" alt="重置步骤截图"></p></li><li><p>点击kindle的create new code，将生成的code输入到刚刚的网址里。</p></li><li><p>提示登录成功，点下一步，进入make this Kindle kid-friendly页面，选择no thanks（不需要儿童模式）</p></li><li><p>进入goodreads页面，选no thanks（不需要开通图书分享社交网站）</p></li><li><p>换区成功，点击右上角三个点，setting，language&amp;dictionaries，language，简体中文。</p></li><li><p>等待重启，就会切换回中文模式。</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_29034_03.web" alt="重置步骤截图"></p></li></ol><h2 id="邮箱传书教程"><a href="#邮箱传书教程" class="headerlink" title="邮箱传书教程"></a>邮箱传书教程</h2><p>邮箱传书的过程比较简单，这里简单介绍一下步骤。</p><ol><li><p>登录美区亚马逊，鼠标移动到账户及心愿单，点击内容和设备。</p></li><li><p>点击首选项，点击个人文档设置。</p></li><li><p>这里会看到【发送至kindle电子邮箱】仍然是<code>.cn</code>结尾（因为我们选了简体中文，所以会显示.cn，无视即可）重点是只要你的kindle里的【发送至kindle电子邮箱】是<code>.com</code>结尾即可。</p></li><li><p>添加认可的电子邮箱，也就是白名单，用来给kindle发送邮件。<br><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_29034_04.jpg" alt="点击首选项"><br><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_29034_05.jpg" alt="添加认可的电子邮箱"></p></li></ol><p>至此，就可以继续用邮箱传书给Kindle了。</p><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><p>本文内容参考了以下文章及文档，感谢！感兴趣的同学可以进行阅读！</p><p><a href="https://www.xiaohongshu.com/explore/6543162200000000230389b2?xsec_token=ABCvZM5bvQasEfFLTUT4mhqDEYhdNZiG_Ka2ArsQeMGtE=&xsec_source=pc_search">Kindle国区换美区其实很简单，可以先收藏 作者：阅读困难症</a></p>]]></content>
    
    
    <summary type="html">&lt;h2 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;根据亚马逊官方消息，kindle中国运营在2024年6月30日将全面停止运营！&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;6月30日后，将无法继续从应用商店下载Kindle APP，以后kindle APP仅能阅读已经下载的电子书。&lt;/li&gt;
&lt;li&gt;云端服务也将全面停止所有云端内容必须在6月30日之前下载，云端内容包含电子书及个人文档。&lt;/li&gt;
&lt;li&gt;Send to Kindle也将于6月30日关闭，包含浏览器传送、邮箱传送等所有无线传输路径，以后传输只能数据线传输。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但是，这能难倒我们国人吗？既然中国区停了，那我们换个正常运营的区是不是就行了！&lt;/p&gt;
&lt;p&gt;于是在即将停服的前3天，通过查询资料，加上自己亲自动手，Kindle换美区成功，完美迁移，继续邮箱传书！&lt;/p&gt;</summary>
    
    
    
    <category term="折腾小记" scheme="https://www.xdxmblog.cn/categories/%E6%8A%98%E8%85%BE%E5%B0%8F%E8%AE%B0/"/>
    
    
    <category term="kindle" scheme="https://www.xdxmblog.cn/tags/kindle/"/>
    
  </entry>
  
  <entry>
    <title>剑指Offer(06)从头到尾打印链表</title>
    <link href="https://www.xdxmblog.cn/posts/10125.html"/>
    <id>https://www.xdxmblog.cn/posts/10125.html</id>
    <published>2023-07-17T05:30:20.000Z</published>
    <updated>2023-07-17T05:30:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天要练习的题目是：剑指 Offer 的第 6 题，从头到尾打印链表</p><h2 id="题目要求"><a href="#题目要求" class="headerlink" title="题目要求"></a>题目要求</h2><blockquote><p>输入一个链表的头节点，从尾到头反过来返回每个节点的值（用数组返回）。</p></blockquote><span id="more"></span><p>示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">输入：head = [1,3,2]<br>输出：[2,3,1]<br></code></pre></td></tr></table></figure><p>提示：</p><ul><li><code>0 &lt;= 链表长度 &lt;= 10000</code></li></ul><h2 id="解题思路"><a href="#解题思路" class="headerlink" title="解题思路"></a>解题思路</h2><p>首先这道题的输入是从头到位，但是输出却是从尾到头。也就是<code>FILO</code><strong>先进后出</strong>，这妥妥的就是<strong>栈结构</strong>嘛。所以我们可以遍历一遍链表，然后将每一项都存入数组中，最后反转数组即可。</p><h3 id="栈结构"><a href="#栈结构" class="headerlink" title="栈结构"></a>栈结构</h3><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * Definition for singly-linked list.</span><br><span class="hljs-comment"> * function ListNode(val) &#123;</span><br><span class="hljs-comment"> *     this.val = val;</span><br><span class="hljs-comment"> *     this.next = null;</span><br><span class="hljs-comment"> * &#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">ListNode</span>&#125; <span class="hljs-variable">head</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">number[]</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> reversePrint = <span class="hljs-keyword">function</span> (<span class="hljs-params">head</span>) &#123;<br>  <span class="hljs-keyword">let</span> arr = [],<br>    node = head,<br>    ans = []<br>  <span class="hljs-keyword">while</span> (node !== <span class="hljs-literal">null</span>) &#123;<br>    arr.<span class="hljs-title function_">push</span>(node.<span class="hljs-property">val</span>)<br>    node = node.<span class="hljs-property">next</span><br>  &#125;<br><br>  <span class="hljs-comment">/* 不用API反转数组</span><br><span class="hljs-comment">    for(let i = arr.length - 1; i &gt;= 0; i--) &#123;</span><br><span class="hljs-comment">      ans.push(arr[i])</span><br><span class="hljs-comment">    &#125;</span><br><span class="hljs-comment">  return ans</span><br><span class="hljs-comment">  */</span><br>  <span class="hljs-keyword">return</span> arr.<span class="hljs-title function_">reverse</span>() <span class="hljs-comment">// 利用API反转数组</span><br>&#125;<br></code></pre></td></tr></table></figure><p>当然，利用<code>API</code>的话，我们还有更加简洁的写法，毕竟 JavaScript 无所不能~（偷笑）</p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * Definition for singly-linked list.</span><br><span class="hljs-comment"> * function ListNode(val) &#123;</span><br><span class="hljs-comment"> *     this.val = val;</span><br><span class="hljs-comment"> *     this.next = null;</span><br><span class="hljs-comment"> * &#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">ListNode</span>&#125; <span class="hljs-variable">head</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">number[]</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> reversePrint = <span class="hljs-keyword">function</span> (<span class="hljs-params">head</span>) &#123;<br>  <span class="hljs-keyword">let</span> ans = [],<br>    node = head<br>  <span class="hljs-keyword">while</span> (node !== <span class="hljs-literal">null</span>) &#123;<br>    ans.<span class="hljs-title function_">unshift</span>(node.<span class="hljs-property">val</span>)<br>    node = node.<span class="hljs-property">next</span><br>  &#125;<br>  <span class="hljs-keyword">return</span> ans<br>&#125;<br></code></pre></td></tr></table></figure><p>既然用到了栈结构，那小呆很自然也能想到使用递归，毕竟递归本质上就是一种栈结构。</p><h3 id="递归算法"><a href="#递归算法" class="headerlink" title="递归算法"></a>递归算法</h3><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * Definition for singly-linked list.</span><br><span class="hljs-comment"> * function ListNode(val) &#123;</span><br><span class="hljs-comment"> *     this.val = val;</span><br><span class="hljs-comment"> *     this.next = null;</span><br><span class="hljs-comment"> * &#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">ListNode</span>&#125; <span class="hljs-variable">head</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">number[]</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> reversePrint = <span class="hljs-keyword">function</span> (<span class="hljs-params">head</span>) &#123;<br>  <span class="hljs-keyword">if</span> (head === <span class="hljs-literal">null</span>) <span class="hljs-keyword">return</span> []<br>  <span class="hljs-keyword">const</span> ans = <span class="hljs-title function_">reversePrint</span>(head.<span class="hljs-property">next</span>)<br>  ans.<span class="hljs-title function_">push</span>(head.<span class="hljs-property">val</span>)<br>  <span class="hljs-keyword">return</span> ans<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>这道题其实并不难，只要能想到栈结构，基本思路就出来了。当然也可以先把链表反转，然后在从头到尾顺序添加到数组中即可，但是如果面试题中不允许修改链表的话，这种方法就不太适合了。具体情况根据面试题要求选择对应解法即可。</p><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><p>本文内容参考了以下书籍，感兴趣的同学可以购买正版图书进行阅读。</p><p>《剑指 Offer 第 2 版》——作者：何海涛</p><p><a href="https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/">剑指 Offer 的第 6 题-从头到尾打印链表</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;今天要练习的题目是：剑指 Offer 的第 6 题，从头到尾打印链表&lt;/p&gt;
&lt;h2 id=&quot;题目要求&quot;&gt;&lt;a href=&quot;#题目要求&quot; class=&quot;headerlink&quot; title=&quot;题目要求&quot;&gt;&lt;/a&gt;题目要求&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;输入一个链表的头节点，从尾到头反过来返回每个节点的值（用数组返回）。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="前端积累" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/"/>
    
    <category term="算法练习" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/%E7%AE%97%E6%B3%95%E7%BB%83%E4%B9%A0/"/>
    
    
    <category term="递归算法" scheme="https://www.xdxmblog.cn/tags/%E9%80%92%E5%BD%92%E7%AE%97%E6%B3%95/"/>
    
    <category term="栈结构" scheme="https://www.xdxmblog.cn/tags/%E6%A0%88%E7%BB%93%E6%9E%84/"/>
    
  </entry>
  
  <entry>
    <title>剑指Offer(05)替换空格</title>
    <link href="https://www.xdxmblog.cn/posts/50720.html"/>
    <id>https://www.xdxmblog.cn/posts/50720.html</id>
    <published>2023-07-13T05:30:06.000Z</published>
    <updated>2023-07-13T05:30:06.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天要练习的题目是：剑指 Offer 的第 5 题，替换空格</p><h2 id="题目要求"><a href="#题目要求" class="headerlink" title="题目要求"></a>题目要求</h2><blockquote><p>请实现一个函数，把字符串<code>s</code>中的每个空格替换成”%20”。</p></blockquote><span id="more"></span><p>示例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs plaintext">输入：s = &quot;We are happy.&quot;<br>输出：&quot;We%20are%20happy.&quot;<br></code></pre></td></tr></table></figure><p>提示：</p><ul><li><code>0 &lt;= s 的长度 &lt;= 10000</code></li></ul><h2 id="解题思路"><a href="#解题思路" class="headerlink" title="解题思路"></a>解题思路</h2><p>这道题其实还是蛮简单的一道题，看过题目后小呆至少能想到三种方法：正则、分割、遍历。</p><h3 id="正则"><a href="#正则" class="headerlink" title="正则"></a>正则</h3><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">string</span>&#125; <span class="hljs-variable">s</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">string</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> replaceSpace = <span class="hljs-keyword">function</span> (<span class="hljs-params">s</span>) &#123;<br>  <span class="hljs-keyword">return</span> s.<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/\s/g</span>, <span class="hljs-string">&#x27;%20&#x27;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="分割"><a href="#分割" class="headerlink" title="分割"></a>分割</h3><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">string</span>&#125; <span class="hljs-variable">s</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">string</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> replaceSpace = <span class="hljs-keyword">function</span> (<span class="hljs-params">s</span>) &#123;<br>  <span class="hljs-keyword">return</span> s.<span class="hljs-title function_">split</span>(<span class="hljs-string">&#x27; &#x27;</span>).<span class="hljs-title function_">join</span>(<span class="hljs-string">&#x27;%20&#x27;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h3 id="遍历-字符串拼接"><a href="#遍历-字符串拼接" class="headerlink" title="遍历+字符串拼接"></a>遍历+字符串拼接</h3><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">string</span>&#125; <span class="hljs-variable">s</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">string</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> replaceSpace = <span class="hljs-keyword">function</span> (<span class="hljs-params">s</span>) &#123;<br>  <span class="hljs-keyword">let</span> ans = <span class="hljs-string">&#x27;&#x27;</span><br>  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; s.<span class="hljs-property">length</span>; i++) &#123;<br>    <span class="hljs-keyword">if</span> (s[i] === <span class="hljs-string">&#x27; &#x27;</span>) &#123;<br>      ans += <span class="hljs-string">&#x27;%20&#x27;</span><br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>      ans += s[i]<br>    &#125;<br>  &#125;<br>  <span class="hljs-keyword">return</span> ans<br>&#125;<br></code></pre></td></tr></table></figure><p>在《剑指 Offer 第 2 版》一书中，作者给出了另一种实现思路。通过计算空格的数量，对字符串的长度进行扩充，然后利用双指针去替换其中的空格。当然因为在 JavaScript 中，字符串无法直接扩充长度，所以还是要进行转换操作，这里我们只学习其中的思路即可。</p><p>老规矩，动态辅助理解：</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_50720_01.gif" alt="替换空格-双指针算法"></p><h3 id="双指针"><a href="#双指针" class="headerlink" title="双指针"></a>双指针</h3><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">string</span>&#125; <span class="hljs-variable">s</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">string</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> replaceSpace = <span class="hljs-keyword">function</span> (<span class="hljs-params">s</span>) &#123;<br>  <span class="hljs-keyword">let</span> strArr = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>(s)<br>  <span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span><br><br>  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; strArr.<span class="hljs-property">length</span>; i++) &#123;<br>    <span class="hljs-keyword">if</span> (strArr[i] === <span class="hljs-string">&#x27; &#x27;</span>) &#123;<br>      count++<br>    &#125;<br>  &#125;<br><br>  <span class="hljs-keyword">let</span> left = strArr.<span class="hljs-property">length</span> - <span class="hljs-number">1</span><br>  <span class="hljs-keyword">let</span> right = strArr.<span class="hljs-property">length</span> + count * <span class="hljs-number">2</span> - <span class="hljs-number">1</span><br><br>  <span class="hljs-keyword">while</span> (left &gt;= <span class="hljs-number">0</span>) &#123;<br>    <span class="hljs-keyword">if</span> (strArr[left] === <span class="hljs-string">&#x27; &#x27;</span>) &#123;<br>      strArr[right--] = <span class="hljs-string">&#x27;0&#x27;</span><br>      strArr[right--] = <span class="hljs-string">&#x27;2&#x27;</span><br>      strArr[right--] = <span class="hljs-string">&#x27;%&#x27;</span><br>      left--<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>      strArr[right--] = strArr[left--]<br>    &#125;<br>  &#125;<br>  <span class="hljs-keyword">return</span> strArr.<span class="hljs-title function_">join</span>(<span class="hljs-string">&#x27;&#x27;</span>)<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>单从解题来看，其实利用现成的 API 或者遍历，要比双指针算法简单的多。并且由于 JavaScript 中字符串无法扩展长度，还是要转成数组进行操作，最终仍然要通过<code>Array.join()</code>方法再转换成字符串。但是双指针的这种思路，仍然值得我们去学习。</p><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><p>本文内容参考了以下书籍，感兴趣的同学可以购买正版图书进行阅读。</p><p>《剑指 Offer 第 2 版》——作者：何海涛</p><p><a href="https://leetcode.cn/problems/ti-huan-kong-ge-lcof/">剑指 Offer 的第 5 题-替换空格</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;今天要练习的题目是：剑指 Offer 的第 5 题，替换空格&lt;/p&gt;
&lt;h2 id=&quot;题目要求&quot;&gt;&lt;a href=&quot;#题目要求&quot; class=&quot;headerlink&quot; title=&quot;题目要求&quot;&gt;&lt;/a&gt;题目要求&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;请实现一个函数，把字符串&lt;code&gt;s&lt;/code&gt;中的每个空格替换成”%20”。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="前端积累" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/"/>
    
    <category term="算法练习" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/%E7%AE%97%E6%B3%95%E7%BB%83%E4%B9%A0/"/>
    
    
    <category term="双指针算法" scheme="https://www.xdxmblog.cn/tags/%E5%8F%8C%E6%8C%87%E9%92%88%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>剑指Offer(04)二维数组中的查找</title>
    <link href="https://www.xdxmblog.cn/posts/8122.html"/>
    <id>https://www.xdxmblog.cn/posts/8122.html</id>
    <published>2023-07-08T05:29:44.000Z</published>
    <updated>2023-07-08T05:29:44.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天要练习的题目是：剑指 Offer 的第 4 题，二维数组中的查找</p><h2 id="题目要求"><a href="#题目要求" class="headerlink" title="题目要求"></a>题目要求</h2><blockquote><p>在一个<code>n * m</code>的二维数组中，每一行都按照从左到右<strong>非递减</strong>的顺序排序，每一列都按照从上到下<strong>非递减</strong>的顺序排序。请完成一个高效的函数，输入这样的一个二维数组和一个整数，判断数组中是否含有该整数。</p><p>来源：力扣（LeetCode）<br>链接：<br>著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。</p></blockquote><span id="more"></span><p>示例：</p><p>现有矩阵 matrix 如下：</p><figure class="highlight plaintext"><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><code class="hljs plaintext">[<br>  [1,   4,  7, 11, 15],<br>  [2,   5,  8, 12, 19],<br>  [3,   6,  9, 16, 22],<br>  [10, 13, 14, 17, 24],<br>  [18, 21, 23, 26, 30]<br>]<br></code></pre></td></tr></table></figure><p>给定 target &#x3D; <code>5</code>，返回<code>true</code>。</p><p>给定 target &#x3D; <code>20</code>，返回<code>false</code>。</p><p>提示：</p><ul><li><code>0 &lt;= n &lt;= 1000</code></li><li><code>0 &lt;= m &lt;= 1000</code></li></ul><h2 id="解题思路"><a href="#解题思路" class="headerlink" title="解题思路"></a>解题思路</h2><p>这道题小呆的思路是先找出题中的二维数组规律。然后进行分析：因为这个二维数组每一行从左到右递增，每一列从上到下递增。所以我们能够得出：</p><ol><li>某列的某个数字，该数字上方的数字都比它小。</li><li>某列的某个数字，该数字右侧的数字都比它小。</li></ol><p>所以我们可以将这个二维数组的左下角定为原点，建立一个直角坐标轴。如果原点的数字比要查询的数字大，就往上移一位；如果原点的数字比要查询的数字小，就往右移一位。</p><p>话不多说，上图：</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_8122_01.gif" alt="二维数组中额查找-坐标轴法"></p><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">number[][]</span>&#125; <span class="hljs-variable">matrix</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">number</span>&#125; <span class="hljs-variable">target</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">boolean</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> findNumberIn2DArray = <span class="hljs-keyword">function</span> (<span class="hljs-params">matrix, target</span>) &#123;<br>  <span class="hljs-keyword">let</span> x = matrix.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>,<br>    y = <span class="hljs-number">0</span><br>  <span class="hljs-keyword">while</span> (x &gt;= <span class="hljs-number">0</span> &amp;&amp; y &lt; matrix[<span class="hljs-number">0</span>].<span class="hljs-property">length</span>) &#123;<br>    <span class="hljs-keyword">if</span> (matrix[x][y] === target) &#123;<br>      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span><br>    &#125;<br>    <span class="hljs-keyword">if</span> (matrix[x][y] &gt; target) &#123;<br>      x--<br>    &#125; <span class="hljs-keyword">else</span> &#123;<br>      y++<br>    &#125;<br>  &#125;<br>  <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span><br>&#125;<br></code></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>做了这么多天题，其实小呆发现解题的一个非常重要的点就是：<strong>要善于发现题目所给的规律，运用恰当的计算机逻辑、数学知识等条件</strong>。写不出来没关系，但是思维模式很重要。不要气馁，凤凰涅槃，浴火重生，我们也要有不屈不挠的坚强的意志。加油！</p><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><p>本文内容参考了以下书籍，感兴趣的同学可以购买正版图书进行阅读。</p><p>《剑指 Offer 第 2 版》——作者：何海涛</p><p><a href="https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof">剑指 Offer 的第 4 题-二维数组中的查找</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;今天要练习的题目是：剑指 Offer 的第 4 题，二维数组中的查找&lt;/p&gt;
&lt;h2 id=&quot;题目要求&quot;&gt;&lt;a href=&quot;#题目要求&quot; class=&quot;headerlink&quot; title=&quot;题目要求&quot;&gt;&lt;/a&gt;题目要求&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;在一个&lt;code&gt;n * m&lt;/code&gt;的二维数组中，每一行都按照从左到右&lt;strong&gt;非递减&lt;/strong&gt;的顺序排序，每一列都按照从上到下&lt;strong&gt;非递减&lt;/strong&gt;的顺序排序。请完成一个高效的函数，输入这样的一个二维数组和一个整数，判断数组中是否含有该整数。&lt;/p&gt;
&lt;p&gt;来源：力扣（LeetCode）&lt;br&gt;链接：&lt;br&gt;著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="前端积累" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/"/>
    
    <category term="算法练习" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/%E7%AE%97%E6%B3%95%E7%BB%83%E4%B9%A0/"/>
    
    
    <category term="坐标轴法" scheme="https://www.xdxmblog.cn/tags/%E5%9D%90%E6%A0%87%E8%BD%B4%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>剑指Offer(03)数组中重复的数字</title>
    <link href="https://www.xdxmblog.cn/posts/18093.html"/>
    <id>https://www.xdxmblog.cn/posts/18093.html</id>
    <published>2023-07-01T03:33:12.000Z</published>
    <updated>2023-07-01T03:33:12.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天要练习的题目是：剑指 Offer 的第 3 题，数组中重复的数字</p><h2 id="题目要求"><a href="#题目要求" class="headerlink" title="题目要求"></a>题目要求</h2><blockquote><p>找出数组中重复的数字。</p><p>在一个长度为<code>n</code>的数组<code>nums</code>里的所有数字都在<code>0～n-1</code>的范围内。数组中某些数字是重复的，但不知道有几个数字重复了，也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。</p></blockquote><span id="more"></span><p>示例：</p><figure class="highlight plaintext"><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><code class="hljs plaintext">输入：<br>[2, 3, 1, 0, 2, 5, 3]<br>输出：2 或 3<br></code></pre></td></tr></table></figure><p>提示：</p><ul><li><code>2 &lt;= n &lt;= 100000</code></li></ul><h2 id="解题思路"><a href="#解题思路" class="headerlink" title="解题思路"></a>解题思路</h2><p>这道题的常规解法，就是利用哈希表，遍历数组，判断哈希表中是否包含当前值，如果包含，当前项就是重复项。这种在数组中查找重复的数字的题型，基本都可以用哈希表去解决，它的时间复杂度为<code>O(n)</code>，空间复杂度为<code>O(n)</code>。</p><h3 id="哈希表"><a href="#哈希表" class="headerlink" title="哈希表"></a>哈希表</h3><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">number[]</span>&#125; <span class="hljs-variable">nums</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">number</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> findRepeatNumber = <span class="hljs-keyword">function</span> (<span class="hljs-params">nums</span>) &#123;<br>  <span class="hljs-keyword">let</span> hasMap = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Set</span>()<br>  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> num <span class="hljs-keyword">of</span> nums) &#123;<br>    <span class="hljs-keyword">if</span> (hasSet.<span class="hljs-title function_">has</span>(num)) <span class="hljs-keyword">return</span> num<br>    hasMap.<span class="hljs-title function_">add</span>(num)<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>当然我们也可以先将数组进行排序，排序后的数组，重复的两个数字一定相邻。</p><h3 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h3><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">number[]</span>&#125; <span class="hljs-variable">nums</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">number</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> findRepeatNumber = <span class="hljs-keyword">function</span> (<span class="hljs-params">nums</span>) &#123;<br>  nums.<span class="hljs-title function_">sort</span>(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> a - b)<br>  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; nums.<span class="hljs-property">length</span>; i++) &#123;<br>    <span class="hljs-keyword">if</span> (nums[i] === nums[i + <span class="hljs-number">1</span>]) &#123;<br>      <span class="hljs-keyword">return</span> nums[i]<br>    &#125;<br>  &#125;<br>  <span class="hljs-keyword">return</span> -<span class="hljs-number">1</span><br>&#125;<br></code></pre></td></tr></table></figure><p>在《剑指 Offer 第 2 版》中，作者给出了另一种思路，是小呆之前没有想到过的。因为<code>nums</code>里的所有数字都在<code>0 ~ n - 1</code>的范围呢，那不重复的数字排序后，和下标应该是一一对应的。所以遍历时当遍历到下标为<code>i</code>的数字时，首先比较这个数字（用<code>m</code>表示）是不是等于<code>i</code>。如果是，则接着遍历下一个数字；如果不是，则再拿它和第<code>m</code>个数字进行比较。如果它和第<code>m</code>个数字相等，就找到了一个重复的数字；如果它和第<code>m</code>数字不相等，就把第<code>i</code>个数字和第<code>m</code>个数字交换，把<code>m</code>放到属于它的位置。</p><p>老规矩，配合动图去理解更佳：</p><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_18093_01.gif" alt="数组中重复的数字"></p><h3 id="原地交换"><a href="#原地交换" class="headerlink" title="原地交换"></a>原地交换</h3><figure class="highlight javascript"><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><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">number[]</span>&#125; <span class="hljs-variable">nums</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">number</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> findRepeatNumber = <span class="hljs-keyword">function</span> (<span class="hljs-params">nums</span>) &#123;<br>  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; nums.<span class="hljs-property">length</span>; i++) &#123;<br>    <span class="hljs-keyword">while</span> (nums[i] !== i) &#123;<br>      <span class="hljs-keyword">if</span> (nums[i] === nums[nums[i]]) &#123;<br>        <span class="hljs-keyword">return</span> nums[i]<br>      &#125;<br>      <span class="hljs-keyword">let</span> temp = nums[i]<br>      ;[nums[temp], nums[i]] = [nums[i], nums[temp]]<br>    &#125;<br>  &#125;<br>  <span class="hljs-keyword">return</span> -<span class="hljs-number">1</span><br>&#125;<br></code></pre></td></tr></table></figure><p>注意在使用解构<code>[nums[temp], nums[i]] = [nums[i], nums[temp]]</code>前，声明临时变量时，末尾要加<code>;</code>，否则解构语句会报错。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>数组查找重复的数字，这种题型还是比较常见也是比较简单的，变种题型还会有查找数组中重复最多的数字，或者重复最多的次数。其实只要有了思路，基本都能写出来。当然《剑指 Offer 第 2 版》这本书中的思路也是非常能开拓视野的，感兴趣的小伙伴可以买来看看，书中的代码并非是 JavaScript，主要是开拓思路。</p><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><p>本文内容参考了以下书籍，感兴趣的同学可以购买正版图书进行阅读。</p><p>《剑指 Offer 第 2 版》——作者：何海涛</p><p><a href="https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof">剑指 Offer 的第 3 题-数组中重复的数字</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;今天要练习的题目是：剑指 Offer 的第 3 题，数组中重复的数字&lt;/p&gt;
&lt;h2 id=&quot;题目要求&quot;&gt;&lt;a href=&quot;#题目要求&quot; class=&quot;headerlink&quot; title=&quot;题目要求&quot;&gt;&lt;/a&gt;题目要求&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;找出数组中重复的数字。&lt;/p&gt;
&lt;p&gt;在一个长度为&lt;code&gt;n&lt;/code&gt;的数组&lt;code&gt;nums&lt;/code&gt;里的所有数字都在&lt;code&gt;0～n-1&lt;/code&gt;的范围内。数组中某些数字是重复的，但不知道有几个数字重复了，也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="前端积累" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/"/>
    
    <category term="算法练习" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/%E7%AE%97%E6%B3%95%E7%BB%83%E4%B9%A0/"/>
    
    
    <category term="哈希表" scheme="https://www.xdxmblog.cn/tags/%E5%93%88%E5%B8%8C%E8%A1%A8/"/>
    
    <category term="排序算法" scheme="https://www.xdxmblog.cn/tags/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>LeetCode(118)杨辉三角</title>
    <link href="https://www.xdxmblog.cn/posts/24191.html"/>
    <id>https://www.xdxmblog.cn/posts/24191.html</id>
    <published>2023-06-26T07:19:00.000Z</published>
    <updated>2023-06-26T07:19:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>今天要练习的题目是：力扣（LeetCode)的第 118 题，杨辉三角</p><h2 id="题目要求"><a href="#题目要求" class="headerlink" title="题目要求"></a>题目要求</h2><blockquote><p>给定一个非负整数*<code>numRows</code>，<em>生成「杨辉三角」的前</em><code>numRows</code>*行。</p><p>在「杨辉三角」中，每个数是它左上方和右上方的数的和。</p></blockquote><span id="more"></span><p><img src= "/img/loading.gif" data-lazy-src="https://img.xdxmblog.cn/images/article_24191_01.gif" alt="杨辉三角"></p><p>示例：</p><figure class="highlight plaintext"><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><code class="hljs plaintext">输入: numRows = 5<br>输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]<br><br>输入: numRows = 1<br>输出: [[1]]<br></code></pre></td></tr></table></figure><p>提示：</p><ul><li><code>1 &lt;= numRows &lt;= 30</code></li></ul><h2 id="解题思路"><a href="#解题思路" class="headerlink" title="解题思路"></a>解题思路</h2><p>这道题其实官方已经给了非常容易理解的规律和图示：<strong>每个数是它左上方和右上方的数的和</strong>。</p><p>我们可以用一个二维数组来描述示例 1 的数据：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-keyword">let</span> list = [[<span class="hljs-number">1</span>], [<span class="hljs-number">1</span>, <span class="hljs-number">1</span>], [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">1</span>], [<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">3</span>, <span class="hljs-number">1</span>], [<span class="hljs-number">1</span>, <span class="hljs-number">4</span>, <span class="hljs-number">6</span>, <span class="hljs-number">4</span>, <span class="hljs-number">1</span>]]<br></code></pre></td></tr></table></figure><p>根据上面给出的规律能够得出, 首尾的数字一定是 1, 其次从第三行开始，除去首尾，其他的数字都是它左上方和右上方的数的和，所以我们可以做出以下公式：（用<code>i</code>表示行，用<code>j</code>表示列）</p><ol><li><code>list[i][0] = 1</code> 每行的第一个数字是 1</li><li><code>list[i][j] = list[i - 1][j - 1] + list[i - 1][j]</code> 当前行的第<code>j</code>个数等于上一行的<code>j - 1</code> + 上一行的<code>j</code></li><li><code>list[i][i] = 1</code> 每行的最后一个数字是 1</li></ol><p>不难看出，这道题其实用动态规划就可以解决:（本题动态 gif 图直接看上面给的官方图即可）</p><figure class="highlight javascript"><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></pre></td><td class="code"><pre><code class="hljs javascript"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * <span class="hljs-doctag">@param</span> &#123;<span class="hljs-type">number</span>&#125; <span class="hljs-variable">numRows</span></span><br><span class="hljs-comment"> * <span class="hljs-doctag">@return</span> &#123;<span class="hljs-type">number[][]</span>&#125;</span><br><span class="hljs-comment"> */</span><br><span class="hljs-keyword">var</span> generate = <span class="hljs-keyword">function</span> (<span class="hljs-params">numRows</span>) &#123;<br>  <span class="hljs-keyword">let</span> ans = []<br>  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; numRows; i++) &#123;<br>    ans[i] = [<span class="hljs-number">1</span>] <span class="hljs-comment">// 每一行的第一个数都是1</span><br>    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> j = <span class="hljs-number">1</span>; j &lt; i; j++) &#123;<br>      ans[i][j] = ans[i - <span class="hljs-number">1</span>][j - <span class="hljs-number">1</span>] + ans[i - <span class="hljs-number">1</span>][j] <span class="hljs-comment">// 当前行的第j个数等于上一行的j - 1 + 上一行的j</span><br>    &#125;<br>    ans[i][i] = <span class="hljs-number">1</span> <span class="hljs-comment">// 每一行的最后一个数都是1</span><br>  &#125;<br>  <span class="hljs-keyword">return</span> ans<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>刷题也有一段时间了，目前大部分题目都是刷的 LeetCode 热门 100 题，目前来看，简单难度的题目仔细思考还是能做出来的，但是看了几道中等难度的题目，基本上没思路的居多。只能先把同类型的简单题目多练习练习再去尝试中等难度的题目了。加油吧！</p><h2 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h2><p><a href="https://leetcode.cn/problems/pascals-triangle/">力扣 LeetCode 的第 118 题-杨辉三角</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;今天要练习的题目是：力扣（LeetCode)的第 118 题，杨辉三角&lt;/p&gt;
&lt;h2 id=&quot;题目要求&quot;&gt;&lt;a href=&quot;#题目要求&quot; class=&quot;headerlink&quot; title=&quot;题目要求&quot;&gt;&lt;/a&gt;题目要求&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;给定一个非负整数*&lt;code&gt;numRows&lt;/code&gt;，&lt;em&gt;生成「杨辉三角」的前&lt;/em&gt;&lt;code&gt;numRows&lt;/code&gt;*行。&lt;/p&gt;
&lt;p&gt;在「杨辉三角」中，每个数是它左上方和右上方的数的和。&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="前端积累" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/"/>
    
    <category term="算法练习" scheme="https://www.xdxmblog.cn/categories/%E5%89%8D%E7%AB%AF%E7%A7%AF%E7%B4%AF/%E7%AE%97%E6%B3%95%E7%BB%83%E4%B9%A0/"/>
    
    
    <category term="动态规划" scheme="https://www.xdxmblog.cn/tags/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
    
  </entry>
  
</feed>
