|
|
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}.code-container:hover .copy-btn,.highlight:hover .copy-btn{opacity:1}.code-container{position:relative}.copy-btn{color:#333;cursor:pointer;line-height:1.6;opacity:0;padding:2px 6px;position:absolute;transition:opacity .2s ease-in-out;background-color:#eee;background-image:linear-gradient(#fcfcfc,#eee);border:1px solid #d5d5d5;border-radius:3px;font-size:.8125em;right:4px;top:8px}code,figure.highlight,kbd,pre{background:var(--highlight-background);color:var(--highlight-foreground)}figure.highlight,pre{line-height:1.6;margin:0 auto 20px}figure.highlight figcaption,pre .caption,pre figcaption{background:var(--highlight-gutter-background);color:var(--highlight-foreground);display:flow-root;font-size:.875em;line-height:1.2;padding:.5em}figure.highlight figcaption a,pre .caption a,pre figcaption a{color:var(--highlight-foreground);float:right}figure.highlight figcaption a:hover,pre .caption a:hover,pre figcaption a:hover{border-bottom-color:var(--highlight-foreground)}code,pre{font-family:consolas,Menlo,monospace,'PingFang SC','Microsoft YaHei'}code{border-radius:3px;font-size:.875em;padding:2px 4px;overflow-wrap:break-word}kbd{border:2px solid #ccc;border-radius:.2em;box-shadow:.1em .1em .2em rgba(0,0,0,.1);font-family:inherit;padding:.1em .3em;white-space:nowrap}figure.highlight{overflow:auto;position:relative}figure.highlight pre{border:0;margin:0;padding:10px 0}figure.highlight table{border:0;margin:0;width:auto}figure.highlight td{border:0;padding:0}figure.highlight .gutter{-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}figure.highlight .gutter pre{background:var(--highlight-gutter-background);color:var(--highlight-gutter-foreground);padding-left:10px;padding-right:10px;text-align:right}figure.highlight .code pre{padding-left:10px;width:100%}figure.highlight .marked{background:rgba(0,0,0,.3)}pre .caption,pre figcaption{margin-bottom:10px}.gist table{width:auto}.gist table td{border:0}pre code{background:0 0;padding:0;text-shadow:none}.blockquote-center{border-left:0;margin:40px 0;padding:0;position:relative;text-align:center}.blockquote-center::after,.blockquote-center::before{left:0;line-height:1;opacity:.6;position:absolute;width:100%}.blockquote-center::before{border-top:1px solid #ccc;text-align:left;top:-20px;content:'\f10d';font-family:'Font Awesome 6 Free';font-weight:900}.blockquote-center::after{border-bottom:1px solid #ccc;bottom:-20px;text-align:right;content:'\f10e';font-family:'Font Awesome 6 Free';font-weight:900}.blockquote-center div,.blockquote-center p{text-align:center}.group-picture{margin-bottom:20px}.group-picture .group-picture-row{display:flex;gap:3px;margin-bottom:3px}.group-picture .group-picture-column{flex:1}.group-picture .group-picture-column img{height:100%;margin:0;object-fit:cover;width:100%}.post-body .label{color:#555;padding:0 2px}.post-body .label.default{background:#f0f0f0}.post-body .label.primary{background:#efe6f7}.post-body .label.info{background:#e5f2f8}.post-body .label.success{background:#e7f4e9}.post-body .label.warning{background:#fcf6e1}.post-body .label.danger{background:#fae8eb}.post-body .link-grid{display:grid;grid-gap:1.5rem;gap:1.5rem;grid-template-columns:1fr 1fr;margin-bottom:20px;padding:1rem}.post-body .link-grid .link-grid-container{border:solid #ddd;box-shadow:1rem 1rem .5rem rgba(0,0,0,.5);min-height:5rem;min-width:0;padding:.5rem;position:relative;transition:background .3s}.post-body .link-grid .link-grid-container:hover{animation:.5s next-shake;background:var(--card-bg-color)}.post-body .link-grid .link-grid-container:active{box-shadow:.5rem .5rem .25rem rgba(0,0,0,.5);transform:translate(.2rem,.2rem)}.post-body .link-grid .link-grid-container .link-grid-image{border:1px solid #ddd;border-radius:50%;box-sizing:border-box;height:5rem;padding:3px;position:absolute;width:5rem}.post-body .link-grid .link-grid-container p{margin:0 1rem 0 6rem}.post-body .link-grid .link-grid-container p:first-of-type{font-size:1.2em}.post-body .link-grid .link-grid-container p:last-of-type{font-size:.8em;line-height:1.3rem;opacity:.7}.post-body .link-grid .link-grid-container a{border:0;height:100%;left:0;position:absolute;top:0;width:100%}@keyframes next-shake{0%{transform:translate(1pt,1pt) rotate(0)}10%{transform:translate(-1pt,-2pt) rotate(-1deg)}20%{transform:translate(-3pt,0) rotate(1deg)}30%{transform:translate(3pt,2pt) rotate(0)}40%{transform:translate(1pt,-1pt) rotate(1deg)}50%{transform:translate(-1pt,2pt) rotate(-1deg)}60%{transform:translate(-3pt,1pt) rotate(0)}70%{transform:translate(3pt,1pt) rotate(-1deg)}80%{transform:translate(-1pt,-1pt) rotate(1deg)}90%{transform:translate(1pt,2pt) rotate(0)}100%{transform:translate(1pt,-2pt) rotate(-1deg)}}.post-body .note{border-radius:3px;margin-bottom:20px;padding:1em;position:relative;border:1px solid #eee;border-left-width:5px}.post-body .note summary{cursor:pointer;outline:0}.post-body .note summary p{display:inline}.post-body .note h2,.post-body .note h3,.post-body .note h4,.post-body .note h5,.post-body .note h6{border-bottom:initial;margin:0;padding-top:0}.post-body .note :first-child{margin-top:0}.post-body .note :last-child{margin-bottom:0}.post-body .note.default{border-left-color:#777}.post-body .note.default h2,.post-body .note.default h3,.post-body .note.default h4,.post-body .note.default h5,.post-body .note.default h6{color:#777}.post-body .note.primary{border-left-color:#6f42c1}.post-body .note.primary h2,.post-body .note.primary h3,.post-body .note.primary h4,.post-body .note.primary h5,.post-body .note.primary h6{color:#6f42c1}.post-body .note.info{border-left-color:#428bca}.post-body .note.info h2,.post-body .note.info h3,.post-body .note.info h4,.post-body .note.info h5,.post-body .note.info h6{color:#428bca}.post-body .note.success{border-left-color:#5cb85c}.post-body .note.success h2,.post-body .note.success h3,.post-body .note.success h4,.post-body .note.success h5,.post-body .note.success h6{color:#5cb85c}.post-body .note.warning{border-left-color:#f0ad4e}.post-body .note.warning h2,.post-body .note.warning h3,.post-body .note.warning h4,.post-body .note.warning h5,.post-body .note.warning h6{color:#f0ad4e}.post-body .note.danger{border-left-color:#d9534f}.post-body .note.danger h2,.post-body .note.danger h3,.post-body .note.danger h4,.post-body .note.danger h5,.post-body .note.danger h6{color:#d9534f}.post-body .tabs{margin-bottom:20px}.post-body .tabs,.tabs-comment{padding-top:10px}.post-body .tabs ul.nav-tabs,.tabs-comment ul.nav-tabs{background:var(--content-bg-color);display:flex;display:flex;flex-wrap:wrap;justify-content:center;margin:0;padding:0;position:-webkit-sticky;position:sticky;top:0;z-index:5}.post-body .tabs ul.nav-tabs li.tab,.tabs-comment ul.nav-tabs li.tab{border-bottom:1px solid #ddd;border-left:1px solid transparent;border-right:1px solid transparent;border-radius:0;border-top:3px solid transparent;flex-grow:1;list-style-type:none}@media (max-width:413px){.post-body .tabs ul.nav-tabs,.tabs-comment ul.nav-tabs{display:block;margin-bottom:5px}.post-body .tabs ul.nav-tabs li.tab,.tabs-comment ul.nav-tabs li.tab{border-bottom:1px solid transparent;border-left:3px solid transparent;border-right:1px solid transparent;border-top:1px solid transparent;border-radius:0}}.post-body .tabs ul.nav-tabs li.tab a,.tabs-comment ul.nav-tabs li.tab a{border-bottom:initial;display:block;line-height:1.8;padding:.25em .75em;text-align:center;transition:.2s ease-out}.post-body .tabs ul.nav-tabs li.tab a i[class^=fa],.tabs-comment ul.nav-tabs li.tab a i[class^=fa]{width:1.285714285714286em}.post-body .tabs ul.nav-tabs li.tab.active,.tabs-comment ul.nav-tabs li.tab.active{border-color:#fc6423 #ddd transparent}@media (max-width:413px){.post-body .tabs ul.nav-tabs li.tab.active,.tabs-comment ul.nav-tabs li.tab.active{border-color:#ddd #ddd #ddd #fc6423}}.post-body .tabs ul.nav-tabs li.tab.active a,.tabs-comment ul.nav-tabs li.tab.active a{cursor:default}.post-body .tabs .tab-content,.tabs-comment .tab-content{border:1px solid #ddd;border-radius:0;border-top-color:transparent}@media (max-width:413px){.post-body .tabs .tab-content,.tabs-comment .tab-content{border-radius:0;border-top-color:#ddd}}.post-body .tabs .tab-content .tab-pane,.tabs-comment .tab-content .tab-pane{padding:20px 20px 0}.post-body .tabs .tab-content .tab-pane:not(.active),.tabs-comment .tab-content .tab-pane:not(.active){display:none}.pagination .next,.pagination .page-number,.pagination .prev,.pagination .space{display:inline-block;margin:-1px 10px 0;padding:0 10px}.algolia-pagination .current .page-number,.pagination .page-number.current{background:#ccc;border-color:#ccc;color:var(--content-bg-color)}.pagination{border-top:1px solid #eee;margin:120px 0 0;text-align:center}.pagination .next,.pagination .page-number,.pagination .prev{border-bottom:0;border-top:1px solid #eee;transition:border-color .2s ease-in-out}.pagination .next:hover,.pagination .page-number:hover,.pagination .prev:hover{border-top-color:var(--link-hover-color)}@media (max-width:767px){.post-body .link-grid{grid-template-columns:1fr}.pagination .next,.pagination .page-number,.pagination .prev,.pagination .space{margin:0 5px}.pagination{border-top:0}.pagination .next,.pagination .page-number,.pagination .prev{border-bottom:1px solid #eee;border-top:0}.pagination .next:hover,.pagination .page-number:hover,.pagination .prev:hover{border-bottom-color:var(--link-hover-color)}.site-meta{text-align:center}}.pagination .space{margin:0;padding:0}.comments{margin-top:60px;overflow:hidden}.comment-button-group{display:flex;display:flex;flex-wrap:wrap;justify-content:center;justify-content:center;margin:1em 0}.comment-button-group .comment-button{margin:.1em .2em}.comment-button-group .comment-button.active{background:var(--btn-default-hover-bg);border-color:var(--btn-default-hover-border-color);color:var(--btn-default-hover-color)}.comment-position{display:none}.comment-position.active{display:block}.tabs-comment{margin-top:4em;padding-top:0}.tabs-comment .comments{margin-top:0;padding-top:0}.headband{background:var(--theme-color);height:3px}@media (max-width:991px){.headband{display:none}}.site-brand-container{display:flex;flex-shrink:0;padding:0 10px}.use-motion .column,.use-motion .site-brand-container .toggle{opacity:0}.site-meta{flex-grow:1;text-align:center}.custom-logo-image{margin-top:20px}@media (max-width:991px){.custom-logo-image{display:none}}.brand{border-bottom:0;color:var(--brand-color);display:inline-block;padding:0}.brand:hover{color:var(--brand-hover-color)}.site-title{font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;font-size:1.375em;font-weight:400;line-height:1.5;margin:0}.site-subtitle{color:#ddd;font-size:.8125em;margin:10px 10px 0}.use-motion .custom-logo-image,.use-motion .site-subtitle,.use-motion .site-title{opacity:0;position:relative;top:-10px}.site-nav-right,.site-nav-toggle{display:none}.site-nav-right .toggle,.site-nav-toggle .toggle{color:var(--text-color);padding:10px;width:22px}.site-nav-right .toggle .toggle-line,.site-nav-toggle .toggle .toggle-line{background:var(--text-color);border-radius:1px}@media (max-width:767px){.site-nav-right,.site-nav-toggle{display:flex;flex-direction:column;justify-content:center}.site-nav{--scroll-height:0;height:0;overflow:hidden;transition:height .2s ease-in-out,visibility .2s ease-in-out;visibility:hidden}body:not(.site-nav-on) .site-nav .animated{animation:none}body.site-nav-on .site-nav{height:var(--scroll-height);visibility:unset}}.menu{margin:0;padding:1em 0;text-align:center}.menu-item{display:inline-block;list-style:none;margin:0 10px}@media (max-width:767px){.menu-item{display:block;margin-top:10px}.menu-item.menu-item-search{display:none}}.menu-item a{border-bottom:0;display:block;font-size:.8125em;transition:border-color .2s ease-in-out}.menu-item a.menu-item-active,.menu-item a:hover{background:var(--menu-item-bg-color)}.menu-item i[class^=fa]{margin-right:8px}.menu-item .badge{display:inline-block;font-weight:700;line-height:1;margin-left:.35em;margin-top:.35em;text-align:center;white-space:nowrap}.use-motion .menu-item{visibility:hidden}.github-corner :hover .octo-arm{animation:560ms ease-in-out octocat-wave}.github-corner svg{color:#fff;fill:var(--theme-color);position:absolute;right:0;top:0;z-index:5}@media (max-width:991px){.github-corner{display:none}.github-corner svg{color:var(--theme-color);fill:#fff}.github-corner .github-corner:hover .octo-arm{animation:none}.github-corner .github-corner .octo-arm{animation:560ms ease-in-out octocat-wave}}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}.sidebar-inner{color:#999;padding:18px 10px;text-align:center;display:flex;flex-direction:column;justify-content:center}.cc-license .cc-opacity{border-bottom:0;opacity:.7}.cc-license .cc-opacity:hover{opacity:.9}.cc-license img{display:inline-block}.site-author-image{border:1px solid #eee;max-width:120px;padding:2px}.site-author-name{color:var(--text-color);font-weight:600;margin:0}.site-description{color:#999;font-size:.8125em;margin-top:0}.links-of-author a{font-size:.8125em}.links-of-author i[class^=fa]{margin-right:2px}.sidebar .sidebar-button:not(:first-child){margin-top:15px}.sidebar .sidebar-button button{background:0 0;cursor:pointer;line-height:2;padding:0 15px;border-radius:4px}.sidebar .sidebar-button button i[class^=fa]{margin-right:5px}.links-of-blogroll{font-size:.8125em}.links-of-blogroll-title{font-size:.875em;font-weight:600}.links-of-blogroll-list{list-style:none;margin:0;padding:0}.sidebar-nav{font-size:.875em;height:0;margin:0;overflow:hidden;padding-left:0;pointer-events:none;transition:height .2s ease-in-out,visibility .2s ease-in-out;visibility:hidden}.sidebar-nav-active .sidebar-nav{height:calc(2em + 1px);pointer-events:unset;visibility:unset}.sidebar-nav li{border-bottom:1px solid transparent;color:var(--text-color);cursor:pointer;display:inline-block;transition:border-bottom-color .2s ease-in-out,color .2s ease-in-out}.sidebar-nav li.sidebar-nav-overview{margin-left:10px}.sidebar-nav li:hover{color:#fc6423}.sidebar-overview-active .sidebar-nav-overview,.sidebar-toc-active .sidebar-nav-toc{border-bottom-color:#fc6423;color:#fc6423;transition-delay:0.2s}.sidebar-overview-active .sidebar-nav-overview:hover,.sidebar-toc-active .sidebar-nav-toc:hover{color:#fc6423}.sidebar-panel-container{align-items:start;display:grid;flex:1;overflow-x:hidden;overflow-y:auto;padding-top:0;transition:padding-top .2s ease-in-out}.sidebar-nav-active .sidebar-panel-container{padding-top:20px}.sidebar-panel{animation:.2s ease-in-out deactivate-sidebar-panel;grid-area:1/1;height:0;opacity:0;overflow:hidden;pointer-events:none;transform:translateY(0);transition:.2s ease-in-out;transition-property:opacity,transform,visibility;visibility:hidden}.sidebar-nav-active .sidebar-panel,.sidebar-overview-active .sidebar-panel.post-toc-wrap{transform:translateY(-20px)}.sidebar-overview-active:not(.sidebar-nav-active) .sidebar-panel.post-toc-wrap{transition-delay:0s,0.2s,0s}.sidebar-overview-active .sidebar-panel.site-overview-wrap,.sidebar-toc-active .sidebar-panel.post-toc-wrap{animation-name:activate-sidebar-panel;height:auto;opacity:1;pointer-events:unset;transform:translateY(0);transition-delay:0.2s,0.2s,0s;visibility:unset}.sidebar-panel.site-overview-wrap{display:flex;flex-direction:column;justify-content:center;gap:10px;justify-content:flex-start}@keyframes deactivate-sidebar-panel{from{height:var(--inactive-panel-height,0)}to{height:var(--active-panel-height,0)}}@keyframes activate-sidebar-panel{from{height:var(--inactive-panel-height,auto)}to{height:var(--active-panel-height,auto)}}.sidebar-toggle{bottom:61px;height:16px;padding:5px;width:16px;background:#222;cursor:pointer;opacity:.6;position:fixed;z-index:30;right:30px}.sidebar-toggle:hover{opacity:.8}@media (max-width:991px){.sidebar-toggle{right:20px;opacity:.8}}.sidebar-toggle:hover .toggle-line{background:#fc6423}@media (any-hover:hover){body:not(.sidebar-active) .sidebar-toggle:hover :first-child{left:50%;top:2px;transform:rotate(45deg);width:50%}body:not(.sidebar-active) .sidebar-toggle:hover :last-child{left:50%;top:-2px;transform:rotate(-45deg);width:50%}}.sidebar-active .sidebar-toggle :nth-child(2){opacity:0}.sidebar-active .sidebar-toggle :first-child{top:6px;transform:rotate(45deg)}.sidebar-active .sidebar-toggle :last-child{top:-6px;transform:rotate(-45deg)}.post-toc{font-size:.875em}.post-toc ol{list-style:none;margin:0;padding:0 2px 0 10px;text-align:left}.post-toc ol>:last-child{margin-bottom:5px}.post-toc ol>ol{padding-left:0}.post-toc ol a{transition:.2s ease-in-out}.post-toc .nav-item{line-height:1.8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.post-toc .nav .nav-child{--height:0;height:0;opacity:0;overflow:hidden;transition:.2s ease-in-out;visibility:hidden}.post-toc .nav .active>.nav-child{height:var(--height,auto);opacity:1;visibility:unset}.post-toc .nav .active>a{border-bottom-color:#fc6423;color:#fc6423}.post-toc .nav .active-current>a,.post-toc .nav .active-current>a:hover{color:#fc6423}.site-state{display:flex;flex-wrap:wrap;justify-content:center;line-height:1.4}.site-state-item a{border-bottom:0;display:block}.site-state-item-count{display:block;font-size:1em;font-weight:600}.site-state-item-name{color:#999;font-size:.8125em}.sidebar-post-related{font-size:.8125em;padding:18px 0 0}.popular-posts{margin:0;padding:1em 0;text-align:left}.popular-posts .popular-posts-item{display:block}.popular-posts .popular-posts-item .popular-posts-link{border-bottom:0;display:block;padding:5px 20px;transition:background .2s ease-in-out}.popular-posts .popular-posts-item .popular-posts-link:hover{background:var(--menu-item-bg-color)}.popular-posts .popular-posts-item .popular-posts-time{color:#999}.footer{color:#999;font-size:.875em;padding:20px 0;transition:left .2s ease-in-out,right .2s ease-in-out}.footer.footer-fixed{bottom:0;left:0;position:absolute;right:0}.footer-inner{box-sizing:border-box;text-align:center;display:flex;flex-direction:column;justify-content:center;margin:0 auto;width:calc(100% - 20px)}@media (max-width:767px){.menu-item .badge{float:right;margin-left:0}.footer-inner{width:auto}}@media (min-width:1200px){.footer-inner{width:1160px}}@media (min-width:1600px){.footer-inner{width:73%}}.use-motion .footer{opacity:0}.languages{display:inline-block;font-size:1.125em;position:relative}.languages .lang-select-label span{margin:0 .5em}.languages .lang-select{height:100%;left:0;opacity:0;position:absolute;top:0;width:100%}.with-love{color:red;display:inline-block;margin:0 5px}.beian img{display:inline-block;margin:0 3px;vertical-align:middle}.busuanzi-count #busuanzi_container_site_pv,.busuanzi-count #busuanzi_container_site_uv{display:none}@keyframes icon-animate{0%,100%{transform:scale(1)}10%,30%{transform:scale(.9)}20%,40%,50%,60%,70%,80%{transform:scale(1.1)}}@media (max-width:567px){.main-inner{padding:initial!important}.posts-expand .post-header{margin-bottom:10px!important}.post-block{margin-top:initial!important;padding:8px 18px!important}.post-body h1,.post-body h2,.post-body h3,.post-body h4,.post-body h5,.post-body h6{margin:20px 0 8px}.post-body .note h1,.post-body .note h2,.post-body .note h3,.post-body .note h4,.post-body .note h5,.post-body .note h6,.post-body .tabs .tab-content .tab-pane h1,.post-body .tabs .tab-content .tab-pane h2,.post-body .tabs .tab-content .tab-pane h3,.post-body .tabs .tab-content .tab-pane h4,.post-body .tabs .tab-content .tab-pane h5,.post-body .tabs .tab-content .tab-pane h6{margin:0 5px}.post-body>p{margin:0 0 10px}.post-body .note>p,.post-body .tabs .tab-content .tab-pane>p{padding:0 5px}.post-body img,.post-body video{margin-bottom:10px!important}.post-body .fancybox+figcaption,.post-body img+figcaption{margin:-5px auto 15px!important}.post-body .note{margin-bottom:10px!important;padding:10px!important}.post-body .tabs .tab-content .tab-pane{padding:10px 10px 0!important}.post-eof{margin:40px auto 20px!important}.pagination{margin-top:40px}}.back-to-top{font-size:12px;align-items:center;bottom:-100px;color:#fff;display:flex;height:26px;transition:bottom .2s ease-in-out;background:#222;cursor:pointer;opacity:.6;position:fixed;z-index:30;right:30px}.back-to-top span{margin-right:8px;display:none}.back-to-top .fa{text-align:center;width:26px}.back-to-top:hover{opacity:.8;color:#fc6423}.back-to-top.back-to-top-on{bottom:30px}.rtl.post-body a,.rtl.post-body h1,.rtl.post-body h2,.rtl.post-body h3,.rtl.post-body h4,.rtl.post-body h5,.rtl.post-body h6,.rtl.post-body li,.rtl.post-body ol,.rtl.post-body p,.rtl.post-body ul{direction:rtl;font-family:UKIJ Ekran}.rtl.post-title{font-family:UKIJ Ekran}.post-button{margin-top:40px;text-align:center}.use-motion .collection-header,.use-motion .comments,.use-motion .pagination,.use-motion .post-block,.use-motion .post-body,.use-motion .post-header{visibility:hidden}.posts-collapse .post-content{margin-bottom:35px;margin-left:35px;position:relative}@media (max-width:767px){.posts-collapse .post-content{margin-left:0;margin-right:0}}.posts-collapse .post-content .collection-title{font-size:1.125em;position:relative}.posts-collapse .post-content .collection-title::before{background:#999;border:1px solid #fff;margin-left:-6px;margin-top:-4px;position:absolute;top:50%;border-radius:50%;content:' ';height:10px;width:10px}.posts-collapse .post-content .collection-year{font-size:1.5em;font-weight:700;margin:60px 0;position:relative}.posts-collapse .post-content .collection-year::before{background:#bbb;margin-left:-4px;margin-top:-4px;position:absolute;top:50%;border-radius:50%;content:' ';height:8px;width:8px}.posts-collapse .post-content .collection-header{display:block;margin-left:20px}.posts-collapse .post-content .collection-header small{color:#bbb;margin-left:5px}.posts-collapse .post-content .post-header{border-bottom:1px dashed #ccc;margin:30px 2px 0;padding-left:15px;position:relative;transition:border .2s ease-in-out}.posts-collapse .post-content .post-header::before{background:#bbb;border:1px solid #fff;left:-6px;position:absolute;top:.75em;transition:background .2s ease-in-out;border-radius:50%;content:' ';height:6px;width:6px}.posts-collapse .post-content .post-header:hover{border-bottom-color:#666}.posts-collapse .post-content .post-header:hover::before{background:#222}.posts-collapse .post-content .post-meta-container{display:inline;font-size:.75em;margin-right:10px}.posts-collapse .post-content .post-title{display:inline}.posts-collapse .post-content .post-title a{border-bottom:0;color:var(--link-color)}.posts-collapse .post-content::before{background:#f5f5f5;content:' ';height:100%;margin-left:-2px;position:absolute;top:1.25em;width:4px}.post-body{font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;overflow-wrap:break-word}@media (min-width:1200px){.post-body{font-size:1.125em}}@media (min-width:992px){.post-body{text-align:justify}}.post-body h1 .header-anchor,.post-body h1 .headerlink,.post-body h2 .header-anchor,.post-body h2 .headerlink,.post-body h3 .header-anchor,.post-body h3 .headerlink,.post-body h4 .header-anchor,.post-body h4 .headerlink,.post-body h5 .header-anchor,.post-body h5 .headerlink,.post-body h6 .header-anchor,.post-body h6 .headerlink{border-bottom-style:none;color:inherit;float:right;font-size:.875em;margin-left:10px;opacity:0}.post-body h1 .header-anchor::before,.post-body h1 .headerlink::before,.post-body h2 .header-anchor::before,.post-body h2 .headerlink::before,.post-body h3 .header-anchor::before,.post-body h3 .headerlink::before,.post-body h4 .header-anchor::before,.post-body h4 .headerlink::before,.post-body h5 .header-anchor::before,.post-body h5 .headerlink::before,.post-body h6 .header-anchor::before,.post-body h6 .headerlink::before{content:'\f0c1';font-family:'Font Awesome 6 Free';font-weight:900}.post-body h1:hover .header-anchor,.post-body h1:hover .headerlink,.post-body h2:hover .header-anchor,.post-body h2:hover .headerlink,.post-body h3:hover .header-anchor,.post-body h3:hover .headerlink,.post-body h4:hover .header-anchor,.post-body h4:hover .headerlink,.post-body h5:hover .header-anchor,.post-body h5:hover .headerlink,.post-body h6:hover .header-anchor,.post-body h6:hover .headerlink{opacity:.5}.post-body h1:hover .header-anchor:hover,.post-body h1:hover .headerlink:hover,.post-body h2:hover .header-anchor:hover,.post-body h2:hover .headerlink:hover,.post-body h3:hover .header-anchor:hover,.post-body h3:hover .headerlink:hover,.post-body h4:hover .header-anchor:hover,.post-body h4:hover .headerlink:hover,.post-body h5:hover .header-anchor:hover,.post-body h5:hover .headerlink:hover,.post-body h6:hover .header-anchor:hover,.post-body h6:hover .headerlink:hover{opacity:1}.post-body .exturl .fa{font-size:.875em;margin-left:4px}.post-body .fancybox+figcaption,.post-body img+figcaption{color:#999;font-size:.875em;font-weight:700;line-height:1;margin:-15px auto 15px;text-align:center}.post-body embed,.post-body iframe,.post-body img,.post-body video{margin-bottom:20px}.post-body .video-container{height:0;margin-bottom:20px;overflow:hidden;padding-top:75%;position:relative;width:100%}.post-body .video-container embed,.post-body .video-container iframe,.post-body .video-container object{height:100%;left:0;margin:0;position:absolute;top:0;width:100%}.post-gallery{display:flex;min-height:200px}.post-gallery .post-gallery-image{flex:1}.post-gallery .post-gallery-image:not(:first-child){clip-path:polygon(40px 0,100% 0,100% 100%,0 100%);margin-left:-20px}.post-gallery .post-gallery-image:not(:last-child){margin-right:-20px}.post-gallery .post-gallery-image img{height:100%;object-fit:cover;opacity:1;width:100%}.posts-expand .post-gallery{margin-bottom:60px}.posts-collapse .post-gallery{margin:15px 0}.posts-expand .post-header{font-size:1.125em;margin-bottom:60px;text-align:center}.posts-expand .post-title{font-size:1.5em;font-weight:400;margin:initial;overflow-wrap:break-word}.posts-expand .post-title-link{border-bottom:0;color:var(--link-color);display:inline-block;position:relative}.posts-expand .post-title-link::before{background:var(--link-color);bottom:0;content:'';height:2px;left:0;position:absolute;transform:scaleX(0);transition:transform .2s ease-in-out;width:100%}.posts-expand .post-title-link:hover::before{transform:scaleX(1)}.posts-expand .post-title-link .fa{font-size:.875em;margin-left:5px}.post-sticky-flag{display:inline-block;margin-right:8px;transform:rotate(30deg)}.posts-expand .post-meta-container{color:#999;font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;font-size:.75em;margin-top:3px}.posts-expand .post-meta-container .post-description{font-size:.875em;margin-top:2px}.posts-expand .post-meta-container time{border-bottom:1px dashed #999}.post-meta{display:flex;flex-wrap:wrap;justify-content:center}:not(.post-meta-break)+.post-meta-item::before{content:'|';margin:0 .5em}.post-meta-item-icon{margin-right:3px}@media (max-width:991px){.back-to-top{right:20px;opacity:.8}.post-body{text-align:justify}.post-meta-item-text{display:none}}.post-meta-break{flex-basis:100%;height:0}#busuanzi_container_page_pv{display:none}.post-nav{border-top:1px solid #eee;display:flex;gap:30px;justify-content:space-between;margin-top:1em;padding:10px 5px 0}.post-nav-item{flex:1}.post-nav-item a{border-bottom:0;display:block;font-size:.875em;line-height:1.6}.post-nav-item a:active{top:2px}.post-nav-item .fa{font-size:.75em}.post-nav-item:first-child .fa{margin-right:5px}.post-nav-item:last-child{text-align:right}.post-nav-item:last-child .fa{margin-left:5px}.post-footer{display:flex;flex-direction:column;justify-content:center}.post-eof{background:#ccc;height:1px;margin:80px auto 60px;width:8%}.post-block:last-of-type .post-eof{display:none}.post-copyright ul{list-style:none;overflow:hidden;padding:.5em 1em;position:relative;background:var(--card-bg-color);border-left:3px solid #ff2a2a;margin:1em 0 0}.post-copyright ul::after{content:'\f25e';font-family:'Font Awesome 6 Brands';font-size:200px;opacity:.1;position:absolute;right:-50px;top:-150px}.post-tags{margin-top:40px;text-align:center}.post-tags a{display:inline-block;font-size:.8125em}.post-tags a:not(:last-child){margin-right:10px}.social-like{border-top:1px solid #eee;font-size:.875em;margin-top:1em;padding-top:1em;display:flex;flex-wrap:wrap;justify-content:center}.social-like a{border-bottom:none}.reward-container{margin:1em 0 0;padding:1em 0;text-align:center}.reward-container button{background:0 0;color:#fc6423;cursor:pointer;line-height:2;padding:0 15px;border:2px solid #fc6423;border-radius:2px;outline:0;transition:.2s ease-in-out;vertical-align:text-top}.reward-container button:hover{background:#fc6423;color:#fff}.post-reward{display:none;padding-top:20px}.post-reward.active{display:block}.post-reward div{display:inline-block}.post-reward div span{display:block}.post-reward img{display:inline-block;margin:.8em 2em 0;max-width:100%;width:180px}@keyframes next-roll{from{transform:rotateZ(30deg)}to{transform:rotateZ(-30deg)}}.category-all-page .category-all-title{text-align:center}.category-all-page .category-all{margin-top:20px}.category-all-page .category-list{list-style:none;margin:0;padding:0}.category-all-page .category-list-item{margin:5px 10px}.category-all-page .category-list-count{color:#bbb}.category-all-page .category-list-count::before{content:' ('}.category-all-page .category-list-count::after{content:') '}.category-all-page .category-list-child{padding-left:10px}.event-list hr{background:#222;margin:20px 0 45px}.event-list hr::after{background:#222;color:#fff;content:'NOW';display:inline-block;font-weight:700;padding:0 5px}.event-list .event{--event-background:#222;--event-foreground:#bbb;--event-title:#fff;background:var(--event-background);padding:15px}.event-list .event .event-summary{border-bottom:0;color:var(--event-title);margin:0;padding:0 0 0 35px;position:relative}.event-list .event .event-summary::before{animation:1s ease-in-out infinite alternate dot-flash;background:var(--event-title);left:0;margin-top:-6px;position:absolute;top:50%;border-radius:50%;content:' ';height:12px;width:12px}.event-list .event:nth-of-type(odd) .event-summary::before{animation-delay:.5s}.event-list .event:not(:last-child){margin-bottom:20px}.event-list .event .event-relative-time{color:var(--event-foreground);display:inline-block;font-size:12px;font-weight:400;padding-left:12px}.event-list .event .event-details{color:var(--event-foreground);display:block;line-height:18px;padding:6px 0 6px 35px}.event-list .event .event-details::before{color:var(--event-foreground);display:inline-block;margin-right:9px;width:14px;font-family:'Font Awesome 6 Free';font-weight:900}.event-list .event .event-details.event-location::before{content:'\f041'}.event-list .event .event-details.event-duration::before{content:'\f017'}.event-list .event .event-details.event-description::before{content:'\f024'}.event-list .event-past{--event-background:#f5f5f5;--event-foreground:#999;--event-title:#222}@keyframes dot-flash{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.8)}}ul.breadcrumb{font-size:.75em;list-style:none;margin:1em 0;padding:0 2em;text-align:center}ul.breadcrumb li{display:inline}ul.breadcrumb li:not(:first-child)::before{content:'/\00a0';font-weight:400;padding:.5em}ul.breadcrumb li:last-child{font-weight:700}.tag-cloud{text-align:center}.tag-cloud a{display:inline-block;margin:10px}.tag-cloud-0{border-bottom-color:#aaa;color:#aaa}.tag-cloud-1{border-bottom-color:#9a9a9a;color:#9a9a9a}.tag-cloud-2{border-bottom-color:#8b8b8b;color:#8b8b8b}.tag-cloud-3{border-bottom-color:#7c7c7c;color:#7c7c7c}.tag-cloud-4{border-bottom-color:#6c6c6c;color:#6c6c6c}.tag-cloud-5{border-bottom-color:#5d5d5d;color:#5d5d5d}.tag-cloud-6{border-bottom-color:#4e4e4e;color:#4e4e4e}.tag-cloud-7{border-bottom-color:#3e3e3e;color:#3e3e3e}.tag-cloud-8{border-bottom-color:#2f2f2f;color:#2f2f2f}.tag-cloud-9{border-bottom-color:#202020;color:#202020}.tag-cloud-10{border-bottom-color:#111;color:#111}.search-active{overflow:hidden}.search-pop-overlay{background:rgba(0,0,0,0);display:flex;height:100%;left:0;position:fixed;top:0;transition:visibility .4s,background .4s;visibility:hidden;width:100%;z-index:40}.search-active .search-pop-overlay{background:rgba(0,0,0,.3);visibility:visible}.search-popup{background:var(--card-bg-color);border-radius:5px;height:80%;margin:auto;transform:scale(0);transition:transform .4s;width:700px}.search-active .search-popup{transform:scale(1)}@media (max-width:767px){.search-popup{border-radius:0;height:100%;width:100%}}.search-popup .popup-btn-close,.search-popup .search-icon{color:#999;font-size:18px;padding:0 10px}.search-popup .popup-btn-close{cursor:pointer}.search-popup .popup-btn-close:hover .fa{color:#222}.search-popup .search-header{background:#eee;border-top-left-radius:5px;border-top-right-radius:5px;display:flex;padding:5px}.search-popup input.search-input{background:0 0;border:0;outline:0;width:100%}.search-popup input.search-input::-webkit-search-cancel-button{display:none}.search-popup .search-result-container{height:calc(100% - 55px);overflow:auto;padding:5px 25px}.search-popup .search-result-container hr{margin:5px 0 10px}.search-popup .search-result-container hr:first-child{display:none}.search-popup .search-result-list{margin:0 5px;padding:0}.search-popup a.search-result-title{font-weight:700}.search-popup p.search-result{border-bottom:1px dashed #ccc;padding:5px 0}.search-input-container{flex-grow:1}.search-input-container form{padding:2px}.search-stats{align-items:center;display:flex;justify-content:space-between}.search-stats img{height:1em;margin:0}.algolia-pagination{margin:40px 0;opacity:1;padding:0}.algolia-pagination .pagination-item{display:inline-block}.algolia-pagination .current .page-number{cursor:default}.algolia-pagination .disabled-item{visibility:hidden}.use-motion .animated{animation-fill-mode:none;visibility:inherit}.use-motion .sidebar .animated{animation-fill-mode:both}header.header{background:var(--content-bg-color);border-radius:initial;box-shadow:initial}.main{align-items:stretch;display:flex;justify-content:space-between;margin:0 auto;width:calc(100% - 20px)}@media (max-width:767px){.main{width:auto}}@media (min-width:1200px){.main{width:1160px}}@media (min-width:1600px){.main{width:73%}}@media (max-width:991px){header.header{border-radius:initial}.main{display:block;width:auto}}.main-inner{border-radius:initial;box-sizing:border-box;width:calc(100% - 252px)}.footer-inner{padding-left:252px}@media (max-width:991px){.main-inner{border-radius:initial;width:100%}.footer-inner{padding-left:0;padding-right:0;width:auto}}.column{width:240px}.site-brand-container{background:var(--theme-color)}.site-meta{padding:20px 0}.site-nav-right .toggle,.site-nav-toggle .toggle{color:#fff}.site-nav-right .toggle .toggle-line,.site-nav-toggle .toggle .toggle-line{background:#fff}@media (min-width:768px) and (max-width:991px){.site-nav-right,.site-nav-toggle{display:flex;flex-direction:column;justify-content:center}.site-nav{--scroll-height:0;height:0;overflow:hidden;transition:height .2s ease-in-out,visibility .2s ease-in-out;visibility:hidden}body:not(.site-nav-on) .site-nav .animated{animation:none}body.site-nav-on .site-nav{height:var(--scroll-height);visibility:unset}}.menu .menu-item{display:block;margin:0}.menu .menu-item a{padding:5px 20px;position:relative;text-align:left;transition-property:background-color}.menu .menu-item .badge{background:#ccc;border-radius:10px;color:var(--content-bg-color);float:right;padding:2px 5px;text-shadow:1px 1px 0 rgba(0,0,0,.1)}.main-menu .menu-item-active::after{background:#bbb;border-radius:50%;content:' ';height:6px;margin-top:-3px;position:absolute;right:15px;top:50%;width:6px}.sub-menu{margin:0;padding:6px 0}.sub-menu .menu-item{display:inline-block}.sub-menu .menu-item a{background:0 0;margin:5px 10px;padding:initial}.sub-menu .menu-item a:hover{background:0 0;color:#fc6423}.sub-menu .menu-item-active{border-bottom-color:#fc6423;color:#fc6423}.sub-menu .menu-item-active:hover{border-bottom-color:#fc6423}.sidebar{position:-webkit-sticky;position:sticky;top:12px}@media (max-width:991px){.column{width:auto}.site-nav-on .site-brand-container{box-shadow:0 0 16px rgba(0,0,0,.5)}.menu .menu-item.menu-item-search,.sidebar{display:none}}.sidebar-inner{background:var(--content-bg-color);border-radius:initial;box-shadow:initial;box-sizing:border-box;color:var(--text-color);margin-top:12px;max-height:calc(100vh - 24px);visibility:hidden}.site-state-item{padding:0 10px}.sidebar .sidebar-button{border-bottom:1px dotted #ccc;border-top:1px dotted #ccc}.sidebar .sidebar-button button{border:0;color:#fc6423;display:block;width:100%}.sidebar .sidebar-button button:hover{background:0 0;border:0;color:#e34603}.links-of-author{display:flex;flex-wrap:wrap;justify-content:center}.links-of-author-item{margin:5px 0 0;width:50%}.links-of-author-item a{box-sizing:border-box;max-width:100%;overflow:hidden;padding:0 5px;text-overflow:ellipsis;white-space:nowrap;border-bottom:0;border-radius:4px;display:block}.links-of-author-item a:hover{background:var(--body-bg-color)}.main-inner{background:var(--content-bg-color);box-shadow:initial;padding:40px}@media (max-width:991px){.main-inner{padding:20px}}.sub-menu{border-bottom:1px solid #ddd}.post-block:first-of-type{padding-top:40px}@media (max-width:767px){.pagination{margin-bottom:10px}}</style><link as=style href=https://fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&display=swap&subset=latin,latin-ext onload=this.rel='stylesheet' rel=preload><link crossorigin=anonymous href=https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css integrity=sha256-yIDrPSXHZdOZhAqiBP7CKzIwMQmRCJ8UeB8Jo17YC4o= rel=stylesheet><link crossorigin=anonymous href=https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.1.1/animate.min.css integrity=sha256-PR7ttpcvz8qrF57fur/yAx1qXMFJeJFiA6pSzWi0OIE= rel=stylesheet><link crossorigin=anonymous href=https://cdnjs.cloudflare.com/ajax/libs/fancyapps-ui/5.0.28/fancybox/fancybox.css integrity=sha256-6cQIC71/iBIYXFK+0RHAvwmjwWzkWd+r7v/BX3/vZDc= rel=stylesheet><script class=next-config data-name=main type=application/json>{"hostname":"nicksxs.me","root":"/","images":"/images","scheme":"Pisces","darkmode":false,"version":"8.19.1","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":{"enable":true,"style":"default"},"fold":{"enable":false,"height":500},"bookmark":{"enable":false,"color":"#222","save":"auto"},"mediumzoom":false,"lazyload":true,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"stickytabs":false,"motion":{"enable":true,"async":false,"transition":{"menu_item":"fadeInDown","post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果:${query}","hits_time":"找到 ${hits} 个搜索结果(用时 ${time} 毫秒)","hits":"找到 ${hits} 个搜索结果"},"algolia":{"appID":"663Q99OQQD","apiKey":"e461a1371d12cec1491c1153b288a9ed","indexName":"nicksxs","hits":{"per_page":10}}}</script><script src=/js/config.js></script><meta content="learn from zero,技术博客,Nicksxs,史学森" name=description><meta content=website property=og:type><meta content="Nicksxs's Blog" property=og:title><meta content=https://nicksxs.me/index.html property=og:url><meta content="Nicksxs's Blog" property=og:site_name><meta content="learn from zero,技术博客,Nicksxs,史学森" property=og:description><meta content=zh_CN property=og:locale><meta content=Nicksxs property=article:author><meta content=Nicksxs,史学森,米方方,米方方的男朋友,森哥 property=article:tag><meta content=summary name=twitter:card><link href=https://nicksxs.me/ rel=canonical><script class=next-config data-name=page type=application/json>{"sidebar":"","isHome":true,"isPost":false,"lang":"zh-CN","comments":"","permalink":"","path":"index.html","title":""}</script><script class=next-config data-name=calendar type=application/json>""</script><title>Nicksxs's Blog - What hurts more, the pain of hard work or the pain of regret?</title><script async src=https://www.googletagmanager.com/gtag/js?id=UA-61358619-1></script><script class=next-config data-name=google_analytics type=application/json>{"tracking_id":"UA-61358619-1","only_pageview":false,"measure_protocol_api_secret":null}</script><script src=/js/third-party/analytics/google-analytics.js></script><script src=/js/third-party/analytics/baidu-analytics.js></script><script async src=https://hm.baidu.com/hm.js?20f33b3c0c0eff9b1522999c0015646d></script><noscript><link href=/css/noscript.css rel=stylesheet></noscript><link title="Nicksxs's Blog" href=/atom.xml rel=alternate type=application/atom+xml><body class=use-motion itemscope itemtype=http://schema.org/WebPage><div class=headband></div><main class=main><div class=column><header class=header itemscope itemtype=http://schema.org/WPHeader><div class=site-brand-container><div class=site-nav-toggle><div aria-label=切换导航栏 class=toggle role=button><span class=toggle-line></span><span class=toggle-line></span><span class=toggle-line></span></div></div><div class=site-meta><a class=brand href=/ rel=start><i class=logo-line></i><h1 class=site-title>Nicksxs's Blog</h1><i class=logo-line></i></a><p class=site-subtitle itemprop=description>What hurts more, the pain of hard work or the pain of regret?</div><div class=site-nav-right><div class="toggle popup-trigger" aria-label=搜索 role=button><i class="fa fa-search fa-fw fa-lg"></i></div></div></div><nav class=site-nav><ul class="main-menu menu"><li class="menu-item menu-item-home"><a href=/ rel=section><i class="fa fa-home fa-fw"></i>首页</a><li class="menu-item menu-item-about"><a href=/about/ rel=section><i class="fa fa-user fa-fw"></i>关于我</a><li class="menu-item menu-item-mirror"><a href=https://nicksxs.com/ rel=section target=_blank><i class="fa fa-user fa-fw"></i>国内镜像</a><li class="menu-item menu-item-tags"><a href=/tags/ rel=section><i class="fa fa-tags fa-fw"></i>标签</a><li class="menu-item menu-item-categories"><a href=/categories/ rel=section><i class="fa fa-th fa-fw"></i>分类</a><li class="menu-item menu-item-archives"><a href=/archives/ rel=section><i class="fa fa-archive fa-fw"></i>归档</a><li class="menu-item menu-item-top"><a href=/top/ rel=section><i class="fa fa-th fa-fw"></i>热度</a><li class="menu-item menu-item-sitemap"><a href=/sitemap.xml rel=section><i class="fa fa-sitemap fa-fw"></i>站点地图</a><li class="menu-item menu-item-commonweal"><a href=/404/ rel=section><i class="fa fa-heartbeat fa-fw"></i>公益 404</a><li class="menu-item menu-item-search"><a class=popup-trigger role=button><i class="fa fa-search fa-fw"></i>搜索</a></ul></nav><div class=search-pop-overlay><div class="popup search-popup"><div class=search-header><span class=search-icon><i class="fa fa-search"></i></span><div class=search-input-container></div><span class=popup-btn-close role=button><i class="fa fa-times-circle"></i></span></div><div class=search-result-container><div class=algolia-stats><hr></div><div class=algolia-hits></div><div class=algolia-pagination></div></div></div></div></header><aside class=sidebar><div class="sidebar-inner sidebar-overview-active"><ul class=sidebar-nav><li class=sidebar-nav-toc>文章目录<li class=sidebar-nav-overview>站点概览</ul><div class=sidebar-panel-container><div class="post-toc-wrap sidebar-panel"></div><div class="site-overview-wrap sidebar-panel"><div class="site-author animated" itemprop=author itemscope itemtype=http://schema.org/Person><img alt=Nicksxs class=site-author-image itemprop=image src=/uploads/avatar.jpg><p class=site-author-name itemprop=name>Nicksxs<div class=site-description itemprop=description>learn from zero,技术博客,Nicksxs,史学森</div></div><div class="site-state-wrap animated"><nav class=site-state><div class="site-state-item site-state-posts"><a href=/archives/><span class=site-state-item-count>381</span> <span class=site-state-item-name>日志</span></a></div><div class="site-state-item site-state-categories"><a href=/categories/><span class=site-state-item-count>181</span> <span class=site-state-item-name>分类</span></a></div><div class="site-state-item site-state-tags"><a href=/tags/><span class=site-state-item-count>321</span> <span class=site-state-item-name>标签</span></a></div></nav></div><div class="links-of-author animated"><span class=links-of-author-item><a rel="noopener me" title="GitHub → https://github.com/nicksxs" href=https://github.com/nicksxs target=_blank><i class="fab fa-github fa-fw"></i>GitHub</a> </span><span class=links-of-author-item><a rel="noopener me" title="E-Mail → mailto:nicksxs1202@gmail.com" href=mailto:nicksxs1202@gmail.com target=_blank><i class="fa fa-envelope fa-fw"></i>E-Mail</a></span></div><div class="cc-license animated" itemprop=license><a class=cc-opacity href=https://creativecommons.org/licenses/by-nc-sa/4.0/ rel=noopener target=_blank><img alt="Creative Commons" src=https://cdnjs.cloudflare.com/ajax/libs/creativecommons-vocabulary/2020.11.3/assets/license_badges/small/by_nc_sa.svg></a></div><div class=post-gallery itemscope itemtype=http://schema.org/ImageGallery style=height:120px;min-height:120px><div class=post-gallery-row><a class="post-gallery-img fancybox" href=https://url.cn/LLWrL7gx itemprop=url itemscope itemtype=http://schema.org/ImageObject rel=gallery_ target=_blank><img alt=腾讯云推广 itemprop=contentUrl src=https://img.nicksxs.com/blog/345X200.jpg style=padding:12px></a></div></div><div data-text-font="Trebuchet MS, Helvetica" class=widget-wrap data-canvas-id=sidebar-tag-cloud-canvas data-freeze-active=true data-max-speed=0.1 data-outline-colour=#E2E1D1 data-script-src=/js/tagcanvas.js data-tag-cloud data-text-colour=#333 data-text-height=20><div class="widget tagcloud" data-tag-cloud-container id=myCanvasContainer><canvas height=250 id=sidebar-tag-cloud-canvas style=width:100% width=250><ul class=tag-list itemprop=keywords><li class=tag-list-item><a class=tag-list-link href=/tags/Java/ rel=tag>Java</a><li class=tag-list-item><a class=tag-list-link href=/tags/java/ rel=tag>java</a><li class=tag-list-item><a class=tag-list-link href=/tags/leetcode/ rel=tag>leetcode</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%94%9F%E6%B4%BB/ rel=tag>生活</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E9%A2%98%E8%A7%A3/ rel=tag>题解</a><li class=tag-list-item><a class=tag-list-link href=/tags/LLM/ rel=tag>LLM</a><li class=tag-list-item><a class=tag-list-link href=/tags/SpringBoot/ rel=tag>SpringBoot</a><li class=tag-list-item><a class=tag-list-link href=/tags/c/ rel=tag>c++</a><li class=tag-list-item><a class=tag-list-link href=/tags/Tomcat/ rel=tag>Tomcat</a><li class=tag-list-item><a class=tag-list-link href=/tags/Mysql/ rel=tag>Mysql</a><li class=tag-list-item><a class=tag-list-link href=/tags/Mybatis/ rel=tag>Mybatis</a><li class=tag-list-item><a class=tag-list-link href=/tags/redis/ rel=tag>redis</a><li class=tag-list-item><a class=tag-list-link href=/tags/mysql/ rel=tag>mysql</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/ rel=tag>数据结构</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%BA%90%E7%A0%81/ rel=tag>源码</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B0%8F%E6%8A%80%E5%B7%A7/ rel=tag>小技巧</a><li class=tag-list-item><a class=tag-list-link href=/tags/MQ/ rel=tag>MQ</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/ rel=tag>消息队列</a><li class=tag-list-item><a class=tag-list-link href=/tags/RocketMQ/ rel=tag>RocketMQ</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%BF%90%E5%8A%A8/ rel=tag>运动</a><li class=tag-list-item><a class=tag-list-link href=/tags/php/ rel=tag>php</a><li class=tag-list-item><a class=tag-list-link href=/tags/Spring/ rel=tag>Spring</a><li class=tag-list-item><a class=tag-list-link href=/tags/linux/ rel=tag>linux</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%87%8F%E8%82%A5/ rel=tag>减肥</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%B7%91%E6%AD%A5/ rel=tag>跑步</a><li class=tag-list-item><a class=tag-list-link href=/tags/Dubbo/ rel=tag>Dubbo</a><li class=tag-list-item><a class=tag-list-link href=/tags/headscale/ rel=tag>headscale</a><li class=tag-list-item><a class=tag-list-link href=/tags/Windows/ rel=tag>Windows</a><li class=tag-list-item><a class=tag-list-link href=/tags/Docker/ rel=tag>Docker</a><li class=tag-list-item><a class=tag-list-link href=/tags/git/ rel=tag>git</a><li class=tag-list-item><a class=tag-list-link href=/tags/nas/ rel=tag>nas</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B9%B2%E6%B4%BB/ rel=tag>干活</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%AE%97%E6%B3%95/ rel=tag>算法</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B7%A5%E5%85%B7/ rel=tag>工具</a><li class=tag-list-item><a class=tag-list-link href=/tags/llm/ rel=tag>llm</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%B3%9F%E5%BF%83%E4%BA%8B/ rel=tag>糟心事</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/ rel=tag>年终总结</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%AF%BB%E5%90%8E%E6%84%9F/ rel=tag>读后感</a><li class=tag-list-item><a class=tag-list-link href=/tags/Apollo/ rel=tag>Apollo</a><li class=tag-list-item><a class=tag-list-link href=/tags/RPC/ rel=tag>RPC</a><li class=tag-list-item><a class=tag-list-link href=/tags/nginx/ rel=tag>nginx</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%89%8A%E5%B3%B0%E5%A1%AB%E8%B0%B7/ rel=tag>削峰填谷</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%B8%AD%E9%97%B4%E4%BB%B6/ rel=tag>中间件</a><li class=tag-list-item><a class=tag-list-link href=/tags/markdown/ rel=tag>markdown</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E8%A7%84%E5%88%99/ rel=tag>规则</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%9D%AD%E5%B7%9E/ rel=tag>杭州</a><li class=tag-list-item><a class=tag-list-link href=/tags/2021/ rel=tag>2021</a><li class=tag-list-item><a class=tag-list-link href=/tags/aqs/ rel=tag>aqs</a><li class=tag-list-item><a class=tag-list-link href=/tags/C/ rel=tag>C++</a><li class=tag-list-item><a class=tag-list-link href=/tags/Disruptor/ rel=tag>Disruptor</a><li class=tag-list-item><a class=tag-list-link href=/tags/Binary-Tree/ rel=tag>Binary Tree</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/ rel=tag>二叉树</a><li class=tag-list-item><a class=tag-list-link href=/tags/grep/ rel=tag>grep</a><li class=tag-list-item><a class=tag-list-link href=/tags/namespace/ rel=tag>namespace</a><li class=tag-list-item><a class=tag-list-link href=/tags/ssh/ rel=tag>ssh</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E7%AB%AF%E5%8F%A3%E8%BD%AC%E5%8F%91/ rel=tag>端口转发</a><li class=tag-list-item><a class=tag-list-link href=/tags/Rust/ rel=tag>Rust</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%89%80%E6%9C%89%E6%9D%83/ rel=tag>所有权</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E5%86%85%E5%AD%98%E5%88%86%E5%B8%83/ rel=tag>内存分布</a><li class=tag-list-item><a class=tag-list-link href=/tags/%E6%96%B0%E8%AF%AD%E8%A8%80/ rel=tag>新语言</a></ul></canvas></div></div><script defer src=/js/tagcloud-loader.js></script></div></div></div><div class="sidebar-inner sidebar-blogroll"><div class="links-of-blogroll animated"><div class=links-of-blogroll-title><i class="fa fa-globe fa-fw"></i> 链接</div><ul class=links-of-blogroll-list><li class=links-of-blogroll-item><a href=https://covermusic.cn/ rel=noopener target=_blank title=https://covermusic.cn>69伙伴</a></ul></div></div></aside></div><div class="main-inner index posts-expand"><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article><link href=https://nicksxs.me/2026/07/26/%E8%81%94%E6%83%B3-G400-%E5%AE%89%E8%A3%85-Windows-7/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="learn from zero,技术博客,Nicksxs,史学森" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="undefined | Nicksxs's Blog" itemprop=name><meta itemprop=description></span><header class=post-header><h2 itemprop="name headline" class=post-title><a class=post-title-link href=/2026/07/26/%E8%81%94%E6%83%B3-G400-%E5%AE%89%E8%A3%85-Windows-7/ itemprop=url>联想 G400 安装 Windows 7 折腾记录</a></h2><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2026-07-26 22:00:00 / 修改时间:18:13:16" datetime=2026-07-26T22:00:00+08:00>2026-07-26</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/Windows/ itemprop=url rel=index><span itemprop=name>Windows</span></a> </span>, <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/Windows/%E5%B0%8F%E6%8A%80%E5%B7%A7/ itemprop=url rel=index><span itemprop=name>小技巧</span></a> </span></span><span class="post-meta-item cloudflare-page-counter" data-title="联想 G400 安装 Windows 7 折腾记录" data-increment=false data-path=/2026/07/26/%E8%81%94%E6%83%B3-G400-%E5%AE%89%E8%A3%85-Windows-7/ title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=cloudflare-page-counter-count></span> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-comment"></i> </span><span class=post-meta-item-text>Disqus:</span> <a href=/2026/07/26/%E8%81%94%E6%83%B3-G400-%E5%AE%89%E8%A3%85-Windows-7/#disqus_thread itemprop=discussionUrl title=disqus><span class="post-comments-count disqus-comment-count" data-disqus-identifier=2026/07/26/联想-G400-安装-Windows-7/ itemprop=commentCount></span></a></span></div></div></header><div class=post-body itemprop=articleBody><p>最近给一台联想 G400 重装 Windows 7。本来以为只是做个启动盘、装好系统就结束了,实际操作时却遇到了驱动缺失、旧版 IE 无法打开部分网站、Windows Update 报错,以及显卡驱动依赖 .NET Framework 等一连串问题。这里把完整过程和用到的资料整理一下,方便以后再遇到类似的老电脑时查阅。<p>先提醒一句:Windows 7 已于 2020 年 1 月 14 日结束支持,不再获得常规安全更新;Chrome 109 也早已停止更新。因此,这套方案更适合需要兼容老软件或老硬件的场景,不建议把它作为长期联网、登录重要账号或进行支付操作的主力系统。<h2 id=一、安装前的准备><a class=headerlink href=#一、安装前的准备 title=一、安装前的准备></a>一、安装前的准备</h2><h3 id=1-下载-Windows-7-SP1-镜像><a title="1. 下载 Windows 7 SP1 镜像" class=headerlink href=#1-下载-Windows-7-SP1-镜像></a>1. 下载 Windows 7 SP1 镜像</h3><p>我使用的是 Windows 7 SP1 原版镜像,可以从 <a href=https://msdn.itellyou.cn/ rel=noopener target=_blank>MSDN,我告诉你</a> 查找。<p>需要注意的是,这个网站不是微软官方网站,而是第三方原版软件信息收录站。下载时应选择与授权和硬件相符的版本,并核对镜像的 SHA-1 等校验值,避免使用被二次修改、捆绑软件或来源不明的镜像。<p>联想 G400 通常可以安装 64 位系统。如果机器内存很小,或者有必须使用 32 位系统的老软件,再根据实际情况选择 x86 版本。后面下载补丁和驱动时,系统架构也必须保持一致。<h3 id=2-提前下载驱动><a title="2. 提前下载驱动" class=headerlink href=#2-提前下载驱动></a>2. 提前下载驱动</h3><p>Windows 7 对很多硬件并不能做到安装后自动识别,最麻烦的情况是系统装好了,却因为有线网卡和无线网卡都没有驱动而无法联网。因此,重装前最好先从 <a href=https://newsupport.lenovo.com.cn/driveList.html?fromsource=driveList&selname=Lenovo%20G400 rel=noopener target=_blank>联想 G400 驱动下载页面</a> 下载对应版本的驱动,放到另一个 U 盘中。<p>建议至少准备这些驱动:<ul><li>芯片组驱动;<li>有线网卡和无线网卡驱动;<li>核芯显卡和独立显卡驱动;<li>声卡、触控板、读卡器等驱动。</ul><p>G400 有不同的硬件配置,最好根据机器背面的具体型号或主机编号查找,不要只看系列名称。网卡驱动尤其可以多准备一个可能的版本,以免装完系统后完全无法联网。<h3 id=3-提前下载补丁和常用软件><a title="3. 提前下载补丁和常用软件" class=headerlink href=#3-提前下载补丁和常用软件></a>3. 提前下载补丁和常用软件</h3><p>为了避免装好系统后才发现网站打不开,建议把下面这些内容也提前放进 U 盘:<ul><li>.NET Framework 4 独立安装包;<li>IE11 安装包;<li>本文后面提到的 Windows 7 补丁;<li>Chrome 109 离线安装包;<li>已下载好的联想官方驱动。</ul><h2 id=二、制作启动-U-盘并安装系统><a title="二、制作启动 U 盘并安装系统" class=headerlink href=#二、制作启动-U-盘并安装系统></a>二、制作启动 U 盘并安装系统</h2><p>我使用 UltraISO 将 Windows 7 SP1 镜像写入 U 盘。操作前一定要确认目标盘符,因为写入过程会清空 U 盘中的原有数据。<p>制作完成后,从联想 G400 的启动菜单中选择 U 盘,按照安装向导完成系统安装即可。如果进入安装界面后出现键盘、鼠标或 U 盘突然无法使用的情况,可以先换到 USB 2.0 接口再试;Windows 7 安装环境对 USB 3.0 的原生支持并不完善。<p>分区或格式化系统盘前,务必确认重要文件已经备份。安装完成后也不要急着联网,先把基础驱动和补丁装好。<h2 id=三、安装驱动和-NET-Framework-4><a title="三、安装驱动和 .NET Framework 4" class=headerlink href=#三、安装驱动和-NET-Framework-4></a>三、安装驱动和 .NET Framework 4</h2><p>驱动建议按下面的顺序安装:<ol><li>芯片组驱动;<li>有线网卡或无线网卡驱动;<li>核芯显卡和独立显卡驱动;<li>声卡、触控板、读卡器等其他驱动。</ol><p>这次安装显卡驱动时,安装程序提示缺少 .NET Framework 4。可以从微软官方下载 <a href=https://www.microsoft.com/zh-cn/download/details.aspx?id=17718 rel=noopener target=_blank>.NET Framework 4 独立安装程序</a>,安装并重启后,再继续安装显卡驱动。<p>驱动全部安装完成后,可以打开“设备管理器”,检查是否还存在带黄色感叹号的设备。<h2 id=四、先安装-Windows-7-汇总补丁><a title="四、先安装 Windows 7 汇总补丁" class=headerlink href=#四、先安装-Windows-7-汇总补丁></a>四、先安装 Windows 7 汇总补丁</h2><p>刚装好的 Windows 7 SP1 与现在的网络环境相差太远,直接使用 Windows Update 往往会失败。可以先手动安装微软的便利汇总补丁 <code>KB3125574</code>,它包含了 Windows 7 SP1 发布之后到 2016 年 4 月之间的大部分更新。<p>这里有一个容易遗漏的前置条件:安装 <code>KB3125574</code> 之前,必须先安装 2015 年 4 月的服务堆栈更新 <code>KB3020369</code>。<p>推荐顺序如下:<ol><li>在 Microsoft Update Catalog 中下载并安装 <a href=https://www.catalog.update.microsoft.com/Search.aspx?q=KB3020369 rel=noopener target=_blank>KB3020369</a>,然后重启;<li>下载并安装 <a href=https://www.catalog.update.microsoft.com/Search.aspx?q=KB3125574 rel=noopener target=_blank>KB3125574</a>,然后再次重启。</ol><p>下载时要认准 Windows 7,并根据当前系统选择 x86 或 x64 包,不要误下成 Windows Server 2008 R2 版本。安装 <code>KB3125574</code> 还需要给系统盘预留至少 4 GB 可用空间。<p>如果提示“此更新不适用于你的计算机”,优先检查以下几项:<ul><li>当前系统是否已经是 Windows 7 SP1;<li>下载的补丁架构是否与系统一致;<li>前置补丁是否已经安装;<li>该补丁是否已经安装,或被更新的补丁取代。</ul><h2 id=五、安装-IE11><a title="五、安装 IE11" class=headerlink href=#五、安装-IE11></a>五、安装 IE11</h2><p>系统补丁安装完成后,可以安装微软提供的 <a href=https://www.microsoft.com/zh-cn/download/details.aspx?id=41628 rel=noopener target=_blank>Windows 7 版 IE11(64 位)</a>。IE11 本身也已经停止支持,这里安装它主要是为了改善老系统的网页兼容性,并补齐部分旧软件依赖,不建议把它当作日常浏览器。<p>如果刚装好 Windows 7 时打不开补丁目录或下载页面,可以在另一台正常联网的电脑上先把安装包下载好,再通过 U 盘复制过去。不要为了打开网站而关闭证书校验或安装来源不明的所谓“证书修复包”。<h2 id=六、解决-Windows-Update-的-80072EFE-错误><a title="六、解决 Windows Update 的 80072EFE 错误" class=headerlink href=#六、解决-Windows-Update-的-80072EFE-错误></a>六、解决 Windows Update 的 80072EFE 错误</h2><p>如果运行 Windows Update 时出现 <code>80072EFE</code>,可以参考<a href=https://www.reddit.com/r/windows7/comments/lnobhu/fix_update_error_80072efe_on_new_installations_of/ rel=noopener target=_blank>这篇 Reddit 讨论</a>,手动安装下面三个补丁:<ol><li><a href=https://www.catalog.update.microsoft.com/Search.aspx?q=KB4490628 rel=noopener target=_blank>KB4490628</a>:2019 年 3 月服务堆栈更新;<li><a href=https://www.catalog.update.microsoft.com/Search.aspx?q=KB4474419 rel=noopener target=_blank>KB4474419</a>:SHA-2 代码签名支持更新;<li><a href=https://www.catalog.update.microsoft.com/Search.aspx?q=KB3138612 rel=noopener target=_blank>KB3138612</a>:Windows Update Client 更新。</ol><p>建议按上面的顺序逐个安装。遇到提示需要重启时就先重启,再继续安装下一个;三个补丁安装完毕后,再次重启并运行 Windows Update。<p>如果 Windows Update 仍然失败,可以先确认系统时间、时区是否正确,并检查有线或无线网络是否稳定。由于 Windows 7 已结束支持,即使更新功能恢复,也不能把它理解为系统重新获得了完整、持续的安全支持。<h2 id=七、浏览器选择><a class=headerlink href=#七、浏览器选择 title=七、浏览器选择></a>七、浏览器选择</h2><p>Windows 7 能使用的最后一个 Chrome 大版本是 Chrome 109,后续版本已经不再支持 Windows 7。如果确实需要,可以安装 109 版本,但它已经不能获得新的功能和安全修复。<p>因此,Chrome 109 只适合临时访问普通网页或兼容老系统,不要用它登录网银、邮箱等重要账号。离线安装包也应尽量从可信渠道取得,并核对数字签名,不建议随便从软件站下载所谓的“绿色版”或“特别版”。<h2 id=八、完整安装顺序><a class=headerlink href=#八、完整安装顺序 title=八、完整安装顺序></a>八、完整安装顺序</h2><p>最后把这次的操作顺序汇总一下:<ol><li>备份原系统中的重要数据;<li>下载 Windows 7 SP1 镜像并用 UltraISO 制作启动 U 盘;<li>提前准备联想 G400 驱动、系统补丁和离线安装软件;<li>安装 Windows 7 SP1;<li>安装芯片组和网卡驱动;<li>安装 .NET Framework 4,再安装显卡及其他驱动;<li>依次安装 <code>KB3020369</code> 和 <code>KB3125574</code>;<li>安装 IE11;<li>遇到 <code>80072EFE</code> 时,依次安装 <code>KB4490628</code>、<code>KB4474419</code> 和 <code>KB3138612</code>;<li>根据需要安装 Chrome 109,并尽量减少这台机器的联网用途。</ol><p>这次最深的感受是,给老电脑装 Windows 7,真正花时间的并不是系统安装本身,而是提前准备驱动、补齐系统组件和解决更新问题。把驱动和补丁都提前放进 U 盘,整个过程会省心很多。<h2 id=参考资料><a class=headerlink href=#参考资料 title=参考资料></a>参考资料</h2><ul><li><a href=https://www.chiphell.com/thread-2524069-2-1.html rel=noopener target=_blank>Windows 7 安装与更新问题讨论</a><li><a href=https://zhuanlan.zhihu.com/p/1944089407373149968 rel=noopener target=_blank>Windows 7 更新相关整理</a><li><a href=https://www.catalog.update.microsoft.com/ rel=noopener target=_blank>Microsoft Update Catalog</a><li><a href=https://support.google.com/chrome/a/answer/7100626 rel=noopener target=_blank>Google Chrome 浏览器系统要求</a></ul></div><footer class=post-footer><div class=post-eof></div></footer></article></div><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article><link href=https://nicksxs.me/2026/07/19/%E8%AE%B0%E4%B8%80%E4%B8%AAgiea%E6%8E%A8%E9%80%81%E5%A4%B1%E8%B4%A5%E7%9A%84%E9%97%AE%E9%A2%98/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="learn from zero,技术博客,Nicksxs,史学森" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="undefined | Nicksxs's Blog" itemprop=name><meta itemprop=description></span><header class=post-header><h2 itemprop="name headline" class=post-title><a class=post-title-link href=/2026/07/19/%E8%AE%B0%E4%B8%80%E4%B8%AAgiea%E6%8E%A8%E9%80%81%E5%A4%B1%E8%B4%A5%E7%9A%84%E9%97%AE%E9%A2%98/ itemprop=url>记一个gitea推送失败的问题</a></h2><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2026-07-19 20:53:14" datetime=2026-07-19T20:53:14+08:00>2026-07-19</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/git/ itemprop=url rel=index><span itemprop=name>git</span></a> </span></span><span class="post-meta-item cloudflare-page-counter" data-increment=false data-path=/2026/07/19/%E8%AE%B0%E4%B8%80%E4%B8%AAgiea%E6%8E%A8%E9%80%81%E5%A4%B1%E8%B4%A5%E7%9A%84%E9%97%AE%E9%A2%98/ data-title=记一个gitea推送失败的问题 title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=cloudflare-page-counter-count></span> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-comment"></i> </span><span class=post-meta-item-text>Disqus:</span> <a href=/2026/07/19/%E8%AE%B0%E4%B8%80%E4%B8%AAgiea%E6%8E%A8%E9%80%81%E5%A4%B1%E8%B4%A5%E7%9A%84%E9%97%AE%E9%A2%98/#disqus_thread itemprop=discussionUrl title=disqus><span class="post-comments-count disqus-comment-count" data-disqus-identifier=2026/07/19/记一个giea推送失败的问题/ itemprop=commentCount></span></a></span></div></div></header><div class=post-body itemprop=articleBody><p>我的博客的推送一份是在github<br>作为github page,另一份是在自建的gitea上面<br>上周突然就出现推送失败了<br>在gpt5.5老师的指导下一顿查gitea服务器的日志也没查出来原因<br>一开始说是可以尝试从https切换到ssh形式也没起作用<br>服务端日志也没有用的<br>然后这次升级了gpt5.6老师以后,就再问了下<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></pre><td class=code><pre><span class=line>send-pack: unexpected disconnect while reading sideband packet</span><br><span class=line>Writing objects: 100% (6455/6455), 2.95 MiB | 1.53 MiB/s, done.</span><br><span class=line>Total 6455 (delta 2752), reused 6281 (delta 2580), pack-reused 0</span><br><span class=line>fatal: the remote end hung up unexpectedly</span><br></pre></table></figure><p>这次的日志比较有价值可能<br>另外还有个信息是之前<code>git gc</code>因为仓库的小文件太多了可能<br>然后这次gpt老师给出的方法是<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>http.postBuffer = 16777216</span><br><span class=line>http.version = HTTP/1.1</span><br></pre></table></figure><p>把postBuffer给调大了,然后直接在仓库里执行<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>git -C .deploy_git push --verbose</span><br></pre></table></figure><p>发现直接可以了<br>具体原因分析下来是: 推送产生的请求体约为 2.95 MiB,超过了 Git 默认 1 MiB 的 http.postBuffer。<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></pre><td class=code><pre><span class=line>推送数据 2.95 MiB</span><br><span class=line> ↓ 超过默认缓冲区 1 MiB</span><br><span class=line>Git 改用 chunked 分块传输</span><br><span class=line> ↓</span><br><span class=line>Traefik / Gitea 链路没有正确处理该请求</span><br><span class=line> ↓</span><br><span class=line>返回 HTTP 200,但响应体为 0</span><br><span class=line> ↓</span><br><span class=line>Git 收不到 receive-pack 的 sideband 结果</span><br><span class=line> ↓</span><br><span class=line>unexpected disconnect</span><br></pre></table></figure><p>因为默认的postBuffer不够大,然后使用了chunked传输,是指一块块传,直到最后一块大小为0<br>然后因为我的gitea可能经过了traefik和gitea中间有不支持的<br>关于postBuffer在git官方文档也有解释<h3 id=http-postBuffer><a class=headerlink href=#http-postBuffer title=http.postBuffer></a>http.postBuffer</h3><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></pre><td class=code><pre><span class=line>Maximum size in bytes of the buffer used by smart HTTP transports when POSTing data to the remote system. For requests larger than this buffer size, HTTP/1.1 and Transfer-Encoding: chunked is used to avoid creating a massive pack file locally. Default is 1 MiB, which is sufficient for most requests.</span><br><span class=line></span><br><span class=line>Note that raising this limit is only effective for disabling chunked transfer encoding and therefore should be used only where the remote server or a proxy only supports HTTP/1.0 or is noncompliant with the HTTP standard. Raising this is not, in general, an effective solution for most push problems, but can increase memory consumption significantly since the entire buffer is allocated even for small pushes.</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>Git 使用智能 HTTP 传输协议向远程服务器发送 POST 请求时,所使用缓冲区的最大大小,单位为字节。</span><br><span class=line>当请求大小超过这个缓冲区时,Git 会使用 HTTP/1.1 的 Transfer-Encoding: chunked,也就是分块传输编码,以避免在本地创建一个非常大的 pack 文件。</span><br><span class=line>默认值是 1 MiB,对于大多数请求来说已经足够。</span><br><span class=line>需要注意的是,增大这个值的作用仅仅是停用分块传输编码。因此,只有在远程服务器或中间代理仅支持 HTTP/1.0,或者没有正确遵循 HTTP 标准时,才应该提高这个限制。</span><br><span class=line>一般来说,增大这个值并不能解决大多数 Git 推送问题。同时它还可能显著增加内存占用,因为即使实际推送的数据很小,Git 也会分配完整大小的缓冲区。</span><br></pre></table></figure><p>对于我这样的hexo是在.deploy_git目录里的.git/config文件<br>普通git仓库如果要配置就是在.git/config里进行配置<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 class=code><pre><span class=line>[http]</span><br><span class=line> postBuffer = 16777216</span><br><span class=line> version = HTTP/1.1</span><br></pre></table></figure><p>可以直接改这个文件,也可以用git配置命令<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>git config http.postBuffer 16777216</span><br><span class=line>git config http.version HTTP/1.1</span><br></pre></table></figure><p>这样都可以,在现在这个时代<br>发现这样一个小技巧或者小问题的答案<br>我还是想记录下<br>因为很多的东西感觉已经被GPT这样的颠覆掉了<br>有这些小的问题被解决<br>感觉有种久违的愉悦感<br>排查问题,找到原因是个很有意思的过程<br>并把它解决也是很有意思<br>这个可能就算是初心了吧<br>就能把解决问题的思路方法分享下<br>也是一种比较开始的事情</div><footer class=post-footer><div class=post-eof></div></footer></article></div><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article><link href=https://nicksxs.me/2026/07/12/%E5%AD%A6%E4%B8%80%E4%B8%8Bchrome%E7%9A%84%E6%89%A9%E5%B1%95%E5%BC%80%E5%8F%91/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="learn from zero,技术博客,Nicksxs,史学森" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="undefined | Nicksxs's Blog" itemprop=name><meta itemprop=description></span><header class=post-header><h2 itemprop="name headline" class=post-title><a class=post-title-link href=/2026/07/12/%E5%AD%A6%E4%B8%80%E4%B8%8Bchrome%E7%9A%84%E6%89%A9%E5%B1%95%E5%BC%80%E5%8F%91/ itemprop=url>学一下chrome的扩展开发</a></h2><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2026-07-12 22:18:19" datetime=2026-07-12T22:18:19+08:00>2026-07-12</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/%E5%89%8D%E7%AB%AF/ itemprop=url rel=index><span itemprop=name>前端</span></a> </span></span><span class="post-meta-item cloudflare-page-counter" data-increment=false data-path=/2026/07/12/%E5%AD%A6%E4%B8%80%E4%B8%8Bchrome%E7%9A%84%E6%89%A9%E5%B1%95%E5%BC%80%E5%8F%91/ data-title=学一下chrome的扩展开发 title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=cloudflare-page-counter-count></span> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-comment"></i> </span><span class=post-meta-item-text>Disqus:</span> <a href=/2026/07/12/%E5%AD%A6%E4%B8%80%E4%B8%8Bchrome%E7%9A%84%E6%89%A9%E5%B1%95%E5%BC%80%E5%8F%91/#disqus_thread itemprop=discussionUrl title=disqus><span class="post-comments-count disqus-comment-count" data-disqus-identifier=2026/07/12/学一下chrome的扩展开发/ itemprop=commentCount></span></a></span></div></div></header><div class=post-body itemprop=articleBody><p>最近因为看了下 Chrome 里面置入的小模型,顺手又看了下 Chrome 扩展。<p>之前其实也接触过一些浏览器扩展,但是一直没有特别系统地看过它的结构,感觉好像就是写一个 <code>html</code>,然后再配几个 <code>js</code> 文件就可以了。<p>但是真正写起来以后,会发现 Chrome 扩展里面有几个不同的运行环境,尤其是 <code>popup</code> 和 <code>content script</code>,如果没有分清楚,后面调用网页内容或者操作页面 DOM 的时候就容易有点混乱。<p>这次先做一个比较简单的例子,点击扩展按钮以后,给当前页面加一个背景色,再在右下角显示一个提示。<div class=post-button><a class=btn href=/2026/07/12/%E5%AD%A6%E4%B8%80%E4%B8%8Bchrome%E7%9A%84%E6%89%A9%E5%B1%95%E5%BC%80%E5%8F%91/#more rel=contents>阅读全文 »</a></div></div><footer class=post-footer><div class=post-eof></div></footer></article></div><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article><link href=https://nicksxs.me/2026/07/05/%E7%9C%8B%E4%B8%8Bchrome%E7%9A%84%E5%86%85%E7%BD%AE%E6%A8%A1%E5%9E%8B/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="learn from zero,技术博客,Nicksxs,史学森" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="undefined | Nicksxs's Blog" itemprop=name><meta itemprop=description></span><header class=post-header><h2 itemprop="name headline" class=post-title><a class=post-title-link href=/2026/07/05/%E7%9C%8B%E4%B8%8Bchrome%E7%9A%84%E5%86%85%E7%BD%AE%E6%A8%A1%E5%9E%8B/ itemprop=url>看下chrome的内置模型</a></h2><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2026-07-05 20:52:32 / 修改时间:20:53:17" datetime=2026-07-05T20:52:32+08:00>2026-07-05</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/LLM/ itemprop=url rel=index><span itemprop=name>LLM</span></a> </span></span><span class="post-meta-item cloudflare-page-counter" data-increment=false data-path=/2026/07/05/%E7%9C%8B%E4%B8%8Bchrome%E7%9A%84%E5%86%85%E7%BD%AE%E6%A8%A1%E5%9E%8B/ data-title=看下chrome的内置模型 title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=cloudflare-page-counter-count></span> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-comment"></i> </span><span class=post-meta-item-text>Disqus:</span> <a href=/2026/07/05/%E7%9C%8B%E4%B8%8Bchrome%E7%9A%84%E5%86%85%E7%BD%AE%E6%A8%A1%E5%9E%8B/#disqus_thread itemprop=discussionUrl title=disqus><span class="post-comments-count disqus-comment-count" data-disqus-identifier=2026/07/05/看下chrome的内置模型/ itemprop=commentCount></span></a></span></div></div></header><div class=post-body itemprop=articleBody><p>之前听说chrome在你的电脑里塞了一个小模型,相比于说这个比较容易引起公愤的,可能是因为模型有4g大小,但是感觉也提供了挺大的想象空间<br>比如在浏览器内就可以直接调用模型进行对话处理,最简单的比如做个翻译啥的,<br>首先也有具体的硬件条件,<blockquote><ol><li>系统:Windows 10/11、macOS 13+、Linux、ChromeOS<li>显存:4GB 以上 VRAM,或者 16GB 内存加 4 核 CPU<li>Chrome 版本:138 以上</ol></blockquote><p>然后可以用这个地址看下怎么来开启这个内置模型<br><code>https://prompt-api.com/</code><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></pre><td class=code><pre><span class=line>Open chrome://flags/#optimization-guide-on-device-model and set to Enabled BypassPerfRequirement</span><br><span class=line>Open chrome://flags/#prompt-api-for-gemini-nano and set to Enabled</span><br><span class=line>Open chrome://flags/#prompt-api-for-gemini-nano-multimodal-input and set to Enabled (for image/audio input)</span><br><span class=line>Click Relaunch to restart Chrome</span><br><span class=line>After relaunch, visit chrome://on-device-internals to check model status</span><br></pre></table></figure><p>主要是这些配置项,开启之后可以通过 <code>chrome://on-device-internals</code>当前的状态,比如硬件条件是否符合,我一开始是本地的磁盘空间不够,<br>默认需要有大概22g空间以上才可以下载使用,<table><thead><tr><th>Property<th>Value<tbody><tr><td>device capable<td>true<tr><td>disk space available<td>18671 MiB available, 20480 MiB required<tr><td>enabled by enterprise policy<td>true<tr><td>enabled by feature<td>true<tr><td>enabled by user setting<td>true<tr><td>is already installing<td>true<tr><td>on device feature recently used<td>true<tr><td>out of retention<td>false<tr><td>Detected VRAM (MiB)<td>12288<tr><td>Minimum VRAM required (MiB)<td>0</table><p>符合之后可以在console查看当前的状态,<figure class="highlight js"><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 class=code><pre><span class=line><span class=keyword>await</span> <span class="title class_">LanguageModel</span>.<span class="title function_">availability</span>({</span><br><span class=line> <span class=attr>expectedInputs</span>: [{ <span class=attr>type</span>: <span class=string>'text'</span>, <span class=attr>languages</span>: [<span class=string>'en'</span>] }],</span><br><span class=line> <span class=attr>expectedOutputs</span>: [{ <span class=attr>type</span>: <span class=string>'text'</span>, <span class=attr>languages</span>: [<span class=string>'en'</span>] }]</span><br><span class=line> })</span><br></pre></table></figure><p>比如正在下载会显示<br><code>downloading</code>,可以使用的话就是<code>'available'</code><br>如果想对话尝试下可以在上面的 <code>https://prompt-api.com/</code> 里进行对话体验<br>还有就是这个模型能用来干嘛呢<br>chrome其实提供了一系列的api<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><span class=line>14</span><br></pre><td class=code><pre><span class=line>Translator API</span><br><span class=line>Translator API 可在 Chrome 138 稳定版中使用。根据要求翻译用户生成的内容和动态内容。</span><br><span class=line></span><br><span class=line>使用场景</span><br><span class=line>用户可以使用自己的第一语言输入请求,您可以使用 Language Detector API 识别该语言。然后,使用 Translator API 将请求转换为您的业务运营语言,并将其发送给支持代理。</span><br><span class=line>在社交网络应用中,当用户的时间轴上显示他们不熟悉的语言的帖子时,他们可以按需请求翻译。</span><br><span class=line>Language Detector API</span><br><span class=line>Language Detector API 自 Chrome 138 稳定版开始提供。您可以使用此 API 检测输入文本的语言。 这是翻译流程的关键部分,因为您可能并不总是知道翻译的输入语言。</span><br><span class=line></span><br><span class=line>使用场景</span><br><span class=line>语言检测有多种应用场景:</span><br><span class=line></span><br><span class=line>确定未知源语言,以便将内容翻译为已知目标语言,这样用户就不必同时指定这两种语言。</span><br><span class=line>标记文本,例如,为了改进在线社交网站中的屏幕阅读器发音。</span><br></pre></table></figure><p>比如翻译的,语言检测的等等<br>那比如我们可以自己做个翻译的扩展小工具,既不用联网,也不用科学上网,还是很方便的<br>感觉端侧的小模型还是挺有前景的,随着模型能力越来越强<br>顶尖的模型都很贵,但是很多活其实可以拆分着来<br>最复杂核心的架构分析重构设计需要最好的模型<br>但是常规的一些简单的重复性的工作,一般的小模型也能完成<br>这样就chrome本身这么大的一个入口,带上这么个模型<br>还是非常有想象力的<br>当然这个也是我作为一个软件开发从业者的角度<br>而不是商业思维上来看<br>因为这些看起来都是没啥商业空间<br>毕竟免费的好用的都已经一大堆了,没有啥必要再付费或者订阅啥的<br>只是对于自己使用来说,有时候还是比较方便的<br>就比如这个翻译的场景,能够有个不需要联网,不用科学上网的<br>不过还一点是它现在好像不支持中文<br>所以还可以在期待下后续的发展<br>一个是模型能力的再提升<br>另一个是支持的范围,可以由中文支持这种</div><footer class=post-footer><div class=post-eof></div></footer></article></div><div class=post-block><article class=post-content itemscope itemtype=http://schema.org/Article><link href=https://nicksxs.me/2026/06/28/%E4%BB%8E-app-test-%E5%88%B0%E5%B0%8F%E9%94%81%EF%BC%9A%E6%9C%AC%E5%9C%B0-HTTPS-%E7%9A%84%E5%AE%8C%E6%95%B4%E9%93%BE%E8%B7%AF/ itemprop=mainEntityOfPage><span hidden itemprop=author itemscope itemtype=http://schema.org/Person><meta content=/uploads/avatar.jpg itemprop=image><meta content=Nicksxs itemprop=name></span><span hidden itemprop=publisher itemscope itemtype=http://schema.org/Organization><meta content="Nicksxs's Blog" itemprop=name><meta content="learn from zero,技术博客,Nicksxs,史学森" itemprop=description></span><span hidden itemprop=post itemscope itemtype=http://schema.org/CreativeWork><meta content="undefined | Nicksxs's Blog" itemprop=name><meta itemprop=description></span><header class=post-header><h2 itemprop="name headline" class=post-title><a class=post-title-link href=/2026/06/28/%E4%BB%8E-app-test-%E5%88%B0%E5%B0%8F%E9%94%81%EF%BC%9A%E6%9C%AC%E5%9C%B0-HTTPS-%E7%9A%84%E5%AE%8C%E6%95%B4%E9%93%BE%E8%B7%AF/ itemprop=url>从 app.test 到小锁:valet 本地 HTTPS 的完整链路</a></h2><div class=post-meta-container><div class=post-meta><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-calendar"></i> </span><span class=post-meta-item-text>发表于</span> <time itemprop="dateCreated datePublished" title="创建时间:2026-06-28 22:18:00" datetime=2026-06-28T22:18:00+08:00>2026-06-28</time> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-folder"></i> </span><span class=post-meta-item-text>分类于</span> <span itemprop=about itemscope itemtype=http://schema.org/Thing><a href=/categories/php/ itemprop=url rel=index><span itemprop=name>php</span></a> </span></span><span class="post-meta-item cloudflare-page-counter" data-title="从 app.test 到小锁:valet 本地 HTTPS 的完整链路" data-increment=false data-path=/2026/06/28/%E4%BB%8E-app-test-%E5%88%B0%E5%B0%8F%E9%94%81%EF%BC%9A%E6%9C%AC%E5%9C%B0-HTTPS-%E7%9A%84%E5%AE%8C%E6%95%B4%E9%93%BE%E8%B7%AF/ title=阅读次数><span class=post-meta-item-icon><i class="far fa-eye"></i> </span><span class=post-meta-item-text>阅读次数:</span> <span class=cloudflare-page-counter-count></span> </span><span class=post-meta-item><span class=post-meta-item-icon><i class="far fa-comment"></i> </span><span class=post-meta-item-text>Disqus:</span> <a href=/2026/06/28/%E4%BB%8E-app-test-%E5%88%B0%E5%B0%8F%E9%94%81%EF%BC%9A%E6%9C%AC%E5%9C%B0-HTTPS-%E7%9A%84%E5%AE%8C%E6%95%B4%E9%93%BE%E8%B7%AF/#disqus_thread itemprop=discussionUrl title=disqus><span class="post-comments-count disqus-comment-count" data-disqus-identifier=2026/06/28/从-app-test-到小锁:本地-HTTPS-的完整链路/ itemprop=commentCount></span></a></span></div></div></header><div class=post-body itemprop=articleBody><p>之前在用到valet的时候就觉得这个工具很厉害,因为本地部署很多时候都是比较费劲的,也比较简陋,就直接localhost启动下,但是有时候需要验一下回调的,就需要有域名跟https,当然之前也没想明白的是在本地其实用本地的地址也是可以的,意思就是如果回调地址没限制必须要域名,也可以用127.0.0.1或者localhost,当然如果需要域名和https那就正好是valet的用武之地,所以想把 Valet 这类工具背后的逻辑捋一下。<p>平时用 Valet 的时候体验是非常顺的:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>valet park</span><br><span class=line>valet secure demo</span><br></pre></table></figure><p>然后浏览器里访问:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>https://demo.test</span><br></pre></table></figure><p>就能看到本地项目,而且还有 HTTPS 的小锁。<p>这个体验乍一看很像魔法。一个本地域名,既不用手动写 hosts,也不用自己配 Nginx,还不用去公网 CA 申请证书,怎么就能跑起来了呢?<p>其实拆开之后,Valet 并没有发明什么新协议,它只是把本地开发里几件比较麻烦的事情串起来了:<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></pre><td class=code><pre><span class=line>本地域名解析</span><br><span class=line>Nginx 入口</span><br><span class=line>PHP-FPM 执行 PHP</span><br><span class=line>项目目录映射</span><br><span class=line>本地 CA 和站点证书</span><br><span class=line>框架 driver 判断入口文件</span><br></pre></table></figure><h2 id=先看完整链路><a class=headerlink href=#先看完整链路 title=先看完整链路></a>先看完整链路</h2><p>先放一个整体流程,后面再一段一段拆。<p>假设我们访问:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>https://demo.test/users/1</span><br></pre></table></figure><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><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 class=code><pre><span class=line>浏览器访问 https://demo.test/users/1</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>macOS 发现 .test 域名要走 /etc/resolver/test</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>本地 dnsmasq 把 demo.test 解析到 127.0.0.1</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>请求到达本机 443 端口</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>Nginx 接收请求,并使用 demo.test 的证书完成 HTTPS</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>Nginx 把请求交给 Valet 的 server.php</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>server.php 根据 Host 找到 demo 对应的本地目录</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>Valet driver 判断这是 Laravel 项目</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>静态文件直接返回,非静态请求交给 public/index.php</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>Laravel 应用开始处理路由</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>DNS 负责把域名带到本机</span><br><span class=line>Nginx 负责接住 HTTP/HTTPS 请求</span><br><span class=line>server.php 负责把请求分发到具体项目</span><br><span class=line>driver 负责判断不同框架的入口规则</span><br><span class=line>PHP-FPM 负责真正执行 PHP</span><br><span class=line>本地 CA 负责让浏览器信任本地证书</span><br></pre></table></figure><p>这个结论很重要,因为很多时候我们排查 Valet 问题,就是在判断链路断在哪一层。<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></pre><td class=code><pre><span class=line>域名打不开,可能是 DNS/dnsmasq 问题</span><br><span class=line>80/443 端口不通,可能是 Nginx 问题</span><br><span class=line>项目找不到,可能是 park/link 映射问题</span><br><span class=line>PHP 报错,可能是 PHP-FPM 或项目本身问题</span><br><span class=line>HTTPS 不被信任,可能是证书或系统信任问题</span><br></pre></table></figure><h2 id=Valet-的配置中心><a title="Valet 的配置中心" class=headerlink href=#Valet-的配置中心></a>Valet 的配置中心</h2><p>源码里可以先看 <code>Configuration.php</code>。<p>Valet 会在用户目录下维护一个自己的配置目录:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>~/.config/valet</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>~/.config/valet/config.json</span><br><span class=line>~/.config/valet/Sites</span><br><span class=line>~/.config/valet/Drivers</span><br><span class=line>~/.config/valet/Nginx</span><br><span class=line>~/.config/valet/Log</span><br><span class=line>~/.config/valet/Certificates</span><br><span class=line>~/.config/valet/CA</span><br></pre></table></figure><p>从源码看,<code>Configuration::install()</code> 做的事情就是创建这些基础目录,并确保基础配置存在。<p>其中最核心的是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>config.json</span><br></pre></table></figure><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 class=code><pre><span class=line><span class=punctuation>{</span></span><br><span class=line> <span class=attr>"tld"</span><span class=punctuation>:</span> <span class=string>"test"</span><span class=punctuation>,</span></span><br><span class=line> <span class=attr>"loopback"</span><span class=punctuation>:</span> <span class=string>"127.0.0.1"</span><span class=punctuation>,</span></span><br><span class=line> <span class=attr>"paths"</span><span class=punctuation>:</span> <span class=punctuation>[</span><span class=punctuation>]</span></span><br><span class=line><span class=punctuation>}</span></span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>tld 本地域名后缀,默认是 test</span><br><span class=line>loopback 本地回环地址,默认是 127.0.0.1</span><br><span class=line>paths 被 park 的目录列表</span><br></pre></table></figure><p>所以 Valet 的状态不是凭空来的,它有一个非常明确的落盘位置。<p>比如 <code>valet park</code> 之后,当前目录会被写到 <code>paths</code> 里。<p>比如 <code>valet link demo</code> 之后,<code>~/.config/valet/Sites/demo</code> 里会出现一个指向真实项目目录的符号链接。<p>比如 <code>valet secure demo</code> 之后,证书会进入:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>~/.config/valet/Certificates</span><br></pre></table></figure><p>对应的 Nginx 站点配置会进入:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>~/.config/valet/Nginx</span><br></pre></table></figure><p>理解这个目录很有用,因为后面很多问题都可以直接去这里看。<h2 id=valet-install-大概做了什么><a title="valet install 大概做了什么" class=headerlink href=#valet-install-大概做了什么></a>valet install 大概做了什么</h2><p>我们平时执行:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>valet install</span><br></pre></table></figure><p>它背后不是单纯装一个命令,而是在本机准备一整套本地 Web 开发环境。<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></pre><td class=code><pre><span class=line>Configuration.php 创建 ~/.config/valet 相关目录和 config.json</span><br><span class=line>DnsMasq.php 安装和配置 dnsmasq</span><br><span class=line>Nginx.php 安装和配置 Nginx</span><br><span class=line>PhpFpm.php 配置 PHP-FPM 和 valet.sock</span><br><span class=line>Valet.php 处理 valet 命令本身的链接和 sudoers 等辅助事情</span><br></pre></table></figure><p>这里先不展开 PHP 版本管理和 sudoers 等细节,只看主链路。<p>Valet 安装完成后,本机基本会形成这么一个结构:<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><span class=line>14</span><br><span class=line>15</span><br><span class=line>16</span><br></pre><td class=code><pre><span class=line>macOS DNS</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>dnsmasq</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>127.0.0.1:80 / 127.0.0.1:443</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>Nginx</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>Valet server.php</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>具体项目</span><br></pre></table></figure><p>也就是说,Valet 的轻量不是说它什么服务都没有,而是它复用了本机的 Nginx、PHP-FPM、dnsmasq,不需要为每个项目单独开一个虚拟机或容器。<h2 id=demo-test-为什么会到本机><a title="demo.test 为什么会到本机" class=headerlink href=#demo-test-为什么会到本机></a>demo.test 为什么会到本机</h2><p>先看第一个问题:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>demo.test 怎么就指向 127.0.0.1 了?</span><br></pre></table></figure><p>如果不用 Valet,我们最容易想到的方法是改 <code>/etc/hosts</code>:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>127.0.0.1 demo.test</span><br></pre></table></figure><p>但这样有个问题:每加一个项目,就要写一行。<p>Valet 不这么做。它用的是 <code>dnsmasq</code> 加 macOS resolver。<p>源码里对应的是 <code>DnsMasq.php</code>。<p><code>DnsMasq::install()</code> 的流程大概是:<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></pre><td class=code><pre><span class=line>确保 dnsmasq 已安装</span><br><span class=line>让 dnsmasq 加载额外配置目录</span><br><span class=line>创建 .test 后缀对应的 dnsmasq 配置</span><br><span class=line>创建 /etc/resolver/test</span><br><span class=line>重启 dnsmasq</span><br></pre></table></figure><p>这里有两个关键文件。<p>第一个是类似这样的 dnsmasq 配置:<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></pre><td class=code><pre><span class=line>address=/.test/127.0.0.1</span><br><span class=line>address=/.test/::1</span><br><span class=line>listen-address=127.0.0.1</span><br></pre></table></figure><p>它的意思是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>所有 .test 结尾的域名,都解析到本机</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>demo.test</span><br><span class=line>blog.test</span><br><span class=line>api.test</span><br><span class=line>anything.test</span><br></pre></table></figure><p>第二个是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>/etc/resolver/test</span><br></pre></table></figure><p>内容大概是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>nameserver 127.0.0.1</span><br></pre></table></figure><p>这个文件告诉 macOS:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>遇到 .test 这个后缀,不要走普通 DNS,交给 127.0.0.1 上的 DNS 服务处理</span><br></pre></table></figure><p>然后本地的 dnsmasq 再把它解析回 127.0.0.1。<p>这里要注意一件事:DNS 这一层只解决了“域名到 IP”的问题。<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></pre><td class=code><pre><span class=line>demo.test -> 127.0.0.1</span><br><span class=line>blog.test -> 127.0.0.1</span><br><span class=line>api.test -> 127.0.0.1</span><br></pre></table></figure><p>但是 DNS 并不知道:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>demo.test 对应哪个项目目录</span><br><span class=line>blog.test 对应哪个项目目录</span><br></pre></table></figure><p>这个问题要留给后面的 Nginx 和 Valet 自己处理。<h2 id=请求到本机后谁来接><a class=headerlink href=#请求到本机后谁来接 title=请求到本机后谁来接></a>请求到本机后谁来接</h2><p>域名解析到 127.0.0.1 之后,浏览器会根据协议和端口发请求。<p>如果是 HTTP:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>http://demo.test -> 127.0.0.1:80</span><br></pre></table></figure><p>如果是 HTTPS:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>https://demo.test -> 127.0.0.1:443</span><br></pre></table></figure><p>这时接请求的是 Nginx。<p>源码里对应的是 <code>Nginx.php</code>。<p><code>Nginx::install()</code> 大致做几件事:<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></pre><td class=code><pre><span class=line>确保 Nginx 已安装</span><br><span class=line>写入 nginx.conf</span><br><span class=line>写入 Valet 的 valet.conf</span><br><span class=line>创建 ~/.config/valet/Nginx 目录</span><br></pre></table></figure><p>这里比较关键的是 Valet 的 Nginx 不是简单地给某一个项目写死 root。<p>它会准备一个统一入口,把请求转给 Valet 自己的 <code>server.php</code>。<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></pre><td class=code><pre><span class=line>Nginx 接住所有本地 .test 请求</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>统一转给 Valet server.php</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>server.php 再决定这个请求属于哪个项目</span><br></pre></table></figure><p>这一点是 Valet 设计里比较关键的地方。<p>如果完全靠 Nginx,每个项目都要生成一个完整的 server block:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>demo.test -> /Users/me/code/demo/public</span><br><span class=line>blog.test -> /Users/me/code/blog/public</span><br></pre></table></figure><p>Valet 当然也会为 HTTPS、proxy、PHP 隔离这类特殊场景生成站点配置,但普通场景下,它更核心的思路是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>Nginx 做入口</span><br><span class=line>PHP 层的 server.php 做动态分发</span><br></pre></table></figure><p>这样它才能做到 <code>valet park</code> 之后,目录下面新增一个项目,不需要手工改 Nginx 配置也能访问。<h2 id=park-和-link-到底有什么区别><a title="park 和 link 到底有什么区别" class=headerlink href=#park-和-link-到底有什么区别></a>park 和 link 到底有什么区别</h2><p>接着看项目目录映射。<p>Valet 常用两个命令:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>valet park</span><br><span class=line>valet link</span><br></pre></table></figure><p>它们都能让本地域名对应到项目,但思路不一样。<h3 id=park-是登记一个父目录><a title="park 是登记一个父目录" class=headerlink href=#park-是登记一个父目录></a>park 是登记一个父目录</h3><p>假设我们在这个目录执行:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>cd ~/Sites</span><br><span class=line>valet park</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>~/Sites/demo</span><br><span class=line>~/Sites/blog</span><br><span class=line>~/Sites/shop</span><br></pre></table></figure><p>Valet 就可以让这些域名工作:<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></pre><td class=code><pre><span class=line>demo.test -> ~/Sites/demo</span><br><span class=line>blog.test -> ~/Sites/blog</span><br><span class=line>shop.test -> ~/Sites/shop</span><br></pre></table></figure><p>从配置角度看,<code>park</code> 主要就是把 <code>~/Sites</code> 这个路径写入 <code>config.json</code> 的 <code>paths</code>。<p>之后 Valet 查找站点时,会扫描这些 parked path 下面的子目录。<p>所以 <code>park</code> 的心智模型是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>我把一个工作区交给 Valet</span><br><span class=line>工作区下每个子目录都是一个站点</span><br></pre></table></figure><h3 id=link-是创建一个明确映射><a title="link 是创建一个明确映射" class=headerlink href=#link-是创建一个明确映射></a>link 是创建一个明确映射</h3><p><code>link</code> 更像是给当前目录起一个名字。<p>比如:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>cd ~/code/some-long-project-name</span><br><span class=line>valet link demo</span><br></pre></table></figure><p>它会创建类似这样的符号链接:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>~/.config/valet/Sites/demo -> ~/code/some-long-project-name</span><br></pre></table></figure><p>之后:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>demo.test</span><br></pre></table></figure><p>就对应这个真实项目目录。<p>源码里 <code>Site::link()</code> 做的事情也很直接:<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></pre><td class=code><pre><span class=line>确保 ~/.config/valet/Sites 存在</span><br><span class=line>把 Sites 目录加入配置路径</span><br><span class=line>创建一个符号链接</span><br></pre></table></figure><p>所以可以这样理解:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>park 是目录扫描</span><br><span class=line>link 是显式别名</span><br></pre></table></figure><p>这也是排查时很好用的判断。<p>如果是 <code>park</code> 出来的站点,要看父目录有没有在 <code>config.json</code> 的 <code>paths</code> 里。<p>如果是 <code>link</code> 出来的站点,要看 <code>~/.config/valet/Sites</code> 下的符号链接是否存在、是否指向正确目录。<h2 id=每次请求真正进入的是-server-php><a title="每次请求真正进入的是 server.php" class=headerlink href=#每次请求真正进入的是-server-php></a>每次请求真正进入的是 server.php</h2><p>现在 DNS 和 Nginx 都说完了,请求已经进入 Valet。<p>真正处理请求分发的是仓库根目录下的:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>server.php</span><br></pre></table></figure><p>这段源码非常值得看,因为它基本把 Valet 的请求生命周期写清楚了。<p>它做的事情可以简化成这样:<figure class="highlight php"><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 class=code><pre><span class=line><span class=variable>$config</span> = <span class="title function_ invoke__">read_valet_config</span>();</span><br><span class=line></span><br><span class=line><span class=variable>$uri</span> = <span class="title function_ invoke__">parse_request_uri</span>();</span><br><span class=line><span class=variable>$siteName</span> = <span class="title function_ invoke__">site_name_from_http_host</span>(<span class=variable>$_SERVER</span>[<span class=string>'HTTP_HOST'</span>]);</span><br><span class=line><span class=variable>$sitePath</span> = <span class="title function_ invoke__">find_site_path</span>(<span class=variable>$siteName</span>);</span><br><span class=line></span><br><span class=line><span class=variable>$driver</span> = <span class="title class_">ValetDriver</span>::<span class="title function_ invoke__">assign</span>(<span class=variable>$sitePath</span>, <span class=variable>$siteName</span>, <span class=variable>$uri</span>);</span><br><span class=line></span><br><span class=line><span class=keyword>if</span> (<span class=variable>$driver</span>-><span class="title function_ invoke__">isStaticFile</span>(...)) {</span><br><span class=line> <span class=keyword>return</span> <span class=variable>$driver</span>-><span class="title function_ invoke__">serveStaticFile</span>(...);</span><br><span class=line>}</span><br><span class=line></span><br><span class=line><span class=variable>$frontController</span> = <span class=variable>$driver</span>-><span class="title function_ invoke__">frontControllerPath</span>(...);</span><br><span class=line></span><br><span class=line><span class=keyword>require</span> <span class=variable>$frontController</span>;</span><br></pre></table></figure><p>这里面有几个关键点。<p>第一个,<code>siteName</code> 来自 HTTP Host。<p>访问:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>demo.test</span><br></pre></table></figure><p>Valet 会从 Host 里解析出:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>demo</span><br></pre></table></figure><p>第二个,<code>sitePath</code> 来自 Valet 的站点映射。<p>也就是前面说的:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>parked paths</span><br><span class=line>linked sites</span><br></pre></table></figure><p>第三个,Valet 不会一上来就假设这是 Laravel 项目,而是会通过 driver 选择。<p>这一层是 Valet 能支持很多框架的关键。<h2 id=driver-是-Valet-的框架适配层><a title="driver 是 Valet 的框架适配层" class=headerlink href=#driver-是-Valet-的框架适配层></a>driver 是 Valet 的框架适配层</h2><p>源码里 <code>ValetDriver.php</code> 定义了几个关键方法:<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></pre><td class=code><pre><span class=line>serves()</span><br><span class=line>isStaticFile()</span><br><span class=line>frontControllerPath()</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>这个 driver 能不能处理当前项目?</span><br><span class=line>当前请求是不是静态文件?</span><br><span class=line>如果不是静态文件,入口文件在哪里?</span><br></pre></table></figure><p>比如 Laravel 项目的 driver,也就是 <code>LaravelValetDriver.php</code>,判断逻辑可以概括成:<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></pre><td class=code><pre><span class=line>项目目录下有 public/index.php</span><br><span class=line>并且项目目录下有 artisan</span><br><span class=line>那就认为这是 Laravel 项目</span><br></pre></table></figure><p>这就很符合 Laravel 项目的基本结构。<p>对于静态文件,它会优先找:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>项目目录/public/当前 URI</span><br><span class=line>项目目录/storage/app/public/当前 URI</span><br></pre></table></figure><p>如果请求的是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>/css/app.css</span><br></pre></table></figure><p>并且真实文件存在:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>public/css/app.css</span><br></pre></table></figure><p>那就直接返回静态文件。<p>如果请求的是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>/users/1</span><br></pre></table></figure><p>这显然不是一个真实静态文件,那就交给:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>public/index.php</span><br></pre></table></figure><p>然后 Laravel 自己的路由系统再接着处理。<p>这就是 Valet driver 的意义。<p>Nginx 并不知道 Laravel、WordPress、Statamic、普通 PHP 项目的入口规则有什么区别。Valet 通过 driver 把这些规则封装起来。<p>所以 Valet 支持多框架的本质不是 Nginx 有多聪明,而是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>server.php 统一接管请求</span><br><span class=line>driver 负责识别项目类型和入口文件</span><br></pre></table></figure><h2 id=静态文件为什么不是-PHP-直接读出来><a title="静态文件为什么不是 PHP 直接读出来" class=headerlink href=#静态文件为什么不是-PHP-直接读出来></a>静态文件为什么不是 PHP 直接读出来</h2><p>Valet 处理静态文件还有一个有意思的细节。<p>在 driver 判断某个请求是静态文件后,它不是简单地在 PHP 里 <code>readfile()</code>。<p>Valet driver 会通过一个内部跳转机制,让 Nginx 去返回真实文件。<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></pre><td class=code><pre><span class=line>PHP 判断这是静态文件</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>告诉 Nginx 这个文件在哪里</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>Nginx 直接返回文件</span><br></pre></table></figure><p>这么做的好处是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>PHP 只负责判断逻辑</span><br><span class=line>真正传输文件这种事情交给 Nginx</span><br></pre></table></figure><p>这也符合 Web Server 和应用层的分工。<p>Nginx 擅长处理静态文件,PHP 擅长处理动态逻辑。<h2 id=PHP-FPM-在链路里的位置><a title="PHP-FPM 在链路里的位置" class=headerlink href=#PHP-FPM-在链路里的位置></a>PHP-FPM 在链路里的位置</h2><p>再说一下 PHP-FPM。<p>Nginx 自己不会执行 PHP。<p>当请求需要执行 <code>server.php</code> 或项目的 <code>public/index.php</code> 时,Nginx 会把请求交给 PHP-FPM。<p>Valet 里常见的通信方式是 Unix socket,比如:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>~/.config/valet/valet.sock</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>Nginx 是前台</span><br><span class=line>PHP-FPM 是后厨</span><br><span class=line>server.php 和 Laravel index.php 是菜单上的具体菜</span><br></pre></table></figure><p>Nginx 接到请求后,如果需要执行 PHP,就把请求交给 PHP-FPM。<p>PHP-FPM 执行完 PHP 脚本,再把响应交回给 Nginx,最后返回给浏览器。<p>这也解释了为什么有时候 Valet 域名能解析、Nginx 也启动了,但页面仍然打不开,可能是 PHP-FPM 没起来,或者 socket 路径不对。<h2 id=本地-HTTPS-是怎么来的><a title="本地 HTTPS 是怎么来的" class=headerlink href=#本地-HTTPS-是怎么来的></a>本地 HTTPS 是怎么来的</h2><p>现在再看 HTTPS。<p>问题是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>为什么 https://demo.test 能有小锁?</span><br></pre></table></figure><p>公网 HTTPS 的常见逻辑是:<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></pre><td class=code><pre><span class=line>浏览器内置信任一批根 CA</span><br><span class=line>网站证书由这些 CA 或它们的中间 CA 签发</span><br><span class=line>浏览器验证证书链</span><br><span class=line>验证通过后显示可信</span><br></pre></table></figure><p>本地 HTTPS 没法直接去公网 CA 申请 <code>demo.test</code> 的证书,因为它不是公网真实站点。<p>Valet 的思路是:<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></pre><td class=code><pre><span class=line>自己创建一个本地 CA</span><br><span class=line>把这个本地 CA 加到 macOS 系统信任</span><br><span class=line>再用这个本地 CA 给 demo.test 签发证书</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>Laravel Valet 本地 CA</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>demo.test 站点证书</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>浏览器验证到系统信任的本地 CA,于是显示可信</span><br></pre></table></figure><p>这里最关键的一句话是:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>浏览器信任的不是 demo.test 这个域名本身,而是它的证书链能追溯到系统信任的 CA。</span><br></pre></table></figure><p>所以 Valet 本地 HTTPS 的本质是:<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></pre><td class=code><pre><span class=line>本地生成证书</span><br><span class=line>本地信任证书颁发者</span><br><span class=line>本地 Nginx 使用这张证书</span><br></pre></table></figure><p>这个信任只在你的机器上成立。<p>换一台电脑,如果没有信任你这台机器上的 Valet CA,<code>demo.test</code> 的证书就不会被认为可信。<h2 id=valet-secure-在源码里做了什么><a title="valet secure 在源码里做了什么" class=headerlink href=#valet-secure-在源码里做了什么></a>valet secure 在源码里做了什么</h2><p>源码里 HTTPS 的主线在 <code>Site.php</code>。<p><code>valet secure demo</code> 最终会走到类似 <code>Site::secure()</code> 的逻辑。<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></pre><td class=code><pre><span class=line>保留当前站点可能存在的 PHP 版本隔离配置</span><br><span class=line>确保本地 CA 存在</span><br><span class=line>删除旧的站点证书和旧 Nginx 配置</span><br><span class=line>创建 demo.test 的站点证书</span><br><span class=line>生成 HTTPS Nginx server 配置</span><br><span class=line>写入 ~/.config/valet/Nginx/demo.test</span><br></pre></table></figure><p>里面又可以拆成两个层次。<h3 id=第一层:创建本地-CA><a title="第一层:创建本地 CA" class=headerlink href=#第一层:创建本地-CA></a>第一层:创建本地 CA</h3><p><code>createCa()</code> 负责创建本地 CA。<p>它会生成类似这些文件:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>~/.config/valet/CA/LaravelValetCASelfSigned.pem</span><br><span class=line>~/.config/valet/CA/LaravelValetCASelfSigned.key</span><br></pre></table></figure><p>然后通过 macOS 的 <code>security</code> 命令把这个 CA 加入系统钥匙串。<p>也就是告诉系统:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>这个本地 CA 我信任</span><br></pre></table></figure><p>这是小锁能出现的根本原因。<p>如果这一步失败,比如钥匙串没有信任成功,证书就算生成了,浏览器也可能不认。<h3 id=第二层:给具体站点签证书><a class=headerlink href=#第二层:给具体站点签证书 title=第二层:给具体站点签证书></a>第二层:给具体站点签证书</h3><p><code>createCertificate()</code> 负责给具体域名生成证书。<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></pre><td class=code><pre><span class=line>demo.test.key</span><br><span class=line>demo.test.csr</span><br><span class=line>demo.test.crt</span><br><span class=line>demo.test.conf</span><br></pre></table></figure><p>这些文件放在:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>~/.config/valet/Certificates</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>生成私钥</span><br><span class=line>生成证书签名请求</span><br><span class=line>用本地 CA 签出站点证书</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>CA 私钥 + CA 证书</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>签发 demo.test 证书</span><br></pre></table></figure><p>这和公网证书的逻辑是类似的,只是 CA 从公网机构换成了你本机的本地 CA。<h2 id=secure-之后-Nginx-多了什么><a title="secure 之后 Nginx 多了什么" class=headerlink href=#secure-之后-Nginx-多了什么></a>secure 之后 Nginx 多了什么</h2><p>只生成证书还不够。<p>Nginx 还得知道:<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></pre><td class=code><pre><span class=line>demo.test 用哪张证书</span><br><span class=line>demo.test 用哪把私钥</span><br><span class=line>443 端口怎么监听</span><br><span class=line>HTTP 要不要跳 HTTPS</span><br><span class=line>请求最终仍然转给哪个入口</span><br></pre></table></figure><p><code>Site::buildSecureNginxServer()</code> 会读取 Valet 的 HTTPS Nginx 模板,然后把占位符替换成真实值。<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></pre><td class=code><pre><span class=line>VALET_SITE -> demo.test</span><br><span class=line>VALET_CERT -> ~/.config/valet/Certificates/demo.test.crt</span><br><span class=line>VALET_KEY -> ~/.config/valet/Certificates/demo.test.key</span><br><span class=line>VALET_HOME_PATH</span><br><span class=line>VALET_SERVER_PATH</span><br></pre></table></figure><p>所以 <code>valet secure demo</code> 之后,Nginx 站点配置就会知道:<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></pre><td class=code><pre><span class=line>server_name demo.test</span><br><span class=line>ssl_certificate demo.test.crt</span><br><span class=line>ssl_certificate_key demo.test.key</span><br></pre></table></figure><p>同时它还会保留 Valet 的基本请求分发逻辑:<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></pre><td class=code><pre><span class=line>HTTPS 请求进来</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>Nginx 完成 TLS</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>请求继续交给 Valet server.php</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>server.php 找项目和 driver</span><br></pre></table></figure><p>也就是说,HTTPS 只是入口层多了一次证书和 TLS 处理,后面的项目分发逻辑并没有变。<h2 id=SNI-在这里扮演什么角色><a title="SNI 在这里扮演什么角色" class=headerlink href=#SNI-在这里扮演什么角色></a>SNI 在这里扮演什么角色</h2><p>这里稍微提一下 SNI,但不展开太远。<p>本机可能同时有很多 HTTPS 站点:<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></pre><td class=code><pre><span class=line>demo.test</span><br><span class=line>blog.test</span><br><span class=line>api.test</span><br></pre></table></figure><p>它们都走:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>127.0.0.1:443</span><br></pre></table></figure><p>那 Nginx 怎么知道应该拿哪张证书?<p>客户端在 TLS 握手时会带上要访问的域名,这就是 SNI。<p>Nginx 根据这个域名匹配对应的 server block,然后选择对应证书。<p>所以访问:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>https://demo.test</span><br></pre></table></figure><p>Nginx 会用 <code>demo.test</code> 的证书。<p>访问:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>https://blog.test</span><br></pre></table></figure><p>Nginx 会用 <code>blog.test</code> 的证书。<p>这也是为什么 <code>server_name</code> 和证书配置要对应起来。<h2 id=proxy-又是怎么回事><a title="proxy 又是怎么回事" class=headerlink href=#proxy-又是怎么回事></a>proxy 又是怎么回事</h2><p>Valet 还有一个很实用的功能:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>valet proxy api http://127.0.0.1:3000</span><br></pre></table></figure><p>这个功能不是 PHP 项目映射,而是 Nginx 反向代理。<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></pre><td class=code><pre><span class=line>api.test</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>Nginx</span><br><span class=line> |</span><br><span class=line> v</span><br><span class=line>http://127.0.0.1:3000</span><br></pre></table></figure><p>源码里也在 <code>Site.php</code>,对应 <code>proxyCreate()</code> 这类逻辑。<p>它会生成一份 Nginx proxy 配置,把 <code>api.test</code> 的流量转发到指定地址。<p>如果加上 secure,本质就是:<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></pre><td class=code><pre><span class=line>给 api.test 生成本地证书</span><br><span class=line>Nginx 负责 HTTPS</span><br><span class=line>后端仍然代理到 127.0.0.1:3000</span><br></pre></table></figure><p>所以 Valet 不只能服务 Laravel 或 PHP 项目。对于 Node、Go、Docker 暴露出来的本地端口,Valet 也可以提供一个统一的本地域名和本地 HTTPS 入口。<h2 id=从源码角度再串一次><a class=headerlink href=#从源码角度再串一次 title=从源码角度再串一次></a>从源码角度再串一次</h2><p>现在把源码文件和职责再汇总一下。<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>Configuration.php</span><br></pre></table></figure><p>负责 Valet 自己的配置目录和 <code>config.json</code>:<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></pre><td class=code><pre><span class=line>tld</span><br><span class=line>loopback</span><br><span class=line>paths</span><br></pre></table></figure><figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>DnsMasq.php</span><br></pre></table></figure><p>负责 <code>.test</code> 这类本地域名后缀:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br><span class=line>2</span><br></pre><td class=code><pre><span class=line>/etc/resolver/test</span><br><span class=line>dnsmasq address=/.test/127.0.0.1</span><br></pre></table></figure><figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>Nginx.php</span><br></pre></table></figure><p>负责安装和写入 Valet 的 Nginx 配置:<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></pre><td class=code><pre><span class=line>nginx.conf</span><br><span class=line>valet.conf</span><br><span class=line>~/.config/valet/Nginx</span><br></pre></table></figure><figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>Site.php</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>link</span><br><span class=line>secure</span><br><span class=line>unsecure</span><br><span class=line>proxy</span><br><span class=line>证书路径</span><br><span class=line>Nginx 站点配置生成</span><br></pre></table></figure><figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>server.php</span><br></pre></table></figure><p>是每次请求真正进入的 Valet 分发入口:<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></pre><td class=code><pre><span class=line>解析 Host</span><br><span class=line>找到 site path</span><br><span class=line>选择 driver</span><br><span class=line>判断静态文件</span><br><span class=line>加载 front controller</span><br></pre></table></figure><figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>ValetDriver.php</span><br></pre></table></figure><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></pre><td class=code><pre><span class=line>serves()</span><br><span class=line>isStaticFile()</span><br><span class=line>frontControllerPath()</span><br></pre></table></figure><figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>LaravelValetDriver.php</span><br></pre></table></figure><p>是 Laravel 项目的具体规则:<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></pre><td class=code><pre><span class=line>有 artisan</span><br><span class=line>有 public/index.php</span><br><span class=line>静态文件从 public 或 storage/app/public 找</span><br><span class=line>动态请求交给 public/index.php</span><br></pre></table></figure><p>这几个文件串起来,就能看到 Valet 的整体设计。<h2 id=排查问题时可以顺着这条链路看><a class=headerlink href=#排查问题时可以顺着这条链路看 title=排查问题时可以顺着这条链路看></a>排查问题时可以顺着这条链路看</h2><p>理解了链路之后,排查也会清楚很多。<h3 id=1-域名有没有解析到本机><a title="1. 域名有没有解析到本机" class=headerlink href=#1-域名有没有解析到本机></a>1. 域名有没有解析到本机</h3><p>可以看:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>cat /etc/resolver/test</span><br></pre></table></figure><p>以及:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>cat ~/.config/valet/dnsmasq.d/tld-test.conf</span><br></pre></table></figure><p>如果这里不对,问题通常还没到 Nginx。<h3 id=2-Valet-配置里有没有这个路径><a title="2. Valet 配置里有没有这个路径" class=headerlink href=#2-Valet-配置里有没有这个路径></a>2. Valet 配置里有没有这个路径</h3><p>可以看:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>cat ~/.config/valet/config.json</span><br></pre></table></figure><p>如果是 <code>park</code>,看 <code>paths</code> 里有没有父目录。<p>如果是 <code>link</code>,看:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>ls -l ~/.config/valet/Sites</span><br></pre></table></figure><h3 id=3-Nginx-配置有没有生成><a title="3. Nginx 配置有没有生成" class=headerlink href=#3-Nginx-配置有没有生成></a>3. Nginx 配置有没有生成</h3><p>可以看:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>ls ~/.config/valet/Nginx</span><br></pre></table></figure><p>如果是 secure 或 proxy 站点,这里一般会有对应配置。<h3 id=4-证书是否存在><a title="4. 证书是否存在" class=headerlink href=#4-证书是否存在></a>4. 证书是否存在</h3><p>可以看:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>ls ~/.config/valet/Certificates</span><br></pre></table></figure><p>也可以检查本地 CA:<figure class="highlight shell"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>ls ~/.config/valet/CA</span><br></pre></table></figure><h3 id=5-PHP-FPM-是否正常><a title="5. PHP-FPM 是否正常" class=headerlink href=#5-PHP-FPM-是否正常></a>5. PHP-FPM 是否正常</h3><p>如果 Nginx 能接请求,但 PHP 执行异常,就要看 PHP-FPM 和 socket。<p>Valet 的 Nginx 配置会把 PHP 请求交给类似:<figure class="highlight text"><table><tr><td class=gutter><pre><span class=line>1</span><br></pre><td class=code><pre><span class=line>~/.config/valet/valet.sock</span><br></pre></table></figure><p>如果这个 socket 不存在,或者 PHP-FPM 没启动,就会出问题。<h2 id=最后总结一下><a class=headerlink href=#最后总结一下 title=最后总结一下></a>最后总结一下</h2><p>Valet 的原理可以压缩成一句话:<blockquote><p>Valet 用 dnsmasq 解决本地域名,用 Nginx 接住请求,用 server.php 和 driver 找到真正项目入口,用 PHP-FPM 执行 PHP,再用本地 CA 和 Nginx 配置补上 HTTPS。</blockquote><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></pre><td class=code><pre><span class=line>不用每个项目手写 hosts</span><br><span class=line>不用每个项目手写 Nginx 配置</span><br><span class=line>不用自己维护 PHP-FPM socket</span><br><span class=line>不用自己生成和信任本地证书</span><br><span class=line>不用每个项目单独起一套容器或虚拟机</span><br></pre></table></figure><p>如果只看表面,Valet 好像只是让 <code>demo.test</code> 能访问。<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></pre><td class=code><pre><span class=line>所有本地域名都先进本机</span><br><span class=line>所有请求都先进 Nginx</span><br><span class=line>所有动态判断都进 Valet server.php</span><br><span class=line>所有框架差异都交给 driver</span><br><span class=line>所有 PHP 执行都交给 PHP-FPM</span><br><span class=line>所有 HTTPS 信任都交给本地 CA</span><br></pre></table></figure><p>理解这一点之后,再看 <code>valet park</code>、<code>valet link</code>、<code>valet secure</code>、<code>valet proxy</code>,就不太像魔法了。<p>它们只是分别在这条链路上改了一小段配置。<h2 id=参考><a class=headerlink href=#参考 title=参考></a>参考</h2><ul><li><a href=https://laravel.com/docs/12.x/valet rel=noopener target=_blank>Laravel Valet 官方文档</a><li><a href=https://github.com/laravel/valet rel=noopener target=_blank>laravel/valet GitHub 仓库</a><li><a href=https://github.com/laravel/valet/blob/master/server.php rel=noopener target=_blank>server.php</a><li><a href=https://github.com/laravel/valet/blob/master/cli/Valet/Site.php rel=noopener target=_blank>Site.php</a><li><a href=https://github.com/laravel/valet/blob/master/cli/Valet/DnsMasq.php rel=noopener target=_blank>DnsMasq.php</a><li><a href=https://github.com/laravel/valet/blob/master/cli/Valet/Nginx.php rel=noopener target=_blank>Nginx.php</a><li><a href=https://github.com/laravel/valet/blob/master/cli/Valet/Drivers/ValetDriver.php rel=noopener target=_blank>ValetDriver.php</a></ul></div><footer class=post-footer><div class=post-eof></div></footer></article></div><nav class=pagination><span class="page-number current">1</span><a class=page-number href=/page/2/>2</a><span class=space>…</span><a class=page-number href=/page/77/>77</a><a class="extend next" aria-label=下一页 href=/page/2/ rel=next title=下一页><i class="fa fa-angle-right"></i></a></nav></div></main><footer class=footer><div class=footer-inner><div class=beian><a href=https://beian.miit.gov.cn/ rel=noopener target=_blank>浙ICP备16002580号-2 </a><img alt src=/uploads/beian.png><a href=https://beian.mps.gov.cn/#/query/webSearch?code=33010602011094 rel=noopener target=_blank>浙公网安备 33010602011094号</a></div><div class=copyright>© <span itemprop=copyrightYear>2026</span><span class=with-love><i class="fa fa-heart"></i> </span><span class=author itemprop=copyrightHolder>Nicksxs</span></div><div class=busuanzi-count><span class=post-meta-item id=busuanzi_container_site_uv><span class=post-meta-item-icon><i class="fa fa-user"></i> </span><span class=site-uv title=总访客量><span id=busuanzi_value_site_uv></span> </span></span><span class=post-meta-item id=busuanzi_container_site_pv><span class=post-meta-item-icon><i class="fa fa-eye"></i> </span><span class=site-pv title=总访问量><span id=busuanzi_value_site_pv></span></span></span></div></div></footer><div aria-label=返回顶部 class=back-to-top role=button><i class="fa fa-arrow-up fa-lg"></i><span>0%</span></div><a aria-label="在 GitHub 上关注我" title="在 GitHub 上关注我" class=github-corner href=https://github.com/nicksxs rel=noopener target=_blank><svg viewbox="0 0 250 250" aria-hidden=true height=80 width=80><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" style="transform-origin:130px 106px" class=octo-arm fill=currentColor></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" class=octo-body fill=currentColor></path></svg></a><noscript><div class=noscript-warning>Theme NexT works best with JavaScript enabled</div></noscript><script crossorigin=anonymous integrity=sha256-XL2inqUJaslATFnHdJOi9GfQ60on8Wx1C2H8DYiN1xY= src=https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js></script><script crossorigin=anonymous integrity=sha256-ytMJGN3toR+a84u7g7NuHm91VIR06Q41kMWDr2pq7Zo= src=https://cdnjs.cloudflare.com/ajax/libs/fancyapps-ui/5.0.28/fancybox/fancybox.umd.js></script><script crossorigin=anonymous integrity=sha256-mOFREFhqmHeQbXpK2lp4nA3qooVgACfh88fpJftLBbc= src=https://cdnjs.cloudflare.com/ajax/libs/lozad.js/1.16.0/lozad.min.js></script><script src=/js/comments.js></script><script src=/js/utils.js></script><script src=/js/motion.js></script><script src=/js/next-boot.js></script><script crossorigin=anonymous integrity=sha256-DABVk+hYj0mdUzo+7ViJC6cwLahQIejFvC+my2M/wfM= src=https://cdnjs.cloudflare.com/ajax/libs/algoliasearch/4.20.0/algoliasearch-lite.umd.js></script><script crossorigin=anonymous integrity=sha256-9242vN47QUX50UG5Gf5XDO1YREWCEJRyXHofh5fsl24= src=https://cdnjs.cloudflare.com/ajax/libs/instantsearch.js/4.60.0/instantsearch.production.min.js></script><script src=/js/third-party/search/algolia-search.js></script><script src=/js/third-party/fancybox.js></script><script async src=https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js></script><script crossorigin=anonymous integrity=sha256-yvJQOINiH9fWemHn0vCA5lsHWJaHs6/ZmO+1Ft04SvM= src=https://cdnjs.cloudflare.com/ajax/libs/quicklink/2.3.0/quicklink.umd.js></script><script class=next-config data-name=quicklink type=application/json>{"enable":false,"home":false,"archive":false,"delay":true,"timeout":3000,"priority":true,"url":"https://nicksxs.me/"}</script><script src=/js/third-party/quicklink.js></script><script data-cloudflare-page-counter-config type=application/json>{"enable":true,"endpoint":"https://counter.nicksxs.me","site":"nicksxs","local_cache":true,"timeout":5000,"icon":"far fa-eye","item_text":true}</script><script src=/js/cloudflare-page-counter.js></script><script class=next-config data-name=disqus type=application/json>{"enable":true,"shortname":"nicksxs","count":true,"i18n":{"disqus":"disqus"}}</script><script src=/js/third-party/comments/disqus.js></script>
|