From c470a4504e08b04a868b402e58453cab62dcf524 Mon Sep 17 00:00:00 2001 From: Auto Doc Publisher Date: Thu, 11 Jul 2019 07:37:23 +0000 Subject: [PATCH 01/11] docs: auto generate by ci chore: deploy to master branch (#7) --- .gitignore | 3 - 404.html | 17 ++ README.md | 6 - assets/css/0.styles.5203916d.css | 1 + assets/img/middleware.5fabc0c7.gif | Bin 0 -> 800441 bytes assets/img/onion.2972bdca.png | Bin 0 -> 22392 bytes assets/img/search.83621669.svg | 1 + assets/img/todomvc.c40dbbd3.png | Bin 0 -> 22202 bytes assets/js/10.c3972b62.js | 1 + assets/js/11.e9585f68.js | 1 + assets/js/12.3815fe1e.js | 1 + assets/js/13.5e06d49d.js | 1 + assets/js/14.3b6fddd8.js | 1 + assets/js/15.494082f6.js | 1 + assets/js/16.b546ed30.js | 1 + assets/js/17.97cab730.js | 1 + assets/js/18.bbddf561.js | 1 + assets/js/19.1c667745.js | 1 + assets/js/2.7bd862e7.js | 1 + assets/js/20.79b3431f.js | 1 + assets/js/21.4be0f224.js | 1 + assets/js/22.36f8502b.js | 1 + assets/js/23.4a400231.js | 1 + assets/js/24.e0d88e74.js | 1 + assets/js/25.e176e978.js | 1 + assets/js/26.e04f1b85.js | 1 + assets/js/27.46820c07.js | 1 + assets/js/28.56abde8a.js | 1 + assets/js/29.4e4c2114.js | 1 + assets/js/3.e44b477d.js | 1 + assets/js/30.dcc0f8a4.js | 1 + assets/js/31.7a987d06.js | 1 + assets/js/32.28875310.js | 1 + assets/js/4.d58994b3.js | 1 + assets/js/5.10b31697.js | 1 + assets/js/6.3a86b35b.js | 1 + assets/js/7.bb785290.js | 1 + assets/js/8.80b3eee6.js | 1 + assets/js/9.3f5434be.js | 1 + assets/js/app.317272b7.js | 14 + guide/index.html | 17 ++ icon.svg | 94 ++++++ index.html | 21 ++ quickstart/egg.html | 21 ++ quickstart/index.html | 21 ++ zh/guide/application.html | 81 +++++ zh/guide/config.html | 79 +++++ zh/guide/context.html | 277 +++++++++++++++++ zh/guide/controller.html | 229 ++++++++++++++ zh/guide/cookie.html | 76 +++++ zh/guide/directory.html | 49 +++ zh/guide/error_handler.html | 167 +++++++++++ zh/guide/faq.html | 33 ++ zh/guide/helper.html | 68 +++++ zh/guide/httpclient.html | 465 +++++++++++++++++++++++++++++ zh/guide/i18n.html | 88 ++++++ zh/guide/index.html | 21 ++ zh/guide/lifecycle.html | 147 +++++++++ zh/guide/logger.html | 163 ++++++++++ zh/guide/middleware.html | 133 +++++++++ zh/guide/plugin.html | 96 ++++++ zh/guide/router.html | 154 ++++++++++ zh/guide/service.html | 65 ++++ zh/guide/session.html | 84 ++++++ zh/guide/upload.html | 194 ++++++++++++ zh/index.html | 21 ++ zh/quickstart/egg.html | 158 ++++++++++ zh/quickstart/index.html | 21 ++ 68 files changed, 3107 insertions(+), 9 deletions(-) delete mode 100644 .gitignore create mode 100644 404.html delete mode 100644 README.md create mode 100644 assets/css/0.styles.5203916d.css create mode 100644 assets/img/middleware.5fabc0c7.gif create mode 100644 assets/img/onion.2972bdca.png create mode 100644 assets/img/search.83621669.svg create mode 100644 assets/img/todomvc.c40dbbd3.png create mode 100644 assets/js/10.c3972b62.js create mode 100644 assets/js/11.e9585f68.js create mode 100644 assets/js/12.3815fe1e.js create mode 100644 assets/js/13.5e06d49d.js create mode 100644 assets/js/14.3b6fddd8.js create mode 100644 assets/js/15.494082f6.js create mode 100644 assets/js/16.b546ed30.js create mode 100644 assets/js/17.97cab730.js create mode 100644 assets/js/18.bbddf561.js create mode 100644 assets/js/19.1c667745.js create mode 100644 assets/js/2.7bd862e7.js create mode 100644 assets/js/20.79b3431f.js create mode 100644 assets/js/21.4be0f224.js create mode 100644 assets/js/22.36f8502b.js create mode 100644 assets/js/23.4a400231.js create mode 100644 assets/js/24.e0d88e74.js create mode 100644 assets/js/25.e176e978.js create mode 100644 assets/js/26.e04f1b85.js create mode 100644 assets/js/27.46820c07.js create mode 100644 assets/js/28.56abde8a.js create mode 100644 assets/js/29.4e4c2114.js create mode 100644 assets/js/3.e44b477d.js create mode 100644 assets/js/30.dcc0f8a4.js create mode 100644 assets/js/31.7a987d06.js create mode 100644 assets/js/32.28875310.js create mode 100644 assets/js/4.d58994b3.js create mode 100644 assets/js/5.10b31697.js create mode 100644 assets/js/6.3a86b35b.js create mode 100644 assets/js/7.bb785290.js create mode 100644 assets/js/8.80b3eee6.js create mode 100644 assets/js/9.3f5434be.js create mode 100644 assets/js/app.317272b7.js create mode 100644 guide/index.html create mode 100644 icon.svg create mode 100644 index.html create mode 100644 quickstart/egg.html create mode 100644 quickstart/index.html create mode 100644 zh/guide/application.html create mode 100644 zh/guide/config.html create mode 100644 zh/guide/context.html create mode 100644 zh/guide/controller.html create mode 100644 zh/guide/cookie.html create mode 100644 zh/guide/directory.html create mode 100644 zh/guide/error_handler.html create mode 100644 zh/guide/faq.html create mode 100644 zh/guide/helper.html create mode 100644 zh/guide/httpclient.html create mode 100644 zh/guide/i18n.html create mode 100644 zh/guide/index.html create mode 100644 zh/guide/lifecycle.html create mode 100644 zh/guide/logger.html create mode 100644 zh/guide/middleware.html create mode 100644 zh/guide/plugin.html create mode 100644 zh/guide/router.html create mode 100644 zh/guide/service.html create mode 100644 zh/guide/session.html create mode 100644 zh/guide/upload.html create mode 100644 zh/index.html create mode 100644 zh/quickstart/egg.html create mode 100644 zh/quickstart/index.html diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 74a6311..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -checklist -node_modules/ -yarn.lock diff --git a/404.html b/404.html new file mode 100644 index 0000000..1e2e361 --- /dev/null +++ b/404.html @@ -0,0 +1,17 @@ + + + + + + Egg + + + + + + + +

404

There's nothing here.
Take me home.
+ + + diff --git a/README.md b/README.md deleted file mode 100644 index 9ee19e2..0000000 --- a/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# eggjs.github.io - -```bash -$ npm install -$ npm run dev -``` diff --git a/assets/css/0.styles.5203916d.css b/assets/css/0.styles.5203916d.css new file mode 100644 index 0000000..321ab2a --- /dev/null +++ b/assets/css/0.styles.5203916d.css @@ -0,0 +1 @@ +#nprogress{pointer-events:none}#nprogress .bar{background:#3eaf7c;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #3eaf7c,0 0 5px #3eaf7c;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border-color:#3eaf7c transparent transparent #3eaf7c;border-style:solid;border-width:2px;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.icon.outbound{color:#aaa;display:inline-block;vertical-align:middle;position:relative;top:-1px}.home{padding:3.6rem 2rem 0;max-width:960px;margin:0 auto;display:block}.home .hero{text-align:center}.home .hero img{max-width:100%;max-height:280px;display:block;margin:3rem auto 1.5rem}.home .hero h1{font-size:3rem}.home .hero .action,.home .hero .description,.home .hero h1{margin:1.8rem auto}.home .hero .description{max-width:35rem;font-size:1.6rem;line-height:1.3;color:#6a8bad}.home .hero .action-button{display:inline-block;font-size:1.2rem;color:#fff;background-color:#3eaf7c;padding:.8rem 1.6rem;border-radius:4px;transition:background-color .1s ease;box-sizing:border-box;border-bottom:1px solid #389d70}.home .hero .action-button:hover{background-color:#4abf8a}.home .features{border-top:1px solid #eaecef;padding:1.2rem 0;margin-top:2.5rem;display:flex;flex-wrap:wrap;align-items:flex-start;align-content:stretch;justify-content:space-between}.home .feature{flex-grow:1;flex-basis:30%;max-width:30%}.home .feature h2{font-size:1.4rem;font-weight:500;border-bottom:none;padding-bottom:0;color:#3a5169}.home .feature p{color:#4e6e8e}.home .footer{padding:2.5rem;border-top:1px solid #eaecef;text-align:center;color:#4e6e8e}@media (max-width:719px){.home .features{flex-direction:column}.home .feature{max-width:100%;padding:0 2.5rem}}@media (max-width:419px){.home{padding-left:1.5rem;padding-right:1.5rem}.home .hero img{max-height:210px;margin:2rem auto 1.2rem}.home .hero h1{font-size:2rem}.home .hero .action,.home .hero .description,.home .hero h1{margin:1.2rem auto}.home .hero .description{font-size:1.2rem}.home .hero .action-button{font-size:1rem;padding:.6rem 1.2rem}.home .feature h2{font-size:1.25rem}}.search-box{display:inline-block;position:relative;margin-right:1rem}.search-box input{cursor:text;width:10rem;height:2rem;color:#4e6e8e;display:inline-block;border:1px solid #cfd4db;border-radius:2rem;font-size:.9rem;line-height:2rem;padding:0 .5rem 0 2rem;outline:none;transition:all .2s ease;background:#fff url(http://github.com/assets/img/search.83621669.svg) .6rem .5rem no-repeat;background-size:1rem}.search-box input:focus{cursor:auto;border-color:#3eaf7c}.search-box .suggestions{background:#fff;width:20rem;position:absolute;top:1.5rem;border:1px solid #cfd4db;border-radius:6px;padding:.4rem;list-style-type:none}.search-box .suggestions.align-right{right:0}.search-box .suggestion{line-height:1.4;padding:.4rem .6rem;border-radius:4px;cursor:pointer}.search-box .suggestion a{white-space:normal;color:#5d82a6}.search-box .suggestion a .page-title{font-weight:600}.search-box .suggestion a .header{font-size:.9em;margin-left:.25em}.search-box .suggestion.focused{background-color:#f3f4f5}.search-box .suggestion.focused a{color:#3eaf7c}@media (max-width:959px){.search-box input{cursor:pointer;width:0;border-color:transparent;position:relative}.search-box input:focus{cursor:text;left:0;width:10rem}}@media (-ms-high-contrast:none){.search-box input{height:2rem}}@media (max-width:959px) and (min-width:719px){.search-box .suggestions{left:0}}@media (max-width:719px){.search-box{margin-right:0}.search-box input{left:1rem}.search-box .suggestions{right:0}}@media (max-width:419px){.search-box .suggestions{width:calc(100vw - 4rem)}.search-box input:focus{width:8rem}}.sidebar-button{cursor:pointer;display:none;width:1.25rem;height:1.25rem;position:absolute;padding:.6rem;top:.6rem;left:1rem}.sidebar-button .icon{display:block;width:1.25rem;height:1.25rem}@media (max-width:719px){.sidebar-button{display:block}}.dropdown-enter,.dropdown-leave-to{height:0!important}.dropdown-wrapper{cursor:pointer}.dropdown-wrapper .dropdown-title{display:block}.dropdown-wrapper .dropdown-title:hover{border-color:transparent}.dropdown-wrapper .dropdown-title .arrow{vertical-align:middle;margin-top:-1px;margin-left:.4rem}.dropdown-wrapper .nav-dropdown .dropdown-item{color:inherit;line-height:1.7rem}.dropdown-wrapper .nav-dropdown .dropdown-item h4{margin:.45rem 0 0;border-top:1px solid #eee;padding:.45rem 1.5rem 0 1.25rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper{padding:0;list-style:none}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper .dropdown-subitem{font-size:.9em}.dropdown-wrapper .nav-dropdown .dropdown-item a{display:block;line-height:1.7rem;position:relative;border-bottom:none;font-weight:400;margin-bottom:0;padding:0 1.5rem 0 1.25rem}.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active,.dropdown-wrapper .nav-dropdown .dropdown-item a:hover{color:#3eaf7c}.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active:after{content:"";width:0;height:0;border-left:5px solid #3eaf7c;border-top:3px solid transparent;border-bottom:3px solid transparent;position:absolute;top:calc(50% - 2px);left:9px}.dropdown-wrapper .nav-dropdown .dropdown-item:first-child h4{margin-top:0;padding-top:0;border-top:0}@media (max-width:719px){.dropdown-wrapper.open .dropdown-title{margin-bottom:.5rem}.dropdown-wrapper .nav-dropdown{transition:height .1s ease-out;overflow:hidden}.dropdown-wrapper .nav-dropdown .dropdown-item h4{border-top:0;margin-top:0;padding-top:0}.dropdown-wrapper .nav-dropdown .dropdown-item>a,.dropdown-wrapper .nav-dropdown .dropdown-item h4{font-size:15px;line-height:2rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem{font-size:14px;padding-left:1rem}}@media (min-width:719px){.dropdown-wrapper{height:1.8rem}.dropdown-wrapper:hover .nav-dropdown{display:block!important}.dropdown-wrapper .dropdown-title .arrow{border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid #ccc;border-bottom:0}.dropdown-wrapper .nav-dropdown{display:none;height:auto!important;box-sizing:border-box;max-height:calc(100vh - 2.7rem);overflow-y:auto;position:absolute;top:100%;right:0;background-color:#fff;padding:.6rem 0;border:1px solid;border-color:#ddd #ddd #ccc;text-align:left;border-radius:.25rem;white-space:nowrap;margin:0}}.nav-links{display:inline-block}.nav-links a{line-height:1.4rem;color:inherit}.nav-links a.router-link-active,.nav-links a:hover{color:#3eaf7c}.nav-links .nav-item{position:relative;display:inline-block;margin-left:1.5rem;line-height:2rem}.nav-links .nav-item:first-child{margin-left:0}.nav-links .repo-link{margin-left:1.5rem}@media (max-width:719px){.nav-links .nav-item,.nav-links .repo-link{margin-left:0}}@media (min-width:719px){.nav-links a.router-link-active,.nav-links a:hover{color:#2c3e50}.nav-item>a:not(.external).router-link-active,.nav-item>a:not(.external):hover{margin-bottom:-2px;border-bottom:2px solid #46bd87}}.navbar{padding:.7rem 1.5rem;line-height:2.2rem}.navbar a,.navbar img,.navbar span{display:inline-block}.navbar .logo{height:2.2rem;min-width:2.2rem;margin-right:.8rem;vertical-align:top}.navbar .site-name{font-size:1.3rem;font-weight:600;color:#2c3e50;position:relative}.navbar .links{padding-left:1.5rem;box-sizing:border-box;background-color:#fff;white-space:nowrap;font-size:.9rem;position:absolute;right:1.5rem;top:.7rem;display:flex}.navbar .links .search-box{flex:0 0 auto;vertical-align:top}@media (max-width:719px){.navbar{padding-left:4rem}.navbar .can-hide{display:none}.navbar .links{padding-left:1.5rem}}.page-edit,.page-nav{max-width:740px;margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.page-edit,.page-nav{padding:2rem}}@media (max-width:419px){.page-edit,.page-nav{padding:1.5rem}}.page{padding-bottom:2rem;display:block}.page-edit{padding-top:1rem;padding-bottom:1rem;overflow:auto}.page-edit .edit-link{display:inline-block}.page-edit .edit-link a{color:#4e6e8e;margin-right:.25rem}.page-edit .last-updated{float:right;font-size:.9em}.page-edit .last-updated .prefix{font-weight:500;color:#4e6e8e}.page-edit .last-updated .time{font-weight:400;color:#aaa}.page-nav{padding-top:1rem;padding-bottom:0}.page-nav .inner{min-height:2rem;margin-top:0;border-top:1px solid #eaecef;padding-top:1rem;overflow:auto}.page-nav .next{float:right}@media (max-width:719px){.page-edit .edit-link{margin-bottom:.5rem}.page-edit .last-updated{font-size:.8em;float:none;text-align:left}}.sidebar-group .sidebar-group{padding-left:.5em}.sidebar-group:not(.collapsable) .sidebar-heading:not(.clickable){cursor:auto;color:inherit}.sidebar-group.is-sub-group{padding-left:0}.sidebar-group.is-sub-group>.sidebar-heading{font-size:.95em;line-height:1.4;font-weight:400;padding-left:2rem}.sidebar-group.is-sub-group>.sidebar-heading:not(.clickable){opacity:.5}.sidebar-group.is-sub-group>.sidebar-group-items{padding-left:1rem}.sidebar-group.is-sub-group>.sidebar-group-items>li>.sidebar-link{font-size:.95em;border-left:none}.sidebar-group.depth-2>.sidebar-heading{border-left:none}.sidebar-heading{color:#2c3e50;transition:color .15s ease;cursor:pointer;font-size:1.1em;font-weight:700;padding:.35rem 1.5rem .35rem 1.25rem;width:100%;box-sizing:border-box;margin:0;border-left:.25rem solid transparent}.sidebar-heading.open,.sidebar-heading:hover{color:inherit}.sidebar-heading .arrow{position:relative;top:-.12em;left:.5em}.sidebar-heading.clickable.active{font-weight:600;color:#3eaf7c;border-left-color:#3eaf7c}.sidebar-heading.clickable:hover{color:#3eaf7c}.sidebar-group-items{transition:height .1s ease-out;font-size:.95em;overflow:hidden}.sidebar .sidebar-sub-headers{padding-left:1rem;font-size:.95em}a.sidebar-link{font-size:1em;font-weight:400;display:inline-block;color:#2c3e50;border-left:.25rem solid transparent;padding:.35rem 1rem .35rem 1.25rem;line-height:1.4;width:100%;box-sizing:border-box}a.sidebar-link:hover{color:#3eaf7c}a.sidebar-link.active{font-weight:600;color:#3eaf7c;border-left-color:#3eaf7c}.sidebar-group a.sidebar-link{padding-left:2rem}.sidebar-sub-headers a.sidebar-link{padding-top:.25rem;padding-bottom:.25rem;border-left:none}.sidebar-sub-headers a.sidebar-link.active{font-weight:500}.sidebar ul{padding:0;margin:0;list-style-type:none}.sidebar a{display:inline-block}.sidebar .nav-links{display:none;border-bottom:1px solid #eaecef;padding:.5rem 0 .75rem}.sidebar .nav-links a{font-weight:600}.sidebar .nav-links .nav-item,.sidebar .nav-links .repo-link{display:block;line-height:1.25rem;font-size:1.1em;padding:.5rem 0 .5rem 1.5rem}.sidebar>.sidebar-links{padding:1.5rem 0}.sidebar>.sidebar-links>li>a.sidebar-link{font-size:1.1em;line-height:1.7;font-weight:700}.sidebar>.sidebar-links>li:not(:first-child){margin-top:.75rem}@media (max-width:719px){.sidebar .nav-links{display:block}.sidebar .nav-links .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active:after{top:calc(1rem - 2px)}.sidebar>.sidebar-links{padding:1rem 0}}code[class*=language-],pre[class*=language-]{color:#ccc;background:none;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;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}.theme-default-content code{color:#476582;padding:.25rem .5rem;margin:0;font-size:.85em;background-color:rgba(27,31,35,.05);border-radius:3px}.theme-default-content code .token.deleted{color:#ec5975}.theme-default-content code .token.inserted{color:#3eaf7c}.theme-default-content pre,.theme-default-content pre[class*=language-]{line-height:1.4;padding:1.25rem 1.5rem;margin:.85rem 0;background-color:#282c34;border-radius:6px;overflow:auto}.theme-default-content pre[class*=language-] code,.theme-default-content pre code{color:#fff;padding:0;background-color:transparent;border-radius:0}div[class*=language-]{position:relative;background-color:#282c34;border-radius:6px}div[class*=language-] .highlight-lines{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding-top:1.3rem;position:absolute;top:0;left:0;width:100%;line-height:1.4}div[class*=language-] .highlight-lines .highlighted{background-color:rgba(0,0,0,.66)}div[class*=language-] pre,div[class*=language-] pre[class*=language-]{background:transparent;position:relative;z-index:1}div[class*=language-]:before{position:absolute;z-index:3;top:.8em;right:1em;font-size:.75rem;color:hsla(0,0%,100%,.4)}div[class*=language-]:not(.line-numbers-mode) .line-numbers-wrapper{display:none}div[class*=language-].line-numbers-mode .highlight-lines .highlighted{position:relative}div[class*=language-].line-numbers-mode .highlight-lines .highlighted:before{content:" ";position:absolute;z-index:3;left:0;top:0;display:block;width:3.5rem;height:100%;background-color:rgba(0,0,0,.66)}div[class*=language-].line-numbers-mode pre{padding-left:4.5rem;vertical-align:middle}div[class*=language-].line-numbers-mode .line-numbers-wrapper{position:absolute;top:0;width:3.5rem;text-align:center;color:hsla(0,0%,100%,.3);padding:1.25rem 0;line-height:1.4}div[class*=language-].line-numbers-mode .line-numbers-wrapper .line-number,div[class*=language-].line-numbers-mode .line-numbers-wrapper br{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div[class*=language-].line-numbers-mode .line-numbers-wrapper .line-number{position:relative;z-index:4;font-size:.85em}div[class*=language-].line-numbers-mode:after{content:"";position:absolute;z-index:2;top:0;left:0;width:3.5rem;height:100%;border-radius:6px 0 0 6px;border-right:1px solid rgba(0,0,0,.66);background-color:#282c34}div[class~=language-js]:before{content:"js"}div[class~=language-ts]:before{content:"ts"}div[class~=language-html]:before{content:"html"}div[class~=language-md]:before{content:"md"}div[class~=language-vue]:before{content:"vue"}div[class~=language-css]:before{content:"css"}div[class~=language-sass]:before{content:"sass"}div[class~=language-scss]:before{content:"scss"}div[class~=language-less]:before{content:"less"}div[class~=language-stylus]:before{content:"stylus"}div[class~=language-go]:before{content:"go"}div[class~=language-java]:before{content:"java"}div[class~=language-c]:before{content:"c"}div[class~=language-sh]:before{content:"sh"}div[class~=language-yaml]:before{content:"yaml"}div[class~=language-py]:before{content:"py"}div[class~=language-docker]:before{content:"docker"}div[class~=language-dockerfile]:before{content:"dockerfile"}div[class~=language-makefile]:before{content:"makefile"}div[class~=language-javascript]:before{content:"js"}div[class~=language-typescript]:before{content:"ts"}div[class~=language-markup]:before{content:"html"}div[class~=language-markdown]:before{content:"md"}div[class~=language-json]:before{content:"json"}div[class~=language-ruby]:before{content:"rb"}div[class~=language-python]:before{content:"py"}div[class~=language-bash]:before{content:"sh"}div[class~=language-php]:before{content:"php"}.custom-block .custom-block-title{font-weight:600;margin-bottom:-.4rem}.custom-block.danger,.custom-block.tip,.custom-block.warning{padding:.1rem 1.5rem;border-left-width:.5rem;border-left-style:solid;margin:1rem 0}.custom-block.tip{background-color:#f3f5f7;border-color:#42b983}.custom-block.warning{background-color:rgba(255,229,100,.3);border-color:#e7c000;color:#6b5900}.custom-block.warning .custom-block-title{color:#b29400}.custom-block.warning a{color:#2c3e50}.custom-block.danger{background-color:#ffe6e6;border-color:#c00;color:#4d0000}.custom-block.danger .custom-block-title{color:#900}.custom-block.danger a{color:#2c3e50}.arrow{display:inline-block;width:0;height:0}.arrow.up{border-bottom:6px solid #ccc}.arrow.down,.arrow.up{border-left:4px solid transparent;border-right:4px solid transparent}.arrow.down{border-top:6px solid #ccc}.arrow.right{border-left:6px solid #ccc}.arrow.left,.arrow.right{border-top:4px solid transparent;border-bottom:4px solid transparent}.arrow.left{border-right:6px solid #ccc}.theme-default-content:not(.custom){max-width:740px;margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.theme-default-content:not(.custom){padding:2rem}}@media (max-width:419px){.theme-default-content:not(.custom){padding:1.5rem}}.table-of-contents .badge{vertical-align:middle}body,html{padding:0;margin:0;background-color:#fff}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:16px;color:#2c3e50}.page{padding-left:20rem}.navbar{z-index:20;right:0;height:3.6rem;background-color:#fff;box-sizing:border-box;border-bottom:1px solid #eaecef}.navbar,.sidebar-mask{position:fixed;top:0;left:0}.sidebar-mask{z-index:9;width:100vw;height:100vh;display:none}.sidebar{font-size:16px;background-color:#fff;width:20rem;position:fixed;z-index:10;margin:0;top:3.6rem;left:0;bottom:0;box-sizing:border-box;border-right:1px solid #eaecef;overflow-y:auto}.theme-default-content:not(.custom)>:first-child{margin-top:3.6rem}.theme-default-content:not(.custom) a:hover{text-decoration:underline}.theme-default-content:not(.custom) p.demo{padding:1rem 1.5rem;border:1px solid #ddd;border-radius:4px}.theme-default-content:not(.custom) img{max-width:100%}.theme-default-content.custom{padding:0;margin:0}.theme-default-content.custom img{max-width:100%}a{font-weight:500;text-decoration:none}a,p a code{color:#3eaf7c}p a code{font-weight:400}kbd{background:#eee;border:.15rem solid #ddd;border-bottom:.25rem solid #ddd;border-radius:.15rem;padding:0 .15em}blockquote{font-size:1rem;color:#999;border-left:.2rem solid #dfe2e5;margin:1rem 0;padding:.25rem 0 .25rem 1rem}blockquote>p{margin:0}ol,ul{padding-left:1.2em}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25}.theme-default-content:not(.custom)>h1,.theme-default-content:not(.custom)>h2,.theme-default-content:not(.custom)>h3,.theme-default-content:not(.custom)>h4,.theme-default-content:not(.custom)>h5,.theme-default-content:not(.custom)>h6{margin-top:-3.1rem;padding-top:4.6rem;margin-bottom:0}.theme-default-content:not(.custom)>h1:first-child,.theme-default-content:not(.custom)>h2:first-child,.theme-default-content:not(.custom)>h3:first-child,.theme-default-content:not(.custom)>h4:first-child,.theme-default-content:not(.custom)>h5:first-child,.theme-default-content:not(.custom)>h6:first-child{margin-top:-1.5rem;margin-bottom:1rem}.theme-default-content:not(.custom)>h1:first-child+.custom-block,.theme-default-content:not(.custom)>h1:first-child+p,.theme-default-content:not(.custom)>h1:first-child+pre,.theme-default-content:not(.custom)>h2:first-child+.custom-block,.theme-default-content:not(.custom)>h2:first-child+p,.theme-default-content:not(.custom)>h2:first-child+pre,.theme-default-content:not(.custom)>h3:first-child+.custom-block,.theme-default-content:not(.custom)>h3:first-child+p,.theme-default-content:not(.custom)>h3:first-child+pre,.theme-default-content:not(.custom)>h4:first-child+.custom-block,.theme-default-content:not(.custom)>h4:first-child+p,.theme-default-content:not(.custom)>h4:first-child+pre,.theme-default-content:not(.custom)>h5:first-child+.custom-block,.theme-default-content:not(.custom)>h5:first-child+p,.theme-default-content:not(.custom)>h5:first-child+pre,.theme-default-content:not(.custom)>h6:first-child+.custom-block,.theme-default-content:not(.custom)>h6:first-child+p,.theme-default-content:not(.custom)>h6:first-child+pre{margin-top:2rem}h1:hover .header-anchor,h2:hover .header-anchor,h3:hover .header-anchor,h4:hover .header-anchor,h5:hover .header-anchor,h6:hover .header-anchor{opacity:1}h1{font-size:2.2rem}h2{font-size:1.65rem;padding-bottom:.3rem;border-bottom:1px solid #eaecef}h3{font-size:1.35rem}a.header-anchor{font-size:.85em;float:left;margin-left:-.87em;padding-right:.23em;margin-top:.125em;opacity:0}a.header-anchor:hover{text-decoration:none}.line-number,code,kbd{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}ol,p,ul{line-height:1.7}hr{border:0;border-top:1px solid #eaecef}table{border-collapse:collapse;margin:1rem 0;display:block;overflow-x:auto}tr{border-top:1px solid #dfe2e5}tr:nth-child(2n){background-color:#f6f8fa}td,th{border:1px solid #dfe2e5;padding:.6em 1em}.theme-container.sidebar-open .sidebar-mask{display:block}.theme-container.no-navbar .theme-default-content:not(.custom)>h1,.theme-container.no-navbar h2,.theme-container.no-navbar h3,.theme-container.no-navbar h4,.theme-container.no-navbar h5,.theme-container.no-navbar h6{margin-top:1.5rem;padding-top:0}.theme-container.no-navbar .sidebar{top:0}@media (min-width:720px){.theme-container.no-sidebar .sidebar{display:none}.theme-container.no-sidebar .page{padding-left:0}}@media (max-width:959px){.sidebar{font-size:15px;width:16.4rem}.page{padding-left:16.4rem}}@media (max-width:719px){.sidebar{top:0;padding-top:3.6rem;transform:translateX(-100%);transition:transform .2s ease}.page{padding-left:0}.theme-container.sidebar-open .sidebar{transform:translateX(0)}.theme-container.no-navbar .sidebar{padding-top:0}}@media (max-width:419px){h1{font-size:1.9rem}.theme-default-content div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}.badge[data-v-c13ee5b0]{display:inline-block;font-size:14px;height:18px;line-height:18px;border-radius:3px;padding:0 6px;color:#fff}.badge.green[data-v-c13ee5b0],.badge.tip[data-v-c13ee5b0],.badge[data-v-c13ee5b0]{background-color:#42b983}.badge.error[data-v-c13ee5b0]{background-color:#da5961}.badge.warn[data-v-c13ee5b0],.badge.warning[data-v-c13ee5b0],.badge.yellow[data-v-c13ee5b0]{background-color:#e7c000}.badge+.badge[data-v-c13ee5b0]{margin-left:5px} \ No newline at end of file diff --git a/assets/img/middleware.5fabc0c7.gif b/assets/img/middleware.5fabc0c7.gif new file mode 100644 index 0000000000000000000000000000000000000000..784fc73146f58b526b2785cc5f24ff4f6df1c7a2 GIT binary patch literal 800441 zcmWh#XHb((7k$zLNg(u4O{k%VPz=?CP(l%cpmYcl6%aKPX|^;VC_*R-3TUVzqS91Q z)KHWnD#e0|8W0gXA}W?wKEIvW*_r*bf6m=|_uO;V+sBLI7`F-B3;qH8f2ad00LTdh zLW4jmAdof)WQ+oVR6zg^q_++PkU)SDSVJF;2P-I}6_hj;z#uFIiq!!r0jf&Mx=L0C zN?8$+TJLN=V)92&YMBs0u^i7*=pB zwTP8^iM4|nmzo|I;IcK+FFtTbd`uL3cN)9m06U^JkNAlx@gyO`4 ztfa8Sok?NI*@u!(Ki-x0c2`p1?ojrg%*;LP+&#=Vj-QxQ<(smtA@%UqG~v#)jI6Ze z+VqOI>6rz4qi*l@Ov}tH$-Jn1E^3)!i{EmwtSGjhvwbNMH8lgke8%Q;dZ zC@iTd%xFJ)>(F)!Zzu~td#bvnt*fQ- z+PUKwTALcqxAQJ=J3CqnJ992{cJ+5wpY6JOv#asY<)VnISC3xHzj>`|=-S!N-W%6$ z^d1=~zc4pu*?dhp=x z(?@q|rfZ)+x%=$tNf2+m63H(1y5Xd!P8&ZeSCg~F(Rm`G>+iC~$p`PWHjP|-)M{u@DGsErmL&bRd zVI7~2hLJL2a7)xkN8_CeHNt4MPiNEJDoWbBnUT&j_fJy=^18lV%@g&EonXdj2mz3D z1)=d278l9ap)553R0!?t#(~4SewSOHbS5ZHf=bmBo9x!5gDItNm1qv>rPvVt+%uX^ zQE;;qPw(54mqjY(F=Jt8kVf1|J9VVwSZ{E?$!Dn4GlEIQ( zNp`YiYkKd|dQJWKuHYJ!*7^CRRIq+q*~mk}U100;s$=06N)%$h)AXR}^Re1R8TqDhGfo`Bark`~Z}Y z&UAMlnfN-oNgLpV!2t?Bj;6dFxj7`*=bJPy53~dTdRQne55VFfU|`AnNr>up(d;H= zH;Mi>eK&hC=$?#@QE*adIOWL{37upeW#Z%XQNnU{KY~lBK zIqM~NMc23EfQK{=&W&OX5q;6^43 z57AjkE{k&cEVJFXoyd6=V9)ry4+Lr$HwdIqh6tc5--MH$XlU0O8KYz*zw#WeM2uIY z_!vsYT;6a8vh*<=ytiZF=>dvjx&5ZQ8ZjEGJp1LveOg-Y7wJdA$TJsyy<2bDs&dOt zhsYpj0VHR0#xPUPMlUjG*_zR8Q>blXuI2Y4pml%14z02sKwtvK+!=+Uj=sY`#(+!1>ie6$l>woBco=QP;P zwP+pCO{vs#?J|a>S3)=nrrr*?w(excKNwXaC{5#BG#`SnUb^j)Ob{4nz0#p1Ft)uT z6pn?x(mj))_8UB!3RTQ@ViUKM`K%6FvXW4}CvsQVS%d-N%#@aEvg zTjdOR7 z87P^Nxk#0EQ8tba0*U(Mf``SB2XVXwP2_4a<4#1ALjHOJXgI7EaHf8UAD9+W-AT^H zj(r+c#8bg--o^V?KilPnvG%Z~!o8va)O!-*-$}QxC%#daq2E~g@*L}q(yOc~4fgMN zw=*o5@K~1tIW|k`_CgWEQ$@OAOxMI+J@xW6uV|pdefMk^{B^9TbeF3sR{30kZebPw zvY8B%(Nzm3Gq_f6JwbupJC9+`K62jWGg+!vsGKn(C+|T+6Wdhf0PHD}Viz6INmB`6 zGXyO>h0H~B$fLSE^f^-nhp;m8C+&Een+%qcr=tGaMU*udBuJO{03`chc;+vp)gVjf z(<&d`(oF>(d{(dos0QjHC*%i}T{EO1RVNbx?@_%*q(vyuZ+)Cl$k%p7d)s-qit~E1L|ziqhQwFjAHzf^NgR$CD($kxm!@#=2+t$JH+% z=$n(;K-3nLy7ftemd%p0cePff+q1#iqOt-~8_k+h>q!<(z_3U*`=IjNQnHr+HZsF; z3CT!WC{tDZ$q^u8>9^L0}fJ+=ZwH~%C)OxlmhjlpPSnh{4?)`u(#^*Y;XkUtK zpzB>qJY5{cQ^f`s5l2k6&$3z(rNXr@7wDeCN%e_{iJePS^CMM=s~1y>Ovj$$ks_{LMn)wj*<^Zi8%Kf& z;7Hm$;mM9!0TL{fUr9x>xqNhtl*2>T&MJoAp)P@iN?9y)<04c|;Vai<#v5+3#u~85 ztZNc53YEX{mf&n^fq#SeA*$tP)aHX4PQZ42Aj6JE#zUyxb>||JggM&@f^MRR`~fAA z>cY5&d3L`_E|EcSA>=|CwHQ++HJlJEl%``{y7R5AG?o@ehq@@{h6cL@dplhy0gsy)0l;#-R6eo z(3DB4GW8c9EHwmKB}ce!fq&tl-s zUytwq6;Pg=sFl)W$6o&Z_3WidcC4F*s+GpcDFU#430a!D=SL_PPE-*GrJbN6G8QtB zOvHOhh$BJk8^^)nmBKj59MR3w8O9ck%WveGg zG9x~j2C{oBhe0a+rC|-3F29uUX=D(D3`*OPDW3umQ`J}{gqlua8m2=Fe`c;A$ibHsM z2L~0~rg~v~TN@X0k_>EAL(9^2s{0a2oG5Ug!6Y5q%RzfEMLR2x%niXs#u17F#4Q64 zJ1+Pz32iuFqu1$ICq^65UF6ymKRod7WC5&O==Jfsv3THN*3sL?u#rV+3e@NC}#fe{sg11xo4m7Yk z)yC8gbR)$1MhJ>2f!M!x9$7-|#=G1Tk^kGOP{Dyh?NMNkUG*@W{TKz8V7`$`*^k}j zQlM6EOQ{^_pHOTS5v?S}Y+sAV-I5u2;32M)=*{y+ZDMFjz5R%R0)>?a@3XA6RYP); zJ}n{Q^u-}(55zFn{kK=eiA{E0L7iHl$1*)`2|e5&z!*Z*ZcZY!5Bv)7GPgTAF@>_| z*z$l1giVs)syGA=Zupq)HM;}@dzbCB$Ap3Rn}N4%rUJ;eNYYZQavd|GKAm|R-^)}~ z)W_;Cxo++`u_b(+6EA`w0LUENal8Gm|fO+De=Lg!e%4LBYlUYDF|`N z{!1GM*%vafTy;eZx#Csy$3Stl4fBl=_-N2~ZdM^m;;>l)zIxXFzmDW`$0HD%qUxTS z){1$AuO#aW4 z(-&lUi1vc#vSiP?YKAHH?g=={9+pR|h*VW9E5JgiOuz>pJW_*;;H(>lnCTZ$`ox4+ zs8&wIS+(FB(ul<#tUT2ztlVw%tNc=osJ$bPMT_VI75|t*p9KnmNtAh8+wu;H=D+Pm*#F#S=LAmI-wKv z6>(&ZTXvlZ{Px0}zXwdBpaW+mYEJ?<9IBj<5EP+U(Pn)rNAUr^<=xeA>n9hSZQwW)tQiY- zkO~KbF8xuHJUR%I}~GzUDug5Ig}QukGx_@`RRK zaVpZS3;6ndZ#pS0twA7tr-$!+LX1gpQET{=VAgmu`Z%*5!AN%alJAdF|@H9xgMX5qsd8c5lTUICuP7 z4YRqws<$K({6bDL`ny;B`-=9FBjb_R>zqvVnTV;K*JqwxQZ&ZKtS!|%cfRpHDd7Z= zc0wFKf9;0mFK9ZmckAelGTl-c{^yNHGPcA=2^t>0Z}=o(-x+96qzcaj@+DF= zP7Fn2`%qs1GFRi>wVRg7evQfs*E4Wt5_q??shbOhyQ}H~iMHEROu^_wg1mEdxht~M z7dWUWt;V+;xauD)LIUj*D|GFx-OvYqx}-3*gwSik;;8a(WdW%pH=&7QhX>GyvkLdI z2d>(oZ%UDT5tMvQrLIjb%p7Zo52k<)CzZi3h~c48#;?}WYztepTUQziT7%DID9*N_ z>Pu8duC1AHN3QNBDyP znUN2?*`N1fXovMcdMjFOSx5gvGA?-J4uTZ(V7w#J9XQiekLGDzGbL;}d2=1*8dm2< z0}85-)r2zJ*K*Vd6kxCHqOGI`9MsFF$vLhq48AH)DC$68;po)(${R0$*Qe@Zz?>Vdl3o(uS8r-pM1a;yC=yI z%7}f|DoNKlBd|F5&@3#da9xLUfJ++z$AUW@KotYfbsT896k;uwyFx`&qk$O76s4O` zPKA|sPeEuvIws(dE7n?uyaJ#?+Hy7L+{RJ}TuG=D)m|E>1Cb%yI4x$(_G!XERVhOm zr+IJgqKm{-^_)nR2{x|KT_>w4Evc}y8!rbja&BXtNz|sk+r5?WBJrm6JWz*Ndp+F{ zOrrM5*lLSl3-0r$YyTPW_icUy)!HKmaC&$87%1f7P@h&5EDW)4_9Eg+|CJDO8+zpq z@SzaW1GhHtUW;?rAaW&e7xJ~jWV1vv9(fY)v3g!kuQOo~u(|aWe8d55)G6o4avlWa zl(&>blRyz40%{#@%hP~KnZig$@*D{QaCd!Z(2iw-wW#tvvjHBHhsT(@-((84O8^dA zY;C2G2mli+=!cx-Oc+Rc964I%bteV)-Y{u_gV3Hk70;%cnudNAZeCzwR>cZ^SjEHh zWwj1T6%r7Z%@6FSEnbZ)Jw4uD;VUA-ywgjWsvz0|yI1A$= zf8@uSGj8b13}xc*CjiBz)`O`E>mit`3Tq{*_N5ZJ`k-1k=<@t?`M?+QZl&;Knf#ej zx5ue+#ycA>g<}U-2DAjIZr1TXA7A1jvJo-*5*4nfVq?=*SOvh6@yLA=NZr!F4eI-D z{5kLI2_qq$=jBV>+X_3JoYmgKJ3HkRpJJY}6dq9)p`f$t4HQn0UX1SzjcY6G$b>+( z;3r+@|05^0v23050vj%W3hD%W-#oIr5pR*98O3ZrBnIKe4W^{9>CT0kcW&3jn&nh^ zRD^ys^3%I7==}}s=VJI;QKaO#?Jf+aeEj$&n2-A>Bq9VR-{s}nZ=1(e_;|PI;hKu^ z?uQEK&;Qs5rk44GQB=iH^V?p*r zxE=R$(ZKn4cfn#$ZMW>_yT5Qb@ln^)=ldH6yfHxAK2*<^2hYx33KkuYqwI@{aE(6b;lh;&%)a4kWIZSOBE4iy% z0%~|Vo3{R=n3ePS!{wcRfc|K7f=Zn%Lld%X=cgoT_S*V8Qc6MqqxgcxVPJiBiodzX zjtjR@;Es3COWFfsuBOjEZS=U)ujQ8J7DGpST+{@V^e1oQ-h3+Lbs<5Vy8O@2Yd~*b z-#-)k^6!B^Kfir`^YZ1|+HbU}kqX$)W=U!7QT>jS0N$+WlpmDpt@ACS?@Wcfu@Iym zu%ra%3CTnPA#dynmK)YgXQ=O5>^W1V7lwPIyPe-|Q%Qex?o0(_^5k2Mb(_Q^jRbbL z>6sc)K|fR?)NeVr((G$yc9AU^HEZpfOFB|{L*AT#3^}(4NSsl6WiRiBqxS6Hjc!ua zV`KZU+a1zORAD=8HgX@`V2*KbGv#3lpN(y$eY;6&1yiZiu_q6wmx|vtHg?;X7 zdcOR*)%50)48u6$m5lx;+a|3Q%H9;sDo1!%=LbXtvx_e&QKzTm-Nbo=|@*BO>YC$Ik^OrCIz^?^23>Utco~04UW*IQ=kybzaZI zP}Sr)vh}RD@y z9#lF}aK@-gXS67;Up`JKceSfSU6Z;fxl^@&!iS-nKv+OGgh^gk=yj*oLxj?I#r|K% zgNs%7--w)k%-tr-J(Y>rXT+k+Wj{W#HZ-pPRnG&tYdc6&;rf^mUHWKDS8H6dqpBVh zMss~ISWe(SN}~D!JKZqf91>3M`?07Br}`Mpv2qzb&SAuRx%IKO)RQ?ai9;?^0DjR~ z37A$OpsKSjh5rIyHl78ysvSt5^jjajqOS8ZdGN0VHC{Ksi zhpK04W~1RdjAZPZ-6GEMz-RCAXY_K2@(Y!A+$>(w;b|3a~hB**|YRA(BHkH?W z`{4H*^i+0hWxeL&@U3_g{r+vgZ^H7{fkR^A10u6D}Gq z(F9C3!=tn)^1c(zW|3B$BQn6PR^CFnO%`SLE}{~C4m*=gi>w&{0nW*D zlo$dTKdRq~1oHY>JcBsL5(rl6{;XRH+u@F0$83cp;ovrTUr-yT6#>m7rAEi62+2o2hvfP-X#DdxgI>cByrTb&11Wg{dzBx0c6PqOu+?z&Sx%FPJmNvM@ z?VEym(SV`948G&j+ad}DfK7HIywsLk!oJMut(5ldeUWm1v(l(eFd#)`RIJyGy6^h7 zQgm_uD$KoU2jW&AO~2+>NVC^Bw?N!%XUsUQb6BR%qj@{+Rw@jT;S0#P{Lq_2kRbhe z2&ehB)yc!+bL3aJf5pWGqD08Hd8K&;?;a5<6R}+4HBQgI+}qR#0SYg{;yD9o)l&YN zO#)b%A@0+YkoYzq2ne}$3wMS@;L3QU$YdbdYPE73m=1)bX~8-$GSof+e90DDs_|V) z6*kXa`fbYN&UKZ_RTA39h@Q$=uqcvLuayklXumt<$OeBn0Y>s*WqyH85#uz|sUoCK z&|%G1E+#hGijM{JuDS^{yM2cdW88E5`yMLYZ&ZcrOvq^x`_y77AS1T{?@&_w^`24? z)UF~B1+Y9Cx;-+7c18))ITsJfsJfhQfRIR2KeKus?Zk+B1-HO1GJ_7Na`Unz7$EUj z(vj#tTc_GTg`-pgC&wp_<~sVpZSeUNRV+!@gbh`dwz;btj}Er1y-E0zJ3OJ>0Xiak zWj*7`K(v&L6~Y{iRanY$#@P8%Q#LqU!S?u2)OMji3k=A@-ESvv(=GF7B<{97bld1O zX(DU{bwm+>3rv8)p<(Y&uL8?0j(44(Pq4^nem$Amb;$^$U!tB~ruoR5E~e-VRW>$g z>(1BTGVw3`-qqOmDlVDYp`p=T4C|0p$5SGHN!-KN(bc=@1 zd^`=Rj+j+BlXc7TU`pYRs8`x=vW8s$rik;S<_%1;M>ZZzEk6_W+WhhA#OP3J#A#&clNd5kQA78wIL$1mrm-j0G+`9WwZ~cb4xoEZ|ARRaF+m^SXcH7bT z-eWJ-Un4erpZ(@&e89+zIHl=6l-_mbKiQ9sblgnLW_pp7Eq7wy@zCQ~<-*}Rx}@&? zMc+9nkE8ByXKmjT@u%8AG6-^{AGMFU-J+PAAi22C538`QAiLu4)SDf-FVr-l! z^4@^$!+AoojlOT$_dfdc@Nf5d$U4^YTOl#~3=Vn25S1k8i0aEmd#le3WUl1CNXBFlhS8nz%*uiCxcc}U z6=kSPU%au$r$GS=-AX<47gQ?8gzQ?*jl;p$Q&HeeLX|&Kb3fD$VZD!%6L( z5(tA7rXY`IN)UQVr+3R>2a|~5O!YJhT>Gcs)_cd8to-OzQ4T5JnW0Zv>|0lzhmt|F zmoRrIiY4F@@2L#{07f&YIV0fo#j9%<$`CjaEO|Kp05*SzG%uRi78*~_MmB=!kf(29 zHoa7u&fr?YO~{G$bf?mYX|aLkVURwY!4wqw&`e0QT2^2ElJj@50~ML)Y$r%xYRF8; zAL7A)rGXr7-X_V(cz|n+TbDB~%A&wKmJPGEOaoon*BjMOniL!nWNP1@Y3B&?Zj!(T$4)gsrT01R0=&7IH$IQ(A|1-@e*{hin( znW;}OL8T6S8!L+%Kc)#f&Fzn$z@bDysSWtjgFc5V?8mN41N1ckVH60c5~_hE4lV-7E+pdK zhq2uZ$a1ja32^n^={!i5cv0rxG{Q?+%-f1Y>?Y-JOF(!u+Zj~dPV(^GIY`uFfe&zf z*Pt_qD-rBs05V0&pu700!tiA!MTVS=R#HThdPV*-&y~DVDx8O^9#zgNte5~C(J=A z>l3*O2??-lWPX8f%M{+Xi$b3bK^*Lc#y{b`oDpj3@vXNTu{R6^Wdtj27MpoeKxPe z7Y5D+AyEn7%4GnV)gz?-6DnYW{?IbJ1>@=B7dVA^N1#0GWW6ci-IBi+npKmt2-m{a z{}GG_&m#^DgVODScP!@TNO;N=kOB;+`WB(%tQB9vyG8d<-}pe@!$)(8@81nQYuoUP z0C!2Sja|*P!t#I7XrE?;dY2S;UdzRr=N;d4bZ(XJIeVcvE7zng&pT<8ypL1y;wCv3 zq~EvrYMpiTM(|rG5Q7XBcDJhtbbUl^kxXEdx!6Q ztK>>^FXzXvN7WQ5k9UY_BPqF(r=*|VElFV)4hYp1s2A*5HIdSbK!Zy1w7Hq&a%2MO zhkPC-p}z~srFK8NcI=s9sZMGFBBC%iu^IdY2L#~qGw}K06eaq2UIrsSNmMHo=SKKE zKC>(O@k2$sbL7&vQu2}L-`MMjrJiVibASV^0V17-xA$NC7o8+mwJL8nTF>Qbn$K@8 zK2upd7%z=ZCNtB>Mt7nE({5xi?mzw*^ZCCQ8o@r(jsj~~>{r*=l^S*VW`Py|$;yG) z)uS(ek4OJJAN%`y?4P@_|2>cW`!ROyzgR%wU$4pacBB2<{dR9Z2CAvC;JaCWMH}7^ z+*B_ESd4tXw!13{;D5Jo5*Oa!TZ~@fCv^+;OdB9b3L;?k)AJjk6OS23%{rVNezEv> z4UZQ2DnjwZ3bO{*BJ6x{96m3Ua{!utuE(Qea+ZpsX3ZMM+kt_+%M|C_RUj4Q{sTib z^1J%QPlRA~S#9s#o8{l%If=K5r}bK|b>)~D#1yp5@f8nr4UY*8o4d+HTXToGYTY;4 z&u3Sf9Z{k&dL?f^O`bXowZaEvPdT>mk*bwq<3|zH;Yiz4Y}5mTF@Hl9wK9@zUjG-Z6!;_iZ z-1c_KR9k#>cFBCl)}Nw^oGe7p)Qv@g$(8HT2d^LdB!g>MyXZk$=C z8QXKcoD;2J#7%eT%LZ$O?o-(yE=GhrxaH^ot5!s8Ne|eD=*y4CZg8J{bxZ`zf*Q?s z-=d5T_fhiJRn>B+`Bt7sNE{)lNw9yaClY%l_itij+JjBQYH~~bP2%~w+G?ZDCne@0>khl!&%(mNSpAfQkt4D&O)4l-*;mB! zl9#hHHn*4D*m-Qg{Hr^1&E2q~1{Ofv{7v-E4_Bm%&(Gw}T)PfUqHkN7<_eYbF(YxA z0mb;7d|L_#iC2vy0W#)?BqHoMu(01OPE)+e=Jw8q+pqdhzjwL%&|Paw8Xl3k$_gn> z(v-b3<2bHePDFKalVgH4j)UI|k^0#pFn0RG(LXx5DdeNoiDwnmDd;qtVLBF({%E(nqGL8pxL>%7{xtbbcieF? z>_;=^?Nw2_6#g_wuHsw6F6N}>FLpM=UT`@%yXW8QZnDA?G@F7*#uvmlbGJ7l(Zsy{ z*!)X_T*~B^k4KMDwDhMga7|VPAh71cgiUI0@$Z!{F`R145)lWt5W3x>+F!5}FIo z-k<8Z{!aZ7;I`bG2lcV1P&m!s6Hs_@eVOYoq>90jkr%r0+v74IFd#o(28w*s4aVp0sN^r-+8ZD0Y?V+L`?-5v zO=tX}?~k2b5>96%UCh{dGb8za#;%GUqh}d=ZYtuDKR7R?Ye!Sx9jP;K>fM&vLJH6P zLwd2t+HYl_6Sy|@&+DrPR-F`mR=UD?TbuJt!&qk~0dI;vZ%9#IQbsNQkV)W9j2UE>g3t#{~bw zq`7imvKxz40rf)e%gH%dE2^*W)3}#k;s5BJ?!NM~5}M|tdAVThvf*gCS!hS{+hwD%O7f1$*2J&I z_o|&T=f+kyKnF*xF z{h9OI9!X{s`jmm0ZcexsXUoOs*LFNz`Ub`eo?TDu7=K6~T$6igVf_A`&((>5%9rIp z^|$(k3a-<^TA=xBzM^)|!R4&MS36F=bUV1fMlwyD!g@zbvg^jTC`O!ROSWJlg6N( zae3FUL404hYFe>#DZuh{nL_6W+Hz`XfwloIj~FiBkPvU@sua%?+sJ?vv!Q}BdEa$2 zz!A7D56eX6KR+1w8(+}@115ilJEbMRW*`IdmY}sSuPdx>af_T&2Df3gxd{e=)?9X} z2pwMd4FSM8^)sB6;m&ZI&#RI_gURVdV+L77mA6e#7}u*m>hz1)P*OMKWtu2jT7>@H zHCJJMsk^QGjAeuDbV$fUFdgZ=%7cnZFS*~h02dk*fu#*o5S2w)yn^i^s!TofsAK?3 zf7=y*PeZLKL@MfBJ?7~=g_VuD&XIyQ+OMS74>;XdjaP7bK0J9(7xsZzg|38+Y}{~h zH8<~=hE(mo+g7P(ANxNUJ-}&xslFID+7$BG`L=Q92w)M%M7u4Aq{%BnadszMMAFy5 zu~n)u9i8dsp~JQWQc;Sl;Na;K1l=`&3oX6+hdgF;XKc~iYHAsU;TsANKq$JifOg`q z_d{b{XidK__PRg>%!h41rwFa=^LNIq6VUzQ60YyXMYV-+ZPh{(JiFjXEd__?ton`C zSUYRsi?9#CNHNuD2_dQBiX&*&)4>2`un$7r3#HJ*rs}Rf793hetZ;`wfeF&wp(?X zJ>6D^R+JiXZFBOJkzvof*OtI9td`RP6%Pz}o^W zHk0e8yW2(pUl2l`)PtjTa~0B7q~0cJk5JQiL6}USWyKX}mpr_xRL7?LSVY15rFxpZ zT#dJtFz6bacqe?aC_l@Nz>q7{7HJrmM5?*U@V4(K+$$`ohi{+4$ptPO9Qw1f_)}#+ z$G=|jVDGC~*DTt5W?9jG)kJc09CE`u8bobr9^i?FoUaC9);kOsVx=Vmt9$btUKr@E zAQ6u9DGb((f!JgO&{FSo$i&xeJN#s`8kKqH#AH{KjV(ks#sO@e(62}gf?9=5zz7Nb znDO_fcj&LO3Xt@II?@RUfYQH+rPXE8slTIVgnPBxen< z3xlX@69L$ojN5PttUGX6Tk+F&z<#b?+eMs5Y2sYbP!;GE3vR<`(j!^(^?Gk9o>^?( z(a!IDgtU}vKvgM<$HoD9l^^Vyr3FcqBiM(?XkX$|c&bFr89wWodauo~YVz{s%esNk zXa}BNo*|UXIE6Nr-I4urFa|iNJ8Z1;Yvae>auCHsT1akBKH+87r=Tpc$Fx=qe_8)?W0yYyCZ9}iC@L1=p0z=mP^icwymb;#Lw>Z{XuV;mT_ ze|$;2*zISNErk$W!SC`6QD2Y&ranQ{;X+LiC1*(2@HoJnhUid8-1Q6XhuIZ@8ehBf zEDK!~*{6jwrnB7poB8u%pvH*y`R6!2HT^j8mDVV6$VEDlo}>*`J%znGQFd>4f(6yY z0BK%A=YN?wc*cBA!n<{-s1$DDTDYSikx?$Woyl+?g^vJFez>xfnD26mch}YHV4;5H z1gO*BZT*QH@Va>^6l1}YH@1Sr{*oeW*SH0-(*2WQA^}Yv{;cq*9b@ra18lk%QKQhR zo5`ewYl(iEsoNB9cYd}|z(o-k{xxrLhEEPO^<3G8T`XVyYsfz4uFVGiygm4tZq=A_#f#%SoE@ZTM=e%V!^3 zsH@k@v{}WRU!xi~)-;792WAdE*F6^HJy0N3Sppj0MsvSK(ug#vy*!p0)u#r6FGyRI z_)1GWBM*1YVQQ-4$F79V} zcU`g1eA=XxN;)P?2co(LzRZI-8}={PdVrEoqoLdnnvlDu1CA{1ha*%%RAd*Au8jqV zT)IAy-W@%#a-P?oSi5WqreuM;+95bCq1M*OF2`EhqPruj%0}t|CJx-2s8-GzaOBdj zXcm!i0xQll{e(z;^iy2kGqT-Nko>H_2x(vFx1Q;qF6GCnLfX6N){A%jpaMh2m|qy6 zC6&Y)a!uI+GfG{%CZy*r;c`2@PE!aY-gd~!cL_rMQ=~)0?sR_|#7VXbV#X}# z_jt3w^o(8%gtB-FY)A998ko8(umrfKV{~<`xx2fhs6m~3rVveI05PrOal6_k>U1Ya z|NaP9IZDu8LqBnA=5h_>fhGi)f8jW<SX0<1e<0oiqJ9) z+t)g--peyu={sS`gD-=OvHgk^ss@X%zFHc;l}~Y+`KN-{q;Mfz?$wdCskji;D7*$q zItBX9r@rWKuja>IeNNe{VU`DlNNC+g?hfY%!+f7#3W@{|$2brHfE&4o%+qlSf)Qgs zwF@kDfbJTI!gRF_^GWI)^;5Q?eEC55C+_806~Is%!PTa$B2i&){Qmr-X@p>@4pS>& z>`A|V*Nj%Vz@}HKH>WaI5SbF-zoTC$YGXJz^-s@ z%h&)J|HNKZpAxo184GMYHE``NB z*r#$?2(-#wx6x?HBkpo^Z%m+iJrLJ70DZ4{v?#rPB`~IoD>sM+%g2# zzh?CV`Y=)xKx&8o z{3%4wgz1)p)v`WjHB+%3{LYh7v|&^KiSw75&L7=<@1M;`>7Sni3+S_DImXx4fcr>xx&u*yNJ-wm9YLi}L-$66aQbS7mPY>P6n$_GJ#*nb9|9rr6xW(A@1m-(cl=11%>sY+k+h;M4v(5i z^{WMU`giy_#Hnex*PkDhuiQ)=RF0+$+`UY{w@H9zx@!&I@@gyg&8jf$Ia1v-*jb>B z;?jQ}+(GD~eNSBHvwX@AFQxCIGuYhjj$3~wtt~-(a~F49*s?)&%09e~@d|C`c%NL` zNlY_W&Sl|Ji=|u=4|$`{q=M(L#o%>UfLlo6Ai$M~=xUCL-yFzQn}KdU!HxE+OA?4( z!<~f7NPoMA#f}+APAbErcC6sShxUJI%gonG(Eeoig93IC{{q21y0awm^9)PU_SGs@c5Crl0hE@Z5#9!8VVt_3Cha)>*q|}R} zXtf^wgPFSPD9w0gg+MW=Zw9Wku-5fH(JtpWy-)0;ecJ(;ZVU|Cn4lQ&)v4Z+{epH_A7C>V+MTB4UUf3 zhZk_IdS@lC_Yz_T3>-lIN!lL2CVUh=sHbvO{|QTCqQR>C*xx#JC2IbBIzfwGKVfw| zX0680j(TrfQq=ExRcun#D6jtpmz>ovu>=#e-l<=P5|58OtP_HU`|Tb8Dj^VkEY)U( z4-@kpxQY&gwCPJ8_DgviAAD1-bolYsz0NV?xcXN8wk}%2lY>sF3?^XGKzGeD+W7d9 z(qdPAvs7+Oif#a2mjebRy7MQv#kwzmz!yFuD$Wp;Y57>GTubgJ?BI^(*k%ngJ*uw7 zZ95%)&ZWT#*YMJBIxO|r`GDHh|4y5G5kI>UmO$%Rbsj*}=xQ$X^M9w7x7LgsQ7;-B zF5T)pSTx}J{AjKrj>Sg;hGYN!0D!U+{0g}NItCDnm-+dFD$M>EJnGO8_+!>udgkLW zRD*7S$BShL86t|p4@df<0iDACQFQL{O#OcxKWBG#vANH6bC=sJ_t54ZVkGx!bIUDF zu1U4cZG`5MbkimzsU%4%ZSIL$Qc0KZNG08L*VWJO|8xF3pY!;9-k;a=`FiW3~Aj|!iV}7c;#*|jjR1*s* zT02UD`YoCy<+rn~GodQdo@2UB{EcdjhqX&3aLg$WMC)>6pi^^LZ}T>%mhHVQJDpDF z^q$`D)a2k8FzRXg&ARoZQ`^mHB{rS=8m7dwS&MI?$NA9J^V5#S7JOF{hC zClfDLPo)K(T5ElmuHRL1c@XB3%j8h&G_KmC0oY#y?b&A)oG+0sj}PDQ%jYO2?g(&j z9`ySI2473wQvdj_%d^{nrW91`knofD24Day%+EUq-EV3Z4!wmgZpeRT{O`8ei;cfY zf%*9Id3!|trrz5C0$3@?ShzN~^WW|D^s`@J>9QanEw27MPiDN;&>wIv!}aYIS0#3V z_bq6D;o#O$_r&-G(cplcn0D<--^aUqDb53y<3BClL9}VK?h%e%CY0dw>*MU8w$OW67K zf0eFNd)<~h!G9TC>OcBlXd#9Rl~c9J#k1Us&_uh;WqgcBsgh}ES1uvet3pxxP^0@B zyu|-t0wAb5v#DB9Gom~5r(%Ntk+n$=yLKyW4>+NI5~u=q2@eM+!60jFGq(pf`yH{% z`lXy4awc&7AB?GBWuxgD*m!MT?VH7ri?O%rZ1E>-GZ0t7WEP-TB0fgyQ@gai7QB-NPD>Za8eW zS0if*V?m3=lcj4_04t|9|Gk06K>qqQ!{$1~U;eY@1 z=`)MV3uGPX)tn#yv_Do+c#^~R`*r?Kw!L|LC1*uvU;fW8ZysIQue*QW-`_uf{8-Ty z18^;Y7=m?XlK?;<6yl8Ph0x86L2W2Jbnl!#yt# zVi0*OjHhyXZ1GB+4+U)OE<~RJ9C)=+xrrh}AXjUlE`Yy`Z}jJ} zj4HncvId%bOlpj#HQDbVGz|IlVS6e|3&(!hXufnqZEKE91f?C?IM4jR+{B zu!|dV7L$e{ty^XB+WF%@A+WkyppefcjY3ZBnSAV7tfDj5(vb69u4v5SwdN`1m-3DJ z4_?|p#e(d`-LLQ`;}%GPp@Jn|xmILjv#M6T!Ki%D)7HcS^R6R9cZ0jGHcjys7hvjn zM}uT%)Q+bs*Xcz-gfn55TBDchz)ERYO z<)#mhLH);;lDS9$7s3?}ojzs?-6}`{oI9pAaFh0hGfTKvy*Khq0FGSRLgjZMR72#g zUi#w1U9@CT`|fjZyXxkn$qg(cEXd<}7Bl;g7~yJoN18vg+e%-g6skUhBZxMr`6KI1 z@FHAFzZ_EY4sy=(7+WZUK#B4uh?8P;9=v4D;w1~Z_F+v4$VN-Oq8G*^^uIO}CN z*Qvv;W_x(jJ`(fLN-k#OU`}iycPwoFNTeMxrck+G*9{vK&T9G^H*)5*)Xyz&6yh** zwT^TkZ;S1}B@q}$R@fRuc$nws|yC^rH&D9zLYFN87Z6fJ^YdCHIy+qBd+; zU`~0VxRw2EZJ!V%Y5}-$l(&5V847V|)TphIpyMQNTtI)Lp@Z9$l+?dwK8;L%SByq% zKiMXGTYA2YZ@2Ubw@dm$=P;gwx-gCN-b{upu)Wo^zrhVOJb;)uJs^gwU@Kv1f5Ri~ zX2>}8>%$tA@<`V~YeWC51*jAy9~6v>-iCpymA-RZ8Mf<&C`OZs$42WL@Q#?6R5DDT zQDDWlzQO8nLpD+j^ptL)fa%$dRf9)pGa=|ugBNN?HWfC=<`2ZlK)_?|%)RH<2LKZf zFmYM88>U#`d=#qra}N!GRGrCe8VIr(ZC-mn=-_%#NO&*sw(la!%VPlkGrqUe%xvYF z@^fktXQ3|RLDreFSWO3$O6K24V+&Znzd$q?RFEPwmY8%dDx1&ydGBfIaGEYG|* zHmU$n-Um7MP_Knnd0NiBY^RbC-Jqbgvb%^?->Gt7nh;d#*(H2eU+30*!W504{jUF#@zjHcs)!A5y0oUzP) zC`0^sTZ9SknC#aHK@eoC53mawRVanoFtwvX5s=h#3b~r=a=ydvP;z;2=y7$qxgFySjLCl+Idt0Lil&k^x7zAw?NqpmG!%LfN94P|CED)E%gHFZU+ z_MzcCl1;KCgFfQR;jV~OzqJBBYxGGJkc@G!-d){9OEaJ-1Aa^kB|i6NuTkWmeq>eN z(y;P4RdqQg(0}p%$vgD|=0AmGs+eOt&*fhr&@?_TyX&th<0uGn!2PF+A#TEm;eQ*o zwxz=ypQaaI40-{PwIQ$&v=6;+>B_;y!)xd76`1TVQapB}0E!}^^MJ3tF$acR7%ba@ zc6TjX^*XvdXCU*s`~I{(0rK|}qCBUn-!>f5c%}^YqUFW)xvj8)4lC*C+R$XZq<6+E#4i9uiz{zVybW1q)a$@wBvV6!<

X=(FUBi=1|3*;_T)ps0tl%jpj)`neGb-DBOqwx4o$ zrGMiU=Up8E$+g-q*DHpbKVQvwPfa}khJQpimlbAbLZ#?*59hj`gn}z2tG5js#bP6f zw^l!-_VDNl;>r2sX1j`Ki63=uQa;@aYrh1I@Cw)K2nGahp{D_wx-WzSs@t5nGkUr> zU=_(mZPMyr<8wL#pHY!Me%4lveJ1ck#;F)}KtT`H@qHbaeijYK}nt``v z7hL#ZLx?d>%|ULKCQrz%LQOUB-O448SYc>?fzNFQaRz; zTf!bOkn61#+?;(!jt>Y(4hL6YXP#p4&%)oKhljO-hcqT+R zg^=SRAIm+C10ahe?Y`7i?m;`wO0}#5^&edwrdKc=6f!=m=)h5By;VL5gW8Bxcag5v zc`KWjs!RcDf;9mFJ4oBS5Z!EbhthUfzkDK#Oh_fQld4?LWL6^8R6ncL%LnFzH87s4 zMz9{QRI74W`yU5z730tQga}1m6pmWlkahrFQ~fp&qbN+A)Jo($;7b#qgBtUus!u_J ze85l>*kU<&Tabo>QxP1#5k$t{4w*`m8-Ui-WubEQ78^;>aJ` z`-(q(LeS)NxHq-pCpp-*^Yz1Yt%cy1E0{U!cP$og0O{BBYoe?%Ey&DpXB-}NRtlN$OXk4CZ^M}zA>)8Swb&EZABE9F zkIw3}fc|+gq)L*LwoFd`l20hf9<7II+yv?8C<36(1nnQ66s+2R;k>8c)TD@i;KAyr zyLI@ks_aP&Y5fo5l%6ZcW97x#wBaJ4Or)7SaImeTIZuP=bN4+~S=Z|_ z{{>O1uLo)FzB{DY1KsFzq1g9MlTPm}8l_Ao3HghxVgt$gmtQ`OiZp#oNIvMf>i!oz zP&0r_zZMI`kagsTJ_=dBlZ1^gSsJ^M4KnAAHfwEU*We@1CF2>7uz^_cm z;w8pMFZ!QL$!(b-+f+hhH-~ON6q@oNbf-#K#^x~V*eoi9`GME-wK)*WOUZr^ZtVs$ zf(P9wbV?qwS-~Uqhwoc<$Z1Pw6Wh3j>Uz`3^WoRB^rlHlA@L+FvJ#_GanN?Hxft`1 zt3h#-lZrGo_?D?-y5B`QUq!l0A~lsc*c3~*Y%&V^kc^R-8M%{+7oh#a1SdK*i3ig- zBmtP=I)e!S32tN_9N7Z33?)umk%giBR_nLu_O-^YlFvBKgh~)sRIUJh`Hqo^$He z_5#&sQ(RpsdfPX;?w?s5CP(u#U3VGOT_EfBgl$>o7Oeba>9%YHm;80hCUVkM8htrf z={F;p0WuT_TV^~+23H5Pe;RTW_KVQNO!XyUo=U>W^PZ3f+H|K_qYLcTKxztK?i?o@ zoVQ>fmF<&_W9C@bn&_HiNof|X)(U!<;rj9s2QrG&x5(0LWo}XK;g8Smx^RwIFWZ0g zmCg)7+dP1!F>Qe0X#QfU1{~IH;UZ-;-47zLNu+Jpqa!4I$_BF1%aoSsI*J$MxOe2k}7CT9!l z;1(^G_J=W&*CQ^-QT;WmvM*HY;YR_BMo)N@@R(m+gvlBr<+SNJ+5Zgv)NHmm&N@fK zyZjIIHbC;we<#iRN9_FoFIvV)s|~rLN6`05dh-O-Ui`^UtnxFE z&it-4n|S0v$C2O4K(0uqVI%3GNE0XICuIZI$9qkRQ=@5@cWXqO9D(k0I+4cI{zcbu zy0&d)Gu?nx_>6oNl$D!*RBZu#!mcq+aKjk6zn2 zdQSrA0=Qujp{P*Dm z-EZX{pxtgNn0F4NXP91>+1>Uw z)^TCuX3ZJ1e|kB|4+AMH)=bT(x#f!9a_=s|Nl3C#i??U;G?YKKk7Y9iy!UxRvjZ z(IzY>zQGy_V0yY@e?3#Hd|2zB*u=dX_Yho4oI}lQ{h)c$aGVrkxl@2*LT zE-rEJ+sm)w{%+cq>p_2*%53`G!xoD7xPCjfjXW*8^R_+TN7S~(4ThOj6$Fvauc-3H ztALAYtI$KEf!H8Q+kfea>o!u)oN6|gn6FDVS5Qp=7)L~&xG937&xY`gs=X&S0B*O= z`B8y)G~@FKu7X~iLx^IknnH=PS>$!T^{L?st#x^SP}HW;Dg(cx&ZX4mm9azSQN82K zbVRee`XPr!kC)9}z`3QMYqBMTQ;tW|gHt&l6@fC7zJ4h#9R29S7W>pWOBWLqW}C|f zXr|LXjAQ5gty;6FzVgR&{s$_l@oPL^QxBwn{^vszmo`uU*0gesagupp-m|>rqK&|( z2klfmtnBp%Wqk10!Gv(J@RcQW0J?udm zj>rsIz1(Mbr^jwl=5E>q0^ZJNH$S>f*kI$PV57$AxwG!xH>nh)w%17WR4y|SYG2=v z#vXM2hl&$W@5S9iS_e(a;p>KIGerNvOIQXKk3YawuG$pn^gc1-AO*Eh?2|^-iVIL! zP4t~ek2{i83{u6Squ|r_%kk^JGG5?;i_+L;jOMrr4yO}6C=e9ulqEKnSw>dvRV!tB znTFIdh=EU4HXSV59lBd?CldZ6!t}@z(g;Bf7sDzj%C$$2svY`vhiFU#PE+6pD8JTI zPRa$gU!T5lLo46oL%jh0t;EhT%iF1!Oyi~f{;D?f_{)00O0chDZKM}2WbSK6e@in+ zN{#q@Jbkw4iI)iiu){ro0ujPZce98`_8?GQWhh#fYKx{NFyzyWbv+wYSm|gPop{Q< z)_E>XGvr#2FK&XG$u!0=EQ@!(PwQ+ioeQ5flt^2Xr z@s+i1*^mG1eMP+En_xGk*5c&x@&aqGDc5|<;iU-fCN{)W) z+v-ajbNV)Ide@AKv6X?x2UpX!dTjRd7vE$N(uQxel~c6omXnovi)3wy)7uRWnJ~?t z+;XDqtId~qPhX&aPXKNMbJ*gFEaaZjb*S4Ka4Y8S0Gf0Cd=-&1P9u;=>tsb{12w-; zXQ*A`VSQ7{o}Ia~@Aroii~epZ!^~YyZ}8xeYIfH%%4W4ms{&}#+5PaV z_)hB5O&+kMhiXRwF>`pB1|{&XlCVR|jhhKdi5;^uzgI7e;r-hrob8z9B4UbY=U z*l^h@3q?OkT;UHH$Mvgt^j3|^9~s1ollB%XhOb{1DePMs(B1|}yqpCm?1oOSc_kj( z@TaEIkAxO9?3r0)wjSf|lB{1{3F?8=PNY~ScF#lB{L8cIDIz3vX<*VC{s7c6j+dzP?;#xcB<*PyLXV+himDh^uo%RRY!D zc=fnO|C6;bUE>m|U&A%4AOpFa3a=kaj(`)s*GlBH$j-+}Dthb|bo}lildS z{FaNjab0gkPA*YM$Qkgy|NW~xXk!Xl5Eg4){J9Y|ZQHK;YeN6$J%!y%V&Z##Q}m2= z1dh#dFLed_QFQ1XW~uj8u+HpWU_(u9_oE36$c~qWc)$yUcquR_FKd z5z)aNJwT^{YUwEt0X&SRa;c8Y#peg1HLi1Dt7Zm$fbn8m4;^)gVUe1@K;)SJ$CDle$cTO6x(42K8gJ58+)<5e_1 z^VS454_^Pyr|Bj6u`O7q^+XAjZ~o7-dCO4Gj{o4Hs)v@I~p zp?a*d$s(MwECQY`jF`pD9`u3A5Wjkmm|zZ=QJl2tj{r&F$O`ipP9cq@C{2%-NBsSL z^^f5E6fNU!81Q_>roD*-8-p@0g#2 z^Gw`ke8xz~c9kN?tFK6wp`1D!3=k`P1E3Zi5RDShd-WE+U*U~7XPk}3#P8B@ms5bc zXploB979&so&yq1x;^?W7jpJH-00G7T)H}+rF}i!Kej7>1Ja@&u}&`+rm!iy+8WGe zWKj@MZ6Sh;h#eAJYypeg>h5!f=k^xs^D+g3eWK7HTIkR9;Q1BaetQNfsTStwopR=BC-!v zQ$+tE;SMDfs)+(LrU+_0@Yy_WlN2)L8KzjP9>ZFpO1u@Sa{Y@{N>Q0~kHaAY*@XwQ zV1Y|8%VpTUDM%$v>7)a(OMrRsJ%TDrEf*qMRpPcR@6`$dHrt^qWou&ynEOj878~{g z;UG?zV*#jfw)QzBww8kPPRFE&yNo~&{yogjU%(fZ0RDh&2Og1$$27TX)hdHeISTh> zm3yWYKCsa_3u1GY3~~pkPBW#6H4C)8xk)aC7nYXEY>dNzH}hXDR{`IM_=x zq&HjV%g7p6I(&yGOv=I?UUjGFv5aa0L%4dY;xCC35_`|gl?xs2Iq=WMF5cbl`3uTD zZ{m|Mw4)G$H7xqStW1cq>t0cQX1`iFAh*Rj`W9K0g-G6V4|}BryeL(91E@YCIDG3+ zUG=L?u=mS_C4I3uXt#iWPs5@~V1>*f8iMU3C_LjpAvI}1n=#b}8-5A4*^X?lp2B_- zD#WlMWwi=|L4{?h)C0U`>!iXx7s>gZ zVe!TW?omD z7KIsS$vZRk&9Z{80Gl_(AWRM+tUAohE^gZYlJa7A2=`F1a+REj4CT$QQ~w0M{JAgb zknvU-=7CVTg1v&)3B@PKFw;vIkKoW52KI{3!eSPFgdKcM;I14Lbe0gY|$6xNGCD?4i=TfM?p!M!x#h+Ifju-B_?@Lvr(-d0JL{U|pRtW2Omb zZN+t~HE(N4iS%_(N8Y&AlD_72{Ji`h$J04wrz7H;c8{IjyLfuPd@FZNYk_a84jqh& zY%MKwI_za?C~GwsOZlLPu&O736kjYuu`ZHO@2dzhJ&rpmHY|qYz_zln)7I8!dJC-2 zYOG{!JB1ZJ#O?=9lew7xm}iT`fGHPj5@Jm|0jdz-l62$S_nM6(T|?Lx7-g!PEBm(Y9D3;2x zmSEBNNM#ye*ZGaeq~+ACS&b5f^>?YDFn_)FI0E00P>v6YU!85*R{bZ zlu1#O3TWFx0J@K5_u^I zh7yq(_Cfff;B{1?%d1lcM9#kc*wY*~3NH*jYS(%vH`!~7(BIP>653fDxoHM}k_b)^VurowEsRs*k2mb_CdmC5nH~N}6`VP2wX$@3vsi&KBbC-W7RSX

E&;;qS$I(O*KDv;DM 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

0W%4(z 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

4J7D92A} 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

oN$qB;J%j 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 z

    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

    guLxGU z;Hz zsCh_gDg%$ypC_e)HT^$$Kx3e(YuQck1IMvUdP6TS$x{lG%IIs@)|>CfH~Sqk zyarD*dVb`*7o)P(C;rluIO>S;)~nqnOWx1e#+hbfoni7TFR2a}|FK*ykAlv9GYW)j zgb2C_L*fm)0@io5K_gh=d5}1s2p~#yuw&{lKG3_l@QODSwzRDoE4?Oy!7GRnR=6FW z@|%z6pFqGlFbE(nbocM!qyy{%7Lw6$%R3h_(>VNAETkLbbAcXb>@Z0Fw4tbW1Jq1H z_J0W@YPp(=lTy*BNy&m|YbZPO#(?j&eMEPIT~cr;x4(zyYOm=o>e(rCH*kZ&DLsWR zyXg!iHdbsU?$#dRJzzuojsPG*ArcDj-D>hw=}fDgCMqai-cW>Hdhws6By#uSeBnQz zN_D``_Q@Qi19=xMC(1^rdXL>0sL6+NS~&05{_*>;JNv4B2!rC&S=DGAw9`s#xt6_7 z358rIx$ba=Fw@zVO|J@ya?wPNT>$GH8wg3@+$-!hFIGJMsLH{=d;5eK@QE}&l52>} zArmE}KLA3*Rll(T$u!mkSlX%CWQ{Jf#cYNZ7g;Yf6n`-u7OJU&TC%^8)&vN#&`@Nc zIkSk%1xTq~{|2&=d~snQNJD&!;aLO^I_}KF8;-y60K~W1Np9IF^9IgxJOEo_+r)M0 zG^$xC!pKe%YnBd~=)s;C`dZX&GAsa(vrnTNzdZznud%RYUAPM2HLL*h8)U2uh32~5 z`|0}YfY5pztnIYnjSvN!ICaf!jKYw*%AK;Kpi3<|wu|g98Ju-pgEJNn^L(Kjz9@gW z?grSN=4`ixRAVPv0-^KjtW|nG<{IwqL)fxHU^$0oW$CCe|KIoddG&@2*v8H z_x7FDlh6$cvFXfJL&Y33TUQJ0S2M*F>(>%-+zt6cqKuM#Ijs^8ljuC#erhYhWG07`wm;)_{#lx)gzc$@C}$Qq)OS1awtIaEU)y8pDCT(IvP!O1J&1C%hRT)_#!N^9hn0Lv zYss_{!bywyJZ$~WAaH4h?=QEwnALL#U4SgS};{f5`6AFum}hkYeweuJjS z9KtogmH?{B=t645tuq&{V&cSwq=B9yDfWE`*x*>j0`ri#o3 zFTS%AWt&O6#5~DDi9hLYPP%Sjp$<3gHvQZMj;IfuaGR|nDoZ@@%&D6FNyftDwi|UR z6>o#SGVkB^Z#Dwgj;JIaznogw-SwvJ)U*_AWW{l*-_tOw;M6+pcI+mrY0UURcivGb zw0ueTH5lf(8xl|Q?H*oH}ud<#fqOaX2E)B0-p7U>^zjs9oO3rG9j@mOhC3**(Ex z%D|G_SJ%n1_bM|cmFFg}mO}%M-Sh~t8}ytCdP@G0f7l^^??)hLSVFKHH|9Z|()BtX!f(4hDXa*pT3>6=#+$U(Z?ca`=+=u-oxpw!|B{0iW z{7XFC>7a2D5K)v{pMY{d-)>9qB@myqL!262;XfGr<}^#S=st_u)~Q_5PFZ$wx2JBU zi52UP!sGi7dmdTm9=qFVF6%z^Ec70y-_Zoz^iD`5n3HP%c!UamfF-FW51Dq4pupaT zUw$K39RZ*J-i{cw813aK-qW`;^@}@1f_~`yYEeVxvpq98)#9u1e7pS+yfWsQ4nEI-V3V;tE2UwY>-C0)3B3(d%MHr=cW&ii~f$=1owP9}Qk zNNn^+>$4_GmZowF)osIg70aHbFuXP1z1*&J3d~vQ9q{KhzU}nJj~;Ra#Q6IMLs%!& zx39dRfNXu8XqQpFY{blyNr1Jf=D}8Ah5=%ZQCxU(GUm3mqWCxy)6wL3H>y{Pxt#kg zE_o^5f(po>oAgk?<>;L|ikX4(#aPpx+-4AymMh)ao|;kM>AH#8vU6vJ_quy*+?mMs zXbrW-AMQ=tbPe3kR7|;d9ICH)i{zQSYpx9#eSlfH`gTO!_UUXpP4=?Wjg!PJs7i?g zEJ{6{+hF{LX{5=5aw4a4_iq5h7@uZ7ng=Okq~<@S%y zz6fxWH{pE1sq?m~U4I>dE*Xa3fsrm;QNNNA`@kFBQJU1CUkw1Q2kGzKuU^KtH&oR! zDv$u@!zA2sfDY!Vy$I_X?k2FY0aB8e$%s@Tz{lJ|Jy8ZNcje_SBUGLXMtYplsOda~ zy>x_jQk%Sx{%|zty=Giha$n7*L@Gjyak@26kOP%1YTB5E?5kSWzHznhJnWWzph~2P z<;`QMi?!}&87BiHHtFepKo{IdtY9^7b3nP9v_LtVOxx%ziAe;bbVk`PNWrRd8`!)@ zK_ihed1IHnR2xC~rFNavjNDR;@72g@<(buc(ANKX zJA0gs?No&TPo6C2QL2lW^)7GW3-ESzXve*Rps#cLscU^ zY6m?7Bz>2>t2|C&<6Zxe|1#=i!{W^$$*}!9T-iOPUS=<6)=vfa{>*o^+w*c(JBD{} zcGHfms6GVMtfE&1vBDcQhz~r*{Afp&1Py^5=a}`KEShwCkn2sw&t} z9kBkSNb+qJV?iod*h%Trv7S1!E1SKVPaJJ`d%A0(;r7X0Z4Dpc9&V<~**Bl%_no7I zn{+-M5WWNnJbK%{sU-zD_E$8$Ipmz9o>lll^S}u&G5&yn{k<}|sNqrG)QjFoAacyG zV<92)<$fiYEiBo!!d5Yr`osd?;DE#K|ayC}mPfq;?sSA6j>G z0_o9P-)I?9_ zQ^7=$IoH$DxB3LzsXjY)Mt;w>zBF_6S9>>X_%XuUv7a2i{fat&8J_xj*65{&H$hX3 zqcnr@rIkLr%}!NPG9(lDKuE)TeNflxr2Vx*uf1=1rS+XJ*-StHhyv+EK}@x;P%phd z(mUPijqTrsC56>u2#p%cb(IMZyEAGK%K{hO(Wj+K7Qn;e0(+d1U)J|6P+)ux0RW(M zZz=rWTmy>AFYxi+uTa1run#8=U?tgy>^j_!1R_oqxLeeNj(ct{sAU{jtIb+&y=oV5 zb);~?ihc|WaD(~BGse5s4~iF##$}u`|xuGH=e=o>WCQcvEkZOR?8*pPkY?B zpvPJK^o9eU_MW@fU%RN(cIVEgeaYu`zWIFS?t^cil7#A^llZgkJ=Qn*w%+n1#iqLE zICcxMpw{i?_x5LZJ|AetygWyvov?kKc(Csm$Yo&){p7qSjZQ+j{bALcJuYR%dIB9W zXCHpM^CkHO%Q8qL&&u*jDXW9w>qXzd|BQR|(sk4a#nYO{7D{rpO0}XNO_u(+vy^8( z^xA_PNr-7^q~q6DkiMzyEv1ybINwqnlDq=9x(`1}|HlM%rQrc3Td27Dj~M%5;~~g?a2LB$a;&(UA7jYbEHv9k7!5%A{GjS-z>9Ue6di}w?g326gxaeuC(%dC zIm^1J-c0SQG8SO!+z+>-g1`Q%cygfaYyZ?Cl)@J7(}8laej~ep`i^&CxE*ZTD6GFX zbu|4o(Zi}yHW(i9ld2)#+iq;enfWmwR|@hK;%WG#MsU3KRW}X zt)Y0j;$Eh;^`pbFyS`Mt(93`tC8UM%y0_(jWlwGeeaCAV%x{7@dIo-_o!BqpBl=bG zmo_XNTR&0G@w!}CoU};8*By}BnT1?(D9^xuyfNy$nLdtdBW*~JK3VLmmG4qy*&p`h z+`G=3Y?|3%UBQ@u&^+F4q0T=(Wgv6<&ODBZqg}|UW~yNf&N^GMyY@P!Pt~KSK*#jH zA}gxlSHK<0BVGR) zDe);edUo{*Gg2umTmV!Dfa0cnyoKKWCKGG&=jR>%ykD!xB-UyuM4Z!-a z3y0r?|A0lZUv|eg7woOeDn}jHZXciAmY4}N@1Z?RTiHKQoTZhp zAb#zy?Oo^=j<3mmHE}fzaEcx1 zaK+5F(R$Swn!UflWm&{OjO^b`{-D#e&kkKw>3pt<^o<~*X#%QUCse4Jq^#vAuS9?& zns9H1n?iD9LND*m67|eA?Jg6H$MpY9cq&o^l)ElleU5T@QWX0{NTf65BM0KWNjO!6 zv3jg^O{g#ql3ZI-9h*eItUdH3=5xBcrq!g+wHWwYJ&URRa-!0@4Lg%(>0GhI5G$|6H;O%s1xx^onllOL3dA!-oC+Ht-%g1?_olV{*e`Z zp@l)-euKgmgFPdU!H=j(1yDVJBTvWc@lLX&xtX6i3tWqvWH%R{o*(y)Q+;jNs6o%l z!;%$v$fHXY@1arwzB-vc$|J&sXxC^EgNKV`7vj6%V8P9n6>0_#nZUHjlwkyd-fNz2 zGgK_}1l>h!_KphfBJcJUr}nm3;07_#`3j2blc%5a@Wu(14lL$>EF(gfVv!^%H^SE` zm(h`nGw0#t5ueuq-qChtKc79_}44g?b`xWS*n_B6F>fce^ zKXnRR!?Mizn(xFjScs$t$&_N$UbnbFSlzD+p<^K?hySpjQKO(NW^s>qEDfRf4J88ZRHOTDjmV zu()7x!NAh$ln@uT9~_A^B4o>PO&?Xr=1L1_RUjPRLVh?o(5vql~QO>_g(w zZsZfPns}Mn#8XblMa$KFphZB35ec7D@F2K)f{0Kd z7Hnw&q++!||5!yd)l922&Qfj)%BjH3s#;Y!xSJ@T1W#W3ei;)3KJ6Q4>l6)_@rYUS zV%%B<_#`!kigA!AItbA|v2bV(&S#!42mMy{i0}x6od-FCimTd6Rz8X@kcA!=RClZw z5qDtxW}htqX5)3sWR?U~PTn0PwmLRxGM5Uc=u^@nRKg;#bj6Yw&cAwea8aT9=sY}= zMI_S{^k^ieGTIEVg-IeUBRtlr3NCHfmO!G-WTa^uQ#VRSR=a6VqQ$suIJ)muukeO} zj5H3?e#`c{CV`ve;KHW1t+18DAfF3s}`LcHv4ShPY^w8-{0Q>|9u6> z0PB7D~i+mWS6r(WT3!r8)GjA$)L;-1PZd2!) zxIBPZu!I%_dAydRi87=`Ev}6Y^*NYY`z%$z7MCycaE{hl0Ug0|jY@#%CKgcKh8$Zm zsH$^L*RlExe0S0rj2Gf`b)Z9C3Un4&ZM{|jFCr*kI?*CJa$(fuVHd;&h(9(-5Zfkv zVyHURLIDveY%*ao5~;@mn&?S0UK;NYx_pYLDak|p?F)g@LuCZj5U!P(UB!e?um z%pmsYmY)bddgs0RLZ`0i{#M)=6*m*ehh0RaDHOe@LPdI~_x&`fQr%L-CX42bT3nN=j4Skm)*0T^ zBpJl&5e)DRKYg`m)|Ea-Q6X=&kZVotv`cI6DFXvgDuo!s%f7#zFS%D@2|~xLAkqOa zRRpm+TaqM(+4#r+MGPRADW-3KhzBmSjLzVuqiwVsK#uu3-=lBYt5aFZwRYVp<>yq3 zUV~c`MUWv^GbbKgv_`0^Qz!$em^tuM4qbG7bv8*1)e?4I3#=Cg!aYn7ggW97^~86X zL*>;XXPViCbkYM>H{v!?vofHHRcZ#AC-_D#2^!t3f>1WL_47Wg069~LGY-#PcA z%%oDf#jh;FiVz9&0jad2&v@oMhYyUq{G{&SZ8U95Nwy67+ddb>LFuteKPtd9GRZE= zL?^#w$81GUPoVX9>|E`(mKku|Bu^Hh6`fL;`$+2vNNEgY|8+OJC1FoBj*}TeA#S6K z)_bkKMPI~(_7ilFL4rM}*c5n#xMv&X&yI`sRl`0JDoz_L%hEp zH}sahM-KfNBuv?R%z*83_8A#%NTMpoWz*qbG5O*R+kQ~8!@K|e5JYLNbv zVzA`j0>Lr#Brc9s0cK&_zM7YE_QdI-pkgfX{b1FMMk8>BQ`EjkH`1B=*l^1J(H5M@ zl3uQdld*8aPB?#OsOP`%z$5e%z~k6m5{Hw=J3=dLDUoF`z;sAJ(lse_}(OJHFTy8#nJJt@nd2_{~75viY;`wRLo= zOWtZ6@|JVc3+Dz9Yt>KwmUW*ld_UV@*hqrX!T-Eofnx=rm9j7XQuuI9^h_34OZ;hX z!)N_Ew38GwBs&?HZ)0AMI78j)$I#n4)9g2c>JlyQEm%CCO=pQL53MhJh+RgZ+4x%p z^qPgM8N5%1!MhKAb?Mt)Szk)8D)31^XKS~h8B=>yi;6!3(j2B~xK4rsbH8kI|G}<@ z@29Dx3qBqUr$@fiC;@>CvMom8)5#~f8RDfRflI6d-6suI{vYSs2bLFfX3F-Ef(g1> z8fWL5@4HUTx4fZScc4@s^?6L=sE_Img}+d~EoZN$D%9e}4M=+IRsAUp8AKJewzHc= zk{qOhk%>Va=3cx#K9XPISAEmgCXpxzN($2jpg6mD4}!X7$`H{c^w9Lqb|dSMmPVmz z_2~)|D*5f&IzHJegw4!>1XPMwD|XSiL#`bw0s34x6)5-abJLI;;E9H^Wp@w+j+RY`M7=p*;K<i;5@NZx)a3py-Y{5f+WMNA z@@!T;N%F1tTLT-iDT3cj|F=Be&VY=x=v1RyEfML_Gn5ZbgVQRrYnHd|IHj!AT+ zAr~=(Cm^D(tuB%!L=BCU5gM2sZciND1Y5-hUZ%St$gwv}wB^vuehAV=S~^HbHtF^; zP<3WP{&K`Nu`_L*h$c4o?3&rP?u9JpHc8($2?59LGypkdRIlLReD9#?%@m)VZe}SH z)W)0UBMy%&?#+-N-2_%$2|8%OI*<+Wq@{2++*xpNS$S(57l&mGXw1>=KZa z6;4FE6A(CdZ(TIBV>AR_JzBhB8`7PnQt5ip3u zY3NZ7ldjcmKcD(y-w$u=B_Y}!u9EFdIoWyW2iw$u=1H&g0T&dyHk9-;vKbEx<4wn! zz4Xq7x|I+Lect{o#8`9jE?*k9JhhM9zqHADsXoOB9~i20!tqP`smFFz0>)WwALW_d zRsnCA;+$HmhPSx!;q{yo_Fmg-#X0&TI!6ptO>#Cne#+gl#Y~;aM0*WgwDbUeMRs?t z-sJNh*uPAAx65_<%ub?s$nqUt2e5PXvwL2eJJf1gi}+sNmlIlX>xf8#tiC3 zM|oqntul=^l0YLBY?4j_$p%>T)c$jTG}z&8;b!Edb>7DbhRe1~dG#EP=M5gt#3&dr zPSaeIx7{x$W0mE$s60^VJVE0IvZjJ0TZEy#GMzl^aWmLGfqBThLjjhj{#B6g6upLb zjTQk{czwjl(v!3OPc)aBG(Q6Z-8f>i&S~B#XwhH8{IrqwcWT~Icwe83doOfv0qne; z!ZGJicm8^o$;2f2D4S*VIp@`EfAvr7VJQw2V}&SVkXV0(gwuWmvI3Y~ zXYxOSSXqvuZGZ3Lr~y8C=ERA&NUcqwO{eU!aaq4cdkGV>EbxM{VTwS>hSF5b=lLC; z?C$~9i(TC1`$qkMz^Gp3gncnr{WBWj@Ji)zF@*{&U|EL}i5q8v5U#MbIgopule<)+ zvEcbUr)k72dX}!Sn4@PC*TkC5n?k*b_ls`GX1iB4obsO3;Q~C zEIS$%aN1cGam>2dR~bh=>YTuaoSF+L|Ca7?DL#O(-9V(SRmy_eO%=m(%RK#~*YO^` z5iq6J{KbksqY^R6^2wdI*?ilU})o zeVE?l7)pr4y z+wOagz;W26U0MkMK$tp<3j>&olYYRU40RQ`0`5a1D zs3%zEZj!N)p1H)*n{MU1Q*)f!O92%?pz{m>d{vs&Pc-hnEs9hh$%O{#Y!5aKTk0|t zs|ngbPxHT}Uo+OOh8=P`y*EsOm1(tF{;ESq4Iy7mC|(ZCZs*}58gA`8J2aEapRhi^bNuc{oIKdh zIr>e2hII_znI=Ln`~B+$PYW#1j+San8Xz|us=I|GNj1D_$iEsz{xL=b&5F0$n6^rc z4!a7zxOgVZ)s$7Xcpd0mB<#nE(2Efn)ia~o7HGNu1Ye5fy{Nr&<9CW2);K`84CWu3zIObRsL(@#C=8GbFJ6B_Bi?r}g4Pl$5V=`>sq~+8fj|8g7uDWB6zM z3M>F9RdFv-pxhszgs)&~STAxEU45w@J=Lu~YAg-uU=!+gM1%s}pmjTHSEYt$COEA2}_vy`KP4q=8!A7Tr z0~y~SOvJU^%1;J1o8%)7W)neI4j?3R~ zWB~9otp|#LOP*h5AFv*BSFyi^s?GzN$~I#?({2t^-%-PAdlf)Sn$Xg08Awaj+{_LD zU;u#S0R<(BtQK`WDmj1K-n;pm0siLS3h58>Hrltd49yb?4<;j*L3;&gmX1O{9Mn!T zKdr)Z5*%UyAQ(I5&$fM_VB@oV*ar_{)W~*Qs!9SiVwwserK5k!u=8nn=XB2ZCiG6a z%4a@47|;Pzj{fD&$OV8nI{@=T{-v4H*i6`6@bxY@z#{eW0eybrG!JxN0*bngBo7k_ z2Z`s}gU^akBE77ZbX!&Swiqg+y<)d_aCo{K1lbjQ5(3kj-hVR1;Mkr0)njlT(O?9Q zT<3|QoF(=)q4J72#ib6A%5V)$h{JqbViY3I3KM(=CniQ8up_il5q~o=y2F6S9S=+Z zh){?7WPz^b13f*+FrMbiu{cILx6;vrj7HMb8gF?a}wd zkiX-IYouM@BO#qLJ0nCT<9tXi`C((OMSNYhZD&fI`Jb10q z75Wj(au%fr8r=l_eM!l77*xs4A8AA7T0kMo`r%7n=T8L$wtBk}HQtNxA~9-a_^7h% z#7`FPFwy-h3kf<(81cuvr*fRSG>m&Wm%qg}@g4r8DR+xG&HjcqT*vj-{iqov@H@ZI zyw{ya#?0~v=5t$&Sh4TdFl~GQbGB#;l`tkc!tBAl?$x+JC46O}Wwytv59z5Uqg++0 zL?zI_3%2>>gLx%(r>JxT=3fzMO4Kq`%S*UTQ$UM7>ik`$`)3I$G@4w{l>a||#;Q`~ zqaC^2z}YRJB^0-n^TZ!~HRyO0a1vEY8u$l4l!Mznf*GIQXe-Ng3&?&&MofyFyaM(n z^E9VtScu^gra_L4z<*8^@5nDLdd_0h;%u5gEB35cJdn=S+@Gl+0^PY^fcsad@Zu$Y zh7R*8NH4lVq?f21=!JLmDHWcwW9^|2JqJHYI~1pPZhf0D=9J)EsiGSq9UtPaV+12; zl5qclAiJ7y-&ou)O=vn1I7xN?&csXo35o|HvrG5{fA;;CZtlO%Ee&5B3a;*ZNxCjR zdp14OCWiB#M_A+IJ7oAPR{+Ny7Z14uV+TRkDvtc640|slxld)R2nb{TmFHEj#AZWN zcUN(k4nT!B z%Rdm|wVp&G(X)eyI{54S-z9{378296?b&t+y3*L!-{fH#=AeqgRsj+|2}>E)l-qKO zs_2OkleWkWp8_FwilA?V*8y_l!(T4zC=#MzsqyJ6vse2e!_UwW#N7o;*B?y5#%Q^V zfnnyQq+uoWJ?|U0yfvQ~DQp;qaU9^k?vQ=_4d|9ojz7^^4DC1s`QhD={UM;RqZqyn ziezD{WXez)qLiqgLaHlY#C$$9?B zQda5_;nk`~SN|^^4`<~O;Wn}@%>uNYATwe4YNgRNJt};|P9iuCLKgw~C8saM5jB?1 zJdri;f7NrsTM zCUjE|c`^Wi5zUhKI<4FA0AwTpMS}P51X^9G`(INJ9-|Ui0yzEdU-yZ2NUfB;#3OqF z)#ST{_f1?S*c8^@uN1SRJAuDv?!uNgSR{kYfpy;vO?0t6*GwlzU)>H|;9j`VIa>JU1y>fHmjE+)snd ze~8r!4M&dD2OeyzcaBy8FJV5gaJhV79RyrY!SbvACTd8?8s1~}l6|nUZi^dx0>i6I zpbw%X>lQRnYYA?cX}xbN#E)n3oNBGc7Y33#;St~~P)llPw@vlmLmAzfig({dT!WCIbUt%AvA5zlB;HI%4&50NhnpdL-q&hl}N zR^sx`M~xGYZgvvG1ki9wY_T_MalOB0@zKM@p|^`q{#zW@ zQdVcp(um;1o`NlZz}Mn!Da1!6%U~h^daLy*`O(Lbp@xJZz0m5zry_`2q7e^SunS+0 z#F8N*AmfQDyK#!mhauK)Jtkgh#DK_x&7X-0a9#D=uw`h(@Dl#r^fnW)rCdFg;T2WD zd_Lwr5!)_?@5HG7&LV1w5fUm&M?45-Vn_U;0P#`S2l+_wi}4813*a2ybN8~aQ2|iW z1HJi&((NMrJRn!1dohz>c(MDZeM>WY2WWQ@iODGIj{`*|-CLZJUN8fni;zuj;2lKl z02Ot93B7q~`J@n4#{a4XAbMn|j%ejO3($2rXTFV(ifXb%nrHkJy-J$|I7O-G+SiT@ z{6k`T92vM$g3E299KVLYtoFcli1dYTk<|3b=Ut^|43jIG6RLb{F+;qrXg{7THT6q@ zEzqD#3}z=$QOt;rOz?Fb>Io&@fa?8ok3tln23YdxENJZaXV@fYD;@mB_fOY`6$K{f zVK`CaY3Ap*AdlfJ$%Q}9!;-~cKz)A&U%8lhX_1?B)q}`;x4GpPlyo05jGL!Irq*L0 zz8@TO1}Bgh_m?HuaR$3Pz5TGjW_KKwy=4?cBqw4SA$%&Z`HSv`2|Xctv|e_j-?x|Hk;vHJ+<9&s9`1wuY3s*LoiQ-cYUp7pnyo#g#1n zRM=Xt&d|Mi$a1*%xU*+sH9x@M`3MsVahlBifXmOhuyVY+Rv<$HmvaXIS%iE`I|v>4 zewqHcKs|nJb&csSEjAG@_%A0%Wf$#i_XaAKzUOP$d!SXYY^X2QD1GwL<4yCP@6@l1 zKD%!FCFFSlzODkSR$Es@GT7Rdks3K8&ox|Pv< z)h_cbMTUA6(N6b3c(3f`W|CJMAR`)|Dvbtb$1FfLndrWZ7-DP|23D9lwbz~*(I}of z4y6U&&G{N{vOYCgPK7A7cO8WsX1{$>i3=1x0-UvH@m&O{g0*l3~Fla z;sw6bLkR>Ddce>TDWQj^3892)Xi`KBMVd%Qil}Ko=%ES<7(@{jH6SV~YC=argQB7b zJwZ{y3Wy%`Rk^uy?}yC(mOZoQnO)Yi*6-hMbTaOX`O?F}SG{jrdX&E7R;cyaO^DXE^`Tr`d89?V zh&&D=t*K}jt?+xbiU@k@Q4RDX)+bo297mN!EXLn5dr4m-<_mRGMJ`P;QkM2g<4x^0 zm$c-*LWFK=U3DH-q`pC{^{>l@I|!$xU=k#|UAZ2el9^4!^nm0ZHK!jnDL&?bKA_PJHr-cLs8pxSxzt zyIq+O`-8(euO)GgbCmX?^^dw*k3~@GnoISxgz61`t|sQ({;sX3aa5o|{?wq3e1LxoRW!W zTzz`G?SZ~72mC(k2EtuS4!-e%0TQ0?RV$5HwQtm@SKGlCGSwo?e- zCGc+0Gi{|esjW}KiWpU{j(UUAu{rl^bo%jUQkFr$i~p=X+`x7{KXKC7Q_RBTle+mMqo_y{ zxWlui7P>sMiN-UI>8QjU$q_t8g~*Il7&Pjh&@7edni2HA&Qce~aeKzB0@r3KxHa_t z-glW>(W;p}#cNZ84%}J9WoTLucMOYxJtXg4zhm=-?Urm z)%o+Q*s|}G9sN=QE^dISmeVWyE2JEiX^2eAJp_@V)hM|0Fd6l_QJ=Oh!*|wk6uCUO z9eU)j4UclX^X&F_8lH9SOCl8KT^Wp8cZ&kM?D3Z~T)gUU=G)G!!vGcFyG&IT%~FN5 zO#&#Aq_BbAUvO%*=XjpZgESi=WWzDX0~k1I{cHVo@9wd8t!jD5r30Xc92sD}2*_OdcPlkvNKsuyV4p{qHumP6DiO}Rm;Au0q0*yuMUHH1uZBZu-SQOmW zNE`n^BGS1BaB-)g=Zfw4nFuTN8`SO@51XEts<Z!QN|Wf~AyNp^IaKVkt2lN7ag!wP!EJ*a6OX>z zDuBnvLsrx2M_xbI0QMxuI;oR37XnuyPqwqPlPMn})WAT*(54dzM-u)5Ad3t|66t^p zsYU==mjDPCyp#zmVVpvBkN{hxJOL!1fAcvV2_k~-V?@fa+;NZ~rVOZeE{)5F^xkUm z=`&EIU3$I*=-)Ud!?n;~ZT~t$9$sn3nQ>+4T=hlr$_=uK>`)b6^s>cpo!_@g=c@6A ziUyX&+qj^O;z#_FPzgxsv~`Ked1WZjQ)gyU4ge%oC7yX5w>Z_LDwL>G<$NJ~!Y)*w z6!{7SEhbDjk~UWW$9bK+$nIcA3|NAGpW9JS>l{s$TOe|yu_U30#-{-IqoLYn7}5Gs?OYy z2^awT8`Nl7`MP9*&Neh&E#hkC``Md9JvNy0#CA6c$H8P`9LZi*=e9SaRF=$2(D&Wu z*o;rUFXyMvLwH3#vR1_MEl8g0fcGW|S)9}@H&CK+qNI@2?1LT!HaIShRsyPE%7FXr zP2*03t!E;xCejcgc*LP@L^>TSPk?G%}tnpIV7t<0L4K1J$E#Ns`H3h%g1J6mDu1*K6#56);Isr${P z266Gm17;F14Q?fYy7(Wr1<=)J3ZMl8mp6neTWO^`ivpDT`e$28eZ;(Xj;5-KbQ>g)@#Dev@`%!`ZRi z4auV&jtFQhv6p zIT#XFDV3>qAl|?c(kfCOS@cjJqKDc((U3aXkk z={?(o=b_1Z9>nT%>$1gDipsYdvsShY#O(^Z#Z>8*wz&#EQY=x#E=w@u;SeHyk6r(B zmO{Sxw8*doruH){@WjTzJ`qR(4T_&+7?Z#m$HSTqJg=G~Oxkd}e>3kipE^3Nm~sV; zJc07^m>L2Yk#t3!B#KA(uD%|Q7O(vFjm|z0R@wh@#`IjU;bwb8CN~%;h7r5=cT&vw z+oWth7so|P6;G~xZ%#zN|8C~&mgZTz0DS953CDGS6HcY2oE!ObKjb3v#m@fXxn9lO z33#E>>9{lc`5TUlMGp${DZXrO;6Anj_hwNB{9o4b-MH&<-OQ&Sj+tx91>OW~4wt45 zY3EKT+&r8QDYMbz`Ryf<2>%G^{XOqZC)kdtmW_WFwB#AjS|D4`C0Z*6G zK@+O4U|T;1GlQE${PEzGZ^qmVW^fYR(~F3Q;z^ry-Vg*C+$$b1KG4yWp1R3MUNS-zxv?0`=Rp^38ZOeTK$AhO zS8|79GD!pu0QoCC6C&J*KGPMOR52zl+!%R^ z>76(>p5@pGIrHRS$40-D7WF9Up#LBfJ;4g#5qC1e%A`#&8Pex9LRph*{cg}Q34&*E zRp(gl3>N-~sK@b1|*CJoMU~ZVVzt%uzz~|A%Ml zkHqb{=-o2N)MWVV8CL!0bB!vme#g=!<_b`P83+>==kJ%wf+dA>`YEU_)&QGD0+Hu9 zhT_E?Xc(zE_IG}{-akzEw)u$LT>rzR&MPxYkNGX^H_?1VJRPW+>IxeLoFyuUy5ZXd zqTgtyEstZg5x@8EjANs$frsp$)yF5$Jd=Z}PlDOahTMNoL2O&(%f!cfGxnAenEDe? z(ozDsS$aBin}Zfosn_oa*E(&POD|*7;|N`h!}B=I6|f4CyH}#s?~=eGDwLGH{}T>! zec{cTlk#hb14)~;+!yd8wZm!>h8-H4PJ3StRG~8}SX7HGt$_D0$QJoHf)P@TW?CK* zEy~6s3_cVmjBPu@)F743c@$es0KFeuFSp_p(TtIdHp_sj4c^E)`n)^9L3hWezk=$L zq#*1$<_2@RetC;J*hrmqza4471r(Zt)hAH(9xOrz_)IQ8Tga4kd79?7FSwe!Y*Jdl zyY`14l>IXR9u6<0L*YcO>1{P2gE6>3U$k1ZaR&R|lwK*h@ePft77=r0Y1xQ7)Lm&y z`*QCZ#*_$Fp8L0r`sRj_M^O+IEnznRR<~8Z)+C*jZqb>FMP>|0StsN+KaERhV&C3m zZlQw-gk?))27cw8Hvl0|aHKl6wv=aYh9XK?O@CSLq>TE5{oJHS*<9$C#oluR%z$;R zJR%&O&j}~=hAzp5FX)9(mBKV2tR|!i-L!6`ChmzibsHC)0WI=m&~)!Bm?Km7i8UOM zic9Q0e2AYwd8?9Xd2e$Vm4!w_>;@dXX%J$@l|4MDy&K4H4rT}d+lgzQ-RhQGY<4mr z6-p&L#PWl>D(7_4vp?-`QTLmhVBEon{P@eIEOCsLZU>%Mt~l5Ht00pd&hWPq*m|>M z9zvlv}-auNVxq<_l2e*%dKhXgErVr`Th6tHAz`wjlIMnvMf z?2&zUnhnlb0L>@&2yb`j)|0NQM`hR^f1c%-%-a7<7s`bH zgcT93PS6qQd?)qyAp_2L`hRu1k^b#WFA3`6+VyWH7EZvHVfW8|pKCkJ)jR9p#Hh!w zSj336L5y}=zK%D^<^gSs{V(&Zhb`VLj98AillxOzn+r^Xd(1@_ZW%9Np^mC0#FkFB z(xzQDxn)1C6Mis2>ZxB_kZI*V&?}(&wA7d9^L?IOtooY&{uIj}`E$8_-8+e$(atq` zt2I8@s6|lSL8u7cXY{YK(o?t)ko+vBq7P2!mFHiy3}PzgB664h2$dclkR1NkOMT`M z@@z@Ut|GWZz%)cM2S3l5)(-V1Q70hW+K*M#|SUPXCrXU_#?7qLP$hYq^!B z$^g9e7MUAkJyVAgiYB*lJthX3lKF?p1)E2HSn@{ZoW~Hz$RDYoftVF~ogYK~?*P+H zY^3YpXEHh4HCm+_VqGiB7kIU~pRm6=leu!540|Z4YRT-ByOl|uxP2u{pQT;!L_ysk z??D&&|0d8Y(|Ow0p(e*R6V?BwfSRQ~u^bL*dzIyEA)? zaXvp_LntL(7va`xA5~t3wH=>F&NNkzD><+V$}`@u&c*u!vwv)KFCi4!dm9Ht_RpK7 zVOPA*pMKgbiBEaeTO9ah`(gHcnB_$g;>ihl-@Sty!Ud&n1IzG=JA-dmMJm@bK5xiN zf?X3`c3xZR5O-3zzG+k4`BwUk{_&emt)Ik0<7&0qsCGgoi*hgV+Z>75{_r{K_$~lS zXtI34e7QZFFIXj;1j-6CAVDd$``mPE)otCdw&%X_oxXIl@7XK6bw6-Ejo@vKpqut!FcF?;I3r$!yb>O_O;&Yk8!`Opt^{~X z#;{E5>JNj4Kp#iL#!#Q+Vbn2`xhHZ)T%`)VU6+at3Pq@})8fu?v~$^H-BYxar^?$+Pnd4At^FN4-P8XCbS%k!V8!2gR|qK*@@y*D z8;I`>ol@JT*`ThCN$T=cH2YCoX@Hr^fGXBc(nriEe@}#4RcqVE*Ltbe8z6?eA@Xvu z_na+ne}8f4D=J({fE)k$@xNhtnJoUBoH(oo}hc~iEjuRj?Q8kiXWboSQeNaz8d`fY9AJ$e#`@p@jm7s5u;p6&kAg;z? ztuju80?*bxbbfmJjX(Iuq_U8)MtZ5i{`4s>n+D|nu>)`n)U^{G8e%!u8(6$v)~g9| zOO^3ebwBIN&6}^^pmk=djuG~D^X4qJKe`PAo*lm>L5)h*>;akd1tmwne`BzB8h0OY zeAl8Fg?!(-DPiIMs=gjh`|knZWqj^2nMJm+)$>l$%+Xyojkavb)GI@RZPX6{F*HRM zX`xm*7paHT5SBJL@1HfBSB{>d6x7 zKb;RWvbvTee$IH-`UU}8i~U(;??C+7f*MRf9`y48wI$C)^W%8v_zui1p@;EO1{&^! zR;ycLA~f@l83{!g*Jcu=KBw-PIXc%TV^Tgz_nZ&iEZd?;S3WN3xW!?9$(Dhao6#$G z50!1w!P_g#$u9gBF(6T*biz$qS9a9&1F+dZKG^0F_$%&o8N7uF-jM;>G9O_0RR{(W za_U%60URp{rKx)`SX=ZJ>fD_CZzWeL0w7zpmOdj0jpVL?G8?ANL?w`2_1&^+@Z~o|4bNPDi z;o-F_C;$F7CP7TaOyzz)TpEOgIWI2OghK5&?_|!lT@_ELtOS5$B z{Rs~Xb*AB_V6Lz(?8a92NrW@gr z`W9a6;bFkzKiT>dkbhk8*yzah2B)%9@@g=DqkjXT_;R@Mr0%A05DCZ>$X~(BFTtGN z@q+(a9o%XW?&&OE)op(zxi0#{^Yx8m5SyhDR)B>*oPjzZPqj^SPTRaG?KBh*%LbIs z6SD{t<8{{u;k9fH9)sgH@){McH>XpCM-W;r$(#T<+Au4bnaF+wU1FIXaHtNH`bOJZ zB9w{Gu2X{oRVE-Ib}>fp(o~(De)CHg+u9xw!@~@2E2qLo zeV>!coWB8E`zczG4bK;vbcF+)B{*U1Z4^1<{uOJ7TfV)Z(tlzE@Q|Kh&s;%4XC>{$ zJpSklKamuk<;Q>iC@bz!hE~&S(X?6oFBDjIvxIgL@cIsy7LNAaa+BmOk${8@FuXMc zz5WUxvG|-8P!0MgVpp>r3cW8#|Kp`kY#t;5BSfcXYq=1UTu_Yqqdowl;dT zHeGnvRHClokYoW)5rB637M2>^5k*q^-+Z{#V50(jc`qSWzOdMUhdu9UU{CLSF-S*! zHWzBrJ4Yw(bcjRNL}($Or%PbUIMK*@M!nEKr>1ld-qQ|MRBo^D?XiJwAJ@4Cs>#FY zNAIfQIggRK@tKY4ikg~)K7Msv2)z0%t+b>g2Xr=ER94Xr0c3d~uhJZiC1je#yA;4Z z{Fi}Z_j=di-_X>4$m{$)vXDDUn+oNRDO%A3|=TR8B_`8SQ1ETuT6 z=RC_&@LPA}H{iahLyV7Qv3d_57~FFMWunlxM9bHe=Vq**8m^Vg7VBeOJb*FF1L)ba z%>(v4aCM8-uYTq)1Kso+j->hWgPb7{am>@g=MUq`3}s-BfN<(7Kcjte1Tg7B*f%kY zwsF>JHwRcJZ1J#*o0*Vg@69_qw)9$t&{RKk54bfA$LALQq|3$ACe&W1D|6?QX)i?F zKTq0gEqPEdy4xTE>$~uhImItV5q0FMsM1aX<3+Xu7Q~rk+pO%d3zrry-Lm2vzW-!d z_!1AwP^@zP;Em z977=zaW;I2e)mu*ob*=4q?dP4#<|IJ!;eiNBOv1)Ft@fagRnZ?H|uPdO1@C7DH9RJv{Z z6ipNWFXUtBu&~T2I_eeuBVi#NNh*EPPL2w5rVpF+53lFW0qYtsIADN}vNj{3@T~jh zTXMha$*qlqnLIz%28GdtPK~+_eUIf@8FD>7$_Bd)gm8E~MmMUhd5VNFfl`{E8$ijP zl7;+F3r0&P$tKME18p}{!j(om0z!s0O-_-;Bxy-sUF@zrFGRY}Sq!K4m@kGL~SuC!5nx6Fp5Nz;IYwKJ3L=uirahv@V*)c$n9}eRC z-}~<7>@G!7{}l%=kQWU;E1gn3s2>EMHEChk7?SN{o}%JOeYZjW)4oraC%u=ReQoeN z`RQL*umpSK`Tt&adbJ{8@{d{#nQ{}Aqv#2udKq0X?3fSo$1rGf{fhzCNhXjv^V*x1khw62!**Jfcm#BCzRjH8#*LiR zC6#IvW16C^f5a!k-&UUi647T~-~mN=g->oV%B+lKcqT0pqwMES-2*nsxo_CImn2moR3X&4Y^edsvs^TGR}O` zj=tjsJN=$3KTY}YqDvsNzs)^7g8d_$eI!NEu1zm|>1pXX`kCTz083KA4}FNi+31YO zTO=u%#+>P3=Vgv&)kd38)z-BE4Ye_18&p?kSup(m$HxR+P?SEFoZ0uk*y=FRf6KwMF>Tt&;R5eQ1|9EtmP?#FY1e*=JF7cBT zbeG=_1$0#HY#%va8%-^70|}#L;bDrGOnj0^SOH}rMr8k>yJv{@_`9zG_6eC2a2J~V z7OMEojNI1X?r9oWnyCNYE@7Rw7_}xq6p4H9SPMlZ5NNIIXQh#ay7 zoh_Pshf-%d7u~!0z;tT!4mz>>S@n;|bSJIbbdFB6(*7sLs$_)fOQGU#hKvbMCZDXW zJ%X@pmf4sk6Y2{+7+x?Mtr>uYUwlUA*H2gQ7M{QdXI@AGL8)d`6={iCsBnPsTvZyU z^2+zr2)w%iB_DC8XRdtu^EmSZr1@gKyhi11;Se|hGQ02aSkFSNLSx|Ejy<1~s8}C^ zFQ_7t?h@E)V%V9~F3%*rI$nYb*_V=Iu|NM(B7M?sZg>=D@uAD&4#4OVvIog6ACAD` zIZPra_|q$kF99<4DqwwEpD)#8i+?VyC|IWWMSCh)t{Gikv#|VTfBD;X%XLpn`6put ziCH>hn(cUTeKXDSKUu6YhpfK-?oD3_(w%G*Pq9e?`=BYdCSbY2)c?jV|L((xsH#7; zN78yg#w(^N1nPD8^sARb23}{T67>6lCD>SCH7U{|GSzx~uDldtdSnmx!eM~F9rkg- z9?F6S!Kb+qTq;1`!Wu6U(vb|KS0bd_wyaMa zvi!W%#6}ZmuxEQUPmb;DUGYxJl+%K2FIqN;(J*lHXmoY6pJS0P^={d;O2RYM(3M)7 zT2S2OF-CYfX8gW)56LmLj!b84f~*b0*4{iV+1J6sR6f$ZP=d?>ISSdtek~uOwZU#{ z)6TU`!rPy>1C7s?&}J*OIym(C%n%w0DNHl{+NbcC*2G|mibzD$A!O8>q>NRUd9pFf zD$)!ATk16@SX=JAg8Vur8|+aXTZVIYd(3n*m_PrW1&>y>G9MrB-FPls9(K;qv%8*+ ze3o)|shhk4GEVX~hyokC6H-X`P>F zVTQ=Sj)f+%9sR!Lf$$bRNuvzXChc@8-SXi2qcKF$q)t)mo!@qGVb{JLj9zkQDM}ul z=P*Nd+b8||WuMYe5W4bEv3-j1W&8<{?n!q|zFYj+YpIHt;}X|0gW94VUQ4@RvF%n) zpORbJ*J~LMQx07AL2u(ibIPm~9kS~??Ed`@>2z~S&@jnSc%OPOdowXF=#sPnGJSgJ z%3ZuDalb>sv19mBkz!?D!R5S1t1GB04n^bFiyk^0es=xvzYa&S>hH! zvO6%l%nH+hqLHGnqyY>FgkE7({0mfm6jnYPz#L%c@`)&XZgFYG616Gsa0hYr-R zkbr@n1CE5K3<5->o-a9CqccaRvO8U4iR0+wJu17tDSeQucGTM1nLg8$TL{oeF1j zw-l|PKc+6ZlAr;7{w7;_@cQCQ!1F?~tAuSt1e^zlkKY`=;6!qoJM1dCEzI*gOt?ix z3T?Q0+wrymr`fKR6#J3gG6NvT0f-NUTQX0Q`oo?z5LN6c*H4wwz~mB@kymkFybVMe z0#Vm)H`KbY`2?qFzoT0JU)12=bJ;g##YZkw{0~*R-S~#r_U^hludn-7(~W#plh2lb z=e+EQaSXl>}rpb9HAa|8O_V1p|`c?THQ((~s9xf?N}oV&Me)8Xlt& zpfj-r5Wt||>#5HgnQDxXw7noMEfe)`uWxP2lM4qW5wT#PYa$zd?N#qB(>;KQJ^qMf z;hNtFu3K?SD(}KL_Qa0$+Gpo1R13$Z*FF_4MBctvw@T0eiW+1&4X#@q9!_vh_1Klt-g z)nm>=3Gyku^n~)R4`VvH*bszHqH=zMj=cp{q#&Ky=rL5Yn4|H7sJ>?!SV4!7Bb9KP zd54%!?B%~$k$T?KO($x{G!G0ajpK3$%OH-l8?PN2iQ#X?@tH%F$AL6;y5{OcC+Kp_ zO79ua+CgDO)7Gy}AOIPDyoM+D?tBH-&{4YmYn<|YtUYfC7b65avSNzUrXu5NT&GB} z0P@cgp0z1!>HE>@SpG%_e2dQ;UH51+rEEN2^J;u2lY_iyi%vrZBGoPreT8e?$%+{d z@^@8v5)yi5m8}*qAiBAfMi7On#ZFtCg~`{BYiR)LT{>6BzP$s0dFuN%t1+#!a~6-v zXqP_a1TKa)&KJIy2e1Ogrb~c?p>KHv?{`lbi0Tw)N$v z8KJL5Ff2`-QS}I(VWOf9SbPSvqydtRC?x!~BqTpPxwwxe`@JNxdLi#gwd-H?MV|lF zg%Yj~0tFNTm>cN$=+MbhTy&CFY)6n$h z&7udjG{c%t9bGLE16RjSB-nbB8OgKB@W$YKkK%XteR3?R-_LNwm*;#cgR8{O&V155 z^m_nLyQ;7h?Au5ac4A!~peIJG;4D3Q^oqJLV-&M@Fw;fnzxDdU#gPkZBMRu2Zx38I zaXGEL*BnfUddruR8DGnhvw)7baX397bG%7&qIX<7f3~-{BrN^m9__vSMecIp0}YDb z>(b0|?faik+%BN&@fb}xl1c6MxDZnzuPqxf29}ZEdR8YtXU9j4+_@zZ$80V_3*tPN ziDCTv*C+|^V(-S(W8K5I9NoAA1^(y217$73#tNV6-Rs(Ry?_ntsFy&uhLb}#Rrc*P z*j=t+y@7$b=qktHg{VP-)moHD{!NgK>owE>FG860>MDl}o#=EQ zlELCtyi91;_}As;hXzKp(s-ci6`En_gl=>|U`^iCYlNw2C~<%fQR~3VoCFQY|5o*M zshml7-Kdl*nzh0B1KB^$(K^h+y_{bu)d&kjh@{av*K4LGTL6+zf(Pq(l|D#TsNywI zKjCrSJtTT94leBg)h{lGXiNz8Zmg@gT{>NVQvOL8wv2Q*GagbgDBL<1qnJO(GXHy% zQStklZsaQfb}fEVKIdoK^Q)6;O7WAnQ?XqxI+&Kpkc4yhW-c@dGSc!fe)Sf8Oj$t1 z3v_m(#PSthe^3b4*}wZ8aWWzxTVbD~Uuqd{HiB&XMA5M$byBV!4L{R{Ir($;{dPw% zDDeEq$=?PKWd3fYv30N=GNDUu8ZT(2+p(vV8kgL+Mg~?yV>>mTEO|P5+3Ney{KvW?ZO^DVO2*d=UOn^Y3Pz)9d(SRC2kv0&S z?RxTfq=K@xA{36sL6sm_C1q_T3tJ@!RM}7qqlm%k7-3O*D!NKq`cRxM5(mNIa0WO7 zgpQJ~4%S!)iQ9s3*aFeU8yeto5M9%4x>$ssJyOrpUQY?4uaDC=)7RJErjPT|M<^K> zI~eHb8=9IL8k(D%`&dH!tdw?H8+qE`y@+OEPEMZAt|`t0FBjW=t~!kEcF8+-c#w96 zk&LL6ln4rqM{)LaCxyB@q`3PZ@$d@tbtC%)9`pCI3W%`|2-*>lmJ~q94+!0{b4SQd ztEipi#GRpmwDb_#(H((wzrcWFfezh)-pN5bb_M(SheVZz>^>F}c`d{@Y*$D^WJZ2u zPS9?8_wJbcQ6X-74kYdgOxshMy~m9d9k@H1&WiR7-b>2fYc;Spo)(i;6O;WaCe$T% z_pjK1gt+K~akQxTwCwm}B?+ZdiK*GiQOzlgi2Yf~`%@xQz2j2dFQgVJlI<`I$G72R=tl`Ee#kK{-~wV z>+)Ociz^yh&NLhyZkU>C5V0FiHn%iQ9iOQ_apwGq(xa_Ov#nzPHh#v*!$;#_;{c|rgA=35tL0&#?#keuTXzO4h8rFX_ce@E-5D7f9~tZ&of*D2*>t~g>i*c{ z`?nrH7>Ri(o|<{^{Lx73?1|ag=@(D$x6O6Dd_Mipi$~91zIyxS)%P#2*VexL`t@yd zb5pwbk(<(YK^*n}6F*TCf{x)*s6Vxp+IwoWLfs~1exkQ?yarDx$9eaio~$;&?e{ac4LrQvrWnsj;<03xF2 z1u@1jKm#6>>vOH=`IV%{_J>6wpWQU=*YM1bO-U%N(@%?pG7u?l(Tuz^In1GBOr0Mt z{?uD>uMa6m1-gB2^u4{`%sif?m*oGB2}Diqw)-hP5q~YWA~TqS+Pn1S;Xg5 za*h!*y!)C4Q9nt;EB!bLD0}e)qqdc`{9fGY>-E2q5L3^GpsAZ-7uMI08-tTK|F=(+ zAglFarRDzH=VM6o-dkU<1uEcHTHmbL`4YsnSMt|p`-FK=)g%d1(XsBH-f1&dJwke0 zSRi3S!aL-_{$dsq!;vs$WRkrUlkJlLQnES#h{|AXOQ%B4N<)YN5DDmvHwE*bkdy|H z46wa4=~fO*4h=20xHd3bh6W0vsvQ(0^4n*Z&?@8?6PU^P!6$ipK|szv3J9J`6GEU* zXbf<;20;uwou|C6=k@46)D4j3JgGwdSbl%9u}##hKlgG3uIIu?V#}1Sp;@TZkk{UZ z+^7X6it86cG3>0&8jVIIhwaQmw_5pGdIS7X!d!@c1$aqF*GedJP4il7nNY<%qrd#g}fpO4k0(;tK zKj2N$TWzi%24H*eRW1jRgp z)Ab6<-Pi6Y2eveZ%S)Y=I}{j{~1vbmdLrTpQb2ozndRMLt#n$?@u+ijQm4)_srhYDU%btgtcv9bM(IH@O9i2HWeK_{t#Yv(+B$z z>A&H6x4Q@Rt79fkLEmo5ZRZo2+L+Bh@q`lbv+tE~PZu#Mz-1&51|Zpog<4gpC2{pl z6^c&&1n|0;x%xA7bP!o(naZrUY2(?`>&vYQTo5}PZvnYcU8>c`YFr{-2rAdNz(%&g!O=`U{fp~ zcIJqaH0RI%V5iI0(~3l&V^!KN640~eabt!pkUkZMu4x{(QlK$j{U&f_bcH%gWYpg| zeE6?Jr)}ZoeWGWIU zWDAVI`KM2k1NfW8Q5)Y*-e8O>@*H`WTi(FkmASG#fr_MS>=xVZI4v3-g5G7#N@?aF zvVP%RtQsIYa7CmDrB#;v4NQ4Gz|?#_gM!*}93=G8<@3O0*?hKkW;X*RFO=K!V;v)p z<|;BLc)3Z#xx48@c&a-0}+7rlYQzYy5qG$dNX|ZO|2@tNiWxfkbwKL!a}{6@T0nk zVG}V%t9rs2QRXl%RHz+HjjY)#e@#$eLf##Lx&Lc%P2GkMLZxYz3b2y1Xa467vj>WQ zZKCaZzNErep?U@$cHgfHiyz3D2tjTU42VzLB=lxpMOHO%!f_jDS8{^QWtK|Y_i&hw zNB8+c<%Q~yy7w}*!P|dwYHE%Ym2P?IKxG$Qyv%o`KZy!qtIV#Vfqb_k;0>YDhyQfM z62*Q01iBk5J;32%D3kR!`dah*aO-`~+(Md%Nu;pi@YqSx6iYs3B8+J7#f>Wjb&RgE1Yeu^Iik(KbCv#*7rBl`_Vqa&O%X1Gz&ZvZP7 zghkrD>gpuX09GjLrx`TO{ z3&M?*IuD9w{|p7^E@0X$ttu1_#Hr{pNO<0^nlv7(?v^VOc5V8AY|ixUV$N66)5hvT z+y7~U3t4j?Vx&e!9WZ6X2qo{%?y8 z@oq8nwc@fUj&rMJ0CXtV`u3RO&7hkFwBMTVyN9l$`WSF3JLSyg3{W&@UcBY zA4RD3x0d&dvMzY4TsggDTetjs@K(16T41#s^6>;J8icsVD-Q_outm?gu~Qi=2lrrO zmJ^j@QY-66&<)oVfDE0OlvH*Srfs6=wXA2F@v+RCh;7vW|J$vSi_WH=L>-(h$nawbr+9|pE~dKkE5 zfL7XgWqNH37qDmA=BdBgj$F7$BGHVP8UF%ME#&6Ltsn?Qa2t>158u2s6U$;SGN;31 zm)`H({MBg)ixhVU0%GW14n#Ad!*5|_n?A3I@>t;|A2OMERDO=3uz_jdA>Mmo2aXvh zSm_szU|Etp#W|+V2Gd4VblzScAI>t$XPGp!Ob1wIb1aJu7GVOjgznNt05uLSjt?)9c_hQ)A)?;nf2c)(IG9VdI`71C`!m1?8@IeeJ0=X27KNjCiy^~? z2iy2zHdeaO81A`5T@2p@!_dWS@(bI~qM;J~NU}ar&u2VPW#%l#Gt^z-pq@Ke#X>q{ zJ7fTk$qI=z3}PM%7`AsrmkCP?4-+daCR8EJgC~tJsQ|J}4{bz)4{m_0X>cyB@-Q8G z>=EKP3)#s)`T(X#T@Z`D?QcIELXzzQz$b+AV-grv0zN^PB?zjfcoxnxWpZe>|C+2dd+}<)RKa%!@e!Zjq#v;)C%YgmhB3>*Pf{ea%G@(-&@5y z^1+QH8zE`Wt99AnCpMuR^w?9{hY&dkBYX<5`yhqL@O1{s3I_x=uvL)7&$=xH9yliC zQ%=D@49dsNdWGH@l=q4~pSJyj3Q-vpnd%t}O@Rjr)Yq%3y~@eFZ1!fR(jnf{MZ8cMAF7 z#`Xf1B>>-Ztis01V|$#32!M7k!}hFu#9-m{({i~Pd!W5wbje-{wC>|KIk(pVvwR>k z*0%O4ikcg(0HZEQU>0F$Pdij57-a?+a1CLy0^qd@q;^&t5CrAc?|OhWy~EqHb+c&) zd86fB^3jgxx%MQQHE7Re0TA)lKhiM34wCxL08QbchkAY9fgi zsDJ~P4`aZ!cW^jS~PDuuLKm-2YIOr4T=`AY(YX?R_6oNNP6=bsEn`m(}o zN_|Wrx?ts^0JjtKgrnsT2INQ`Rz>rOp^N&S2rq5y%!_DYts$!_MqbDr(_};$U(d5&*~Ms zJ1Vg?S2Gl6#eDFsej%*+=sC7c1t14Sb?i)D@q z!I8BW+r%;z1c*}-CQldn>kT+vfYkj5#FFf+K!e=Dpc;_eEFKmhk;hSh+X#1*C%FYtco&Ekq&?!0R`^m;e`8nIAjRpo>8 z-Sypfe;m5IapLZ;TX%o|b60y<_58kyzs^FaJa#uBoRgai8x+d>VR9qGpT@wD{|K>B zA}E=&!{qpZ{UY4F$a@r<^9(=tUSu#OI`|(96E$SobC)HSdD?-Hy)k4xcsCu7WC<_4 zaT<1Ny_?#rG3di}#*J*;2O*PTg{C7^Gr}KnySNx$rOVa}ePB?h zNh@X`5t0~%jTb`Y1bqsbY<6v2-r&92Ma_P+!qP5HYa#q*M$b(G%$$VP;qPzf_gk;( z5AhsqRqt8EXbv;ZHF+YBr-&Iv;i-4>^C}3R9&16>us7w4)x^A z*wL=woP^l(_ryj`$X+Fd+d^o#59X<|!c|fpge)7Yuedma*2nK0sFgb^Fwc_jd@3p- z29{Gc^)!NN4aQ^s4@u|Z&(#0;@w5A|%{I5W&&*xSHTQOrds+*bA)^*@p%~T(RQr#kW zu06NyTiAgWrp{0Z4R%Ur&X~&GQRNFIP@d@Ehw@|f$_K3Pza*(t{D*|-oqU;1NP(&> z7(X4gm=fZ~TaE*#Vvg{rW-rx@dbq3u6mE_3XbMF&s7MZ>LgGJ$zSqaRr#O%H@Btr* zq~{Q*Z_mE(!aui;uYdbm;aPjr3I=uCmB*r*XQhPa56|frU*nA7Z}isykC9#Sn>3N~ zK&xqgnp2ts0pPT>=jOsKBrp7ms_?LX?wa#lJCp&lcNyG3m($WVuas?Uwa1^m zg1OOxHUXde-nhJ$(pZbfRQ}H5aIe8u!jT?x5SJJjOM>c0n^HNjJ}FA}*Ts~haGkuP z${8NbMh5%hwb|FU?!UEI%DS0>Xby*s_8p zXX$$u3~|n@GV?R`gK>nR5YjBtQZ!IJn&UYn)cHP_fagNi$$gnXMDr@{wM6K3)9>Uc;}_Wi$Fh*fkxrVTRPMpS@e11C&nIF z*C})Yr>bW^h%7!1fSBtXBrPXu^U)7`K=c6`yh-vv%=z94p!JfFSDTx3BrRdV@Xab8 z4Q-C4wR}n*!@l6)^c@!vkjrd++-bnRsT+HGJ#wS!A+1GR108ARF`F@!aAnRGb#&9C zdrJvNfX_V%cj)q8I5-FPuDW?d<{uP^3)yUq-AP_2zwzr&iNcX&wMx4Dt_vzSZVISK zenj_9d5Vp1k*izTVLz)KQ+8ZJms7voJQ-$m=&2ju)?f=vZbmlav>SIwXq2_$5rRHQ zxw+XgZ8^~Awp@keZ8(slzjLEm#p z8@eNw{dicbTu!ay*0&VBq_<&&Nz;~T3ptb~wpu%zcv*8*p0O6~U3rfX12H)euOsI? zyZZ=npVXZJdeALa*?uF7t%|MS^oN;ES7prFo@jd^dBili+F94Q(>Hr0s{C^L>(n6K zw{QOLUz&gS=-kf#NBrD5*J|{ncFlkke#EkB;49eE?ntkjrY3VPwNs4EZYB?PcO$Wb zwT74GwQP*?IzrFCw_MZK?Rh3C<(J;2*tWlaHPm_SB1-Dc3*j(vdP?5<-I9KYA=7rR!8@_QK$Q zZ9~r=R8+EkFgl6GR|SQXj*(kHeX=L+k?|54Sc$kPmq`jr&)~s4tL~3!3zRRsyN+yF zfDS{lGAhgB)wDzb>VDElQmX&*Me~cS;k)upez#f^_BFLMe^yl5?OB10y=GS(w)mr# z9A@-8O*@%x+Z?26e$(l=U92RfI(b8(@vCTC?pJ4%KFzPWYn;>#T?=0yJ&^t){`%?H zYuY&x#gG#++&ZA?Y%~Ozq1zJ=DFNgyQxz!DlPqds0nGX!9Xla^^jYnC!9-`GV&Zau z-x+&2(F+1?a{UxlmLc-4`+*|H)mQWgPecPt-17oFvkLV5?{yDSdyYOrA5{Vx&3y*u zdf@!XVh*0+rz0E~nSE@#FC*TlS&Uj~A6n~lV~MX5X`;X^Qajw*)UbcUO;mXEQ9!fn zq3s$m7#cc)+c!nWM*reK4JNKP0XXyXtsdjJwPvwe@#eaf4B+>I+WqtEwbU+bcI|0_ zq7O)HU_uYq3%p+Y$~fk8C~%ERW{gJ} zU`MMDk;@5D5Yjzr4%Tp`rG6W=8)qlSrF)IujWFvuiQ znLvTT)ceE}y7%>Jm9%u5&&=z!+rjdzhj^+N!iy}W;*3DKA=i>}_#3NUg+|DPyB1r+ zV`>~FDCUh*t;{si5E@(Q%x8;u7v%{$@5rf%n2&`2Z47yY(fQ;mE9;P29IM+KR9(^A8UddBxp zsbFFbq_b}C`9dXp;%;i_EC=c6t-*^9@6XMQ;3ob}aMW5hU-<#w>@_ryxVgJqDdQ9F zgUjkxpfpTWABnR}1$RPkj;#L*G4-Cgz|Du40B6M54c`6QSs*BnsSVU@8B4j{f$~cP zF75L=w>H7XQw2Hd6$@aFPWtp0cwCnZi;W!O3ttERkMQ$0NkQ)QMr^?##_l;;?H<$9 zDaES9e@2!yNt>tnwZhtcreLX!9+sw``5S1xq_9}%dt(Va0F$HsXka~28Uj^n>4}Sn*bD_Ir z-I#3ilhroSzZTIYhlu;P=J?RTs~F~U`r+Us-J1T{yZvJCBMCF}HdWm>OrOhOF|umi z@6r#qc8Ce5ISi$MafEULpJ2E2#A(khq+%T*_!?Vdt5>q6W|jDfp9pf&+bU|~9*$zQ zO$GWSy;$uffYi-vt49#gsSXNmC^pNbT~A1g33~fy`saZ#ndjfC!?2Z;I%ucPaJL^) zP;0^%2M}k?9$In;nB|1ja03smS4dG-n=jZiu)|1~yGEHMY->Ah|7Ha1 z!2#Wd*5?Nuf_!{%F6{fo!-p-ST9=wSbbf4n(xxx~!fUSn>mhMh+H|Z%e}0cikWWhL zW(#6;^>67Z`pwbiPOoGSvUZpWZgH_>fIH*)9r1kdU$#H_z{_yi&m~6&(DC-b;<=rF zKgAsS^LpjrQvc3>-_9NS`{mG~<@Y;R{=7f5^7l})v%J{(_qxAIJVlB#)Q^q`6&I5D zmeLkWW8t62Z5I_fUQFB(eL{w#rUUPIaUt|1 zT2U;2V#)F6l4!md?7`>HXWRs&s`B(wjn13qBhUk*C(=#Ut!R=v=8qIy*FOxzDiJ`Z zd6-)G3~C?uboLBNf!%72JvV!5by_eHE*7Bsx#vp-9kelNE97_PSA?Nxp zK{srsD2T78nH+WzqJ6v&X2SIu!Ej9>K$RFPN%?l{Yc_Lt=V@gJ!SznE{QUq_odl7? z3o>d15z6Q^Daw)dpG^R7gH)_dc2<+-C^s=YR|!8Z$HV_XX9w&Y7eIsaIeX-b%Blc+ z<24*IGNKW7W)8ld?iH=!cxjFn1E_QpQ7iS1RtMs$1FU%%v>+Kcx=)n9Q+PIse)!f^ zNJxhEUK&#PD@T#72F&H|o5@u@kJ>AN;qVZrr2%0zQUO3@q7jH7%$`DQ-UnPl*L9r^ zaIZUN4IQP`$H=VW1&i~trKsc#WWr2tFs!SQs@o2wvVP~*b$MQOdGLqU9VmuReCii= zAPVIwj?&j`A_dDiIvLZo^_CD%jP3JEvv zxpb}N%kmB`;ap_WD=@iW@PGt$keg481=40vduIg6T>m^seHxxL#`66< zH2g!#4^i$9G`{B;!23N9TB;X8I(&I}C7=Krp;6fN`9b^?IgZwH?czDn6+U$t(%gY6 zK%dEFKim)I@q+SmpYvTs)pls%+YdO5s5ME9M3+j;I&?0PAI3VKkNl8_kdYOySontI z?*`%BwaCd2I`v%-c>u6|2IUJN4hm5%LA>mad{QibZBYJ!Mg(mDW{p0hwnYaufI0v` z=VcnfUQnMge}nZ84(*3%09X9IpaKn0*#M?$w5AT0h(X6A+>K773Y?E)MpysZQDqhxW_koC&P65KnHn)qz(azrZ_5s})Ub}`OxO`F<>mJCdoiFUECA|IFXL_V($O)8LJvpN&kX`&L}8Z>&{3nY(dv&%^;6XYzqp6 z9h9JSV*Qm;@(woUYtbOe4*fSd&x2?3bN-N@WO`c%tu4OvGONqA5Xgz&%y7&SjX?!_ zoQi11&s+eAQ*T;HKg@w$pRy0O7>}`SW77pgf zJvkVghX!&2XDYH~t6u^1wnijH2qA&cM|Ji$AHLCBq-6yQB0s~;fU%c6(BK356qzcN*pe*Xql^ zoC^cExvI0hN!8cr1*pqaVP9T{6R0^ZLGTcF==`nIN+hUzW6tyuv^@#2v7^8xr$XP* zWyx4Eq{TKt+Dh8KHlQ)jcRP$A12;70#mLTu(hwkH?aEK-gzw`Vs@rj_sLf1zEObVY zb_8thMpCXqM;;*j8hJZ3k**o{=Z!(SEW*M*S6yW6Mb1yBlCtvErNVNzSgV#}3=`0rYBpq^(o;!n*)S zjX50H5q(^vR?-K!%j3~wlD4yE%JV6Qtph#jl?wMWrmuZF6LKc{XIy?_M)&47(Mhw@ z2jba`xE%NI(J2-&dyi?$OJUJs$=;-xeFxv~HK|O_i^*z^*?&7GdpPF6o0x<5hV^D+ zK-|oe{X9is-=VS#_BGPZKjbW`qK$HFL0RlddJRv{;h8}fO%9)zDiru@D-u9xj5se2 z%sa4)O>0E@(M~u}fVm^F#|_`_C^>%Z6)&&3PPfhFCy8f78r$RuuRU6TD6m+*pslEx zx3jV3xPRQ{gOGQ3;j?spv0+55EAOqba--~JVQk&S%|JK_qc>EfXslDdIMftv*1Esr z%hh*8f1p@!GQK5cb)2@$L7xT%VNGW-#;OaRyWYN9q$)ki# zxrq6$EkPgzk1TKFNfDf2FGMJdq&1pz**|e1x^Gocw*zwgX9VkowvNA@85K}<8FN1i zBtJ%G&RJ^G@vRlPnsFB%IecEWHY+{f)|#X^lZjeAN{<^5Qib_D$+@Wf<_xlK{0)qz zuuSOgmq}N?;Y292*h8>n;w3Nrmkztg?ot~+q+8(@_+>K?01JRY178b_3-%My1YYf1 z)t;(=Do)g?d$`(WylHE6K|K*2uZNT3P%p%6Yc3pk3VUZGv{$wYCMB=YWi~V%w{w8V zvER4|BVO|MtYA=^+*O)xZrfcWum?N{>l#ln-rcZVwanhk0HHDHZsY$HO`Ws2 z3-)WZ2X8>smu?q|*5Q6{mE_Z}uuBlwV$MW)2reRWCb zSq_f&0vB?onTtvg5+WPH?DoF2@xtofR}84a}z{mRhhjjFeK%9NsrGDfP0jxl_dOdv7o@y zI{}@q;R{^@Sb@RU4Yuces>vPx`X|kqXVVcH+u`4F`0tOs0Sk?F=i@;OCT8UlC-~Yi zbf*8O{P6=L1INz4bd%RsJR5AgK`D9_I9SwK9{b~Ii{VXfdD`?p|q|)-VjSu})0=8x3nO433c6r5PtlzzB9dSs9g))xhy(MbMju^Qicd;7rhoVv4Yh3(NAx8=p~ z5KweN?Vg&6LqxlW_1mG7YkjlzjTK`V=l_YlenoufRz$5AEQva0a#9_p1J#(xE7gd_ z>L7q|#DIn`3@+jcp@1A>qvcMGw#!!!&gLag_Fq0GyyvT27}%U|knd zH|Hu)Vr#IMW;ttpow&A~QU~`YQK{AQ6B^of3Q`%6kyWs()a{SJtPCfbf}XKzp6V&U z>Pu;5X@6uFK6#KNHCWecosZvZR>tZdw%WTU0FP>}-yHA!Is*cBH#mG7b{kDlSSqK+`iPkPO`dX@{CKyr7+!b9xDT-A zF){Bo%PxIrn)JsNN+SI&9t6QQG%F+?SAK%Q%KTj0-0HAycM9%4bbT!0`@80Y{Ecfi zHf=skwQt*;D3KPO1)Y``&zG%R;;{VJEqulwPMcqd^q0dnKK@CYC+)t(WMZEPq zTgOp3qWFA0)NH_yz(f_1m5@L!dGGz;>{l4NayxnQKE(1F#J0RyqCbqEe6`ohYF5Uz zw_Qt4gRpcZRv4S!0mVa>-YzS#65#HuN!)Z$pH1oexe-i#BTwgHLC;GvL9uwGM*Bd~ z%ZVSGwY|Yw-JjwL?4sUbqC=0$3}wBAYO?9U0ewisNWUu=Fw6@&n~U}4VYj}!^i3kR zS^1qycAC#8#E!?tX#i_|-rwL{>aqNaXDX@cVT<3fBkg5k!>XEm%wluV*1-D;Z@|Vs zr=y^w-vmKmaxqCtFIn5d!_?=mCVDg)dck(CTExWLllZla43&r}HHdRWR+>RY!Fu zSkxQO!K7s|eC^8bP41eFJb`NCsM{2%Jmrqko$^9CF4ZWS(5>~h?MLZ*tI64~nS$OG z=ew**ow~AVzuioqtYPF@O?Yzne;1D_z{`tu$uS!bI2~pT)%|C7sEp;Dpd*SnK4az z8nA-DGv|GDHrp(8T?BMZ2qVM%1h`>8cIVE@(A`q9UNSRVL z8AFi`{95+-QP;JpI48+;u5&ohn1?Y>X}^;`sYJzHmJF^zR6y@;gjj)kIfjj zLIm{Q{nAwrxxZtT@PqyJTJiRS3WBoU=DrRA>i7ApCQ@-GfXJg`KgkPBD{J<{5rzR^ zee+F=?1*!Odl7=`!@Q3B==GhO)J{VVjz$cwfC z^2!|@y}Nu#%R-zwf_)t&UkgvgPh)-xi?x-XQ8c+tD#JOrz4-8T`+Lgd-_UY`WjPvF zBACPg; zRo~w16u_x$3bPO2?K<6vw5RVwU#CRH^{;7RSZ6AFr>ZI}ita%ICI6)c>T4Z#Q*rA{ zhLsbMBgDmg+Ro2qrWwOdzh(W-fWN%2t!Rm)O}Et=r=jdgJjg2BTIESWNmx^B5ms5C zYKBGGKBWV8K`d1bJV3pWj7BfsRhgkvefLR@K|qr3>m{CTUu#v6N{-Hw2sIWaf$Z$l z*ZuC5ZxS&{2;oeQ-;pIVs6f%J++LD98r08n!ksW>lyO= zzMG%6MEU#q(;!Y)8+K`*1Wx_+)+|UkXMVqx7xJuD_d7Uc-np=Wks5jGy0aYp<%!kw zD)SmYI)BYf=*1M=op35z`k2HxcVq?uF15Wz!dGH_Q=SGKTiq1^5}bTOC=xKaNpE%dyNZ;LsJMjk$&SmzUq! zy#18F$8E9B^6!VucYZ&;E!*rQ8Bs<3&rV`2rTK80a2O!jvB zmIK$+JfprHj}Ek^X+fBTZ(=Nfcl5mI>OKAiWh`O2E}g-K!kn|A zFi@;r$+8FZm^8q$I)`FPu@ECLbl%#Q9LljA%9UO_sTf9wIJdA=TmX;Cdk#?=xW%fC zp;)&Lmgdsa^;Igi*pQ|Yf7dD&HMUsQ34zucq%4Zn>zOX+-4*;8hV|W-elZM1;mo$x z0Y9D<#r>DJJC;uWZ6H8r9A{)~bt&k*pi=SE*mq934{9rMo`EEhR_lo{hZ!-D(d&}U zha@Ol4)xl}_`1z2k|DyZCi)M>*GZrevYnr z5`?_~PRa4`%iHhbI3CWaQR!Oye-JFxN= z83;sM82&RcR3U)@FX#|u0O*=D0_YGLJ*f*ox&TbRI3ggLA&mBDK6*9r@p?L4iOf4y z>49yzuh}u+;pA>xhRADu$H<7-{X!fu-H>WA?Qw9@Zfhl@X}u0Hm~lg_x=d%wzw><+ zVY(WYtA6jZ2;vJEhEfLJyqh=$%Ht;q_(^IBqh8caCGqvN7@9@hRLl&up2t%5v%Uti z3#@DnWKr(d22FJvHsv^)^S3RbJzv9gDew>H*-7?%Dj(*IhbDARI*rmHbMLIi>a44C zs1y7!kgo4uOX0J$jKwA(OW(NHFbt+50ni;h-P!by!IbaF}tgQ%^M3mbbPV<`5de=m4#?V3t7+P<1rVNL7FngM`GA9JF=1e0Flvq9OxjaFQf;Qn|4Ye2tqI)j>?xu2k2&sU^ z5>JoIRIA^iV9dKT%(&dpjibRKwnFDVMSH(qTtVTIA8Vr0UzdUTSnt2;G^k?K2?Zhs zonRPxfe{EE(`fLV_K5Omz@mZpqFg833e!2x#6i}>ItY!pFljwdSBexn2Yj#tbHa51 zW%Orjhbb2s6i{v`&NFd|0~H!0T})vP?jMj{k+&{P7EQnIoDe}`O`NAaltOcK&%p2( zA`Q;;vUGZThI<_c^gDa(y57Gca%a{D z7Ywu5kp5MbY@wp;9xzJcFQW$ILaQ6blR`_VVcTLhle1OG)B&U{g9 z1K#{gWMJZ(HTc%;;Y_J-<*%l`)1Z7%uWKM|nZ`Qux&qr|@X7S<#P4YqdBSC=*P2!x z77DB4udwE=c~H)0N3lLs3M$6rSi84YG;0j%^pXP`{^|7gyGN*StyFe>aOs2W3}Y#I znme@6k+B~f%~|@(1^+yvln;Le+Ke5><{bs*ks+4M?A@Y9I`t#4(>`LUlSlQix;qRj zOzC?vz|wSKIAbJ#mHK{$6f;gKge1~`6=s`XVPl2>GB3?n9=~N3EBht+s zwp?u1^Lp8y>*v5U4P_ufh)F6l>%0I1)bU<`+dOs|0=%uzx;JsV(AR5-czvT(ZXz6d zTh{Bm%w#98o=2MYD?OMlx}xK@>QR{D+CEs-mBF&u!T|I873RdN4Te{G$u2A44Q7<~ zvw7RJkUL_!+Ot5)$V6>MhLu4^kNed|LeN!RnfD>`-BPSOU^rm z&z$(RTvK0>-RGqehe~hZI;GAp7I$-Xq)93NC)l(PnDqbZ2eAi$9gT)r3xS_13R9|O2AYRN2 zM9#D^th5DkpCgQhrVho=Ea}{M(%O{6ifG_9|jL*&Q9y}8NHA1fr1}Df>32Z#kL38!O%RF zZ*tt8(i>m+(*gPUtH2QCMS`O?% zfO$nfFRnSdhtj|@r&IcR?T74;bel0@MCL_7EC;Gi&7xfvsQ-op9frtP)f{-g=G5-A zLuK6xtNW*1zFs|~c{pzKi7jsRa!G)gSjgg^9X`32)nEm?P0DGnq8Qb{Y%4)1ItSP_ ze)OXOOjzey4XgGb#{YBLd4;7l)cU|8M|W@)-uQX2J)z1iN-vAD``e6@MU$&mL(Jv~ zsCyzS?lWwOd)5M0h5N%KiKEy4TK8aOuZpesxQ^IF(1vY+Js949W3!18zMd(0=d@g| zhHth)Z1R2Fd+N^Dn{ygA@RN?hk4*fR{}z5Tsq-#@`<9^M<5xf@w?Ebi5n6M9nDyeT z^sVFGFx&M{YiW7i zL~SwOddlssUgmtTv*knRkh=j9x7y>whxh7h^ds3k7XO3M7yxtmXp}+0E|GgTnLB_V zti&d#4(`@I#80*CgAGis*If~~S0tzmwW_}DcKazaA_HhRbn5M2*5Y~R`idu#9@|%9 zrPykya<8Sk*f5s0E4)|P(S6;PUf7nt=Z9`vw;5UneXP^4s9{m4{d4uYU4gRF#9h=faSsDo=Vw~vK&QdJgJ3o*8_sIk5d-SOY&H-bJGM<$y_ZR zuY_=_U(&0`;>pIUG~Ni^rx=f|qGoKK%BzGi5*iD+XT?|w>z?wG*l?Jqk1W}{EjMSB zr>pvczMHku=LTDe*k1C-J+VgH)j^d2k184gl2ns$Pxr*p5n@59o zoj&@sCseAhVu+wTE|OtjqN=Q|R$IuVtuhZC?~W_u{NPcZM}ElJ)5k)BcVy@vr8YI#r?-p_m1Hz|JnsAf7r4xAAk25P_K!*)N zQ(}cesV9g4<;1v}ls5$iXB_TX2Ye7z`os0^ZofM*)o>;zxMMdW!4H*iO-igO4ND1(+(ig`4~HcX`q9=`}x19`+43i|C}8(RJ#USRR! z*)1d!mat|*;m>atI#}9+D>qR*H7BE+Ux(dU{1R<3HjaBgsq&}tQkvcN-V6_^rH&4v z@Y8e5IS>s^2xdU4Lf|H<^S7vBgyN^q;rI)zCtSpqPV0A8VP6tLN2d2)p^xdg>@aEb|1Rnn~) zHES!q{F1RL+#_;9X&Ob5zr;jxub~Zt67o(3?jj;aT!74ltLVdgDvwe^t7sZ`i zlbLz%p+n2@ZsloOVX)t7-+C4CGi`n1xnGQ%(n%v9$!V8WsB{MCs?!m2L}#0x?(?s7 z5_&Y92=^DKHdl|h;AyxBX?&~PgKws>+r;iO0t#ZOttOM3Ih(Qdbf8m!_)O70fM}HU+q(4v6>Um zZiuGH=@o*ilH|jwi#^Dfw!Yo;A7%TxR&NkjNOE!&`PPt4Ub|M&^Dj0Xjxn!ALFefk zcP1lfRXo&K>)kcA>xD`jo=TeWt8m-7Ga-08l^1j~|Jas%e{@b@QKz@FLY?FeIIMML z37Q6g&$qbDV%ladJYuFbCuWB${Pz2lJwQdP6YJa4vN5a!aNv?Lr&d|40Dzyi%WFL` zpH$|J!HE!n7F~@w3a~6I4O6Qu82&K2UoHT~y^!66ouDg-ogs6BGPqQ$0M(Moms>mn zwG4sc)+jsEP=xN$q`EA~5ALv9jWbdg@FW<>W-Dr>x*!^`r;M^tv*eLTT zMV(WImJE_^pS}c&Wgf3z(2v`4;f7>=sHUV=<4Fe6=BWe>AL#KJugcR|wk`>Yx}lL( zpz9wfD-LrvgYhzu`qZnac!9oFavd@7+K7#riw|&D>?zz+3*8E5q3s7QVx+P{aghjJ z*Vqe}e+aYhnovm#vQ%}SFWF|CMb5!Cg*~9jZ+zB$|Hn0k!I@-*jmCO#>zOv9R9v); zObIE~_H?b(D6%qJF@UDVdfM1J>+BTY&^QFLfi_x+@m|XiW#b>M`&(g}bTWR=Y@W%? zFZe2frt)>^7hnyw$z_XUmHP-f~=Vz@U4f3D%t3CcI9Z*lnY?DPtT{1t(V8q z^POG7j@wu6Ayf8w_I~FdZX5~M{*Fi4zaX4GZfmNwghvqo@s@qD@nk)=zyf4oc4)a` z01m=hK!zzsLDbJkn>UvFW1<9laJ^NKO7?I0h^`296S;HbFb)mOg*R+^cKOKhgT>p( zbqy;3Td;BWVA@qBsTg(8a}7uT7_2e;Sy9pEx|W(iZ@LKJ&BQs4ZaTg?Z6glb;OScA z^lUV(F7@K-1O0nlno(b;ZDh8_?O?bm^fOOqy2HY0t`)1@VmtkMChkM{!NcJOl^i&sFFM|I3bg z`_5Ih09N)WWgF1-j`8C`>~^4c_XB`yH$90yj~$XZ$5 z8bf+21LLX7xH)o(jQi;OQ^W5`Vs1un_TVFK!6T#=s5t~dDSTeN@4W1TpnQsTK0-8b zbd6UKv<49T0=OYfRQ%|p^+L>j)s3o`Y*?P)(Bz2|-P)XS+0#6AI!mjnMx$gLWf%#W z;balT=Er;$o_5Do?)hch{A)=2^|4%HzOs9Z&u~5K)-x|az9$=K@CM{{M_eS z9COKgUm&N&2LRsql4A+54R=+6OBXT->e-@mE;aBW1=7JdrxgHf7h6XF>n{eflgGS|05c}>$${t8?|UPN?P4fU7W?OXWK5+WK}Z{*!$tW@H;Y4 z=pQ56CXiqm096K6XKH>@x-FezRA*}}iBU$tCaolLZ4wjsxJLU;w{p@3#b-Ic;61;T zBOydRokm8w!(|4M!%N;vu&Z8|CT$PWqTqTeAS!a#&I797Cs*NAQY#N8lypr+vjGvzte&H!?Rk6e}y zX6%zX&xb6Q`8oKKo6DcUnTYC%sRS~Ru1{PMbpJ4}*IN0KIyM5uz0)qZqCHfD^)U+` zZQ#r$$GupSp1=Y63{b@GCX9_XPH)$R%bGJn+0qr&PH(b@3|G!>Lidm}EwvX#^3D~m zZlg$J-hJg>7SCpNN5~)@pKy;833X{##YTBqc@JO4Y zw8<52nc9HQ*49|ORTX_*7cRUZ|i3>DEnRtDqA zWr$aK4X=GFa7LSTQh@J`;mnm?d7{)9)i*U8Dz3B%*00ki%fG16boVsB*P_Xl*xjE* z7}JqaXmg7QPe(C<1TmKdn0r|3L;s&9HozO zK?CBv_J}2)N9qZWJlTMpDljK(p#)ozGC0nWgD5}-y<^kRv)3h&0OW-GUn(RceO10c zSg~LO7k%6=TQG_MQqnkbx*0dNa1O_^I7EF^@YU19U*4-}NyyStPJIN(9Om@2lIU2G zKGHU!PxT0SVbt&Y2+QEHx=4bA61M1B>J0IepVsVKW?D)Pci^K`l{YP;DNwf9glpt^ zqj$|Wa%AHXb&flg_js6hFWVW2L(bXd2f! z)2;p@{CsURqO`^j(`w1_*D0x~8?9L^tJ@+s0X^pLoRd5oRzn>1|2P}ya>*JRNNz|S zKNA!VPn5A;yD_HSO58cKJvBstfeN!C0X%F1M15#@litxa3X$TZqw0;Ah1BnAUNyh@ ze*fO^)G_5T<{)+V4{k;tI$*sPNAF4xLq0^AB?t93T40ioXtPKdUC*>qIE)uh#7#fHN7p2W90{%;-)us~>6X|c#p<6U80~8hoC`+^7&=+3h|GRc zwGVRfIxYCf>wsMrD-qPSsKgj5aFRo?CETx0G`K9o3dMo<7+Ozd*b6oWc)s$=KaR^Q z5B@j0n}4d_8<42lEpH5L&8|D@=hHW+qC48H92Jkwler{{FsVMs67uB)F&Z6#&g+2X zi!g(tf@hEOABqZ55$F&VN zjd`wLPe6~q`b z@Io?m7~EVay6~?Dnfb6c$KJ&}4aRSEgJ#nq@ z1lH^bH0LC2@c4uEkl#6kEYVrFYor&gs5hX+(oo`Pp&mVsYz$!NIXaz;?HVi_=wQ#> z1nl#<|KxglV#O23S~RO*G>TaE5%PxEe`Jq9i-a?BAVom?O7bC^etJF820*l)PUVFS zBHnxIv$|9H=oghV8XaNJ7_5-lp6|Wi6s|$zA!hr`t;GaUy(X!%CZ4K@L>NWgyDIIy zA@B=1W2cg*r?$v}H}p1^|5gm(oI>a`+5()(oNA;#!y2$jf8l`!8%mE~SV8p@qlT65tT1Bn&;vFd7sFzz2LE8EIOjV>IkCV%bvQJ+|xVJ8S5vz@UO!x z#3WKRTvp_MoScKvQeQtUuPqrDU#P(@elvVE`W*gemqWpX7Nl}Pym1-tNs(cm@-+N^ z(A-6Gk{ZnmM(e@5x<6`k7Hf3p!V~U75o6)Hxb6dv&JhDqMFIDbRZ}o~MBBPt7Z(n< zYI|3c38Oh0>O_aXQ8@LGSq%m5kOhEGnkGeNyTAB&;e+EUegDVNy~nlK|9=3#c0RPN zZJp2Se6Ce1ooyXgQtLpW!#e38SxP72+SZ9uD034l~6G(+P_)xy&gLb6XI zd5t)Y75R~R*rTpHu^t;ui9=_uHmb9At?=m@$LjRpGf>Ugy|uBbyl2RQSfzZ|Vd^xT z3SggYi{+`?q}8c)iAiU0$`l_c1>%1y77s}A#j$uOTcJD_Wd$k+F#5-QR2MaX1Y@0_ z)3Y-yjX_<_4z6036yeMk|Mj>-iPd}wDNzZa6vRFp-`Q=5m|@lE;(S3&aAxr4BdKhWWGc^6BZno7)(g0m2;F`z#cdK96t~O(H z@D-ma&1jaX2>_+_kN=G{18ou@RLD5QOvnhVoJuXK&ImB>d!k+x%8 zoij+qv~4;o-8u`g$WC3I6|T-AS9_VEnT1kZZG81ltWMwu0II9K63bmH2yuS~4=i=V zmYj#~xFYB#4}VvtP@f%UCG5C}D}@lLQ9C)6FAON#L+Ems7#f4r;jVGgNMjnoa8Sq| zr!C`VVoB#18Xiyj??4p10!#qSTyUEQtmDd%~&m?*2z3=Agxhn zbL^x3Y2Why!XV(yG8SWnr!yN%a)yu}YfdZA$O$4pFB!~qy=iOdg^57b^hP+q`gBIB zCN}{4_vES@S2_~6_uI5qPx&cgzSa${r50@~mHyvqIe*hDBu(5sF=~-(ptPI&=V6R; zwRF4m$v8ajH--{Hz&-iA1?4!Qep`2QiEe)8z7K!nfKlBQ2yff{P1vjnNse_fUhUIQ zzYumW5{&!v^W&YtAe{yXX=m3w+O}!1%3wpC>p|OdYr{t^w%snk*qwiLg0|zO=B

      WMJ5Tc#jzDhAd(u|?0Knu==R2K! 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

      scxjGKVc^SRUbpQvC{opE9 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

      e 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%0A8<0E|={vfGoQ)p(exSbTW_6Nbb!oeq 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

        QD|^1cw^x2=k18ZC{)w2u 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

        Z=5;jy74d9lVtn-cC&$RJDW;VrNf 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

        FPusNsL1to<7ViT6@( z7oCUBf9(g2OZXR5JdVQY$aReeALl7HYC23=N8!lZMrL>OfXp{_GygX;d;E@ioiHaP8ag1qk~*2t;6U z9nl$rXxJ5EIM0!xL0mg)94xofnNUhR17hKX6|i(Oq3VCYotXlsrXeu`OY;H?O^v{| zvw~RBhqY$p{PQ5o%Fee++_}#7p&|Ge_myeo+h?}lI18Gr4=6j)c=WuMD(;r}9AvSE zGQsPjI~3oJ!1Mx&%mM$dnuKb${TSQ$WH`2lVTN`jqa77;X*=6ka{M9qAG%rZz>^(! z*xrM-E1>ucY6QadcX3`!&9O)y0{#rFfqQleXs@H`zl#O;UnqMFfe{^S`(79cLn_+g zVsuXUcpSE+isfj7@GEwyE!NP=e6vXq^}z3m8)Sz6hgb9zf5v+Ww)oYHu=rR^E{eo3 z%N~#-u#~2Ax4(dzgmEg>S-La*ajMS(oCFG0gOspA>!BQ*w$}k#{0rVuMZ0KsDU4L% zjNJ7Q`)7j=drl8_ESAc}28LDnVTR%yCX;H15KNxU*nr1qh(~8^fDxe8?p;|6*BTn7 zTHFl~VV5gaDeqg-`e1w^;Q+wthzm147u8ve&ZRj!oDn(MmEp^di?sCjG<5G| zY$gHl61ek#Z57~VWw2KDK7FJR@}dMeGnV6JD;|D*R<#fOF);N?rbflX2MG&&B z_@N)*{Ui>KT#`6q28CT41H52%FEIBM6^=$o=StJe3c&jQlj$8C}0*v?tFNZ0}c^b%)$3_EEfzgPnmMB$svH(^}Q+d}vDC^NY ztHSi7XPq$M6coC=Wh~*@G1InJ3L$Lcbyj!64l_US@bpa#Zs= zZxsT?E57z?zjMvw!}Whb@HijqlNh{ciagrU;@fUy1N8MQC=;msFnN}C5*XJDb2$yS z#`ufhjC>VMH}`n_Y**fyi@XEP4|S-tn{9)3ZH{$uELhdVhjZ+Gg)#w?pEhk@Ja<*R zqu$ANIR+M6(d<;lq0Y#5Y2>5V2a$Z$srab>s0UNbyPA`?(>i~as7=Nr<|EIuCn zhfX1#)`bu168ep4m%n|=A$#QrR4=5Kp3REakE~-*d@pNiIsW`K@bi|bDs8y_rdxQ+ z(2bKip4Inpgp%TTd}B>G?3Uw-zoUJLOG*{es&`0#G;p7ivlbsSGh|{FA}|dk?+tX9 zN;SR$8Ur*&5q0{3ae{pn(yoeK94C_%S#{~zn7(_y*a@~gA=l>Qpk2eX)JXlcDN|d) zAWmUT^JSpH9k(qnp4Y@Zm^J!r;y&!e@>ZXGdam7jqr4(J=YZir8WfW7k>e zTUCVUjkX>etg{$2;s!1^gjvEa+h@Y$(GR6Xf;Z36-WNv6HWxo^ysYS2B{$HOc?lZ` zF^z~Dd{zwg8hp?vzS7yRiGz7(!6-PkX_%``@7m_md%DV(&-gwe6Q5Sv?dtRcXil&^ z`}F~wrj+vJXtdrpO30~6>6g+C+dXEEldjQY3nLXZ7K&Jz7wnDnnEV!q1Ahj=4r`V9 zoMBfR0eTHhlD#+Fk|jVy$6K5NSoV+ho};RjWmXn6|k*Y24n8EWB2 zE=kIM@9<3ovrWc7^YU3m?3kmwr!WG`8XxRXK~?#yxXM@Od|g@R2e1Ffvb)g0fCzCH zB>RY-@$f27XNgzH#~nud?m`AO6R%>Ydp(RvRv)~oDy2>UGnMj2@4jT-k;R~q#aMwyM5a_I*NVlb#Uk8l4CDLVA+Z@952uv!_6Bd@ktd4P@}%q8QbzXdSm}uzd#WQYF7&E#k9pjE zo(k-!{a3itrRG*q>Pc; zGg&cJ5JI~xUF%t4AbZno>itj2YwiSB=;c%oBY}6HV}5CinaM?&V^T=ol12!C@?CUO znZkrk(sn>U9)dj((r=D^I^2_evxX{|cG(AhaHS+~n+~6gs*0^P81D0;oV94>K8~rOraO zoy!cuyu>{!z~=cj>A@>xe4WmF2X>wCIs9nyKe>OcE|#6R zpQN-RTJP_w*VpAQlo))M{ge(sd_AA>`AO|;t8bqUKH_Cx&TvKd`Yw6c<6OtJ8~`WQ z{A{0A>LBzjxY^*DB^g@tGs*EPC#kzEQa*1WK>8@Im*yw#smC265Z<{@o8t#Bt$sAI zh%ZfmA?$pQYR|o;p#kK9p9gti20I*6j3&3ViJ`F+$(L2y%L9v5CDSwiSUP8wyAN$J z4sPFm@G&P1&Zp`2n9{s%e&6eg#n)E!$wa@eEG24o;h(?V>n|l0mZM$2px3Gba4Vjz z@ibjURd=`3f!+SvV8Hi9y5{xq`cnPn73W7m*w>Wf8x4@rIq*eyj>(6{N8iVGDgwh_ z8oxSUSSokiIHxj7;Xm)uww8A~cg9D7a_|5?Z1A1&PRoN+IRHQCbEE05=hLUo`j_p@ zA&NJR@87J{d^vmI>x({EY6M(0WA$|5)vCdlU5eIC^H;Sj8Y7#n{TGLB+dhQz;0#nz&#v0aiZs1z&YQY@6A1{4M+P7*#ns=?;bR}9(QOwl=k6i zN9^TFzKSY=CP^7bp7FFmtof1v}Hl^7%xD?;CkiMu#Rx?BC3( zqD`DzB>L0`lQ>_;%wB5^z|PDIQ=~nV=%xHNwCz00DK|(=}SdZviEr;Hz$-3!F zLW#w>>kg#coV;9jA832qU`GXjW7(p7dH$d4XOXBeB_lW#OY?Jev^&lnYK(G10QmHC z%X2iVEvaOXhz6K7*^Akbo9NTb*ajK=XB(V}ARFpY2(*kPeE_;JMd9f|CC-*O9PGmr2Sfgbpb6?*w^xOyA ze~CEU_y>RUmo9FuXD#(_UxowtB^6}b&YV4y4w!D_dDFDps(&}XmONltMZTDm!fj9a z{5o~I`9=kB+G#HYoE5($Dt2dTO1)xZ4_+^qa(|`)ExRg2P-^IsJ{*yz1o5;9X_gdi z6g-(3g1Xc&+r499(WLw>S31Nc zFYpSyQ|D!OF!Ct`*Fr<$>-YQ`XX6;%t@&Jp!7p--Qlu_a6t`ZLk9BEwjy)8!B9NaW zxLV&Uf=1c#nigwZ?HPW1V~F#r5Jfk-=!hcEVnJQH*3H?ZqP!$^K~r7P-93Sl4JDHB zlth>1HouSwzd99NMGwEyiYm5NqYTsKX29t$1!|kCY?CXX%Nr3cS`p%eo75E$36Yqo zokU1xh{yluZ0~4^DUY(n1Kk*6R-+Kbite!(n>Fa88B}6}EX(v{lY>4qnyHwd&nwzWj3q+cl3@{tHXB4#8oRmd;zsnqiHl~*KLK6L(`#hp|3)g$bCH|!e|}UQJC_TqY0uwsT|MAUv=Bc9GK`1jN`e5c z)wR)lYN}op@t8UceP!6l;2R=M-Q-~YDHo$!r5$&Dp6ue$^0I;>x%YqhCq2=4igFw* z%~)qDDcOvR4(X(ls@P%n#(V@UWC|;BaW`>lQLTHHhS3pJc z8!RFma#qvwt9%|Q&%B{oj80AI^^KqZc!hpPvv`g6lDy|4__6N^!q<8|MK@D#$F2Pr zUsz|Z@-!rBv|+e7W#7q~LqxVA>xZme3rlAAW0>mN+wr*4_G?yUHf6EG!L!?TaWr@~ zwq%m%r*A>e_K`zu%q175(VFpCZ-(q8aqn25qa*W{_iflHv7!z9Z)tsg57EW+O(mD%p_ zt3?fM>WA1?>Z03}RNOkLq2_dX$;dspj3uv8Bb3g(EFlLV!zqA{^g6U=eLQU^Y6-`c;+f4@QzUpIl^k)lbe24Mo0X|%iY*`x%-6CiOo+B zZ8rku*MCW6#Pb2U_t_ZVW+F#&QivB7ii78oR3VH?YrcerV#zQE+N-9KRIb6={N*f5 zLHe(y)MO!2kCZ7QyKGkvp}F0Q&A70Az}`fghZn_yHPGvP>^^d-7Kt^i@zhNpI08}S zZnmPz$u{pnso)Z=9KrNSlGwFTIGII=frt~`s&tUzsovs!iQ2is=?U7n|3`_ddSUI{ zDkoCS*rddXM6u&MDK1}fxgGn!NK~vw7lP1Yyie8iGr`g#=NWaRLM4C35wZXj9 z6`?GAzyQ8*&N-Kk9`b8g6)54+R5BoW2p#GRHH)R{{F~BZG9{KQAH5 zfR1x#=vb>O?r4!f_G3Ot+CZ5>Uq~m6L5_qF)r67}53K+(DSp3A21HS8wU0 zHK~hV3^XZ*63K=|9qEE(%RaLt-V~(JEP8HfZrAsql1 zYt}A8oy7>b0Yn-akm zzB^Ros%<6)1d~*LLC@ImU>dqWcuSYjRqMLSOa@KOaV_JUpJrvV^R=$&z-YppJHs>V>493d>!c6&6eCC1;ibZqGbu0SNmj-#a)=4CtRT=k^oL!v!ckgO$G_UbL^AvB)Ac>zVB8V zBG!x9NFj*J0kC%cvS^l898)w}hPUe%j~hW)UXhG*brnf`!7ey71l1BPbA%>|O_RwU z5-%nN9_43vwBc6uY#(04$;LYUmBx8jZTUcQKI2b_`s$n(RC+{+8TA+M{$Tt-N|pf; zSE>jP1hk6O6>pLlyM(Se5en&Za`%|_W!@cx2Rf9~iCy%-WB`p+mo}M`k!cP)C4|N# z+%m_RY&OeK=P(}0IBK)#GZ_QAO8ubj97OwFziP6h9=GV)Fe_gVkePv~y;47V zZVuUXR&roj{|jB-p*6WmleS$0ZuwBQkWMI!aMJ~_W|0au*Cj|oS1m^9Ru&+nX=+#y zMCpAUK%E+_6$?tpL@HFMYdSz^XpYJcb;B!;==sT@^RQs$HR;&z2K&D&9IQ6n6R>@6 zrM#5boy0(GBhT7iLw2zMjf*r%5g>5=ZrJa#g@RDUm2Erb9YwG*u(0zpZI|hsC#qip zUnx5;Kn(x~ZAs`%zJy6XCb$DpBb0g1M}`9EdsEhM0RcaHX*f?A4H&dEqq5P4GMj)+ ztUCHSsM6cN9n^q+iAqW5?-DF3!m-VvB-vwWGR2^~2`r`u05s<1$jtb(A98kcaysfz zA)l~9V&nzJNJs40+K=+06A~;n6X(QuK#c5o?WKN|U`U>54)zcQ^_3?T6|{F-=N1(| z=cH@Sr_wFYrdKe*iMSFi0Ta6j$Sz>m4mmsA%i$(8H%~tGgrmtyf_A6a&U{liC?q?Q z?B&d{B15r)FEdY)bL1x?w)@rcWlYqug-p8>gSubU0|C;m3A$K?qXb+KSq6SG^Reiq z8``|N?SypSxG4YYDU|`JJE%jo1k!eMz(10DT#N+Y3NOt-*KB@;F|HG!^vtMqC~aQW zgYObP7h`)JtwRGUteg{2o98rN>P#KJZS{EI=iVrD_4_{!A7nH>RCp4i@k$1HL|Tf? z0JH?hw1R1F1*u0lA_4lCwgqX|IrU%3e6p0RN1wT3B|j&i1JOiE^fbc=tHb())}#1H z_sEoCW=g4$dS_AcbcCurTWhA-v>2;5uo$^>z=BYs9aCfRnydASs+da`9hfNOg2*_g z<_TtX(K^>%!+Ot;{ae0i6gFo_wiK#an3b^OZd)1l*Ss6bELOZytSy0T0ZJO#uqDhD z6Ssoev}*Tx#1hpeJFhQS2hk=}ijP=tu;2wSC`p?N{SpIg?wrgl=*#QG}|a?-SqAWMztFO zRhJ$kVe)Om1Ik4(%B`?Fx*z8VHvLNATQp(JffQkRZ*SO8gzl9w)20BG(LJc^I$Jsq zyfzB04$|0BIVVd!lp%Yu`lCM%t)Y<^pG9qrIf3C~9B(yQDq-bnro|wE?X-U9&!B0U8&vMHrzPW)>kcsyYFYo9@@! z7$?NF-uceHbV9TGL9jLe*J5ea1T>6oO-BcUQM+n!J`I_9a{^=Aeu?b<$ERV~ZIzIN>sX6T|KBV;q9v?nQ;Iv#Pn z16_3)?bP@gZ-p6RdIrqngXf+XGovtblKV(0P!RLtit4!b*3l?2I3QU%49{)RYiCwJ zi0x$!>^$*iYDbRqYO-XLnf4b)Ng-2GX_sV;J)CEcs{2p7fwvl3?R$$@M#2SBO?09!tO8&vs%q*IUu+#ehcnCPp#_w#X4JsTwm8z`+0Da-WW-^18%_XZThl`^Wx1-VD{Iw_jMk#Th zw-20tvMB}k&v{Cj0tlqkAtT!a!I!zWU(ddQSe{67I;wq1Xe|IFvrmUJA6g&biw%9V z5%9lvu&;G!iVrh?D!P7ghZea*;YR+uKiyhy5r|}^$dHx@mn9UL5(aB*>Pu;&p#O$M zMsOjzrF6p!&YdyMR8<=G7(``Ko#-?N>D2uG#`$mSATn89A)Ei{ZQkD$(w}jW;RI?k zG#&Gg%%6vmj;xQ;c|yJoRoq~V!VRdDm(Uau3KKhX3J79Q?m~hbSS&3X!A3oJY3%}~ zof|^C6fh9OuPFX}U$EoQ`%>fb_A6%|oS?DARm5fd^&I~r-w8cAnSbJxQ^9qn|E4}x zGLLN{R!cZtf#>=iEnH_@nk~&Ej4sNa`7mB0n#p`zAG;idg_X@t4GxbH&Ebx>^LipR z&q>D5wd0XH=62c|nGQr+OiJw1M8YLkn$Jik4#_?L5@e`;t!R5Y+)#U-vgXsGqsKk1 zoI%k81J>i8eN@yOG2OkeI9^3ugUcnqn2^D&jx}jFArA1Sf=pg_nN1e#e?8Indq9uQ zj2>m_2l58Uqp6HL)3&4X+f44z$9?y6#%f>GC}^L|`FHTm?;wwsu<0 zvwO27ifc8iRKfrvu1enaGxPZ1&exNbo)E}F;+>t=cqRxz7+q&UE$ue>Ef6?sJp>_f zw`(4`1<4#ln#)hmMAcA!NZL4fh2VIG4!%)ER%2ZRIzo@8QKE0+Sf;1{@|vsd7?C6> z44BQ8BKII3GYAb0^b?{Gx*P83KW8n{5x~b{ClnRq=F38vr1HbA5HeW|2_tSfYY^=k zkb^Sg3Rz|Q+DO}^efuY@ZM$eZ=mT^Hv%nhPmP6Udd^DJ=OK3ZJxP92b5EBroQNXB4 zedvDpiVGeZl#~iq2k*kjprqkx5&4A3_)|DsBqs8L0_i_op2;Nb(U=Y*4G^O|S(HpgL@ z^C5?k^Qp}o=Gcg$s5ul$HHS))cESkFp^~IIBuRx-h#E>GX(6dp3Z+sF)mw*8e*6CZ zgzI*@ZrApDJs;2eBM+P4lJ(a{M{7qZM#sg7$cp__)*fWtB`_cZz0z?3n8X%odPwG^ z7_o~bTulD8;HwIa=`c^BHZS1@NxP7sP{PB)r2#LWO8qOEl3n&^K^N_31J&K}GFYcH zMY%5yy-F>zem1dbIAed&=>bIkLRs#4WsfgSUS`>Q9$9)yWniQ0-vR&ik5svRX(;0wvSNf@#DG^<}yLe_LRnC&Jy6ruI)F=Vn$)Tz^zww?N#TfNXUA z`iXMi;a%rSiPQ6<>qCBhps-}>#X@*;8X2%E00%k@gB%w)*mM>ggO-NvSfElv6PTJ) zNq%nqoXY4FHmE^9mwH0Y2cU}D*ap!$XErrGrIlw^CK!&~jC&+6s{N7#1$3z=SbVyT zL>h_BY0@y72j|+%VK8^*XjuT>N8ax3#4xB(S3Q2XAoKO+@Nwa%D|!0WNJISh^(O;Z z;Ox!vhTfLY6Iqc63?Qbt%4)HPre+D2l0I0T{6@F4{=@|V|2DjdJ5%wNv8EC_EvHjB zXL~laOr#a*E^%6FsbyoY7zP*W&63YWUMCQX?2qn=(HrhIc4{+uGeww)4w<6m1j?n8Y(QK~b(c>U6WegCT zA=X#p1UJLk#kwRLg%mzbIIa z)@MG}H#6g|x3Tr*ir7{AhN<{5lhVxR5Z#+h+OLEsK-$!Rx&v;VRd3XZL#)f{KZLp9 zZmI^#n_@j0NpaG?At|m_!B~i5%vGgMM;H(^!_AlY-1bC-8P&Qr`=Zl3m*bm$mTE_d zd_1a48{yIM2kfK7`i>{_7w48cnv9VOnM0R}8D7ek6p)E}Ea=|S*DcFkRn*E~94z^* z@-8+V(Yz~o-PVn0_s)$@^Jyg!P!Lr^bk0glhBH!r0zf7BV*rWssO_mHv{Is|9&c#2 zSiZwZg$+-e>DT?f;Nt6aaMCcW8?#>$N6ofm2JB2QE2U>M+MO zGtNTR=lFo->yotZm9x$7XPPJXaFx6$=W{b7oL)mOXwUT(Lwm&6o&rFOW-O^|VX*r{ z)vI^v_n-OsI5{za0dq%f;020Y@`KUhTG|6UY<4i?f(mdV#XxJd+fWO^paVs1o4Gw~ z#dtP#H{$4v@2wY;p_;D^-b)cNKR}AN_$RU#%1>UAiZJ56RNQ2E@bBr`tO&eUjZvD) z3UyX7>r0-;>$fGYlkxWQ3)_!I4Qu$%76*+rStC2q%j)U;deUlkkhIeqm!6SF^6Te@ z;)f|2&bRX|JCm$+cP?s)DiVCUZ>sz-=m5{C_^a5lE$kN-Ph2g^mzYto1eVc-0{W{( z-?13FJ9~shAS3ts;i7|xA&F{x3g~o9+Fv0_G&Alw+PCS71|l&w!BJh(2ZDZ7pr`ya zRA)ilI!hsAk3`hTlP@Z6Y;zTwm6@1JIL%Qpen-L$f%AI@U~%kxdpFAVt+I)AYI}%d z2R?0jR2!%EwdEOCJlAsD1#}nn==-Za68c(-iJ6-u0X}@keQVhKlRlJqGAs8Tk$z3g^pL}@KwKR;&~kQJ2Exa#TT@NGtA_A~ zt4AmLoPi%)3zo8}6bO!F`8*!JWd)okgPV<$kjo0c*d~ucH4yFAT9h<(d8AGX(jx7t zUbax?0Ea_2fpNz6Wg9`FE%r~d6oi>IOXSV%AUMlL?IiEy2Xy4JqRzp+C-8uU7jAQ> z!MbI2i+Tva001!n7a^-l&ERq1Opg8P3A6H>;7s(d83mm~ z*xhD+KU>ia%0v?m?C>)8d=tJnOg=mO~(nV z&Lj~-6KCa(KQ7AlP47{&(c zM>_0ykeo__OVU_85DVWo`ZDZtdoG`>AkghZ7C8uxSW zt79kMXWIFryJjF?nsBs!rbjwtwPHs+yL?IrSvZ)#0Gte|z*yd-LO6hiahdaT+wUxe z&S!B1yvL>@2_ZY>z9RmRzKEo>!ayq_OC&C; z)$~Inn&?1j$@K$U%}DUmQun=HC$>I0AVcE!gq>SXg=~p9carIwD$U+luGC+uym8$3 zE7R|(tXh+WKKR|Q_`((tfIgPsH_8Cddkc5UO7TnTmapQ~?( zcq^t zuoda;1jq~QI+}cOS@3(aKn{XFXxqJ4lygPD#`XOvutvd#pUF>$Jc53gjD+JfD7+6M zSWet#8Cm0r(8yK>d&y8b_8ohfBV6-YN~6(gJQn}pbmC9~J2N~;vb zzBAP|uBrjJ=DJVH7<}`k2|9!zSLEtrBXTi- z%#`WxcV180OxWMXtYcmUJ-njVe$4^|*)h|&lM=7_7CKVq=)-`*mLMV0Y!^y<`|swh zJ1_rk!s$`q_gYcCR9rd<)wK-A&%~YOp}NVK{Y(vz)TNe^nI~_xQt?}6D60A6M(GNd zTX7o`k$EK4!CS=pSuI1mAt_9xb|oZ_40jT3X=9^Z*~bp{wG94lF`>Ytx8ZCuAqFBK zpHVr|gj*xFo*8ZJINyEC69mcmb8DdqmveUOAupUgMbF2?G(D^n{lwy?DhSP0N7Uu? zIOPKqoJTbQMJ~4zAfZynNui4th3|>K2a5&IW;g{A2jdnm{c;^_f7fMU zQ1{}=kzESPP$o&K$BhkskjKFbUk+wgKgjBNkUjk%=kEhHQNneX@S`Pp*;`rB70}ju z`O}gTx!spCNM?eM<2%`ZZpGT(=u6m^E`yr`qEg~;o%?V@^;Tag!f_p{&l|^-BIc7y zy!DmK{R51NHY+oV2R99O_dM*Ge)s}}Y9gUFX<%|GIPpkz6H|9>cZ+iM6iTgKyOO1N zP<7x=^rOk_N6)5})#+a-$9uah2x@@m1-%vR~ocRd5L*%ww^6^?O0Q+9=r-`aX4n@5XiM+YwTzf&8O8Uu_l^)}5CzW9JW{o< z`QGq71^Fm@GWy}!!E;lE9%H_5A!@52YsVo+08&2wEL0z`K8Jwz;I0}e{rDbt4(EK_ z<3+ViRuK=$~8T8Z5m`wUtK6v4n= z@l_0LweD?G{DxE9a2tmu`(smP2sLk4f?_DGz?x^kUHlVNPt#jDfOqFFNOPv%R5*=N zeqi$N9a(o)?u*)QpLNWcb*`Cpy))}&JM`VEbHnJYm**Vb9kFR0O6YslZ~G1J*K;Ad z9C6CJ&uwG7nFq>bTfKkHZM`$!#fLLUmE0&YguKYy8Ilwtgtp?N&w~ zrNHCQ!E#+l+T=N`8lHDe6@<+m}j&BJp|I<0cL;JSfi3i{z)kt7-Zx}!V zgcLjk5UL`=0J3Q9WtS$=2aFR!#QKM2n~Q}D%ejF7dNbGFR%V!AptrX(`_#r$qZ}ur z$>#sA2KkkSf~3s|1tV!w)Qx@fFP?th(i(fe#CY4}VpK=+c>luTSy%&unj&6hdfkK3 zYAX~x);$q6cpjY_)r7DWn|&OzlpTu#!b)nkJ@4_asy|UiG~G@poX;G&2ZXxTX7OT4 z0OQx-M@kj1(LB~%@jW>%x9+4KdG`rP+pb3^lpsExQwEX}HIj=-Q)ljFC>w=HA30NQ zV9JA-WRKYR1b>w{&`$Eg-Ey;&0|mGImnYl#Z)DmaT(0v2R&b+UQcO zUFrKw!`wBZkI z0kXpX-%<~{vAbi*mzX}0>Zf}|6$Q16GoOPOrd0Cm3DRzH&-YK92g-=CrS}?#7TX@2 zQQmBpNVK?8l(!C{Rn>RlctYvIneWq~#koW~=Ukod@0VbG&<`%D1}YP*IrUhR)O)3- zVD=ROkv>yb=?_S}310jBO*EqmS+S%|h2|!e37$is5`XT;@lyY80Kf;9pGn{rM6W_N z1(|+~4fhT{9A0S+ZJu{Gtv_ry)H1y?uYY9J+=yrVwxzwwcspr~aHzT?kw}spMhMqV zA=&0y$0OZE2Dx?Aaox<6KDJ~}bxUHk6W}~QZ3&2Z=h91@`IUP=)%t40?ONhoBZe;w zj!t&e3C}$>`LV?0t8C7U%}aHs+WXH{A@j^|hV9n~ZmT~63vHSGlL^b+Y+lukw&eYU891dkt8Fz5WUIBnx zRbV!$1ve(UC3rX>qIN{l9{U${?)=zzE~LZRukaT%f;(g8OK_WzW=&kyJYY0_dUUTu zOAyu|>1|9g-(3yO`qy=0&vNY`7gQ9gsJ1fd&%B>>Q*B1G9M%b+DKscG?OluCU26LJ zVbtR6>FlD-v0CaXDVprq>11`?1Jjy`sFvt}y}nnO05dn^-J{;vcgLOnWzLOfb*l7c zKH|`Wyq-H*!K^@+?v*d4ns6?W0&wS(EEis%*?XOPIOzCd?cw{oO8|2=Q8g2OzKC(z z)PS)F$^%NvcwbP4MtukkVO1?|=MB32fUp}PuHA0SLuHa&T>!8$mxD2Z%3PIEgQmOd zz)=P0h?*CDdS&`W?kt&_x`b*fk}6)L7+?neyzG7*iY#cD)$t?4Jx7f2unT>P^#fl1 z_bNn(8e|w~e5~vg8@#V8UiUIU0d6i~b%SCvuGtlAVkezQo0j({2LRyrDWwp53e^Zd z<@KbbQH?C;N$mnC%Zr(Q?(a)8{o`ts4c6ndz0EMkEFXgMB*m`V#RT2sV=f2TT*y6% zCaO>aZ4VS=g&Trx+~R&G{e-2>L3Af0eXx$t6^{sLC+C^?#bxUHOX56)$^r_Mt8Wf5 zu}~R8LYmbVsNP0~xRkR3N>x1%Ctw;FI#(dl*`oKvSdy7A0w3f&!hz1JS;q~Tm%R1# zx(ZeQR4%w%-QMLGO+o@*H9@x)6Hw4Hy3N5Lan=d(@K6PxL_cWs4S|?lA<9DbqFWgePN7u(JuTcNO{hSFi zcA)yNwu~ZP@TXw}c{^@gYa!%_y_N*A3R_Q<6z%f=?L%0SR}C2^5RYOcJY5>Pg*|*+I_POwgBS$s) zBq(n$I}RWx7AWtnt6%0*uo+W`WVI))OJj+zC8v4E?$jeh&Zjq>eS6%p(yq4o@Nlwv zRN*P_+~Tj~7n_$GFndNIgn5&Y zN4zxN(mPC1$%%tnIdc#*=-}u%5zL7Fc>?Q<_f!Lcd)|)YgOU)OIm+4n(Bm;waCvGa zQ;Wv4bj_!35Vq_gda#DbpY*YZ-6wafh}3T_AfK;W29(Zxzv_+iE(v` zemC@I+qPeH>`X-H6g)~o`-%T>xWtC1K7R&Qi>to?2%Mvn%cW-(J>@7& zZ&WkzX3}ZhHD(f%bUfRS@RUPi6procgS4*;y!<&s49UOf!oNwv(DXak>8v{Y5gxbk z)lz38b`btpsInl8cQj&DqIc4lTW`;@c7oPj_0<{p-yt!g#vn-)^7aWCPbS{+HI?+) z$b+nQ-W)ysO0ksbc$c0mN3*5_52#A^~P*2DLFmey;J8TW;4Ba%g}ti>97%a8f?lW24bML}0hOnNML2~@$`mb^)M`y< zB1SK*8*WDfNYlPORE?A?N&+f&m91G#P zK!Kb&Zypx?*Hw6T$@-3f-Q<=Zp9vW>M8;V(a@p&E1-6%eDQe-GR{;rSK${ncY#Rh9 zpuoz00EHgdu$9)%^ir+8Tu3S1@CPYmgY@p9ke6Kw3G5worQMM*J!by~4C;K7fzYhw z&F^}oq@Vx?`u3?HQj+&Q683Ki{l_Ig?ea|xqEDFR{p+MkTYSOH(AuSUw#7S~l_KqY z^jtuWY>)Hie0G}!z3FtS#8j_<&F3O^OL!sQ(FTX%huDZrGnjjYPh$39q7i%t)2C4A zyW2?Dq(i}M$||X+6vl)c$VBWngQAm^uoRvrqtcVjMM~~pl`7b+9yey*+SlTD!R@ZJ>7xsQWDa_jT0Q(ubNPeR9htt3ja6W}hJXpY8Fl4$>tfiw>xw?2rl$ z!-gmV{J<1{S5cvAB{#82oMZ-hWawqr3a<|*1dOctU+x*M=0n35plS6*9D(p^55X-B zG+E$tg}^bNf^%K_918h~ZUh5;ICFthc`x90&%?rM$bASeL{O>VtT4rd8ljDdN&tp~?c%Ey3yY@AXJl5L(o0TzGoBExpG*c*5wu zcGB@%QlViKH%WXhKHSd%qTot=83yQAld@o(#&_Ntz4=lK+0Xy=+h62(Attm z!FGHfB)Dbf{F`h@Wp#hnV0bq#N)XdixV+}_OgyO7F-~TOX?>PcLK!K&T=4Z+B68hLQ zq@n3j?%wBlheeto8JU@+U*>{{5x``6#XPh=nVi?mkaCiM*}8K(IHor@T?xTyeda(9 zliCOc8Ws%1&WVlY;G191AOgvc)PpxU^wt{{)a)f9RY<(n1j3efG$_*Ob1f>8JYIzZ z-Z$$p*cT0NxIJ*mgRoAUJS~J1h@x$60zzIDxPV|A{+}~KXE&sP!c$f~Zt@(EB{@1i)cwOB6*?Q0p4X7!A( zf63__(kTLXLUCH2#se(kHsr*^>O$BP)N8Z+P_D<+OXxUmGxs>#oKz{=$|gijQhVT; zGwYZ=dgYaf9Qh^ha;xH%0)hyfS!$C0DzYSkpe?y#Y1U%&w%&&#ylYfkHBxHIJDtWt zKf1nU?>11fCRbgBf*xldNizZ>t+OeSdtO#sF915l$c*MFKv3HCFyewCL1zZQClxCQ zz{Y~LrjjdLFB@YFUwV`-JR~8p)PU#2Ury_u+p0z3&=$_>OQ4`={;}MkKnZAsbuO&G zI!gvKz0#Vi_9E%N-a$~|CS)$G)09)+k}l;+%TB4j@Sk;u9++`@(6d!i99>Gdb;bTx zh*(GABi~cZj}$%01No{DH%psfq3F}g{=@?k_$khXOsklEV$QFL$A_if%!h@$_VTb( zx05zqtHb%_yTP?3)>WZKPlb&b_QW9xe9TLaem_5c#(b!F`^PWgH3~)e5s;%8dNt@l z_xDl=VJekL#(1Y~DKhw;N?3vBj*DoDnse^il^tDqe z3~}%jnvc<3gT~2)EVp^l<5R#Z%EZ0aeLFX`s3? zPrm?O3C}n1@~9RNW{{uDyuURgRaULu9G*TmgSimtjZBKI7I+zrpoZ_!{wzIL)QvOP zPEcX><|EH%g#(JT>jpV-^0Ny{vhe`HBQL`j(%&Yg^vw#wz$2HANW4=s+&W|j@R`s)9$!dv%B^71%W!w z{#m;>wNVbs1`vn(-i;K3$w2CrVfVTCkVd58!i0<9CGKnZ>y*ldzO`Iy$^a3`v6As? z{y>Xg6>1~dek6FRsE!jk_YT8z#&KKZNHra5PKj?0Dper8aTmR4T?p63gNAr{5z^72 zN1W}r+>y-pz_eScC@+`*((1-53kE{0BU%@SiLIbJ_O}EdU+X4=qELLOHd3jx>(mig z^o(=VC@8)uKYV!$+74nXLaGv=AxYdFSbnGg;xURh;9S?0+@Achxz%ja$ZPz1Yv{G( zh2RBP76lHc2jmWKmSde+ZTY*!VDbna&*1rIg5z`(9lnH*2*)2J>4*Oa*6)kw*WPiE zz9@*aaig64JXJUtT8xIHRp|d`e{*#Cfl1(%xV41;fw^Sad{bn?rAc(Rv!ee$08JkH zu+(-)3|4b4-jS&@obaK5f~y^Z9QQn~Mlrr#El?D34@%*VLmJs=DR{=BWy`hGo6$G&l8^iaUZ#OM#oNQpcnZv(g{YC) zB>OnqW?XbzpycrH))j2P@-Z`!W+A9ULs*3dPBIossQQ zUseZD%Up0-M;fqV^J=a`-blZ-tZM9i$RV-7woQC*Bt%t%%ee!!Wqk@1Ky;Hhlumv? z6VzPziQEL$y36;LI9%x*PZ#nb9@j@Cpo9vYL(XsYxs%G1qx6|d21 zlFJwGm1bInaq9)(On!+kyO14ZbPmbsePX47Qd;H3wDN*lE9jhc7h4>T12YYdXreG!jupu+uFH(zksAD4=(umKa$V#4G%1@GfmV0nxlNgk9nWcn1 z!-_!pVZ3E)P`}ta$<3;bQCh0qX{B`J;mWoG5yx1%6evp7InBkecI%vga8GfuNFJ4x zv>w-?&iE7nEV!{#>wL=-_<3y*xR!sld<=Z&QXbS+BQxogVCqBSF(mU#q2=kPCBuE{ z5Fpq6zy0bc6(%o)u&;16@1U5At`sN<$3I3D=vM71WiB>SD*yKszkzKx1ES z^m>;YWhYH>tG7^H0im9a=J4o+ifv`sw5fdZ^`@Y^4;-Ifi8u*06M%n_6mgLRCEV&2 z1DYL!w{0r-S{wNMsluk^vj&nSZx+}ziK|E9xXtjrNnn7<)o{9oqH_byen#hjLz?8B zlQVv12D=}GA}A!S{xc5yra(Xw_>c$=S$#C~oPU>odWRTcUXDB{gWnxUi+}3=>v~J0 z9UpP(&o27=tF7L}?-!yaI|h4R)}O>r78W|%Uru9^y+?_PS^LlBB7vvt9ksIU-Vz;2miYQEeaur=J{7$DNIx%08dLu zSChgj z{HmtFzJ9CFC>G)Usqjmhks2j1jfFT6x%MJix|eb6=YN_Xw2pV*`Mf6^zd^EQ6dpgo z+->gwAgRWksw(e4GFua3OaMxSr2?R&?>>k8Y3_wpYb^5s0T-uyw6;N)n#>mI7$o*K zUsB2g14!${v6kc&lhJC|L(7XFS4^cMuY6^z-5F*^;Ze*f@AW1K&G7v}L`B!woe$4A z?t9zcrvg|Vxd}`~hg~XpNOEB)yz@57N12>{*CBb*KWx1IK;pm7O$90!osCm1GU3;X zjuUrG6>swTs&k5nmq{)k6dQM?m&Sm}yRKCLrx!bor_4n!yL1ZD+0@zJ4qB6~J1)z& zUo?At71+wMP1m+2S8vf9R<&K4y}D(1$Kk9pLq5&zh+lY5{-+w=7Mr)SsqfBWZ=UF-Ol{)njpFH$0sa<}%|-wA$yz-b&EefWk7AexvR*=c`6 zHV}2>%G1p%E>e<4%)zL>0!{2f5LGPeERKmS>dPA;7g8>J=tlAg%fhhNKPa3WD(*!;u)&t}HF_HdxhvtoA zHDXtVVH%vL2l_RhG0N{_Tw15#Kz_q|B}2uG_!E|pzhNglT;qOX+g(I$VjG(J8EmrK zb745nQPp3;;Zf6NYx}alagVwef9j8Kbn1}>yG4fM!XeK_;{5@an1~?4-tLZ49hHub zL8X2FN=J-Tq(Z)uTCW{M`IeDk(RsFGvTZHo{DVI7Ct$$qqWR?>yN6aAU!W|`Ni5TG z)|Sun^=(gUe}tYfy8h6b!>FKf`iY(6z~ebcP}TZZef|bKsRbxk-40)wZ}mM3b>*N( z%n*qe-S4k*fOA2~vt8)GPW&XGS&9z#i>Hm zgTk3HOi9qYa`+C*+l6!CF6ZbIJCEb@an}DM3sjHTrut3olAPG^hnH!q0+&%rQah+V zV6v}APpRUb6t1i0rCjHGvB!zHS%OF9kd47YaRd=io|HhE1hE*!g$DXFX9USi+~j|h zrtQKA-?)#|MQi0np`}A-s|@u|^VgV|nkkrdw^O<9Kb%JSDhH8WS#2tQD{pLOD?O&E zEG@B)1c|8FXv*=OQFX~9e+&o%P3}QQMMZiHJ%x0DXYU&TRmn6|Pc$e!4*<%C>V3^b z!ol7S0H*%pE%wV8826~*gmtYvODcqrny!(*Nx=ZYOQl^Rg{z7*5(|BRb^!o^*Gt@wjfKxDG4o7E6jQ1%_ z0NqOxjG*nzX!fqKl32(s1oiTL%*uSILGnuQ?pUZ zM2*pkj$foA!{j;%Y+neJ3K8x@{p{@`9(LDW{e63vxjQ=`IC7za#AeM@uNyMA@!Oclg%k5+*8t~egiF>S?9cSG2lQBiq<``RWauX_Tq*t3PI ziZwXGrou;EwEuAkL5re~V+p{^%m>QOEbt#G6b=$61Op7%VbMpEnU-PXh|`H+XbRBz zq&RQ{U!p6P!4;VDg4QS41o5Hvu*UXaM_Ix-cQ*QqbP%}nqzhh2(~ckJoLYN$DF7!- zZ(p@)4c^C`JGj^a3b=kMI+gg=Is@Vi}e$ z+)Q4`GQms@RBZUL$0q);NlY*J+GXMgVuuVJ=j)4IJ`QN70MEO3)t9HUp4*;u5l2RZ z!=~1GFtCO_4wIX+ezoj7{SE<75wk#3BsHI(KNYX}?!(`cw=b#op+aUj=zEjUwgX=x znggKBMCH5ya?c)@)FqeSt%{jFvjj7h#foX$5-+Gi+daY9D|Xu&>;RGHE^^Ox6^zti zL~u+_i-QipU@OeLxb%?XcO<`R82^B#4kHq5ty`9-YVeH8-euV6dl|Ei5|Y;bO*)yB$=lP$0M%2X#lBg zM7X!4-U>5n()|X&!EZpw_8>#O4@rZLqY1p7ptwtOm42F)$>rfYLei85hjP4Z%eNi} zE5tLPSNG_YL|5{3>*(viTFpXP{XHY$W^+6zBBx}Cx4vS#L!=yV?B^1muKnU#8`Uc7z6fE8FU^bhN_kH-bv*bq z6uZEY2mqO99EFS@`UBtR#Txxv{^;}6!#K}l`@Y3KeH2S`o+hpr(F~isDoH@p3`PeE ze5lX^RA2q|(XMV2u2@k3!P(#U0AQ`97E{f2Q01bHNk%0V%HR7Mr^TLB(6R6lsB2-} zBN)_qE0#fL9O%0OrD8-P)F^tQl7nlF#451wW|YD^IqocoCOI!q6y#b>!>wXSq|xTm z`(LiSwwJ@N8JmJF0bO>Dn|=KHX1Vw5b7dZE-U0rV3&F5PwQp`d5+l{fdrROd>3+=v zh-d!my2ri&aTI#F$=;MIvM;5Um;t~C(g1jhZ4j;VKM=t+?(NQL_&I&8UJ(Ms@PLDq~oXMuDfoH4&1dn{Sy1y`1;KT^)kTr3ruQ(NfqUl_22rVWuJx(Py{7(ZTYRO2>J~096vmXP0UziwHXXEM^T3}r9nvU2^5C0wS_WQ& z7Xo5WpccoK396~{6u9K2w)e?;1~M!|u(E7eR?`wVHGg0Jk`QOPW109L6llzV_k z%i_`^NG${5Rt+Jz58~+npg{ASnY#rI@n|_09OUa#O@sOfU5jYCmM1q(Y&D#&QcEjY zSFMEmI`Y(r zRK2lQHzYAw)&EQ}d(*4)<6Zp9k4=Yej*|s6bLPFwUCHZheN5^7#F4(NI~*+!BQ1)T z<#+0E$9>Z3ea%*w>0Bam5=L(>)SMPyrUXynwyUg$CVhfj=jVNRJK&tcTlc&lw8jv- z$WhqHahD3}&VHK9#K24M0oM!S@)_ERd8olpU4VExSlSqMpuY4K#Fb2Yk_-tlU>neD z!|IZL+@M{~DXHv@-QuYiat}BZ#{pXocvQYkyA!)I58j$O8a3v9!{K~bp%*A&huY08 zS|Q27Nn+Jj-sfZvb+`FVgA=cYij3Uv2Qf~Jh?0j#l>U&WFC+6}ij0`GVikU{!NNvt zkQI9^7`>!d^0x*Ov))mG$7g4R51Ecf+EP?0747|7N^3$6|b>Wp- z=e8Ywy{&#C*IYM@s1p`UD&v#Fuuv+lQj9$V5Guxkvc<(`#9JSEdqzY;El(<>=Ajsr zHvw4uyQunB&E&me)1z1mHHy?R0;I|YwLXE{?*rLC{}H=0I7J-sigNZs*vW`9?+wni zc~WNk8E2C<74D_%c4Q|WTzGo(>+tHC6PGI9fbVa73SE3JMi98xIG%eC?W?U4lKqu2 z&EBC4TWa)MTE5p1&GR4^fA+Nb?uiD1FzzYUH9q@QayZUw&Z<8TUi+`zkE-cqNO<_) zKkw#~HoqIMF=RErZ(jRz$L8yS6IRHNsOMY^6WK5CI#AW zbgTzt!a}bpzg0q80rZE8x};T6*+r8!jbo`Ta+zLfmKh z+Xe%6Yg!Ob!Y4B7g>~~_AjQjZ3LNBXXMVap#0*53Q!u{GHy+rEb;sz`D+IStTrA52 znQU}byY;ta-}!u2tz5#{xK1w@nGJFg^PD52gQe|OEA8t)r)fRC5SBeg*7iwkC^mfx zJ&>`yn?XTFe_NlQu3V14!0#W;258oD9My<{Ty~!W*=a5Bw@1R*?N*X@=MUF0>wQ#j z`&Nbw6Y1Pnxm=@+&;N4nlUv#xA4~35Yd!op;6|+X+?avce|bc3DR7l)8eV9`^fH}d zg&GCo50Zf<_89bSP_&rbIpAJN_F22z0i#i$O->z~whpbh81h>t4{e&H&^(JE>IqoD zoo73^K1lD!-<#XAol!1LG!HRvxR&f`wp-nMQU1djXmDfm=anuB&&Cow^O0-Q(+5IM zpi>60+dpr+r7g2Z7mq zw2o@>8NhYr!Fb)x6w?DaNdbDKR}U+HrhIe!tEKmY4#rh%3M-;+U%8pO;Tubh8$1`3 zr*kX)qK!p8$6}nXCF3ZTg*@9st$_0OS-^dzwlHD?X2llvNM0Z92ErE)$>} z?)pwX#<4MDiNC`+FR?RkzjOYf&irG}g8WXwiB*j^Kn`W1V57=BMHL=CI-F9LQpYZO zbFXN_X~SpU#c$e+h0bx0?Sqr{@~@4Q(ZQ^?Cx{u2bw$!fQbd+F<@HByTo)gQqyC-l z)I)J_tL&2>fKw~tQ-`|r_KNi>hZTDqPTVH{Sl!Hl*g~#$5q#L%Y~A~6{Yx#9^^3ff zpL~7hcR6^V+d!_WpjxcSz_k~-8moA8N1tfL%Ysgg6HzpiylOx9)%&^Du5fiLw^}hC z4xagSW=E^OCHPhDK`j4wQ=pHKHvBMydspfbTq!nB;2E>igS`RmqP&fh{M>bhZc`8k z)n_oXZj%L0!$mJc8&Iv1TkVlP7|G4X%*}>;1ZQ+x7uT&SV&^&xY`Q#s@vcnq`(9}C z*sW_n4Mev#%3jYge1f1TL=ez>??7`Z1uE@+mDgxH`OCzAbekXBbck^p0IbpmKpDVK zi7<%la(Da+6j~67)N&Ez&M}qH(Eys>t*QVRyZ-?cC~fgQgGaJ06eu_L^baOxTS(X_ zOA1#)t6%%H9orzt$#4VK-$!`&ooQBn^z!dvZCKn>u3#%f&WJz+LcBZA+1}s~o@YY< z*%EubKl7ou8<%&;``J9~@4XL7VCzNm;J^BV-20QFr67$Vn-rciWdMy)w@&~;4-Z(1 zf&hUDZ)UY9@b@+aS3LAcEr#>zvq$ZUhmAew%?cRryv!f%Jp?`OVAeGvH13yu!z9Erg$rpNaPw|<3%?9$pe;NbSh&>_GA-%7%?I3R>)-JLH~P9x z4LB$822ZlJg*-R+8x<^ntl1-%)$h(pAj=m6FPnJo3Yt-s|GtggQ*NMY)4NTk#7b-A zEPsXHq9dT6A1G~pZuJ-rI(zMgQ#RIl!}>nDCv(&6_%~Lr%;wLjH0@mQ4Bdf3{ohNT zu`{rg;*SOH-N?SVJ>%hj`!-=YVqlK7ii-o{SI{VS5@fO`STP-7pZrGP+gDW zu6x&`CN(f%vEzm^koWdFZ01bw;Rp8Y=0ghN+N^0y%1A0z$>u^&k3wYsx~J@hEHm5O zVdm}#8-`xWh2cPSVLm$T)NbegM>kUs8xaotQQsHcmHu(^#u1JE+k21v`uXFJ28-Fp zLp^};c41AzkLv1%6g4y6;5SX3bE^Z`wnxqqE6ygO#1FGa(}d#$*D#Mn{M=eVL()>M z-;rH9V1d!Mm<~@vXRv6*${xU5Tz%`7=O1<@>nYZKy>)w`d!o&;wCq(cfv!hYMb|H9au^n^uCWeA<+P(oQpL z^`tYRzA(IEP%cq{8?7F@dH3*F}2bJ>fUi7E>A6u-GBO~rjo#ouX z#@CwLMt2t1i-9w`sNxlYy63ccJ zX^EP{GX+sSRr^o5oK)Pc4*n3H%}#=iPYu7#Bkp!-oL}32E=TKA(}hXLWZG{=Kav&~ zWt2y3+3bb7qVaKEE^(|cUu(~&(2F%;j@b`1;->l@1f|K5uN0=`2$dbX?1C6-ORH-< z6Qz6e!P#Ao{#15yxYxYuxMOOk{gIN7b6QMnrGd_oI%+QA0K0oomtrl7xB+4A{jRV2 zo$hnOzU)Q2!i5Im84VA3lgFXy10Q0P-dy-`X`&?C?koG!$sIMn-k*CM*cReSy-*5E z7vyBo{-J@gj0MS}$ z_QlK?#%?gyY%%s-Gxj8gR6-g{wxqF3(rg&AOehi>OHrwYRFY~ep;C>g)KOuCgw z$Y^2J4M9GtOra)Y_xt_<_WOS~HsMMwH*xb9H3BhMA7~l4g;ZjNCn4x+8pYkrqEa_5 zalWHPv6It7%xsZIa*#?QIJA`?jFP_)6k`B_q3Mrwm1NjB}y_-M-xInAqQZWGupICuljTni;;}XF_YC({nO^C-JVMCjJNK2 zI4F`pq}FKDA;UU(K(P6}9_!V}2>>R1q)Y5Jw9tvU?04B3qBF*9;*YQ}aX@V)db_!9 zOF+Ed=8behCK9u2k&l(Eg4^lEZjxxZ7yZlN*yO!6!>1ui{7GXA%KO5>A13?O#Kx0i zN>GAA)S(7Yqzwe+u7}x3cqnSgL*aNLQ~{!-ps1x_VXFXv zDjI4M}T4K(!)O-&6AH*enTZwU#oQrKl} z|(XwO`GX%m%PQ@n-a8}V&vzUyvLK~<3-u&<&f;Po$uvR z;O)!srv-2Gpauk=+fKC#jJ6I8*%FwV5*W25XiI32Rb&t~KFBJVb|{o~>=R?Q$3E3iDcbXW6y9E7y7kuQgog>ATl+?%0ir@V>tC+r{^8_l(}YaIOFD?YsTQ zhiaY-b=M4+-yI$v9q#YCKQZ+1!Kp_lH-xs6k8V#s8IFD`9-nyf>e+C^)S0QN$FE;J zYMgF<^Xl<6`84?sJby@v#pZwq85`_>5!5hnWZ0 z{vRR8zo%oO)g}nYW%ZbNXaWYyp&E2SIRJ9R&j_P8!2or5NS6PN&R5qGC+&|8gnk;- zaQIGQeLU4suG#Q5Uxb1H3V|n(4`0o5CDo=b&zDj9vSC8lkyM~#+pX?*kLpO;tPE85FyL}MUdFT9 z-OG4MZZ((iE)N+ic2aB_RzH~BZ2=RZm1NFin?t}>rtV5z>kt6jODcCgfF$obYQ>~+ zj31I?M0a0CUK=N-zLimwd(B(RtUH;D<8CKIz46Ik$L;Z(pW_vG35Xj-pJ2=7tdeHi z?M#$)m6zinRy0$^K{t{-%PfXTo(LcuKVQ0e42aF(Hb1d81#Y~F4LB&BA_S)FtQW8? zzly7IZfk$$_EPO>z`%>hL;3ha^L4FQw@(ttV~{H4U!2n#k5edz&TnaO*}nrK8Y+k+ zv$ejatherU{YPR`o@2_G544Nhox%dQi}iyXL;luf^?&21U#Fn^t+y+LXQ3+KSmRiE z$3U`R-1R+^0^0o~{B`LoRO!4d6$9vmNR+r<=91&5B%CZ#W=To}&1PzBX5ku%rLZ z!KHX#aGwyDGXpC4D%36mEn^K0rr@B2GeuVduu&>C({ zRNP@c56C9d5A#$i4`P^_xy}=tmUC{+$H}xlA>&-$Mpw)n{RE%yC^k2;X}Ybx-KzXc zgwhQk%H33qMJlpCBJd2ASQLdoF_aXT<6XG*BaDLt3%DAmR=rfciB6yRj{LMNb?&m^QQ#5!Kq^A6~f+W3PB( z+7tEqp~jvUzO|zQDmIxp>BKz;xbGicq6D+B>weAd-pXbM(uuM;l$YP}^oxvQxkgnQ z6H?d>AWk_~qnQ7wydw_SW$j{jzDQ|!hcRL; z>He#Q{o>+L3KIw{mIIV1FvsicAeT<|B#V=cNixszk7fHV%%NTMu9PK2s#=aUJ2#Ly z#0G}y85u@BLdvA%?lPf6_4A{?EjoGjG=4@xbc|BW-TRt(J7$s;aFP+X^HyclRV&>n z3^CPE zMZET0XmOlP+gPd!z{-aJ*7lZm8;iFjC_|=h?Fw@LaCGNwT}5otGavahrCInB!KjOL zwj!7ZfQ$UX-TDSl+kSl~DIst6nn>(8V-C&GJ-eMhSbfSt8lz(`6+6+Rw}LV4+N)3Q zJD<1O;~Wua<>@qx2+z2F6Wj2!dM~+iUjsX zdEpg=Qn>vG6#SPL{#?V@0iTfPKIK=Hk;7_hpx!Eq7bX9atbhb|^nNxIUG|&AHG_bt z>!+c51rVha(&9g*SPcFLn0Bgt|>WB=Y*bC$falfZDuAZ>vK-rCT&zmJ(lK&)^B%rjELmMBO6&$$M`Csl$%0A&Di{-5 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 literal 0 HcmV?d00001 diff --git a/assets/img/onion.2972bdca.png b/assets/img/onion.2972bdca.png new file mode 100644 index 0000000000000000000000000000000000000000..a9fc265d61333b21e7c1af99086fb03073b0d95e GIT binary patch literal 22392 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 diff --git a/assets/img/todomvc.c40dbbd3.png b/assets/img/todomvc.c40dbbd3.png new file mode 100644 index 0000000000000000000000000000000000000000..cbb6fe55bfe6bd5e2e5190e08873a8613486be48 GIT binary patch literal 22202 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")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("home"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("index"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("p",[a("router-link",{attrs:{to:"/zh/guide/middleware.html"}},[t._v("Middleware")]),t._v(":")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/middleware/response_time.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("options"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" app")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 加载期传递 app 实例")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("function")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("responseTime")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" next")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("h2",{attrs:{id:"常用属性和方法"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常用属性和方法","aria-hidden":"true"}},[t._v("#")]),t._v(" 常用属性和方法")]),t._v(" "),a("h3",{attrs:{id:"app-config"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-config","aria-hidden":"true"}},[t._v("#")]),t._v(" app.config")]),t._v(" "),a("p",[t._v("应用的"),a("router-link",{attrs:{to:"/zh/guide/config.html"}},[t._v("配置")]),t._v("。")],1),t._v(" "),a("h3",{attrs:{id:"app-router"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-router","aria-hidden":"true"}},[t._v("#")]),t._v(" app.router")]),t._v(" "),a("p",[t._v("对应的"),a("router-link",{attrs:{to:"/zh/guide/router.html"}},[t._v("路由")]),t._v("对象。")],1),t._v(" "),a("h3",{attrs:{id:"app-controller"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-controller","aria-hidden":"true"}},[t._v("#")]),t._v(" app.controller")]),t._v(" "),a("p",[t._v("对应的 "),a("router-link",{attrs:{to:"/zh/guide/controller.html"}},[t._v("Controller")]),t._v(" 对象。")],1),t._v(" "),a("h3",{attrs:{id:"app-logger"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-logger","aria-hidden":"true"}},[t._v("#")]),t._v(" app.logger")]),t._v(" "),a("p",[t._v("用于应用级别的日志记录,如记录启动阶段的一些数据信息,记录一些业务上与请求无关的信息。")]),t._v(" "),a("p",[t._v("更多参见 "),a("router-link",{attrs:{to:"/zh/guide/logger.html"}},[t._v("日志")]),t._v(" 文档。")],1),t._v(" "),a("h3",{attrs:{id:"app-middleware"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-middleware","aria-hidden":"true"}},[t._v("#")]),t._v(" app.middleware")]),t._v(" "),a("p",[t._v("挂载后的所有 "),a("router-link",{attrs:{to:"/zh/guide/middleware.html"}},[t._v("Middleware")]),t._v(" 对象。")],1),t._v(" "),a("h3",{attrs:{id:"app-server"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-server","aria-hidden":"true"}},[t._v("#")]),t._v(" app.server")]),t._v(" "),a("p",[t._v("对应的 "),a("a",{attrs:{href:"https://nodejs.org/api/http.html#http_class_http_server",target:"_blank",rel:"noopener noreferrer"}},[t._v("HTTP Server"),a("OutboundLink")],1),t._v(" 或 "),a("a",{attrs:{href:"https://nodejs.org/api/https.html#https_class_https_server",target:"_blank",rel:"noopener noreferrer"}},[t._v("HTTPS Server"),a("OutboundLink")],1),t._v(" 实例。")]),t._v(" "),a("p",[t._v("可以在 "),a("router-link",{attrs:{to:"/zh/guide/lifecycle.html"}},[t._v("生命周期")]),t._v(" 的 "),a("code",[t._v("serverDidReady")]),t._v(" 事件之后获取到。")],1),t._v(" "),a("h3",{attrs:{id:"app-curl"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-curl","aria-hidden":"true"}},[t._v("#")]),t._v(" app.curl()")]),t._v(" "),a("p",[t._v("通过 "),a("router-link",{attrs:{to:"/zh/guide/httpclient.html"}},[t._v("HttpClient")]),t._v(" 发起请求。")],1),t._v(" "),a("h3",{attrs:{id:"app-createanonymouscontext"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-createanonymouscontext","aria-hidden":"true"}},[t._v("#")]),t._v(" app.createAnonymousContext()")]),t._v(" "),a("p",[t._v("在某些非用户请求的场景下,我们也需要访问到 "),a("router-link",{attrs:{to:"/zh/guide/context.html"}},[t._v("Context")]),t._v(",此时该方法获取:")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("createAnonymousContext")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br")])]),a("h2",{attrs:{id:"如何扩展"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#如何扩展","aria-hidden":"true"}},[t._v("#")]),t._v(" 如何扩展")]),t._v(" "),a("p",[t._v("我们支持开发者通过 "),a("code",[t._v("app/extend/application.js")]),t._v(" 来扩展 "),a("code",[t._v("Application")]),t._v("。")]),t._v(" "),a("h3",{attrs:{id:"方法扩展"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#方法扩展","aria-hidden":"true"}},[t._v("#")]),t._v(" 方法扩展")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/extend/application.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("foo")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("param")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// this 就是 app 对象,在其中可以调用 app 上的其他方法,或访问属性")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("h3",{attrs:{id:"属性扩展"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#属性扩展","aria-hidden":"true"}},[t._v("#")]),t._v(" 属性扩展")]),t._v(" "),a("p",[t._v("一般来说属性的计算只需要进行一次,否则在多次访问属性时会计算多次,降低应用性能。")]),t._v(" "),a("p",[t._v("推荐的方式是使用 "),a("code",[t._v("Symbol + Getter")]),t._v(" 的模式来实现缓存。")]),t._v(" "),a("p",[t._v("例如,增加一个 "),a("code",[t._v("app.nunjucks")]),t._v(" 属性:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/extend/application.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("NUNJUCKS")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("Symbol")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Application#nunjucks'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" nunjuck "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'nunjuck'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("get")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("nunjucks")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("!")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("NUNJUCKS")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// this 就是 app 对象,可以获取到 app 上的其他属性")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("NUNJUCKS")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("new")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("nunjucks"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("Environment")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("nunjucks"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("NUNJUCKS")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br")])]),a("h3",{attrs:{id:"编写测试"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写测试","aria-hidden":"true"}},[t._v("#")]),t._v(" 编写测试")]),t._v(" "),a("p",[t._v("对于扩展的逻辑,我们一般需要通过"),a("router-link",{attrs:{to:"/zh/workflow/development/unittest.html"}},[t._v("单元测试")]),t._v("来保证代码质量。")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// test/app/extend/application.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" assert "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-mock'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("describe")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'test/app/extend/application.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should export nunjucks'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("assert")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("nunjucks"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("assert")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("nunjucks"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("renderString")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'{{ name }}'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'TZ'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("===")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'TZ'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("p",[t._v("具体的单元测试运行方式,参见 "),a("router-link",{attrs:{to:"/zh/workflow/development/unittest.html"}},[t._v("研发流程 - 单元测试")]),t._v(" 文档。")],1),t._v(" "),a("h3",{attrs:{id:"按照环境进行扩展"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#按照环境进行扩展","aria-hidden":"true"}},[t._v("#")]),t._v(" 按照环境进行扩展")]),t._v(" "),a("p",[t._v("另外,还可以根据运行环境进行有选择的扩展。")]),t._v(" "),a("p",[t._v("如 "),a("code",[t._v("app/extend/application.unittest.js")]),t._v(" 定义的扩展,只在 "),a("code",[t._v("unittest")]),t._v(" 环境生效。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/extend/application.unittest.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("mockXX")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("k"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" v")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("p",[t._v("这个文件只会在 "),a("code",[t._v("unittest")]),t._v(" 环境加载。")]),t._v(" "),a("p",[t._v("同理,对于下文中的 "),a("code",[t._v("Application")]),t._v(","),a("code",[t._v("Context")]),t._v(","),a("code",[t._v("Request")]),t._v(","),a("code",[t._v("Response")]),t._v(","),a("code",[t._v("Helper")]),t._v(" 都可以使用这种方式针对某个环境进行扩展。")])])},[],!1,null,null,null);s.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/14.3b6fddd8.js b/assets/js/14.3b6fddd8.js new file mode 100644 index 0000000..592b483 --- /dev/null +++ b/assets/js/14.3b6fddd8.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[14],{51:function(s,t,a){"use strict";a.r(t);var n=a(0),e=Object(n.a)({},function(){var s=this,t=s.$createElement,a=s._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[a("h2",{attrs:{id:"方案选型"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#方案选型","aria-hidden":"true"}},[s._v("#")]),s._v(" 方案选型")]),s._v(" "),a("p",[s._v("配置的管理有多种方案,以下列一些常见的方案:")]),s._v(" "),a("ul",[a("li",[s._v("使用平台管理配置,应用构建时将当前环境的配置放入包内,启动时指定该配置。但应用就无法一次构建多次部署,而且本地开发环境想使用配置会变的很麻烦。")]),s._v(" "),a("li",[s._v("使用平台管理配置,在启动时将当前环境的配置通过环境变量传入,这是比较优雅的方式,但框架对运维的要求会比较高,需要部署平台支持,同时开发环境也有相同痛点。")]),s._v(" "),a("li",[s._v("使用代码管理配置,在代码中添加多个环境的配置,在启动时传入当前环境的参数即可。但无法全局配置,必须修改代码。")])]),s._v(" "),a("p",[s._v("我们选择了最后一种配置方案,"),a("strong",[s._v("配置即代码")]),s._v(",配置的变更也应该经过 Review 后才能发布。应用包本身是可以部署在多个环境的,只需要指定运行环境即可。")]),s._v(" "),a("h2",{attrs:{id:"运行环境"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#运行环境","aria-hidden":"true"}},[s._v("#")]),s._v(" 运行环境")]),s._v(" "),a("p",[s._v("Egg 应用是一次构建多地部署,所以 Egg 会根据外部传入的一些配置来决定如何运行。")]),s._v(" "),a("h3",{attrs:{id:"env"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#env","aria-hidden":"true"}},[s._v("#")]),s._v(" env")]),s._v(" "),a("p",[s._v("应用开发者可以通过 "),a("code",[s._v("app.config.env")]),s._v(" 获取当前运行环境。")]),s._v(" "),a("p",[s._v("以下为框架支持的运行环境:")]),s._v(" "),a("table",[a("thead",[a("tr",[a("th",[s._v("serverEnv")]),s._v(" "),a("th",[s._v("NODE_ENV")]),s._v(" "),a("th",[s._v("说明")])])]),s._v(" "),a("tbody",[a("tr",[a("td",[s._v("local")]),s._v(" "),a("td",[s._v("-")]),s._v(" "),a("td",[s._v("本地开发环境")])]),s._v(" "),a("tr",[a("td",[s._v("unittest")]),s._v(" "),a("td",[s._v("test")]),s._v(" "),a("td",[s._v("单元测试环境")])]),s._v(" "),a("tr",[a("td",[s._v("prod")]),s._v(" "),a("td",[s._v("production")]),s._v(" "),a("td",[s._v("生产环境")])])])]),s._v(" "),a("p",[s._v("运行环境会决定插件是否开启,选择默认的配置项,对开发者非常友好。")]),s._v(" "),a("h2",{attrs:{id:"配置文件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#配置文件","aria-hidden":"true"}},[s._v("#")]),s._v(" 配置文件")]),s._v(" "),a("p",[s._v("框架会根据不同的运行环境来加载不同的配置文件。")]),s._v(" "),a("div",{staticClass:"language- line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[s._v("showcase\n├── app\n└── config\n ├── config.default.js\n ├── config.prod.js\n ├── config.unittest.js\n ├── config.default.js\n └── config.local.js\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br")])]),a("ul",[a("li",[a("code",[s._v("config.default.js")]),s._v(" 为默认的配置文件,所有环境都会加载它,"),a("strong",[s._v("绝大部分配置应该写在这里")]),s._v("。")]),s._v(" "),a("li",[s._v("然后会根据运行环境加载对应的配置,并覆盖默认配置的同名配置。\n"),a("ul",[a("li",[s._v("如 "),a("code",[s._v("prod")]),s._v(" 环境会加载 "),a("code",[s._v("config.prod.js")]),s._v(" 和 "),a("code",[s._v("config.default.js")]),s._v(" 文件。")]),s._v(" "),a("li",[s._v("然后 "),a("code",[s._v("config.prod.js")]),s._v(" 会覆盖 "),a("code",[s._v("config.default.js")]),s._v(" 的同名配置。")])])])]),s._v(" "),a("p",[s._v("具体的运行环境与配置文件的加载规则,参见"),a("router-link",{attrs:{to:"/zh/workflow/deployment/"}},[s._v("应用部署")]),s._v("文档相关章节。")],1),s._v(" "),a("h2",{attrs:{id:"配置定义"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#配置定义","aria-hidden":"true"}},[s._v("#")]),s._v(" 配置定义")]),s._v(" "),a("p",[s._v("配置文件返回的是一个 Object 对象,支持三种写法,请根据具体场合选择合适的写法。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n logger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n dir"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/home/admin/logs/demoapp'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br")])]),a("p",[s._v("配置文件也可以简化的写成 "),a("code",[s._v("exports.key = value")]),s._v(" 形式。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("keys "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'my-cookie-secret-key'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("logger "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n level"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'DEBUG'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br")])]),a("p",[s._v("也可以是一个 "),a("code",[s._v("function")]),s._v(",入参为 "),a("code",[s._v("appInfo")]),s._v("。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" path "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'path'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[s._v("exports")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("appInfo")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" config "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("logger "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n dir"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" path"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("join")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("appInfo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("root"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'logs'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" appInfo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br")])]),a("div",{staticClass:"tip custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("友情提示")]),s._v(" "),a("p",[s._v("一些插件文档里面,描述配置时,可能会使用 "),a("code",[s._v("exports.pluginName = {}")]),s._v(" 的方式。")]),s._v(" "),a("p",[s._v("复制时,请根据你的具体配置写法进行修正。")])]),s._v(" "),a("h2",{attrs:{id:"appinfo"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#appinfo","aria-hidden":"true"}},[s._v("#")]),s._v(" "),a("code",[s._v("AppInfo")])]),s._v(" "),a("p",[s._v("内置的 "),a("code",[s._v("appInfo")]),s._v(" 有:")]),s._v(" "),a("table",[a("thead",[a("tr",[a("th",[s._v("appInfo")]),s._v(" "),a("th",[s._v("说明")])])]),s._v(" "),a("tbody",[a("tr",[a("td",[s._v("pkg")]),s._v(" "),a("td",[s._v("package.json")])]),s._v(" "),a("tr",[a("td",[s._v("name")]),s._v(" "),a("td",[s._v("应用名,同 "),a("code",[s._v("pkg.name")]),s._v("。")])]),s._v(" "),a("tr",[a("td",[s._v("baseDir")]),s._v(" "),a("td",[s._v("应用的代码根目录。")])]),s._v(" "),a("tr",[a("td",[s._v("HOME")]),s._v(" "),a("td",[s._v("用户目录,如 "),a("code",[s._v("admin")]),s._v(" 账户为 "),a("code",[s._v("/home/admin")]),s._v("。")])]),s._v(" "),a("tr",[a("td",[s._v("root")]),s._v(" "),a("td",[s._v("应用根目录,"),a("code",[s._v("local")]),s._v(" 和 "),a("code",[s._v("unittest")]),s._v(" 环境下为 "),a("code",[s._v("baseDir")]),s._v(",其他都为 "),a("code",[s._v("HOME")]),s._v("。")])])])]),s._v(" "),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("注意事项")]),s._v(" "),a("p",[a("strong",[s._v("值得注意的是:"),a("code",[s._v("appInfo.root")]),s._v(" 是一个优雅的适配。")])]),s._v(" "),a("p",[s._v("比如在服务器环境我们会使用 "),a("code",[s._v("/home/admin/logs")]),s._v(" 作为日志目录,而本地开发时又不想污染用户目录,这样的适配就很好解决这个问题。")])]),s._v(" "),a("h2",{attrs:{id:"加载规则"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#加载规则","aria-hidden":"true"}},[s._v("#")]),s._v(" 加载规则")]),s._v(" "),a("p",[s._v("应用、插件、框架都可以定义这些配置,而且目录结构都是一致的。")]),s._v(" "),a("p",[s._v("但存在优先级("),a("code",[s._v("应用 > 框架 > 插件")]),s._v("),相对于此运行环境的优先级会更高。")]),s._v(" "),a("p",[s._v("框架会按加载顺序使用 "),a("a",{attrs:{href:"https://github.com/eggjs/extend2",target:"_blank",rel:"noopener noreferrer"}},[s._v("extend2"),a("OutboundLink")],1),s._v(" 模块进行深度拷贝。")]),s._v(" "),a("p",[s._v("比如在 "),a("code",[s._v("prod")]),s._v(" 环境加载一个配置的加载顺序如下,后加载的会覆盖前面的同名配置。")]),s._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[s._v("-"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" 插件 config.default.js\n-"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" 框架 config.default.js\n-"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" 应用 config.default.js\n-"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" 插件 config.prod.js\n-"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" 框架 config.prod.js\n-"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v(" 应用 config.prod.js\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br")])]),a("div",{staticClass:"tip custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("注意事项")]),s._v(" "),a("p",[s._v("合并配置时,"),a("strong",[s._v("对于数组的处理是直接覆盖而不是合并。")])]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" a "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n arr"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("1")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("2")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" b "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n arr"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("3")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("extend")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" a"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" b"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// => { arr: [ 3 ] }")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br")])]),a("p",[s._v("根据上面的例子,框架直接覆盖数组而不是进行合并。")])]),s._v(" "),a("h2",{attrs:{id:"常见问题"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常见问题","aria-hidden":"true"}},[s._v("#")]),s._v(" 常见问题")]),s._v(" "),a("h3",{attrs:{id:"为什么我的配置不生效?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#为什么我的配置不生效?","aria-hidden":"true"}},[s._v("#")]),s._v(" 为什么我的配置不生效?")]),s._v(" "),a("p",[s._v("首先,要确保不会犯以下的低级错误:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("someKeys "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'abc'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[s._v("exports")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("appInfo")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" config "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("keys "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'123456'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br")])]),a("p",[s._v("其次,参考下一条 FAQ 来排查问题。")]),s._v(" "),a("h3",{attrs:{id:"如何查看最终的配置?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#如何查看最终的配置?","aria-hidden":"true"}},[s._v("#")]),s._v(" 如何查看最终的配置?")]),s._v(" "),a("p",[s._v("框架的配置功能比较强大,有不同环境变量,又有框架、插件、应用等很多地方配置。")]),s._v(" "),a("p",[s._v("如果你分析问题时,想知道当前运行时使用的最终配置,框架提供了:")]),s._v(" "),a("ul",[a("li",[a("code",[s._v("run/application_config.json")]),s._v(" 文件:最终的配置合并结果,可以用来分析问题。")]),s._v(" "),a("li",[a("code",[s._v("run/application_config_meta.json")]),s._v(" 文件:用来排查属性的来源。")])]),s._v(" "),a("p",[s._v("另外,基于安全的考虑,dump 出的文件中会对一些字段进行"),a("strong",[s._v("脱敏处理")]),s._v(",主要包括两类:")]),s._v(" "),a("ul",[a("li",[s._v("如密码、密钥等安全字段,可以通过 "),a("a",{attrs:{href:"https://github.com/eggjs/egg/blob/master/config/config.default.js",target:"_blank",rel:"noopener noreferrer"}},[s._v("config.dump.ignore"),a("OutboundLink")],1),s._v(" 配置。")]),s._v(" "),a("li",[s._v("如函数、Buffer 等类型,"),a("code",[s._v("JSON.stringify")]),s._v(" 后的内容特别大。")])]),s._v(" "),a("div",{staticClass:"tip custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("友情提示")]),s._v(" "),a("p",[s._v("注意:"),a("code",[s._v("run")]),s._v(" 目录是每次启动期都会 dump 的信息,用于问题排查。")]),s._v(" "),a("p",[s._v("开发者修改该目录的文件将"),a("strong",[s._v("不会有任何效果")]),s._v(",应该把该目录加到 "),a("code",[s._v("gitignore")]),s._v(" 中。")])])])},[],!1,null,null,null);t.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/15.494082f6.js b/assets/js/15.494082f6.js new file mode 100644 index 0000000..2f49266 --- /dev/null +++ b/assets/js/15.494082f6.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[15],{62:function(t,s,a){"use strict";a.r(s);var n=a(0),e=Object(n.a)({},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用场景")]),t._v(" "),a("p",[a("code",[t._v("Context")]),t._v(" 是一个 "),a("strong",[t._v("请求级别")]),t._v(" 的对象,继承自 "),a("a",{attrs:{href:"http://koajs.com/#context",target:"_blank",rel:"noopener noreferrer"}},[t._v("Koa.Context"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("p",[t._v("在每一次收到用户请求时都会实例化一个 "),a("code",[t._v("Context")]),t._v(" 对象,它封装了该次请求的相关信息,并提供了许多便捷的方法来获取请求参数或者设置响应信息。")]),t._v(" "),a("p",[t._v("框架会将所有的 "),a("router-link",{attrs:{to:"/zh/guide/service.html"}},[t._v("Service")]),t._v(" 挂载到 "),a("code",[t._v("Context")]),t._v(" 实例上,某些插件也会将挂载一些其他的方法和对象。")],1),t._v(" "),a("h2",{attrs:{id:"获取方式"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#获取方式","aria-hidden":"true"}},[t._v("#")]),t._v(" 获取方式")]),t._v(" "),a("p",[t._v("最常见的 "),a("code",[t._v("Context")]),t._v(" 实例获取方式是在 "),a("router-link",{attrs:{to:"/zh/guide/middleware.html"}},[t._v("Middleware")]),t._v(", "),a("router-link",{attrs:{to:"/zh/guide/controller.html"}},[t._v("Controller")]),t._v(" 以及 "),a("router-link",{attrs:{to:"/zh/guide/service.html"}},[t._v("Service")]),t._v(" 中。")],1),t._v(" "),a("p",[t._v("在 "),a("router-link",{attrs:{to:"/zh/guide/controller.html"}},[t._v("Controller")]),t._v("、"),a("router-link",{attrs:{to:"/zh/guide/service.html"}},[t._v("Service")]),t._v(" 等可以通过 "),a("code",[t._v("this.ctx")]),t._v(" 获取:")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/home.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HomeController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("index")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("query")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'name'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[a("router-link",{attrs:{to:"/zh/guide/middleware.html"}},[t._v("Middleware")]),t._v(" 和 "),a("a",{attrs:{href:"http://koajs.com/",target:"_blank",rel:"noopener noreferrer"}},[t._v("Koa"),a("OutboundLink")],1),t._v(" 框架保持一致:")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/middleware/response_time.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("function")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("responseTime")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" next")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" start "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" Date"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("now")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("next")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" cost "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" Date"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("now")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("-")]),t._v(" start"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'X-Response-Time'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("cost"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("ms`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("p",[t._v("在某些非用户请求的场景下,我们也需要访问到 "),a("code",[t._v("Context")]),t._v(",此时可以通过 "),a("router-link",{attrs:{to:"/zh/guide/application.html"}},[t._v("Application")]),t._v(" 的 "),a("code",[t._v("createAnonymousContext()")]),t._v(" 方法获取:")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("createAnonymousContext")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br")])]),a("p",[a("router-link",{attrs:{to:"/zh/ecosystem/schedule/timer.html"}},[t._v("定时任务")]),t._v(" 也接收 "),a("code",[t._v("Context")]),t._v(" 实例作为参数,以便执行一些定时的业务逻辑:")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/schedule/refresh.js")]),t._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("task")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("ctx")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("posts"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("refresh")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("h2",{attrs:{id:"常用属性和方法"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常用属性和方法","aria-hidden":"true"}},[t._v("#")]),t._v(" 常用属性和方法")]),t._v(" "),a("h3",{attrs:{id:"ctx-app"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-app","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.app")])]),t._v(" "),a("p",[t._v("对应的 "),a("router-link",{attrs:{to:"/zh/guide/application.html"}},[t._v("Application")]),t._v(" 实例。")],1),t._v(" "),a("h3",{attrs:{id:"ctx-service"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-service","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.service")])]),t._v(" "),a("p",[t._v("对应的 "),a("router-link",{attrs:{to:"/zh/guide/service.html"}},[t._v("Service")]),t._v(" 实例。")],1),t._v(" "),a("h3",{attrs:{id:"ctx-logger"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-logger","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.logger")])]),t._v(" "),a("p",[t._v("与请求相关的 "),a("code",[t._v("ContextLogger")]),t._v(" 实例。")]),t._v(" "),a("p",[t._v("它打印的日志都会在前面带上一些当前请求相关的信息。")]),t._v(" "),a("p",[t._v("如 "),a("code",[t._v("[$userId/$ip/$traceId/${cost}ms $method $url]")]),t._v("。")]),t._v(" "),a("p",[t._v("通过这些信息,我们可以从日志快速定位请求,并串联一次请求中的所有的日志。")]),t._v(" "),a("p",[t._v("更多参见 "),a("router-link",{attrs:{to:"/zh/guide/logger.html"}},[t._v("日志")]),t._v(" 文档。")],1),t._v(" "),a("h3",{attrs:{id:"ctx-curl"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-curl","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.curl()")])]),t._v(" "),a("p",[t._v("通过 "),a("router-link",{attrs:{to:"/zh/guide/httpclient.html"}},[t._v("HttpClient")]),t._v(" 发起请求。")],1),t._v(" "),a("h3",{attrs:{id:"ctx-runinbackground"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-runinbackground","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.runInBackground()")])]),t._v(" "),a("p",[t._v("有些时候,我们在处理完用户请求后,希望立即返回响应,但同时需要异步执行一些操作。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/trade.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("TradeController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("buy")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" goods "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("trade"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("buy")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("goods"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 下单后需要进行一次核对,且不阻塞当前请求")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("runInBackground")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 这里面的异常都会统统被 Backgroud 捕获掉,并打印错误日志")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("trade"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("check")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" msg"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'已下单'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br")])]),a("h3",{attrs:{id:"ctx-query"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-query","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.query")])]),t._v(" "),a("p",[t._v("在 URL 中 "),a("code",[t._v("?")]),t._v(" 后面的部分是一个 "),a("code",[t._v("Query String")]),t._v(",这一部分经常用于 "),a("code",[t._v("GET")]),t._v(" 请求中传递参数。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// GET /api/user/list?limit=10&sort=name")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("query"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// { limit: '10', sort: 'name' }")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'hi, egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br")])]),a("p",[t._v("对应的测试:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// test/controller/home.test.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" mock"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" assert "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-mock'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("describe")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'test/controller/home.test.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should GET /'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'User-Agent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-unittest'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("query")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" limit"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'10'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" sort"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'name'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("200")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("友情提示")]),t._v(" "),a("p",[t._v("鉴于 HTTP 协议的约定,在请求中获取到的查询参数,均为字符串,如有需要需自行转型。")])]),t._v(" "),a("p",[a("strong",[t._v("值得注意的是,"),a("code",[t._v("ctx.query")]),t._v(" 对重复的 "),a("code",[t._v("key")]),t._v(" 只取第一个值,后面将被忽略。")])]),t._v(" "),a("p",[t._v("如 "),a("code",[t._v("/api/user?sort=name&id=2&id=3")]),t._v(" 的 "),a("code",[t._v("query.id === '2'")]),t._v("。")]),t._v(" "),a("p",[t._v("这样处理的原因是为了保持统一性,由于通常情况下我们都不会设计让用户传递相同的 "),a("code",[t._v("key")]),t._v(",所以我们经常会写类似下面的代码:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" key "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("query"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("key "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("||")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("''")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("key"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("startsWith")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// do something")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("p",[t._v("而如果有人故意发起请求带上重复的 "),a("code",[t._v("key")]),t._v(" 就会引发系统异常。因此框架保证了从 "),a("code",[t._v("ctx.query")]),t._v(" 上获取的参数一旦存在,一定是字符串类型。")]),t._v(" "),a("h3",{attrs:{id:"ctx-queries"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-queries","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.queries")])]),t._v(" "),a("p",[t._v("如果你的系统设计允许用户传递相同的 "),a("code",[t._v("key")]),t._v("(不推荐),可以使用 "),a("code",[t._v("ctx.queries")]),t._v(":")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// GET /api/user?sort=name&id=2&id=3")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("queries"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// { sort: [ 'name' ], id: [ '2', '3' ] }")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("ul",[a("li",[a("code",[t._v("queries.id === [ '2', '3']")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("ctx.queries")]),t._v(" 的属性一定是数组类型,如 "),a("code",[t._v("queries.name === [ 'sort' ]")]),t._v("。")]),t._v(" "),a("li",[t._v("如果你确定只会传递一个,则应该使用 "),a("code",[t._v("query.sort")]),t._v(" 而不是 "),a("code",[t._v("queries.sort")]),t._v("。")])]),t._v(" "),a("h3",{attrs:{id:"ctx-params"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-params","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.params")])]),t._v(" "),a("p",[t._v("获取 "),a("router-link",{attrs:{to:"/zh/guide/router.html#获取命名参数"}},[t._v("Router")]),t._v(" 命名参数。")],1),t._v(" "),a("h3",{attrs:{id:"ctx-routerpath"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-routerpath","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.routerPath")])]),t._v(" "),a("p",[t._v("获取当前命中的 "),a("router-link",{attrs:{to:"/zh/guide/router.html#路由路径"}},[t._v("Router")]),t._v(" 路径。")],1),t._v(" "),a("h3",{attrs:{id:"ctx-routername"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-routername","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.routerName")])]),t._v(" "),a("p",[t._v("获取当前命中的 "),a("router-link",{attrs:{to:"/zh/guide/router.html#路由别名"}},[t._v("Router")]),t._v(" 别名。")],1),t._v(" "),a("h3",{attrs:{id:"ctx-request-body"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-request-body","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.request.body")])]),t._v(" "),a("p",[t._v("框架内置了 "),a("a",{attrs:{href:"https://github.com/koajs/bodyparser",target:"_blank",rel:"noopener noreferrer"}},[t._v("bodyParser"),a("OutboundLink")],1),t._v(",用于获取 "),a("code",[t._v("POST")]),t._v(" 等的 "),a("code",[t._v("请求 body")]),t._v("。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("create")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 获取请求信息 `{ name: 'TZ' }`")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// ...")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[t._v("对应的测试:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// test/controller/home.test.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should POST form'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 跳过 `CSRF` 校验")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("mockCsrf")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("post")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user/create'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'form'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("send")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'TZ'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("200")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should POST JSON'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("mockCsrf")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("post")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user/create'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("send")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'TZ'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("200")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br")])]),a("h3",{attrs:{id:"ctx-request-files"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-request-files","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.request.files")])]),t._v(" "),a("p",[t._v("获取 "),a("code",[t._v("file")]),t._v(" 模式上传的文件对象,参见 "),a("router-link",{attrs:{to:"/zh/guide/upload.html"}},[t._v("文件上传")]),t._v(" 文档。")],1),t._v(" "),a("h3",{attrs:{id:"ctx-get-name"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-get-name","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.get(name)")])]),t._v(" "),a("p",[t._v("获取请求 "),a("code",[t._v("Header")]),t._v(" 信息。")]),t._v(" "),a("p",[t._v("由于 HTTP 协议中 "),a("code",[t._v("Header")]),t._v(" 是忽略大小写的,因此 "),a("code",[t._v("ctx.headers")]),t._v(" 中的 Key 一律转为小写。")]),t._v(" "),a("p",[t._v("一般我们推荐使用 "),a("code",[t._v("ctx.get(name)")]),t._v(" 来获取对应的 Header,它会忽略大小写。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'User-Agent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'user-agent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 取不到值")]),t._v("\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'User-Agent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("h3",{attrs:{id:"ctx-cookies"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-cookies","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.cookies")])]),t._v(" "),a("p",[t._v("读取 "),a("code",[t._v("Cookie")]),t._v(" 对象,参见 "),a("router-link",{attrs:{to:"/zh/guide/cookie.html"}},[t._v("Cookie")]),t._v(" 文档。")],1),t._v(" "),a("h3",{attrs:{id:"ctx-status"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-status","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.status =")])]),t._v(" "),a("p",[t._v("HTTP 设计了非常多的"),a("a",{attrs:{href:"https://en.wikipedia.org/wiki/List_of_HTTP_status_codes",target:"_blank",rel:"noopener noreferrer"}},[t._v("状态码"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("p",[t._v("正确地设置状态码,可以让响应更符合语义,参考 "),a("a",{attrs:{href:"https://en.wikipedia.org/wiki/List_of_HTTP_status_codes",target:"_blank",rel:"noopener noreferrer"}},[t._v("List of HTTP status codes"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("p",[t._v("框架提供了一个便捷的 "),a("code",[t._v("Setter")]),t._v(" 来进行状态码的设置:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("create")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 设置状态码为 201")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("status "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("201")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("p",[t._v("对应的测试:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should POST /user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("post")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("201")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("h3",{attrs:{id:"ctx-body"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-body","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.body =")])]),t._v(" "),a("p",[t._v("HTTP 请求的绝大部分数据都是通过 body 发送给请求方的。")]),t._v(" "),a("ul",[a("li",[t._v("作为 API 接口,通常直接赋值一个 Object 对象。")]),t._v(" "),a("li",[t._v("作为 HTML 页面,通常返回 HTML 字符串。")]),t._v(" "),a("li",[t._v("作为文件下载等场景,还可以直接赋值为 "),a("code",[t._v("Stream")]),t._v("。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/home.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HomeController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// GET /")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("index")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("type "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'html'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'

        Hello

        '")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// GET /api/info")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("info")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n category"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'framework'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n language"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Node.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// GET /api/proxy")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("proxy")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n streaming"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("header"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// result.res 是一个 stream")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("res"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br"),a("span",{staticClass:"line-number"},[t._v("22")]),a("br"),a("span",{staticClass:"line-number"},[t._v("23")]),a("br"),a("span",{staticClass:"line-number"},[t._v("24")]),a("br"),a("span",{staticClass:"line-number"},[t._v("25")]),a("br"),a("span",{staticClass:"line-number"},[t._v("26")]),a("br"),a("span",{staticClass:"line-number"},[t._v("27")]),a("br"),a("span",{staticClass:"line-number"},[t._v("28")]),a("br")])]),a("p",[t._v("对应的测试:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should response html'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'

        Hello

        '")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token regex"}},[t._v("/Hello/")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should response json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/info'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n category"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'framework'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n language"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Node.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("res")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("assert")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("res"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("name "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("===")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br")])]),a("h3",{attrs:{id:"ctx-set-name-value"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-set-name-value","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.set(name, value)")])]),t._v(" "),a("p",[t._v("除了 "),a("code",[t._v("状态码")]),t._v(" 和 "),a("code",[t._v("响应体")]),t._v(" 外,还可以通过响应 "),a("code",[t._v("Header")]),t._v(" 设置一些扩展信息。")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("ctx.set(key, value)")]),t._v(":可以设置一个 "),a("code",[t._v("Header")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("ctx.set(headers)")]),t._v(":可以同时设置多个 "),a("code",[t._v("Header")]),t._v("。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/proxy.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("ProxyController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("show")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" start "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" Date"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("now")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("post"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" cost "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" Date"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("now")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("-")]),t._v(" start"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 设置一个响应头")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'x-response-time'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("cost"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("ms`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br")])]),a("p",[t._v("对应的测试:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should send response header'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("post")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/post'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'X-Response-Time'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token regex"}},[t._v("/\\d+ms/")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("h3",{attrs:{id:"ctx-type"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-type","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.type =")])]),t._v(" "),a("p",[t._v("和请求中的 body 一样,在响应也需要对应的 "),a("code",[t._v("Content-Type")]),t._v(" 告知客户端如何对数据进行解析。")]),t._v(" "),a("p",[t._v("框架提供了该语法糖,等价于 "),a("code",[t._v("ctx.set('Content-Type', mime)")]),t._v("。")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("json")]),t._v(":对应于 API 接口的 "),a("code",[t._v("application/json")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("html")]),t._v(":对应于 HTML 页面的 "),a("code",[t._v("text/html")]),t._v("。")]),t._v(" "),a("li",[t._v("更多参见 "),a("a",{attrs:{href:"https://github.com/jshttp/mime-types",target:"_blank",rel:"noopener noreferrer"}},[t._v("mime-types"),a("OutboundLink")],1),t._v("。")])]),t._v(" "),a("p",[a("strong",[t._v("一般可以省略,框架会自动根据取值,来赋值对应的 "),a("code",[t._v("Content-Type")]),t._v("。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/user.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 一般可以省略,框架会自动根据取值")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[t._v("对应的测试:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should response json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Content-Type'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token regex"}},[t._v("/json/")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("h3",{attrs:{id:"ctx-render"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-render","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.render()")])]),t._v(" "),a("p",[t._v("通常来说,我们不会手写 HTML 页面,而是会通过模板引擎进行生成。")]),t._v(" "),a("p",[t._v("我们可以通过使用模板插件,来提供渲染能力。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HomeController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("index")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("render")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'home.tpl'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// ctx.body = await ctx.renderString('hi, {{ name }}', { name: 'egg' });")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[t._v("具体示例可以查看"),a("router-link",{attrs:{to:"/zh/ecosystem/frontend/template.html"}},[t._v("模板引擎")]),t._v("。")],1),t._v(" "),a("h3",{attrs:{id:"ctx-redirect"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-redirect","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.redirect()")])]),t._v(" "),a("p",[t._v("重定向请求,默认为 "),a("code",[t._v("302")]),t._v(",如果需要,可以设置 "),a("code",[t._v("ctx.status = 301")]),t._v("。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("logout")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("logout")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("redirect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'referer'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("||")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br")])]),a("p",[t._v("对应的测试:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should logout'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user/logout'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Location'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("302")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("安全提示")]),t._v(" "),a("p",[a("strong",[t._v("基于安全考虑,默认只允许重定向处于白名单的域名。")])]),t._v(" "),a("p",[t._v("更多参见 "),a("router-link",{attrs:{to:"/zh/ecosystem/security/security_url.html"}},[t._v("安全链接")]),t._v(" 文档。")],1)]),t._v(" "),a("h3",{attrs:{id:"ctx-request"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-request","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.request")])]),t._v(" "),a("p",[t._v("由于 Node.js 原生的 "),a("a",{attrs:{href:"https://nodejs.org/api/http.html#http_class_http_clientrequest",target:"_blank",rel:"noopener noreferrer"}},[t._v("HTTP Request"),a("OutboundLink")],1),t._v(" 对象比较底层。")]),t._v(" "),a("p",[t._v("因此 "),a("a",{attrs:{href:"http://koajs.com/",target:"_blank",rel:"noopener noreferrer"}},[t._v("Koa"),a("OutboundLink")],1),t._v(" 做了一层薄薄的 "),a("a",{attrs:{href:"http://koajs.com/#request",target:"_blank",rel:"noopener noreferrer"}},[t._v("Koa.Request"),a("OutboundLink")],1),t._v(" 封装,提供了一系列方法获取 HTTP 请求相关信息。")]),t._v(" "),a("p",[t._v("一般你不需要直接调用它,"),a("code",[t._v("Context")]),t._v(" 已经代理了它们的大部分方法和属性,如上文所述。")]),t._v(" "),a("p",[a("strong",[t._v("唯一的例外是:获取 "),a("code",[t._v("POST")]),t._v(" 的 body 应该使用 "),a("code",[t._v("ctx.request.body")]),t._v(",而不是 "),a("code",[t._v("ctx.body")]),t._v("。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/user.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("update")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 等价于 ctx.query 这个 getter")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" id "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("query"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 唯一的不同,获取 post body")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" postBody "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 等价于 ctx.body 这个 setter")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("response"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("update")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" postBody"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br")])]),a("h3",{attrs:{id:"ctx-response"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-response","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.response")])]),t._v(" "),a("p",[t._v("由于 Node.js 原生的 "),a("a",{attrs:{href:"https://nodejs.org/api/http.html#http_class_http_serverresponse",target:"_blank",rel:"noopener noreferrer"}},[t._v("HTTP Response"),a("OutboundLink")],1),t._v(" 对象比较底层。")]),t._v(" "),a("p",[t._v("因此 "),a("a",{attrs:{href:"http://koajs.com/",target:"_blank",rel:"noopener noreferrer"}},[t._v("Koa"),a("OutboundLink")],1),t._v(" 做了一层薄薄的 "),a("a",{attrs:{href:"http://koajs.com/#response",target:"_blank",rel:"noopener noreferrer"}},[t._v("Koa.Response"),a("OutboundLink")],1),t._v(" 封装,提供了一系列方法设置 HTTP 响应。")]),t._v(" "),a("p",[t._v("一般你不需要直接调用它,"),a("code",[t._v("Context")]),t._v(" 已经代理了它们的大部分方法和属性,如上文所述。")]),t._v(" "),a("h3",{attrs:{id:"更多"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#更多","aria-hidden":"true"}},[t._v("#")]),t._v(" 更多")]),t._v(" "),a("p",[t._v("更多语法糖,请参见 "),a("a",{attrs:{href:"https://koajs.com/#request-aliases",target:"_blank",rel:"noopener noreferrer"}},[t._v("Koa Aliases"),a("OutboundLink")],1),t._v(" 文档。")]),t._v(" "),a("h2",{attrs:{id:"如何扩展"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#如何扩展","aria-hidden":"true"}},[t._v("#")]),t._v(" 如何扩展")]),t._v(" "),a("p",[t._v("我们支持开发者通过:")]),t._v(" "),a("ul",[a("li",[t._v("通过 "),a("code",[t._v("app/extend/context.js")]),t._v(" 来扩展 "),a("code",[t._v("Context")]),t._v("。")]),t._v(" "),a("li",[t._v("通过 "),a("code",[t._v("app/extend/request.js")]),t._v(" 来扩展 "),a("code",[t._v("Request")]),t._v("。")]),t._v(" "),a("li",[t._v("通过 "),a("code",[t._v("app/extend/response.js")]),t._v(" 来扩展 "),a("code",[t._v("Response")]),t._v("。")]),t._v(" "),a("li",[t._v("同样也支持在 "),a("code",[t._v("app/extend/context.unittest.js")]),t._v(" 来根据运行环境扩展。")])]),t._v(" "),a("h3",{attrs:{id:"属性扩展"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#属性扩展","aria-hidden":"true"}},[t._v("#")]),t._v(" 属性扩展")]),t._v(" "),a("p",[t._v("一般来说属性的计算只需要进行一次,否则在多次访问属性时会计算多次,降低应用性能。")]),t._v(" "),a("p",[t._v("推荐的方式是使用 "),a("code",[t._v("Symbol + Getter")]),t._v(" 的模式来实现缓存。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/extend/context.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("UA")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("Symbol")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Context#ua'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" useragent "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'useragent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("get")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("ua")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("!")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("UA")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// this 就是 ctx 对象,在其中可以调用 ctx 上的其他方法,或访问属性")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" uaString "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'user-agent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("UA")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" useragent"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("parse")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("uaString"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("UA")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br")])]),a("h3",{attrs:{id:"编写测试"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写测试","aria-hidden":"true"}},[t._v("#")]),t._v(" 编写测试")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// test/app/extend/context.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" assert "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-mock'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("describe")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'test/app/extend/contex.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should parse ua'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 创建 ctx")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("mockContext")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'user-agent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) Chrome/15.0.874.24'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("assert")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ua"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("chrome"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br")])]),a("p",[t._v("具体的单元测试运行方式,参见 "),a("router-link",{attrs:{to:"/zh/workflow/development/unittest.html"}},[t._v("研发流程 - 单元测试")]),t._v(" 文档。")],1)])},[],!1,null,null,null);s.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/16.b546ed30.js b/assets/js/16.b546ed30.js new file mode 100644 index 0000000..c63a533 --- /dev/null +++ b/assets/js/16.b546ed30.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[16],{54:function(t,s,a){"use strict";a.r(s);var n=a(0),e=Object(n.a)({},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用场景")]),t._v(" "),a("p",[a("code",[t._v("Controller")]),t._v(" 负责"),a("strong",[t._v("解析用户的输入,处理后返回相应的结果")]),t._v("。")]),t._v(" "),a("p",[a("code",[t._v("Controller")]),t._v(" 其实就是一个特殊的 "),a("router-link",{attrs:{to:"/zh/guide/middleware.html"}},[t._v("Middleware")]),t._v(",它在洋葱模型的最里面。")],1),t._v(" "),a("p",[a("strong",[t._v("场景举例:")])]),t._v(" "),a("ul",[a("li",[t._v("提供 "),a("code",[t._v("AJAX")]),t._v(" 接口,接收用户的参数,查找数据库返回给用户或将用户的请求更新到数据库中。")]),t._v(" "),a("li",[t._v("根据用户访问的 URL,渲染对应的模板返回 HTML 给浏览器渲染。")]),t._v(" "),a("li",[t._v("作为代理服务器时,将用户的请求转发到其他服务上,并将处理结果返回给用户。")])]),t._v(" "),a("div",{staticClass:"tip custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("最佳实践")]),t._v(" "),a("p",[a("code",[t._v("Controller")]),t._v(" 仅负责 HTTP 层的相关处理逻辑,不要包含太多业务逻辑。")]),t._v(" "),a("ol",[a("li",[t._v("获取用户通过 HTTP 传递过来的请求参数。")]),t._v(" "),a("li",[t._v("校验、组装参数。")]),t._v(" "),a("li",[t._v("调用 "),a("router-link",{attrs:{to:"/zh/guide/service.html"}},[t._v("Service")]),t._v(" 进行业务处理。")],1),t._v(" "),a("li",[t._v("必要时处理转换 "),a("code",[t._v("Service")]),t._v(" 的返回结果,如渲染模板。")]),t._v(" "),a("li",[t._v("通过 HTTP 将结果响应给用户。")])])]),t._v(" "),a("h2",{attrs:{id:"编写-controller"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写-controller","aria-hidden":"true"}},[t._v("#")]),t._v(" 编写 Controller")]),t._v(" "),a("p",[t._v("我们约定把 "),a("code",[t._v("Controller")]),t._v(" 放置在 "),a("code",[t._v("app/controller")]),t._v(" 目录下:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/user.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" Controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("create")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" service "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 获取请求信息")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" userInfo "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 校验参数")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("assert")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("userInfo "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("&&")]),t._v(" userInfo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("422")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'user name is required.'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 调用 Service 进行业务处理")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("create")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("userInfo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 响应内容和响应码")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("status "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("201")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" UserController"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br"),a("span",{staticClass:"line-number"},[t._v("22")]),a("br")])]),a("p",[t._v("然后通过"),a("router-link",{attrs:{to:"/zh/guide/router.html"}},[t._v("路由")]),t._v("配置 URL 请求映射:")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("post")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("create"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("p",[t._v("然后通过 "),a("code",[t._v("POST /api/user")]),t._v(" 即可访问。")]),t._v(" "),a("h2",{attrs:{id:"生命周期"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#生命周期","aria-hidden":"true"}},[t._v("#")]),t._v(" 生命周期")]),t._v(" "),a("p",[a("code",[t._v("Controller")]),t._v(" 类会被挂载到 "),a("code",[t._v("app.Controller")]),t._v(" 上,用于在 "),a("router-link",{attrs:{to:"/zh/guide/router.html"}},[t._v("路由")]),t._v(" 配置 URL 映射。")],1),t._v(" "),a("p",[t._v("但处理用户请求时,每一个请求都会实例化一个 "),a("code",[t._v("Controller")]),t._v(" 实例。")]),t._v(" "),a("p",[a("code",[t._v("Controller")]),t._v(" 是延迟实例化的,仅在请求调用到该 "),a("code",[t._v("Controller")]),t._v(" 的时候,才会实例化。")]),t._v(" "),a("p",[t._v("因此,无需担心实例化的性能损耗,经过我们大规模的实践证明,可以忽略不计。")]),t._v(" "),a("h2",{attrs:{id:"挂载规则"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#挂载规则","aria-hidden":"true"}},[t._v("#")]),t._v(" 挂载规则")]),t._v(" "),a("p",[t._v("约定放置在 "),a("code",[t._v("app/controller")]),t._v(" 目录下,支持多级目录,"),a("strong",[t._v("对应的文件名会转换为驼峰格式")]),t._v("。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("app"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("controller"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("biz"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("js")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("biz"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user\napp"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("controller"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("sync_user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("js")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("syncUser\napp"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("controller"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("HackerNews"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("js")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("hackerNews\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br")])]),a("h2",{attrs:{id:"常用属性和方法"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常用属性和方法","aria-hidden":"true"}},[t._v("#")]),t._v(" 常用属性和方法")]),t._v(" "),a("p",[a("code",[t._v("Controller")]),t._v(" 实例继承 "),a("code",[t._v("egg.Controller")]),t._v(",提供以下属性:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("this.ctx")]),t._v(": 当前请求的上下文 "),a("router-link",{attrs:{to:"/zh/guide/context.html"}},[t._v("Context")]),t._v(" 的实例,可以拿到各种便捷属性和方法。")],1),t._v(" "),a("li",[a("code",[t._v("this.app")]),t._v(": 当前应用 "),a("router-link",{attrs:{to:"/zh/guide/application.html"}},[t._v("Application")]),t._v(" 的实例,可以拿到全局对象和方法。")],1),t._v(" "),a("li",[a("code",[t._v("this.service")]),t._v(":应用定义的 "),a("router-link",{attrs:{to:"/zh/guide/service.html"}},[t._v("Service")]),t._v(",可以调用业务逻辑层。")],1),t._v(" "),a("li",[a("code",[t._v("this.config")]),t._v(":应用运行时的"),a("router-link",{attrs:{to:"/zh/guide/config.html"}},[t._v("配置项")]),t._v("。")],1),t._v(" "),a("li",[a("code",[t._v("this.logger")]),t._v(":logger 对象,使用方法类似 "),a("router-link",{attrs:{to:"/zh/guide/logger.html#ctx-logger"}},[t._v("Context Logger")]),t._v(",不同之处是通过这个 Logger 对象记录的日志,会额外加上该日志的文件路径,以便快速定位日志打印位置。")],1)]),t._v(" "),a("h2",{attrs:{id:"controller-实战"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#controller-实战","aria-hidden":"true"}},[t._v("#")]),t._v(" Controller 实战")]),t._v(" "),a("h3",{attrs:{id:"http-基础"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#http-基础","aria-hidden":"true"}},[t._v("#")]),t._v(" HTTP 基础")]),t._v(" "),a("p",[t._v("由于 "),a("code",[t._v("Controller")]),t._v(" 基本上是业务开发中唯一和 "),a("code",[t._v("HTTP")]),t._v(" 协议打交道的地方,在继续往下了解之前,我们首先简单的看一下 "),a("code",[t._v("HTTP")]),t._v(" 协议是怎样的。")]),t._v(" "),a("p",[t._v("如果我们发起一个 HTTP 请求来访问前面写的的 "),a("code",[t._v("Controller")]),t._v(":")]),t._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[t._v("$ "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),t._v(" -X POST http://localhost:7001/api/user -d "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v('\'{"name":"TZ"}\'')]),t._v(" -H "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Content-Type:application/json; charset=UTF-8'")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("p",[t._v("通过 "),a("code",[t._v("curl")]),t._v(" 发出的 HTTP 请求的内容就会是下面这样的:")]),t._v(" "),a("div",{staticClass:"language- line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[t._v('POST /api/user HTTP/1.1\nHost: localhost:7001\nContent-Type:application/json; charset=UTF-8\n\n{"name":"TZ"}\n')])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("p",[t._v("请求的第一行包含了三个信息,我们比较常用的是前面两个:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("method")]),t._v(":HTTP 方法,此处为 "),a("code",[t._v("POST")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("path")]),t._v(":HTTP 路径,此处为 "),a("code",[t._v("/api/user")]),t._v(",如果用户的请求中包含 "),a("code",[t._v("query")]),t._v(",也会在这里出现。")])]),t._v(" "),a("p",[t._v("从第二行开始直到空行位置,都是请求的 "),a("code",[t._v("Headers")]),t._v(" 部分:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("Host")]),t._v(":我们在浏览器发起请求的时候,域名会用来通过 DNS 解析找到服务的 IP 地址,但是浏览器也会将域名和端口号放在 Host 头中一并发送给服务端。")]),t._v(" "),a("li",[a("code",[t._v("Content-Type")]),t._v(":当我们的请求有 body 的时候,都会有 Content-Type 来标明我们的请求体是什么格式的。")])]),t._v(" "),a("p",[t._v("之后的内容全部都是请求的 "),a("code",[t._v("body")]),t._v(",当请求是 "),a("code",[t._v("POST")]),t._v(", "),a("code",[t._v("PUT")]),t._v(" 等方法的时候,可以带上请求体,服务端会根据 "),a("code",[t._v("Content-Type")]),t._v(" 来解析请求体。")]),t._v(" "),a("p",[t._v("在服务端处理完这个请求后,会发送一个 HTTP 响应给客户端:")]),t._v(" "),a("div",{staticClass:"language- line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[t._v('HTTP/1.1 201 Created\nContent-Type: application/json; charset=utf-8\nContent-Length: 13\nDate: Mon, 09 Jan 2019 08:40:28 GMT\nConnection: keep-alive\n\n{"id":1,"name":"TZ"}\n')])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[t._v("第一行中也包含了三段,其中我们常用的主要是"),a("a",{attrs:{href:"https://en.wikipedia.org/wiki/List_of_HTTP_status_codes",target:"_blank",rel:"noopener noreferrer"}},[t._v("响应状态码"),a("OutboundLink")],1),t._v(",这个例子中它的值是 "),a("code",[t._v("201")]),t._v(",它的含义是在服务端成功创建了一条资源。")]),t._v(" "),a("p",[t._v("和请求一样,从第二行开始到下一个空行之间都是响应头,这里的 "),a("code",[t._v("Content-Type")]),t._v(", "),a("code",[t._v("Content-Length")]),t._v(" 表示这个响应的格式是 JSON,长度为 13 个字节。")]),t._v(" "),a("p",[t._v("最后剩下的部分就是这次响应真正的内容。")]),t._v(" "),a("h3",{attrs:{id:"获取请求参数"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#获取请求参数","aria-hidden":"true"}},[t._v("#")]),t._v(" 获取请求参数")]),t._v(" "),a("p",[t._v("在 URL 中 "),a("code",[t._v("?")]),t._v(" 后面的部分是一个 "),a("code",[t._v("Query String")]),t._v(",这一部分经常用于 "),a("code",[t._v("GET")]),t._v(" 请求中传递参数。")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("ctx.query")]),t._v(":解析查询参数,转换为 "),a("code",[t._v("Object")]),t._v(",属性为字符串。")]),t._v(" "),a("li",[a("code",[t._v("ctx.queries")]),t._v(":同上,但支持同名的多个参数解析,属性为数组。")]),t._v(" "),a("li",[a("code",[t._v("ctx.params")]),t._v(":获取 "),a("router-link",{attrs:{to:"/zh/guide/router.html#获取命名参数"}},[t._v("Router")]),t._v(" 命名参数。")],1)]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// GET /api/user/list?limit=10&sort=name")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("query"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// { limit: '10', sort: 'name' }")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("友情提示")]),t._v(" "),a("p",[t._v("鉴于 HTTP 协议的约定,在请求中获取到的查询参数,均为字符串,如有需要需自行转型。")])]),t._v(" "),a("p",[t._v("具体使用参见 "),a("router-link",{attrs:{to:"/zh/guide/context.html"}},[t._v("Context")]),t._v(" 文档。")],1),t._v(" "),a("h3",{attrs:{id:"获取请求-body"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#获取请求-body","aria-hidden":"true"}},[t._v("#")]),t._v(" 获取请求 body")]),t._v(" "),a("p",[t._v("虽然我们可以通过 URL 传递参数,但是还是有诸多限制:")]),t._v(" "),a("ul",[a("li",[a("a",{attrs:{href:"http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers",target:"_blank",rel:"noopener noreferrer"}},[t._v("浏览器中会对 URL 的长度有所限制"),a("OutboundLink")],1),t._v(",如果需要传递的参数过多就会无法传递。")]),t._v(" "),a("li",[t._v("访问的 URL 往往会被记录到日志或浏览器中,有一些敏感数据通过 URL 传递会不安全。")]),t._v(" "),a("li",[a("code",[t._v("GET")]),t._v(" 请求可能会被缓存,导致非预期的意外。")])]),t._v(" "),a("p",[t._v("框架内置了 "),a("a",{attrs:{href:"https://github.com/koajs/bodyparser",target:"_blank",rel:"noopener noreferrer"}},[t._v("bodyParser"),a("OutboundLink")],1),t._v(",开发者可以通过 "),a("code",[t._v("ctx.request.body")]),t._v(" 获取到对应的数据。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("create")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 获取请求信息 `{ name: 'TZ' }`")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("友情提示")]),t._v(" "),a("p",[t._v("一个常见的错误是把 "),a("code",[t._v("ctx.request.body")]),t._v(" 和 "),a("code",[t._v("ctx.body")]),t._v(" 混淆,后者其实是 "),a("code",[t._v("ctx.response.body")]),t._v(" 的简写。")])]),t._v(" "),a("h3",{attrs:{id:"解析-json-form-请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#解析-json-form-请求","aria-hidden":"true"}},[t._v("#")]),t._v(" 解析 JSON / Form 请求")]),t._v(" "),a("p",[t._v("一般通过 "),a("code",[t._v("Content-Type")]),t._v(" 来声明请求 body 的格式,常见的格式有 "),a("code",[t._v("JSON")]),t._v(" 和 "),a("code",[t._v("Form")]),t._v("。")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("application/json")]),t._v(":按 "),a("code",[t._v("JSON")]),t._v(" 格式进行解析。")]),t._v(" "),a("li",[a("code",[t._v("application/x-www-form-urlencoded")]),t._v(":按 "),a("code",[t._v("Form")]),t._v(" 格式进行解析。")])]),t._v(" "),a("p",[a("strong",[t._v("框架默认限制 body 的大小为 "),a("code",[t._v("100kb")])]),t._v(",如果你需要上传更大的内容,需配置:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n bodyParser"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n jsonLimit"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'1mb'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n formLimit"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'1mb'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("ul",[a("li",[t._v("如果 body 超过了最大长度配置,会抛出一个状态码为 "),a("code",[t._v("413")]),t._v(" 的异常。")]),t._v(" "),a("li",[t._v("如果 body 解析失败(错误的 JSON),会抛出一个状态码为 "),a("code",[t._v("400")]),t._v(" 的异常。")]),t._v(" "),a("li",[t._v("支持 "),a("code",[t._v("10mb")]),t._v(" 这种人性化的方式,具体参见 "),a("a",{attrs:{href:"https://github.com/node-modules/humanize-bytes",target:"_blank",rel:"noopener noreferrer"}},[t._v("humanize-bytes"),a("OutboundLink")],1),t._v(" 模块。")])]),t._v(" "),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("友情提示")]),t._v(" "),a("p",[t._v("如果我们应用前面还有一层反向代理("),a("code",[t._v("Nginx")]),t._v("),则也需要调整它的配置,以确保反向代理也支持同样长度的请求 body。")])]),t._v(" "),a("h3",{attrs:{id:"解析-xml-请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#解析-xml-请求","aria-hidden":"true"}},[t._v("#")]),t._v(" 解析 XML 请求")]),t._v(" "),a("p",[t._v("有些时候,我们需要解析 "),a("code",[t._v("XML")]),t._v(" 协议,可配置:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("bodyParser "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n enableTypes"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'form'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'text'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n extendTypes"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n text"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'application/xml'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[t._v("然后可以自行使用 "),a("code",[t._v("XML")]),t._v(" 解析库分析 "),a("code",[t._v("ctx.request.body")]),t._v(" 的原始字符串。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" xml2js "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'xml-js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" xmlContent "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("xml2js")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br")])]),a("h3",{attrs:{id:"解析自定义类型"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#解析自定义类型","aria-hidden":"true"}},[t._v("#")]),t._v(" 解析自定义类型")]),t._v(" "),a("p",[t._v("如需自定义协议,如 "),a("code",[t._v("application/custom-rpc")]),t._v(",内容一样为 "),a("code",[t._v("JSON")]),t._v(",则可以配置:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("bodyParser "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n extendTypes"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n json"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'application/custom-rpc'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("h3",{attrs:{id:"文件上传"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#文件上传","aria-hidden":"true"}},[t._v("#")]),t._v(" 文件上传")]),t._v(" "),a("p",[t._v("请求 body 还可以通过 "),a("code",[t._v("multipart/form-data")]),t._v(" 格式来实现文件上传。")]),t._v(" "),a("p",[t._v("框架内置了 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-multipart",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-multipart"),a("OutboundLink")],1),t._v(" 来支持该特性。")]),t._v(" "),a("p",[t._v("支持 "),a("code",[t._v("file")]),t._v(" 和 "),a("code",[t._v("stream")]),t._v(" 模式,本文仅介绍前者,更多用法请阅读"),a("router-link",{attrs:{to:"/zh/guide/upload.html"}},[t._v("文件上传")]),t._v("文档。")],1),t._v(" "),a("p",[t._v("先启用 "),a("code",[t._v("file")]),t._v(" 模式:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("multipart "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n mode"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'file'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("p",[t._v("然后接收文件:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/upload.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UploadController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("upload")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" file "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("files"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" name "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-multipart-test/'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" path"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("basename")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("filename"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 然后可以对文件进行处理,如上传 OSS 之类的")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// ...")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br")])]),a("h3",{attrs:{id:"获取-header"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#获取-header","aria-hidden":"true"}},[t._v("#")]),t._v(" 获取 Header")]),t._v(" "),a("p",[t._v("框架提供了 "),a("code",[t._v("ctx.get(name)")]),t._v(" 方法来获取请求头,具体参见 "),a("router-link",{attrs:{to:"/zh/guide/context.html#ctx-get-name"}},[t._v("Context")]),t._v(" 文档。")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HomeController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("index")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'user-agent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("h3",{attrs:{id:"代理服务器"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#代理服务器","aria-hidden":"true"}},[t._v("#")]),t._v(" 代理服务器")]),t._v(" "),a("p",[t._v("大部分情况下,我们的 Web 服务都是在代理服务器(如"),a("code",[t._v("Nginx")]),t._v(") 后面,此时需要配置 "),a("code",[t._v("config.proxy = true")]),t._v(",框架对应的 "),a("code",[t._v("Getter")]),t._v(" 会对应的增加处理逻辑。")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("ctx.ips")]),t._v(":获取请求经过所有的中间设备 IP 地址列表。")]),t._v(" "),a("li",[a("code",[t._v("ctx.ip")]),t._v(":获取请求发起方的 IP 地址,对应的代理 "),a("code",[t._v("Header")]),t._v(" 为 "),a("code",[t._v("X-Forwarded-For")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("ctx.host")]),t._v(":获取 HOST,对应的代理 "),a("code",[t._v("Header")]),t._v(" 为 "),a("code",[t._v("X-Forwarded-Host")]),t._v("。")])]),t._v(" "),a("p",[t._v("另外,代理服务器处理 HTTPS 请求时,我们的 Web 服务收到的是内部的 HTTP 请求。")]),t._v(" "),a("p",[t._v("开发者可以通过 "),a("code",[t._v("ctx.protocol")]),t._v(" 来获取客户端访问的协议,框架会解析 "),a("code",[t._v("X-Forwarded-Prot")]),t._v("。")]),t._v(" "),a("p",[t._v("详细参见"),a("a",{attrs:{href:"https://github.com/eggjs/egg/blob/master/app/extend/request.js",target:"_blank",rel:"noopener noreferrer"}},[t._v("源码实现"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("h3",{attrs:{id:"读写-cookie"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#读写-cookie","aria-hidden":"true"}},[t._v("#")]),t._v(" 读写 Cookie")]),t._v(" "),a("p",[t._v("通过 "),a("code",[t._v("ctx.cookies")]),t._v(",我们可以在 "),a("code",[t._v("Controller")]),t._v(" 中便捷、安全的设置和读取 "),a("code",[t._v("Cookie")]),t._v("。")]),t._v(" "),a("p",[t._v("具体可参见 "),a("router-link",{attrs:{to:"/zh/guide/cookie.html"}},[t._v("Cookie")]),t._v(" 文档。")],1),t._v(" "),a("h3",{attrs:{id:"参数校验"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#参数校验","aria-hidden":"true"}},[t._v("#")]),t._v(" 参数校验")]),t._v(" "),a("p",[t._v("在获取到用户请求的参数后,不可避免的要对参数进行一些校验。")]),t._v(" "),a("p",[t._v("在上面的示例中,我们简单的使用 "),a("code",[t._v("ctx.assert")]),t._v(" 进行了校验。")]),t._v(" "),a("p",[t._v("实际业务中,会需要更复杂的校验,可以查看 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-validate",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-validate"),a("OutboundLink")],1),t._v(" 等插件的文档。")]),t._v(" "),a("h3",{attrs:{id:"调用-service"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#调用-service","aria-hidden":"true"}},[t._v("#")]),t._v(" 调用 Service")]),t._v(" "),a("p",[t._v("不建议 "),a("code",[t._v("Controller")]),t._v(" 中实现太多业务逻辑,一般通过 "),a("router-link",{attrs:{to:"/zh/guide/service.html"}},[t._v("Service")]),t._v(" 层进行业务逻辑的封装。")],1),t._v(" "),a("p",[t._v("这不仅能提高代码的复用性,同时可以让我们的业务逻辑更好测试。")]),t._v(" "),a("h3",{attrs:{id:"发送-http-响应"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#发送-http-响应","aria-hidden":"true"}},[t._v("#")]),t._v(" 发送 HTTP 响应")]),t._v(" "),a("p",[t._v("当业务逻辑完成之后,"),a("code",[t._v("Controller")]),t._v(" 的最后一个职责就是将处理结果通过 "),a("code",[t._v("HTTP")]),t._v(" 响应给用户。")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("ctx.body=")]),t._v(":设置响应 body。")]),t._v(" "),a("li",[a("code",[t._v("ctx.type=")]),t._v(":设置响应的 "),a("code",[t._v("Content-Type")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("ctx.status=")]),t._v(":设置响应的状态码。")]),t._v(" "),a("li",[a("code",[t._v("ctx.set(name, header)")]),t._v(":设置响应 "),a("code",[t._v("Header")]),t._v("。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/home.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HomeController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("index")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'powered-by'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n category"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'framework'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n language"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Node.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br")])]),a("p",[t._v("具体可以参见 "),a("router-link",{attrs:{to:"/zh/guide/context.html"}},[t._v("Context")]),t._v(" 文档。")],1),t._v(" "),a("h3",{attrs:{id:"模板渲染"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#模板渲染","aria-hidden":"true"}},[t._v("#")]),t._v(" 模板渲染")]),t._v(" "),a("p",[t._v("通常来说,我们不会手写 HTML 页面,而是会通过模板引擎进行生成。")]),t._v(" "),a("p",[t._v("我们可以通过使用模板插件,来提供渲染能力。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HomeController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("index")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("render")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'home.tpl'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// ctx.body = await ctx.renderString('hi, {{ name }}', { name: 'egg' });")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[t._v("具体示例可以查看"),a("router-link",{attrs:{to:"/zh/ecosystem/frontend/template.html"}},[t._v("模板引擎")]),t._v("。")],1),t._v(" "),a("h3",{attrs:{id:"jsonp"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#jsonp","aria-hidden":"true"}},[t._v("#")]),t._v(" JSONP")]),t._v(" "),a("p",[t._v("有时我们需要给非本域的页面提供接口服务,又由于一些历史原因无法通过 "),a("a",{attrs:{href:"https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS",target:"_blank",rel:"noopener noreferrer"}},[t._v("CORS"),a("OutboundLink")],1),t._v(" 实现,可以通过 "),a("a",{attrs:{href:"https://en.wikipedia.org/wiki/JSONP",target:"_blank",rel:"noopener noreferrer"}},[t._v("JSONP"),a("OutboundLink")],1),t._v(" 来进行响应。")]),t._v(" "),a("p",[t._v("框架内置了 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-jsonp",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-jsonp"),a("OutboundLink")],1),t._v(" 插件,提供了 "),a("code",[t._v("app.jsonp()")]),t._v(" 来支持响应 "),a("code",[t._v("JSONP")]),t._v(" 格式的数据。")]),t._v(" "),a("h4",{attrs:{id:"使用"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用")]),t._v(" "),a("p",[t._v("先通过"),a("router-link",{attrs:{to:"/zh/guide/router.html#路由中间件"}},[t._v("路由中间件")]),t._v("的方式来局部开启:")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" jsonp "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("jsonp")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/posts/:id'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" jsonp"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("posts"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("show"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/posts'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" jsonp"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("posts"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("list"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("p",[t._v("然后在 "),a("code",[t._v("Controller")]),t._v(" 中,只需要正常编写即可:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/posts.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("PostController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("show")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n category"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'framework'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n language"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'Node.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br")])]),a("p",[t._v("用户请求对应的 URL 时带上 "),a("code",[t._v("_callback=fn")]),t._v(" 查询参数,将会返回 "),a("code",[t._v("JSONP")]),t._v(" 格式的数据。")]),t._v(" "),a("h4",{attrs:{id:"配置"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#配置","aria-hidden":"true"}},[t._v("#")]),t._v(" 配置")]),t._v(" "),a("p",[t._v("框架默认支持方法名为 "),a("code",[t._v("callback")]),t._v("、 "),a("code",[t._v("_callback")]),t._v(",并限制长度小于 50 字符。")]),t._v(" "),a("p",[t._v("如有需要,可以自定义配置:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("jsonp "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n callback"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'cb'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 识别 query 中的 `cb` 参数")]),t._v("\n limit"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("100")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 函数名最长为 100 个字符")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("p",[t._v("通过上面的方式配置之后,如果用户通过 "),a("code",[t._v("/api/posts/1?cb=fn")]),t._v(" 请求 "),a("code",[t._v("JSONP")]),t._v("。")]),t._v(" "),a("p",[t._v("也可以在 "),a("code",[t._v("app.jsonp()")]),t._v(" 创建中间件时覆盖默认的配置,以达到不同路由使用不同配置的目的:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" jsonp "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/posts'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("jsonp")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" callback"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'cb'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("posts"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("list"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("h4",{attrs:{id:"安全"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#安全","aria-hidden":"true"}},[t._v("#")]),t._v(" 安全")]),t._v(" "),a("p",[a("code",[t._v("JSONP")]),t._v(" 如果使用不当会导致非常多的安全问题,可以将 "),a("code",[t._v("JSONP")]),t._v(" 接口分为三种类型:")]),t._v(" "),a("ol",[a("li",[t._v("查询非敏感数据,例如获取一个论坛的公开文章列表。")]),t._v(" "),a("li",[t._v("查询敏感数据,例如获取一个用户的交易记录。")]),t._v(" "),a("li",[t._v("提交数据并修改数据库,例如给某一个用户创建一笔订单。")])]),t._v(" "),a("p",[t._v("如果我们的 "),a("code",[t._v("JSONP")]),t._v(" 接口提供下面两类服务,在不做任何跨站防御的情况下,可能泄露用户敏感数据甚至导致用户被钓鱼。")]),t._v(" "),a("p",[t._v("因此框架给 "),a("code",[t._v("JSONP")]),t._v(" 默认提供了 "),a("code",[t._v("CSRF 校验")]),t._v("和 "),a("code",[t._v("referrer 校验")]),t._v(",具体参见 "),a("router-link",{attrs:{to:"/zh/ecosystem/security/jsonp.html"}},[t._v("JSONP XSS 相关的安全防范")]),t._v(" 文档。")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n jsonp"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n csrf"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n whiteList"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token regex"}},[t._v("/^https?:\\/\\/test.com\\//")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// whiteList: '.test.com',")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// whiteList: 'sub.test.com',")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// whiteList: [ 'sub.test.com', 'sub2.test.com' ],")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br")])]),a("div",{staticClass:"tip custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("提示")]),t._v(" "),a("p",[t._v("当 CSRF 和 referrer 校验同时开启时,请求发起方只需要满足任意一个条件即可通过 JSONP 的安全校验。")])]),t._v(" "),a("h3",{attrs:{id:"重定向"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#重定向","aria-hidden":"true"}},[t._v("#")]),t._v(" 重定向")]),t._v(" "),a("h4",{attrs:{id:"使用-2"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用-2","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用")]),t._v(" "),a("p",[t._v("可以通过 "),a("code",[t._v("ctx.redirect(url)")]),t._v(" 来重定向请求。")]),t._v(" "),a("p",[t._v("默认为 "),a("code",[t._v("302")]),t._v(",如果需要,可以设置 "),a("code",[t._v("ctx.status = 301")]),t._v("。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("logout")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("logout")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("redirect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'referer'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("||")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br")])]),a("h4",{attrs:{id:"安全域名"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#安全域名","aria-hidden":"true"}},[t._v("#")]),t._v(" 安全域名")]),t._v(" "),a("p",[t._v("框架通过 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-security",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-security"),a("OutboundLink")],1),t._v(" 插件覆盖了 Koa 原生的 "),a("code",[t._v("ctx.redirect")]),t._v(" 实现,以提供更加安全的重定向。")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("ctx.redirect(url)")]),t._v(" 如果不在配置的白名单域名内,则禁止跳转。")]),t._v(" "),a("li",[a("code",[t._v("ctx.unsafeRedirect(url)")]),t._v(" 不判断域名,直接跳转,一般不建议使用,明确了解可能带来的风险后使用。")])]),t._v(" "),a("p",[t._v("若 "),a("code",[t._v("security.domainWhiteList")]),t._v("数组内为空,则默认会对所有跳转请求放行,即等同于"),a("code",[t._v("ctx.unsafeRedirect(url)")]),t._v("。")]),t._v(" "),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("安全提示")]),t._v(" "),a("p",[t._v("基于安全管控的原因,我们不推荐在应用层直接覆盖该属性,而是应该提交 "),a("code",[t._v("Merge Request")]),t._v(",除非该域名非阿里所属。")])]),t._v(" "),a("p",[t._v("更多参见 "),a("router-link",{attrs:{to:"/zh/ecosystem/security/security_url.html"}},[t._v("安全插件")]),t._v(" 文档。")],1),t._v(" "),a("h2",{attrs:{id:"编写测试"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写测试","aria-hidden":"true"}},[t._v("#")]),t._v(" 编写测试")]),t._v(" "),a("p",[t._v("框架集成了 "),a("a",{attrs:{href:"https://github.com/visionmedia/supertest",target:"_blank",rel:"noopener noreferrer"}},[t._v("SuperTest"),a("OutboundLink")],1),t._v(" 用于 HTTP 测试。")]),t._v(" "),a("p",[t._v("具体的单元测试运行方式,参见 "),a("router-link",{attrs:{to:"/zh/workflow/development/unittest.html"}},[t._v("研发流程 - 单元测试")]),t._v(" 文档。")],1),t._v(" "),a("h3",{attrs:{id:"测试-get-请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#测试-get-请求","aria-hidden":"true"}},[t._v("#")]),t._v(" 测试 GET 请求")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// test/controller/home.test.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" mock"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" assert "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-mock'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("describe")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'test/controller/home.test.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should GET /'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'User-Agent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'unittest'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("query")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" limit"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'10'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'hi, egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'X-Response-Time'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token regex"}},[t._v("/\\d+ms/")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("200")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br")])]),a("h3",{attrs:{id:"测试-post-请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#测试-post-请求","aria-hidden":"true"}},[t._v("#")]),t._v(" 测试 POST 请求")]),t._v(" "),a("p",[t._v("可以通过 "),a("code",[t._v("app.mockCsrf()")]),t._v(" 来跳过 "),a("code",[t._v("CSRF")]),t._v(" 校验。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// test/controller/home.test.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should POST form'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("mockCsrf")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("post")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/body'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'form'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("send")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'TZ'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("200")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should POST JSON'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("mockCsrf")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("post")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/body'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("send")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'TZ'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("200")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br")])]),a("h3",{attrs:{id:"测试文件上传"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#测试文件上传","aria-hidden":"true"}},[t._v("#")]),t._v(" 测试文件上传")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// test/controller/home.test.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should upload file'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("mockCsrf")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("post")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/upload'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("field")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'name'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'just a test'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("attach")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'file'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" path"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("join")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("__dirname"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg.png'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("200")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("h2",{attrs:{id:"常见问题"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常见问题","aria-hidden":"true"}},[t._v("#")]),t._v(" 常见问题")]),t._v(" "),a("h3",{attrs:{id:"missing-csrf-token"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#missing-csrf-token","aria-hidden":"true"}},[t._v("#")]),t._v(" missing csrf token")]),t._v(" "),a("p",[t._v("框架默认开启了 "),a("router-link",{attrs:{to:"/zh/ecosystem/security/csrf.html"}},[t._v("CSRF")]),t._v(" 安全限制。")],1),t._v(" "),a("p",[t._v("因此新手开发者在 "),a("code",[t._v("Postman 测试")]),t._v("、"),a("code",[t._v("前端发起 AJAX")]),t._v("、"),a("code",[t._v("单元测试")]),t._v(" 时经常遇到的一个报错:")]),t._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[t._v("nodejs.ForbiddenError: missing csrf token\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("p",[t._v("如何处理可以阅读上述文档。")]),t._v(" "),a("h3",{attrs:{id:"redirection-is-prohibited"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#redirection-is-prohibited","aria-hidden":"true"}},[t._v("#")]),t._v(" redirection is prohibited")]),t._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[t._v("nodejs.InternalServerError: a security problem has been detected "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("for")]),t._v(" url "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v('"http://www.baidu.com/"')]),t._v(", redirection is prohibited.\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("p",[t._v("如上所述,不允许重定向到非白名单的域名,具体处理参见"),a("a",{attrs:{href:"#%E5%AE%89%E5%85%A8%E5%9F%9F%E5%90%8D"}},[t._v("安全域名")]),t._v("。")])])},[],!1,null,null,null);s.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/17.97cab730.js b/assets/js/17.97cab730.js new file mode 100644 index 0000000..b66b94a --- /dev/null +++ b/assets/js/17.97cab730.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[17],{53:function(t,s,a){"use strict";a.r(s);var n=a(0),e=Object(n.a)({},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用场景")]),t._v(" "),a("p",[t._v("HTTP 请求都是无状态的,但是我们的 Web 应用通常都需要知道发起请求的人是谁。")]),t._v(" "),a("p",[t._v("为了解决这个问题,HTTP 协议设计了一个特殊的请求头:"),a("a",{attrs:{href:"https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies",target:"_blank",rel:"noopener noreferrer"}},[t._v("Cookie"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("p",[t._v("服务端可以通过响应头将少量数据响应给客户端,浏览器会遵循协议将数据保存,并在下次请求同一个服务的时候带上对应的数据。")]),t._v(" "),a("p",[a("code",[t._v("Cookie")]),t._v(" 主要用于:")]),t._v(" "),a("ul",[a("li",[t._v("会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)")]),t._v(" "),a("li",[t._v("个性化设置(如用户自定义设置、主题等)")]),t._v(" "),a("li",[t._v("浏览器行为跟踪(如跟踪分析用户行为等)")])]),t._v(" "),a("p",[t._v("服务器使用 "),a("code",[t._v("Set-Cookie")]),t._v(" 响应头部向用户浏览器发送 "),a("code",[t._v("Cookie")]),t._v(" 信息:")]),t._v(" "),a("div",{staticClass:"language- line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[t._v("HTTP/1.0 200 OK\nContent-type: text/html\nSet-Cookie: uid=123456\nSet-Cookie: user=tz\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("p",[t._v("后续对该服务发起的每一次新请求,浏览器都会将之前保存的信息通过 "),a("code",[t._v("Cookie")]),t._v(" 请求头回传:")]),t._v(" "),a("div",{staticClass:"language- line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[t._v("GET /user HTTP/1.1\nHost: www.example.org\nCookie: uid=123456; user=tz\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br")])]),a("h2",{attrs:{id:"使用-cookie"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用-cookie","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用 Cookie")]),t._v(" "),a("p",[t._v("框架内置了 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-cookies",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-cookies"),a("OutboundLink")],1),t._v(" 插件,提供了 "),a("code",[t._v("ctx.cookies")]),t._v(",用于便捷、安全的读写 "),a("code",[t._v("Cookie")]),t._v("。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/home.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HomeController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("add")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" count "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("cookies"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'count'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n count "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" count "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("?")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("Number")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("count"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("cookies"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'count'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("++")]),t._v("count"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" count"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("remove")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("cookies"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'count'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("null")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("status "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("204")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br")])]),a("div",{staticClass:"tip custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("友情提示")]),t._v(" "),a("p",[t._v("在使用 "),a("code",[t._v("Cookie")]),t._v(" 时我们需要思考清楚它的场景:")]),t._v(" "),a("ul",[a("li",[t._v("需要被浏览器保存多久?")]),t._v(" "),a("li",[t._v("是否可以被 js 获取到?")]),t._v(" "),a("li",[t._v("是否可以被前端修改?")])])]),t._v(" "),a("p",[a("strong",[t._v("框架默认配置下, "),a("code",[t._v("Cookie")]),t._v(" 是加签不加密的,浏览器可以看到明文,js 不能访问,不能被客户端(手工)篡改。")])]),t._v(" "),a("h2",{attrs:{id:"术语解释"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#术语解释","aria-hidden":"true"}},[t._v("#")]),t._v(" 术语解释")]),t._v(" "),a("h3",{attrs:{id:"过期时间"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#过期时间","aria-hidden":"true"}},[t._v("#")]),t._v(" 过期时间")]),t._v(" "),a("p",[a("code",[t._v("Expires")]),t._v(" 和 "),a("code",[t._v("Max-Age")]),t._v(" 用于定义 "),a("code",[t._v("Cookie")]),t._v(" 对应的键值对的持久化时间。")]),t._v(" "),a("p",[a("code",[t._v("Expires")]),t._v(" 优先级低于 "),a("code",[t._v("Max-Age")]),t._v(",如果两者都没设置,则将会在关闭浏览器时失效。")]),t._v(" "),a("h3",{attrs:{id:"作用域"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#作用域","aria-hidden":"true"}},[t._v("#")]),t._v(" 作用域")]),t._v(" "),a("p",[a("code",[t._v("Domain")]),t._v(" 和 "),a("code",[t._v("Path")]),t._v(" 标识定义了 "),a("code",[t._v("Cookie")]),t._v(" 的作用域:即 "),a("code",[t._v("Cookie")]),t._v(" 应该发送给哪些 URL。")]),t._v(" "),a("h3",{attrs:{id:"安全"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#安全","aria-hidden":"true"}},[t._v("#")]),t._v(" 安全")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("Secure")]),t._v(":"),a("code",[t._v("Cookie")]),t._v(" 只有在 "),a("code",[t._v("HTTPS")]),t._v(" 协议下才会发送给服务端。")]),t._v(" "),a("li",[a("code",[t._v("HttpOnly")]),t._v(":"),a("code",[t._v("Cookie")]),t._v(" 将无法被 JavaScript 访问,从而避免 "),a("router-link",{attrs:{to:"/zh/ecosystem/security/xss.html"}},[t._v("XSS")]),t._v(" 攻击。")],1)]),t._v(" "),a("h3",{attrs:{id:"加签-加密"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#加签-加密","aria-hidden":"true"}},[t._v("#")]),t._v(" 加签 && 加密")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("加签")]),t._v(":对 "),a("code",[t._v("Cookie")]),t._v(" 进行签名,避免前端篡改。不会修改原键值,而是新增一个 "),a("code",[t._v("${key}.sig")]),t._v(" 的键值。")]),t._v(" "),a("li",[a("code",[t._v("加密")]),t._v(":对 "),a("code",[t._v("Cookie")]),t._v(" 进行加密,避免 "),a("code",[t._v("Cookie")]),t._v(" 明文写入,泄露给恶意用户。")])]),t._v(" "),a("h2",{attrs:{id:"api-说明"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#api-说明","aria-hidden":"true"}},[t._v("#")]),t._v(" API 说明")]),t._v(" "),a("h3",{attrs:{id:"set-key-value-options"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#set-key-value-options","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("set(key, value, options)")])]),t._v(" "),a("p",[t._v("框架提供了 "),a("code",[t._v("ctx.set(key, value, options)")]),t._v(" 来向用户发送 "),a("code",[t._v("Cookie")]),t._v(" 信息。")]),t._v(" "),a("p",[t._v("其中,"),a("code",[t._v("key")]),t._v(" 和 "),a("code",[t._v("value")]),t._v(" 称之为一个 "),a("code",[t._v("键值对")]),t._v("。配置参数 "),a("code",[t._v("options")]),t._v(" 见"),a("a",{attrs:{href:"#options"}},[t._v("下文")]),t._v("。")]),t._v(" "),a("h3",{attrs:{id:"get-key-options"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#get-key-options","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("get(key, options)")])]),t._v(" "),a("p",[a("code",[t._v("Cookie")]),t._v(" 是通过同一个 "),a("code",[t._v("Header")]),t._v(" 中传输过来的,因此需要通过该方法解析并获取对应的值。")]),t._v(" "),a("p",[t._v("值得注意的是,获取时的 "),a("code",[t._v("options.signed")]),t._v(" 和 "),a("code",[t._v("options.encrypt")]),t._v(" 要和 "),a("code",[t._v("set()")]),t._v(" 的时候保持一致。")]),t._v(" "),a("h3",{attrs:{id:"options"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#options","aria-hidden":"true"}},[t._v("#")]),t._v(" options")]),t._v(" "),a("p",[t._v("与 "),a("a",{attrs:{href:"#%E6%9C%AF%E8%AF%AD%E8%A7%A3%E9%87%8A"}},[t._v("术语")]),t._v(" 一一对应,支持以下参数配置:")]),t._v(" "),a("ul",[a("li",[a("strong",[t._v("maxAge")]),t._v(": "),a("code",[t._v("{Number}")]),t._v(" 在浏览器的最长保存时间。")]),t._v(" "),a("li",[a("strong",[t._v("expires")]),t._v(": "),a("code",[t._v("{Date}")]),t._v(" 失效时间。优先级低于 "),a("code",[t._v("maxAge")]),t._v("。如果两者都没设置,则将会在关闭浏览器时失效。")]),t._v(" "),a("li",[a("strong",[t._v("path")]),t._v(": "),a("code",[t._v("{String}")]),t._v(" 生效的 URL 路径,默认为 "),a("code",[t._v("/")]),t._v(",即当前域名下均可访问这个 Cookie。")]),t._v(" "),a("li",[a("strong",[t._v("domain")]),t._v(": "),a("code",[t._v("{String}")]),t._v(" 对生效的域名,默认没有配置,可以配置成只在指定域名才能访问。")]),t._v(" "),a("li",[a("strong",[t._v("httpOnly")]),t._v(": "),a("code",[t._v("{Boolean}")]),t._v(" 是否可以被 js 访问,"),a("strong",[t._v("默认为 true,不允许被 js 访问")]),t._v("。")]),t._v(" "),a("li",[a("strong",[t._v("secure")]),t._v(": "),a("code",[t._v("{Boolean}")]),t._v(" 框架会自动判断当前请求是否为 HTTPS,从而自动赋值。")]),t._v(" "),a("li",[a("strong",[t._v("signed")]),t._v(": "),a("code",[t._v("{Boolean}")]),t._v(":是否加签,默认为 true。")]),t._v(" "),a("li",[a("strong",[t._v("encrypt")]),t._v(": "),a("code",[t._v("{Boolean}")]),t._v(" 是否加密,默认为 false。")])]),t._v(" "),a("p",[t._v("此外,还扩展了:")]),t._v(" "),a("ul",[a("li",[a("strong",[t._v("overwrite")]),t._v(" "),a("code",[t._v("{Boolean}")]),t._v(":相同的 "),a("code",[t._v("Key")]),t._v(" 的处理逻辑,为 true,则后设置的值会覆盖前面设置的,否则将会发送两个 "),a("code",[t._v("Set-Cookie")]),t._v(" 响应头。")])]),t._v(" "),a("h3",{attrs:{id:"配置秘钥"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#配置秘钥","aria-hidden":"true"}},[t._v("#")]),t._v(" 配置秘钥")]),t._v(" "),a("p",[t._v("由于我们在 "),a("code",[t._v("Cookie")]),t._v(" 中需要用到"),a("code",[t._v("加解密")]),t._v("和"),a("code",[t._v("验签")]),t._v(",所以需要配置一个秘钥供加密使用。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n keys"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'key1,key2'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("p",[t._v("如果你没配置该属性,则在访问时会报错:")]),t._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[t._v("ERROR 17996 "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("-/::1/-/7ms GET /"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" nodejs.Error: Please "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("set")]),t._v(" config.keysfirst\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("p",[a("code",[t._v("keys")]),t._v(" 配置成一个字符串,可以按照逗号分隔配置多个 key。")]),t._v(" "),a("p",[a("code",[t._v("Cookie")]),t._v(" 在使用这个配置进行加解密时:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("加密")]),t._v("和"),a("code",[t._v("加签")]),t._v("时只会使用第一个秘钥。")]),t._v(" "),a("li",[a("code",[t._v("解密")]),t._v("和"),a("code",[t._v("验签")]),t._v("时会遍历 "),a("code",[t._v("keys")]),t._v(" 进行解密。")])]),t._v(" "),a("p",[t._v("如果我们想要更新 "),a("code",[t._v("Cookie")]),t._v(" 的秘钥,但是又不希望之前设置到用户浏览器上的 "),a("code",[t._v("Cookie")]),t._v(" 失效,可以将新的秘钥配置到 "),a("code",[t._v("keys")]),t._v(" 最前面,等过一段时间之后再删去不需要的秘钥即可。")]),t._v(" "),a("h2",{attrs:{id:"cookie-实战"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#cookie-实战","aria-hidden":"true"}},[t._v("#")]),t._v(" Cookie 实战")]),t._v(" "),a("h3",{attrs:{id:"读取前端写入的-cookie"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#读取前端写入的-cookie","aria-hidden":"true"}},[t._v("#")]),t._v(" 读取前端写入的 Cookie")]),t._v(" "),a("p",[t._v("如果要获取前端或者其他系统设置的 "),a("code",[t._v("Cookie")]),t._v(",需要指定参数 "),a("code",[t._v("signed")]),t._v(" 为 "),a("code",[t._v("false")]),t._v(",避免对它做验签导致获取不到 "),a("code",[t._v("Cookie")]),t._v(" 的值。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("cookies"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'frontend-cookie'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n signed"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("false")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br")])]),a("h3",{attrs:{id:"允许前端读取-cookie"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#允许前端读取-cookie","aria-hidden":"true"}},[t._v("#")]),t._v(" 允许前端读取 Cookie")]),t._v(" "),a("p",[t._v("如果想要 "),a("code",[t._v("Cookie")]),t._v(" 在浏览器端可以被 js 访问并修改:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("cookies"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("key"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" value"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n httpOnly"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("false")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n signed"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("false")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("h3",{attrs:{id:"不允许浏览器看到明文内容"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#不允许浏览器看到明文内容","aria-hidden":"true"}},[t._v("#")]),t._v(" 不允许浏览器看到明文内容")]),t._v(" "),a("p",[t._v("如果想要 "),a("code",[t._v("Cookie")]),t._v(" 在浏览器端不能被修改,不能看到明文:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("cookies"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("key"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" value"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n httpOnly"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 默认就是 true")]),t._v("\n encrypt"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 加密传输")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("h3",{attrs:{id:"删除-cookie"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#删除-cookie","aria-hidden":"true"}},[t._v("#")]),t._v(" 删除 Cookie")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("cookies"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("key"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("null")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("h2",{attrs:{id:"编写测试"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写测试","aria-hidden":"true"}},[t._v("#")]),t._v(" 编写测试")]),t._v(" "),a("p",[t._v("类似 "),a("router-link",{attrs:{to:"/zh/guide/controller.html"}},[t._v("Controller")]),t._v(" 的测试。")],1),t._v(" "),a("p",[t._v("需注意的是:模拟 "),a("code",[t._v("Cookies")]),t._v(" 可能需要加上对应的 "),a("code",[t._v("sig")]),t._v(" 加签信息。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// test/controller/cookies.test.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" mock"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" assert "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-mock'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("describe")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'test/controller/cookies.test.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should GET /'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/cookies'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'cookie'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'name=tz; path=/; httponly,name.sig=KdTywxAfCA4vHc1fmNipTZ9zPhBatn1br5tXWomvO14; path=/; httponly'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'set-cookie'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token regex"}},[t._v("/uid=123;/")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("200")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br")])]),a("p",[t._v("具体的单元测试运行方式,参见 "),a("router-link",{attrs:{to:"/zh/workflow/development/unittest.html"}},[t._v("研发流程 - 单元测试")]),t._v(" 文档。")],1),t._v(" "),a("h2",{attrs:{id:"注意事项"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#注意事项","aria-hidden":"true"}},[t._v("#")]),t._v(" 注意事项")]),t._v(" "),a("ol",[a("li",[t._v("由于"),a("a",{attrs:{href:"http://stackoverflow.com/questions/7567154/can-i-use-unicode-characters-in-http-headers",target:"_blank",rel:"noopener noreferrer"}},[t._v("浏览器和其他客户端实现的不确定性"),a("OutboundLink")],1),t._v(",为了保证 Cookie 可以写入成功,建议 value 通过 base64 编码或者其他形式 encode 之后再写入。")]),t._v(" "),a("li",[t._v("由于"),a("a",{attrs:{href:"http://stackoverflow.com/questions/640938/what-is-the-maximum-size-of-a-web-browsers-cookies-key",target:"_blank",rel:"noopener noreferrer"}},[t._v("浏览器对 Cookie 有长度限制限制"),a("OutboundLink")],1),t._v(",所以尽量不要设置太长的 Cookie。一般来说不要超过 4093 bytes。当设置的 Cookie value 大于这个值时,框架会打印一条警告日志。")]),t._v(" "),a("li",[a("strong",[t._v("尽可能少写入数据到 Cookie")]),t._v("。")])])])},[],!1,null,null,null);s.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/18.bbddf561.js b/assets/js/18.bbddf561.js new file mode 100644 index 0000000..3b791cf --- /dev/null +++ b/assets/js/18.bbddf561.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[18],{71:function(s,e,a){"use strict";a.r(e);var n=a(0),t=Object(n.a)({},function(){var s=this,e=s.$createElement,a=s._self._c||e;return a("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[a("p",[s._v("对于一个团队框架来说,『约定优于配置』,按照一套统一的约定进行应用开发,可以极大地减少开发人员的沟通成本。")]),s._v(" "),a("p",[s._v("框架通过 Loader 机制来自动挂载文件,应用开发者只需要添加文件到对应的目录即可。")]),s._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[s._v("showcase\n├── app\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" ├── router.js\n│ ├── controller\n│ "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── home.js\n│ ├── "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("service")]),s._v("\n│ "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── user.js\n│ ├── middleware\n│ "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── response_time.js\n│ └── view\n│ └── home.tpl\n├── config\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" ├── plugin.js\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" ├── config.default.js\n│ ├── config.prod.js\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" ├── config.local.js\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── config.unittest.js\n├── "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("test")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" ├── controller\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── home.test.js\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("service")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── user.test.js\n└── package.json\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br"),a("span",{staticClass:"line-number"},[s._v("15")]),a("br"),a("span",{staticClass:"line-number"},[s._v("16")]),a("br"),a("span",{staticClass:"line-number"},[s._v("17")]),a("br"),a("span",{staticClass:"line-number"},[s._v("18")]),a("br"),a("span",{staticClass:"line-number"},[s._v("19")]),a("br"),a("span",{staticClass:"line-number"},[s._v("20")]),a("br"),a("span",{staticClass:"line-number"},[s._v("21")]),a("br"),a("span",{staticClass:"line-number"},[s._v("22")]),a("br"),a("span",{staticClass:"line-number"},[s._v("23")]),a("br")])]),a("p",[s._v("如上,为一个常见的应用目录结构:")]),s._v(" "),a("ul",[a("li",[a("code",[s._v("app")]),s._v(": 为主要的逻辑代码目录。\n"),a("ul",[a("li",[s._v("常规 MVC 如: "),a("code",[s._v("app/controller")]),s._v(" 、 "),a("code",[s._v("app/service")]),s._v(" 、 "),a("code",[s._v("app/router.js")]),s._v(" 等。")]),s._v(" "),a("li",[s._v("某些插件也会自定义加载规范,如 "),a("code",[s._v("app/rpc")]),s._v(" 等目录的自动挂载。")])])]),s._v(" "),a("li",[a("code",[s._v("config")]),s._v(": 为配置目录,包含不同环境的配置文件,以及插件挂载声明。")]),s._v(" "),a("li",[a("code",[s._v("test")]),s._v(": 为单元测试目录。")]),s._v(" "),a("li",[a("code",[s._v("run")]),s._v(":每次启动期都会 dump 的相关信息,用于问题排查,建议加入 "),a("code",[s._v("gitignore")]),s._v("。")])]),s._v(" "),a("p",[s._v("文件挂载如下:")]),s._v(" "),a("ul",[a("li",[a("code",[s._v("app/controller/home.js")]),s._v(" 会被自动挂载到 "),a("code",[s._v("app.controller.home")]),s._v("。")]),s._v(" "),a("li",[a("code",[s._v("app/service/user.js")]),s._v(" 会被自动挂载到 "),a("code",[s._v("ctx.service.user")]),s._v("。")])]),s._v(" "),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("注意事项")]),s._v(" "),a("p",[s._v("需要注意的是,"),a("strong",[s._v("加载文件时会进行驼峰转换")]),s._v(",因此文件名和挂载的属性名可能会存在差异:")]),s._v(" "),a("ul",[a("li",[s._v("默认情况下,连字符和下划线均会被转换为驼峰格式。")]),s._v(" "),a("li",[s._v("如 "),a("code",[s._v("app/middleware/response_time.js")]),s._v(" 挂载为 "),a("code",[s._v("app.middleware.responseTime")]),s._v("。")]),s._v(" "),a("li",[s._v("部分插件,如 mongoose 插件有特殊约定,会挂载为类格式,如 "),a("code",[s._v("app.model.User")]),s._v("。")])])]),s._v(" "),a("p",[s._v("在后面的章节中,我们会逐步介绍具体的目录约定。")]),s._v(" "),a("p",[s._v("如果需要自定义加载规则,可以参见 Loader 相关文档。")])])},[],!1,null,null,null);e.default=t.exports}}]); \ No newline at end of file diff --git a/assets/js/19.1c667745.js b/assets/js/19.1c667745.js new file mode 100644 index 0000000..fa4ec64 --- /dev/null +++ b/assets/js/19.1c667745.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[19],{60:function(s,t,a){"use strict";a.r(t);var n=a(0),r=Object(n.a)({},function(){var s=this,t=s.$createElement,a=s._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用场景")]),s._v(" "),a("p",[s._v("健壮性,是一个应用的基本要求。如何正确的处理错误是非常重要的一件事。")]),s._v(" "),a("p",[s._v("实际开发中,错误可以分为几类:")]),s._v(" "),a("ul",[a("li",[s._v("非期望的入参,如函数要求传递的是数值,却传递了字符串。")]),s._v(" "),a("li",[s._v("意料之中的错误,如 "),a("code",[s._v("Http 网络断开")]),s._v("、"),a("code",[s._v("文件不存在")]),s._v("等。")]),s._v(" "),a("li",[s._v("完全意料之外的异常,譬如业务进程被外部杀死。")])]),s._v(" "),a("p",[s._v("错误的处理也有一些通用的实践:")]),s._v(" "),a("ul",[a("li",[s._v("需要记录错误的信息,位置,堆栈和上下文。")]),s._v(" "),a("li",[s._v("根据内容协商来返回不同的响应格式。")]),s._v(" "),a("li",[s._v("正式环境下,不能把详细的错误信息和堆栈抛到用户侧。")])]),s._v(" "),a("h2",{attrs:{id:"node-js-异常处理"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#node-js-异常处理","aria-hidden":"true"}},[s._v("#")]),s._v(" Node.js 异常处理")]),s._v(" "),a("p",[s._v("在 "),a("code",[s._v("Node.js")]),s._v(" 里,对异常的处理非常重要,如果有"),a("code",[s._v("未捕获异常")]),s._v("会直接导致进程退出。")]),s._v(" "),a("p",[s._v("在早期的 "),a("code",[s._v("Node.js")]),s._v(" 里, "),a("a",{attrs:{href:"https://nodejs.org/api/errors.html#errors_error_first_callbacks",target:"_blank",rel:"noopener noreferrer"}},[s._v("Error-first callbacks"),a("OutboundLink")],1),s._v(" 是用的比较广泛的一种错误处理的约定。")]),s._v(" "),a("p",[s._v("但嵌套层次一多起来,就需要一层层的往上抛出,非常容易遗漏和出现问题。")]),s._v(" "),a("p",[s._v("因此,在 "),a("code",[s._v("Async Function")]),s._v(" 异步编程模型出来后,通过 "),a("code",[s._v("try..catch")]),s._v(" 来捕获错误,就直观了很多。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("create")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("data")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("try")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("create")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("catch")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("logger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("error")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'create user fail'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("注意事项")]),s._v(" "),a("p",[s._v("避免使用 "),a("code",[s._v("callback")]),s._v(",它抛出的错误,无法被 "),a("code",[s._v("try")]),s._v(" 直接捕获,详见 "),a("a",{attrs:{href:"https://nodejs.org/api/errors.html",target:"_blank",rel:"noopener noreferrer"}},[s._v("Node.js Error"),a("OutboundLink")],1),s._v(" 文档。")])]),s._v(" "),a("h2",{attrs:{id:"框架内置支持"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#框架内置支持","aria-hidden":"true"}},[s._v("#")]),s._v(" 框架内置支持")]),s._v(" "),a("p",[s._v("框架内置了 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-onerror",target:"_blank",rel:"noopener noreferrer"}},[s._v("onerror"),a("OutboundLink")],1),s._v(" 插件,提供了统一的错误处理机制。")]),s._v(" "),a("p",[s._v("对一个请求处理过程中的 "),a("code",[s._v("Middleware")]),s._v("、"),a("code",[s._v("Controller")]),s._v("、"),a("code",[s._v("Service")]),s._v(" 等抛出的任何异常都会被它捕获。")]),s._v(" "),a("h2",{attrs:{id:"业务错误处理"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#业务错误处理","aria-hidden":"true"}},[s._v("#")]),s._v(" 业务错误处理")]),s._v(" "),a("p",[s._v("如果你需要对业务错误进行统一处理,可以如下:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/middleware/error_handler.js")]),s._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[s._v("exports")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("function")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("errorHandler")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" next")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("try")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("next")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("catch")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" app "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志")]),s._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("emit")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'error'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" status "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("status "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("||")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("500")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" error "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" status "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("===")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("500")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("&&")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("env "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("===")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'prod'")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("?")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Internal Server Error'")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("message"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 仅供参考,需按自己的业务逻辑处理。")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" error "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("status "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" status"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br"),a("span",{staticClass:"line-number"},[s._v("15")]),a("br"),a("span",{staticClass:"line-number"},[s._v("16")]),a("br"),a("span",{staticClass:"line-number"},[s._v("17")]),a("br"),a("span",{staticClass:"line-number"},[s._v("18")]),a("br"),a("span",{staticClass:"line-number"},[s._v("19")]),a("br"),a("span",{staticClass:"line-number"},[s._v("20")]),a("br")])]),a("p",[s._v("挂载中间件:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n middleware"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'errorHandler'")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n errorHandler"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 仅对该路径下的接口处理")]),s._v("\n match"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/api'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br")])]),a("h2",{attrs:{id:"框架兜底处理"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#框架兜底处理","aria-hidden":"true"}},[s._v("#")]),s._v(" 框架兜底处理")]),s._v(" "),a("p",[s._v("框架通过 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-onerror",target:"_blank",rel:"noopener noreferrer"}},[s._v("onerror"),a("OutboundLink")],1),s._v(" 插件提供了统一的错误处理机制。")]),s._v(" "),a("p",[s._v("对一个请求的所有处理方法("),a("code",[s._v("Middleware")]),s._v("、"),a("code",[s._v("Controller")]),s._v("、"),a("code",[s._v("Service")]),s._v(")中抛出的任何异常都会被它捕获。")]),s._v(" "),a("p",[s._v("并自动根据请求想要获取的类型返回不同类型的错误(基于 "),a("a",{attrs:{href:"https://tools.ietf.org/html/rfc7231#section-5.3.2",target:"_blank",rel:"noopener noreferrer"}},[s._v("Content Negotiation"),a("OutboundLink")],1),s._v(")。")]),s._v(" "),a("table",[a("thead",[a("tr",[a("th",[s._v("请求需求的格式")]),s._v(" "),a("th",[s._v("环境")]),s._v(" "),a("th",[s._v("errorPageUrl 是否配置")]),s._v(" "),a("th",[s._v("返回内容")])])]),s._v(" "),a("tbody",[a("tr",[a("td",[s._v("HTML & TEXT")]),s._v(" "),a("td",[s._v("local & unittest")]),s._v(" "),a("td",[s._v("-")]),s._v(" "),a("td",[s._v("onerror 自带的错误页面,展示详细的错误信息")])]),s._v(" "),a("tr",[a("td",[s._v("HTML & TEXT")]),s._v(" "),a("td",[s._v("其他")]),s._v(" "),a("td",[s._v("是")]),s._v(" "),a("td",[s._v("重定向到 errorPageUrl")])]),s._v(" "),a("tr",[a("td",[s._v("HTML & TEXT")]),s._v(" "),a("td",[s._v("其他")]),s._v(" "),a("td",[s._v("否")]),s._v(" "),a("td",[s._v("onerror 自带的没有错误信息的简单错误页(不推荐)")])]),s._v(" "),a("tr",[a("td",[s._v("JSON & JSONP")]),s._v(" "),a("td",[s._v("local & unittest")]),s._v(" "),a("td",[s._v("-")]),s._v(" "),a("td",[s._v("JSON 对象或对应的 JSONP 格式响应,带详细的错误信息")])]),s._v(" "),a("tr",[a("td",[s._v("JSON & JSONP")]),s._v(" "),a("td",[s._v("其他")]),s._v(" "),a("td",[s._v("-")]),s._v(" "),a("td",[s._v("JSON 对象或对应的 JSONP 格式响应,不带详细的错误信息")])])])]),s._v(" "),a("h3",{attrs:{id:"errorpageurl"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#errorpageurl","aria-hidden":"true"}},[s._v("#")]),s._v(" errorPageUrl")]),s._v(" "),a("p",[a("code",[s._v("onerror")]),s._v(" 插件支持 "),a("code",[s._v("errorPageUrl")]),s._v(" 配置,当配置了 "),a("code",[s._v("errorPageUrl")]),s._v(" 时,一旦用户请求线上应用的 HTML 页面异常,就会重定向到这个地址。")]),s._v(" "),a("p",[s._v("在 "),a("code",[s._v("config/config.default.js")]),s._v(" 中")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n onerror"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 线上页面发生异常时,重定向到这个页面上")]),s._v("\n errorPageUrl"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/50x.html'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br")])]),a("h3",{attrs:{id:"自定义统一异常处理"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#自定义统一异常处理","aria-hidden":"true"}},[s._v("#")]),s._v(" 自定义统一异常处理")]),s._v(" "),a("p",[s._v("尽管框架提供了默认的统一异常处理机制,但是应用开发中经常需要对异常时的响应做自定义,特别是在做一些接口开发的时候。框架自带的 "),a("code",[s._v("onerror")]),s._v(" 插件支持自定义配置错误处理方法,可以覆盖默认的错误处理方法。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n onerror"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("all")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" ctx")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 在此处定义针对所有响应类型的错误处理方法")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 注意,定义了 config.all 之后,其他错误处理方法不会再生效")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'error'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("status "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("500")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("html")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" ctx")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// html hander")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'

        error

        '")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("status "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("500")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("json")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" ctx")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// json hander")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" message"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'error'")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("status "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("500")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("jsonp")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" ctx")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 一般来说,不需要特殊针对 jsonp 进行错误定义,jsonp 的错误处理会自动调用 json 错误处理,并包装成 jsonp 的响应格式")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br"),a("span",{staticClass:"line-number"},[s._v("15")]),a("br"),a("span",{staticClass:"line-number"},[s._v("16")]),a("br"),a("span",{staticClass:"line-number"},[s._v("17")]),a("br"),a("span",{staticClass:"line-number"},[s._v("18")]),a("br"),a("span",{staticClass:"line-number"},[s._v("19")]),a("br"),a("span",{staticClass:"line-number"},[s._v("20")]),a("br"),a("span",{staticClass:"line-number"},[s._v("21")]),a("br"),a("span",{staticClass:"line-number"},[s._v("22")]),a("br"),a("span",{staticClass:"line-number"},[s._v("23")]),a("br"),a("span",{staticClass:"line-number"},[s._v("24")]),a("br")])]),a("h2",{attrs:{id:"_404"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#_404","aria-hidden":"true"}},[s._v("#")]),s._v(" 404")]),s._v(" "),a("p",[a("code",[s._v("404 - NOT FOUND")]),s._v(" 是我们比较熟悉的一种错误。")]),s._v(" "),a("p",[s._v("框架并不是把它视为是一种异常,并在上面的兜底流程做处理,而是另行提供了处理逻辑。")]),s._v(" "),a("h3",{attrs:{id:"默认返回值"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#默认返回值","aria-hidden":"true"}},[s._v("#")]),s._v(" 默认返回值")]),s._v(" "),a("p",[s._v("如果一次用户请求,经过了 "),a("code",[s._v("Middleware")]),s._v(" 和 "),a("code",[s._v("Controller")]),s._v(" 处理后,对应的 "),a("code",[s._v("ctx.body")]),s._v(" 和 "),a("code",[s._v("ctx.status")]),s._v(" 都未被赋值时,框架会视为 "),a("code",[s._v("404")]),s._v("。")]),s._v(" "),a("p",[s._v("此时框架会默认根据 "),a("code",[s._v("Accepet")]),s._v(" 头来响应对应的值:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// Accpet: application/json")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v('"message"')]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v('"Not Found"')]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// Accept: text/html")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("<")]),s._v("h1"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("404")]),s._v(" Not Found"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("<")]),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("h1"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v(">")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br")])]),a("h3",{attrs:{id:"重定向"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#重定向","aria-hidden":"true"}},[s._v("#")]),s._v(" 重定向")]),s._v(" "),a("p",[s._v("框架也支持通过配置,将默认的 HTML 请求的 404 响应重定向到指定的页面。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n notfound"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 也可以是一个统一的 404 外链")]),s._v("\n pageUrl"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/404.html'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br")])]),a("h3",{attrs:{id:"自定义-404-响应"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#自定义-404-响应","aria-hidden":"true"}},[s._v("#")]),s._v(" 自定义 404 响应")]),s._v(" "),a("p",[s._v("在一些场景下,我们需要自定义服务器 404 时的响应,只需要加入一个中间件即可统一处理:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/middleware/notfound_handler.js")]),s._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[s._v("exports")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("function")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("notFoundHandler")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" next")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("next")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("if")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("status "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("===")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("404")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("&&")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("!")]),s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("if")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("acceptJSON"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" error"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Not Found'")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("else")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'

        Page Not Found

        '")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br")])]),a("p",[s._v("挂载中间件:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n middleware"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'notfoundHandler'")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br")])]),a("h2",{attrs:{id:"常见问题"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常见问题","aria-hidden":"true"}},[s._v("#")]),s._v(" 常见问题")]),s._v(" "),a("h3",{attrs:{id:"该不该-catch"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#该不该-catch","aria-hidden":"true"}},[s._v("#")]),s._v(" 该不该 Catch")]),s._v(" "),a("p",[s._v("具体情况具体分析,没有绝对的银弹。")]),s._v(" "),a("p",[s._v("如果错误是非主流程的,是可选的,那可以自行兜底处理。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/service/ad.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("AdService")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Service")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 查询推荐的广告位数据,失败则返回空。")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("try")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("db"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("ad"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("catch")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 打印错误日志")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("logger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("error")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'list ad fail'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 返回空数据,不影响主流程")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br")])]),a("p",[s._v("如果对应的错误,是需要告知用户或通知前端代码的,那可以通过上述的 "),a("a",{attrs:{href:"#%E4%B8%9A%E5%8A%A1%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86"}},[s._v("业务错误处理")]),s._v(" 来统一反馈给用户。")]),s._v(" "),a("h3",{attrs:{id:"回调错误无法捕获"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#回调错误无法捕获","aria-hidden":"true"}},[s._v("#")]),s._v(" 回调错误无法捕获")]),s._v(" "),a("p",[s._v("按照正常代码写法,所有的异常都可以用这个方式进行捕获并处理,但是一定要注意一些特殊的写法可能带来的问题。")]),s._v(" "),a("p",[s._v("打一个不太正式的比方,我们的代码全部都在一个异步调用链上,所有的异步操作都通过 "),a("code",[s._v("await")]),s._v(" 串接起来了,但是只要有一个地方跳出了异步调用链,异常就捕获不到了。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/controller/home.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("HomeController")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("error")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 在回调里面抛错")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("setTimeout")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("throw")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("new")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Error")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'this is an error throw from callback'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br")])]),a("p",[s._v("正确的做法")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/controller/home.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("HomeController")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("buy")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" config "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("trade"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("buy")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'12345'")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 下单后需要进行一次核对,且不阻塞当前请求")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("setImmediate")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("trade"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("check")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("catch")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("err")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("logger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("error")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br")])]),a("p",[s._v("在这个场景中,如果 "),a("code",[s._v("service.trade.check")]),s._v(" 方法中代码有问题,导致执行时抛出了异常,尽管框架会在最外层通过 "),a("code",[s._v("try catch")]),s._v(" 统一捕获错误,但是由于 "),a("code",[s._v("setImmediate")]),s._v(" 中的代码『跳出』了异步链,它里面的错误就无法被捕捉到了。因此在编写类似代码的时候一定要注意。")]),s._v(" "),a("p",[s._v("当然,框架也考虑到了这类场景,提供了 "),a("code",[s._v("ctx.runInBackground(scope)")]),s._v(" 辅助方法,通过它又包装了一个异步链,所有在这个 scope 里面的错误都会统一捕获。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("HomeController")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("buy")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" request "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" config "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("trade"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("buy")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 下单后需要进行一次核对,且不阻塞当前请求")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("runInBackground")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 这里面的异常都会统统被 Backgroud 捕获掉,并打印错误日志")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("trade"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("check")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br")])])])},[],!1,null,null,null);t.default=r.exports}}]); \ No newline at end of file diff --git a/assets/js/2.7bd862e7.js b/assets/js/2.7bd862e7.js new file mode 100644 index 0000000..ece7bb4 --- /dev/null +++ b/assets/js/2.7bd862e7.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[2],[,,,,,,,,,,function(t,e,s){"use strict";s.d(e,"d",function(){return n}),s.d(e,"a",function(){return a}),s.d(e,"i",function(){return r}),s.d(e,"f",function(){return l}),s.d(e,"g",function(){return c}),s.d(e,"h",function(){return u}),s.d(e,"b",function(){return h}),s.d(e,"e",function(){return p}),s.d(e,"k",function(){return d}),s.d(e,"l",function(){return f}),s.d(e,"c",function(){return g}),s.d(e,"j",function(){return m});const n=/#.*$/,i=/\.(md|html)$/,a=/\/$/,r=/^(https?:|mailto:|tel:)/;function o(t){return decodeURI(t).replace(n,"").replace(i,"")}function l(t){return r.test(t)}function c(t){return/^mailto:/.test(t)}function u(t){return/^tel:/.test(t)}function h(t){if(l(t))return t;const e=t.match(n),s=e?e[0]:"",i=o(t);return a.test(i)?t:i+".html"+s}function p(t,e){const s=t.hash,i=function(t){const e=t.match(n);if(e)return e[0]}(e);return(!i||s===i)&&o(t.path)===o(e)}function d(t,e,s){if(l(e))return{type:"external",path:e};s&&(e=function(t,e,s){const n=t.charAt(0);if("/"===n)return t;if("?"===n||"#"===n)return e+t;const i=e.split("/");s&&i[i.length-1]||i.pop();const a=t.replace(/^\//,"").split("/");for(let t=0;t({type:"auto",title:e.title,basePath:t.path,path:t.path+"#"+e.slug,children:e.children||[]}))}]}(t);const o=r.sidebar||a.sidebar;if(o){const{base:t,config:s}=function(t,e){if(Array.isArray(e))return{base:"/",config:e};for(const n in e)if(0===(s=t,/(\.html|\/)$/.test(s)?s:s+"/").indexOf(encodeURI(n)))return{base:n,config:e[n]};var s;return{}}(e,o);return s?s.map(e=>(function t(e,s,n,i=1){if("string"==typeof e)return d(s,e,n);if(Array.isArray(e))return Object.assign(d(s,e[0],n),{title:e[1]});{i>3&&console.error("[vuepress] detected a too deep nested sidebar group.");const a=e.children||[];return 0===a.length&&e.path?Object.assign(d(s,e.path,n),{title:e.title}):{type:"group",path:e.path,title:e.title,sidebarDepth:e.sidebarDepth,children:a.map(e=>t(e,s,n,i+1)),collapsable:!1!==e.collapsable}}})(e,i,t)):[]}return[]}function g(t){let e;return(t=t.map(t=>Object.assign({},t))).forEach(t=>{2===t.level?e=t:e&&(e.children||(e.children=[])).push(t)}),t.filter(t=>2===t.level)}function m(t){return Object.assign(t,{type:t.items&&t.items.length?"links":"link"})}},function(t,e,s){},function(t,e,s){},function(t,e,s){},function(t,e,s){},function(t,e,s){},function(t,e,s){},function(t,e,s){},function(t,e,s){},function(t,e,s){},function(t,e,s){},function(t,e,s){},function(t,e,s){},,function(t,e,s){"use strict";s.r(e);var n=s(10),i={name:"SidebarGroup",props:["item","open","collapsable","depth"],components:{DropdownTransition:s(25).a},beforeCreate(){this.$options.components.SidebarLinks=s(24).default},methods:{isActive:n.e}},a=(s(34),s(0)),r=Object(a.a)(i,function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("section",{staticClass:"sidebar-group",class:[{collapsable:t.collapsable,"is-sub-group":0!==t.depth},"depth-"+t.depth]},[t.item.path?s("router-link",{staticClass:"sidebar-heading clickable",class:{open:t.open,active:t.isActive(t.$route,t.item.path)},attrs:{to:t.item.path},nativeOn:{click:function(e){return t.$emit("toggle")}}},[s("span",[t._v(t._s(t.item.title))]),t._v(" "),t.collapsable?s("span",{staticClass:"arrow",class:t.open?"down":"right"}):t._e()]):s("p",{staticClass:"sidebar-heading",class:{open:t.open},on:{click:function(e){return t.$emit("toggle")}}},[s("span",[t._v(t._s(t.item.title))]),t._v(" "),t.collapsable?s("span",{staticClass:"arrow",class:t.open?"down":"right"}):t._e()]),t._v(" "),s("DropdownTransition",[t.open||!t.collapsable?s("SidebarLinks",{staticClass:"sidebar-group-items",attrs:{items:t.item.children,sidebarDepth:t.item.sidebarDepth,depth:t.depth+1}}):t._e()],1)],1)},[],!1,null,null,null).exports;function o(t,e,s,n){return t("router-link",{props:{to:e,activeClass:"",exactActiveClass:""},class:{active:n,"sidebar-link":!0}},s)}function l(t,e,s,i,a,r=1){return!e||r>a?null:t("ul",{class:"sidebar-sub-headers"},e.map(e=>{const c=Object(n.e)(i,s+"#"+e.slug);return t("li",{class:"sidebar-sub-header"},[o(t,s+"#"+e.slug,e.title,c),l(t,e.children,s,i,a,r+1)])}))}var c={functional:!0,props:["item","sidebarDepth"],render(t,{parent:{$page:e,$site:s,$route:i,$themeConfig:a,$themeLocaleConfig:r},props:{item:c,sidebarDepth:u}}){const h=Object(n.e)(i,c.path),p="auto"===c.type?h||c.children.some(t=>Object(n.e)(i,c.basePath+"#"+t.slug)):h,d="external"===c.type?function(t,e,s){return t("a",{attrs:{href:e,target:"_blank",rel:"noopener noreferrer"},class:{"sidebar-link":!0}},[s,t("OutboundLink")])}(t,c.path,c.title||c.path):o(t,c.path,c.title||c.path,p),f=e.frontmatter.sidebarDepth||u||r.sidebarDepth||a.sidebarDepth,g=null==f?1:f,m=r.displayAllHeaders||a.displayAllHeaders;if("auto"===c.type)return[d,l(t,c.children,c.basePath,i,g)];if((p||m)&&c.headers&&!n.d.test(c.path)){return[d,l(t,Object(n.c)(c.headers),c.path,i,g)]}return d}};s(35);var u={name:"SidebarLinks",components:{SidebarGroup:r,SidebarLink:Object(a.a)(c,void 0,void 0,!1,null,null,null).exports},props:["items","depth","sidebarDepth"],data:()=>({openGroupIndex:0}),created(){this.refreshIndex()},watch:{$route(){this.refreshIndex()}},methods:{refreshIndex(){const t=function(t,e){for(let s=0;s"page"===e.type&&Object(n.e)(t,e.path)))return s}return-1}(this.$route,this.items);t>-1&&(this.openGroupIndex=t)},toggleGroup(t){this.openGroupIndex=t===this.openGroupIndex?-1:t},isActive(t){return Object(n.e)(this.$route,t.regularPath)}}},h=Object(a.a)(u,function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.items.length?s("ul",{staticClass:"sidebar-links"},t._l(t.items,function(e,n){return s("li",{key:n},["group"===e.type?s("SidebarGroup",{attrs:{item:e,open:n===t.openGroupIndex,collapsable:e.collapsable||e.collapsible,depth:t.depth},on:{toggle:function(e){return t.toggleGroup(n)}}}):s("SidebarLink",{attrs:{sidebarDepth:t.sidebarDepth,item:e}})],1)}),0):t._e()},[],!1,null,null,null);e.default=h.exports},function(t,e,s){"use strict";var n={name:"DropdownTransition",methods:{setHeight(t){t.style.height=t.scrollHeight+"px"},unsetHeight(t){t.style.height=""}}},i=(s(29),s(0)),a=Object(i.a)(n,function(){var t=this.$createElement;return(this._self._c||t)("transition",{attrs:{name:"dropdown"},on:{enter:this.setHeight,"after-enter":this.unsetHeight,"before-leave":this.setHeight}},[this._t("default")],2)},[],!1,null,null,null);e.a=a.exports},function(t,e,s){"use strict";var n=s(11);s.n(n).a},function(t,e,s){"use strict";var n=s(12);s.n(n).a},function(t,e,s){"use strict";var n=s(13);s.n(n).a},function(t,e,s){"use strict";var n=s(14);s.n(n).a},function(t,e,s){"use strict";var n=s(15);s.n(n).a},function(t,e,s){"use strict";var n=s(16);s.n(n).a},function(t,e,s){"use strict";var n=s(17);s.n(n).a},function(t,e,s){"use strict";var n=s(18);s.n(n).a},function(t,e,s){"use strict";var n=s(19);s.n(n).a},function(t,e,s){"use strict";var n=s(20);s.n(n).a},function(t,e,s){"use strict";var n=s(21);s.n(n).a},function(t,e,s){"use strict";var n=s(22);s.n(n).a},,,,,function(t,e,s){"use strict";s.r(e);var n=s(10),i={props:{item:{required:!0}},computed:{link(){return Object(n.b)(this.item.link)},exact(){return this.$site.locales?Object.keys(this.$site.locales).some(t=>t===this.link):"/"===this.link}},methods:{isExternal:n.f,isMailto:n.g,isTel:n.h}},a=s(0),r=Object(a.a)(i,function(){var t=this,e=t.$createElement,s=t._self._c||e;return t.isExternal(t.link)?s("a",{staticClass:"nav-link external",attrs:{href:t.link,target:t.isMailto(t.link)||t.isTel(t.link)?null:"_blank",rel:t.isMailto(t.link)||t.isTel(t.link)?null:"noopener noreferrer"}},[t._v("\n "+t._s(t.item.text)+"\n "),s("OutboundLink")],1):s("router-link",{staticClass:"nav-link",attrs:{to:t.link,exact:t.exact}},[t._v(t._s(t.item.text))])},[],!1,null,null,null).exports,o={components:{NavLink:r},computed:{data(){return this.$page.frontmatter},actionLink(){return{link:this.data.actionLink,text:this.data.actionText}}}},l=(s(26),Object(a.a)(o,function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("main",{staticClass:"home",attrs:{"aria-labelledby":"main-title"}},[s("header",{staticClass:"hero"},[t.data.heroImage?s("img",{attrs:{src:t.$withBase(t.data.heroImage),alt:t.data.heroAlt||"hero"}}):t._e(),t._v(" "),null!==t.data.heroText?s("h1",{attrs:{id:"main-title"}},[t._v(t._s(t.data.heroText||t.$title||"Hello"))]):t._e(),t._v(" "),s("p",{staticClass:"description"},[t._v("\n "+t._s(t.data.tagline||t.$description||"Welcome to your VuePress site")+"\n ")]),t._v(" "),t.data.actionText&&t.data.actionLink?s("p",{staticClass:"action"},[s("NavLink",{staticClass:"action-button",attrs:{item:t.actionLink}})],1):t._e()]),t._v(" "),t.data.features&&t.data.features.length?s("div",{staticClass:"features"},t._l(t.data.features,function(e,n){return s("div",{key:n,staticClass:"feature"},[s("h2",[t._v(t._s(e.title))]),t._v(" "),s("p",[t._v(t._s(e.details))])])}),0):t._e(),t._v(" "),s("Content",{staticClass:"theme-default-content custom"}),t._v(" "),t.data.footer?s("div",{staticClass:"footer"},[t._v("\n "+t._s(t.data.footer)+"\n ")]):t._e()],1)},[],!1,null,null,null).exports),c={data:()=>({query:"",focused:!1,focusIndex:0}),computed:{showSuggestions(){return this.focused&&this.suggestions&&this.suggestions.length},suggestions(){const t=this.query.trim().toLowerCase();if(!t)return;const{pages:e}=this.$site,s=this.$localePath,n=e=>e.title&&e.title.toLowerCase().indexOf(t)>-1,i=[];for(let t=0;t=5);t++){const a=e[t];if(this.getPageLocalePath(a)===s&&this.isSearchable(a))if(n(a))i.push(a);else if(a.headers)for(let t=0;t=5);t++){const e=a.headers[t];n(e)&&i.push(Object.assign({},a,{path:a.path+"#"+e.slug,header:e}))}}return i},alignRight(){return(this.$site.themeConfig.nav||[]).length+(this.$site.repo?1:0)<=2}},methods:{getPageLocalePath(t){for(const e in this.$site.locales||{})if("/"!==e&&0===t.path.indexOf(e))return e;return"/"},isSearchable(t){let e=null;return null===e||(e=Array.isArray(e)?e:new Array(e)).filter(e=>t.path.match(e)).length>0},onUp(){this.showSuggestions&&(this.focusIndex>0?this.focusIndex--:this.focusIndex=this.suggestions.length-1)},onDown(){this.showSuggestions&&(this.focusIndex "+t._s(e.header.title))]):t._e()])])}),0):t._e()])},[],!1,null,null,null).exports),h=(s(28),Object(a.a)({},function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"sidebar-button",on:{click:function(e){return t.$emit("toggle-sidebar")}}},[s("svg",{staticClass:"icon",attrs:{xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",role:"img",viewBox:"0 0 448 512"}},[s("path",{attrs:{fill:"currentColor",d:"M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"}})])])},[],!1,null,null,null).exports),p={components:{NavLink:r,DropdownTransition:s(25).a},data:()=>({open:!1}),props:{item:{required:!0}},methods:{toggle(){this.open=!this.open}}},d=(s(30),{components:{NavLink:r,DropdownLink:Object(a.a)(p,function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"dropdown-wrapper",class:{open:t.open}},[s("a",{staticClass:"dropdown-title",on:{click:t.toggle}},[s("span",{staticClass:"title"},[t._v(t._s(t.item.text))]),t._v(" "),s("span",{staticClass:"arrow",class:t.open?"down":"right"})]),t._v(" "),s("DropdownTransition",[s("ul",{directives:[{name:"show",rawName:"v-show",value:t.open,expression:"open"}],staticClass:"nav-dropdown"},t._l(t.item.items,function(e,n){return s("li",{key:e.link||n,staticClass:"dropdown-item"},["links"===e.type?s("h4",[t._v(t._s(e.text))]):t._e(),t._v(" "),"links"===e.type?s("ul",{staticClass:"dropdown-subitem-wrapper"},t._l(e.items,function(t){return s("li",{key:t.link,staticClass:"dropdown-subitem"},[s("NavLink",{attrs:{item:t}})],1)}),0):s("NavLink",{attrs:{item:e}})],1)}),0)])],1)},[],!1,null,null,null).exports},computed:{userNav(){return this.$themeLocaleConfig.nav||this.$site.themeConfig.nav||[]},nav(){const{locales:t}=this.$site;if(t&&Object.keys(t).length>1){const e=this.$page.path,s=this.$router.options.routes,n=this.$site.themeConfig.locales||{},i={text:this.$themeLocaleConfig.selectText||"Languages",items:Object.keys(t).map(i=>{const a=t[i],r=n[i]&&n[i].label||a.lang;let o;return a.lang===this.$lang?o=e:(o=e.replace(this.$localeConfig.path,i),s.some(t=>t.path===o)||(o=i)),{text:r,link:o}})};return[...this.userNav,i]}return this.userNav},userLinks(){return(this.nav||[]).map(t=>Object.assign(Object(n.j)(t),{items:(t.items||[]).map(n.j)}))},repoLink(){const{repo:t}=this.$site.themeConfig;if(t)return/^https?:/.test(t)?t:`https://github.com/${t}`},repoLabel(){if(!this.repoLink)return;if(this.$site.themeConfig.repoLabel)return this.$site.themeConfig.repoLabel;const t=this.repoLink.match(/^https?:\/\/[^\/]+/)[0],e=["GitHub","GitLab","Bitbucket"];for(let s=0;s({linksWrapMaxWidth:null}),mounted(){const t=parseInt(g(this.$el,"paddingLeft"))+parseInt(g(this.$el,"paddingRight")),e=()=>{document.documentElement.clientWidth<719?this.linksWrapMaxWidth=null:this.linksWrapMaxWidth=this.$el.offsetWidth-t-(this.$refs.siteName&&this.$refs.siteName.offsetWidth||0)};e(),window.addEventListener("resize",e,!1)},computed:{algolia(){return this.$themeLocaleConfig.algolia||this.$site.themeConfig.algolia||{}},isAlgoliaSearch(){return this.algolia&&this.algolia.apiKey&&this.algolia.indexName}}},v=(s(32),Object(a.a)(m,function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("header",{staticClass:"navbar"},[s("SidebarButton",{on:{"toggle-sidebar":function(e){return t.$emit("toggle-sidebar")}}}),t._v(" "),s("router-link",{staticClass:"home-link",attrs:{to:t.$localePath}},[t.$site.themeConfig.logo?s("img",{staticClass:"logo",attrs:{src:t.$withBase(t.$site.themeConfig.logo),alt:t.$siteTitle}}):t._e(),t._v(" "),t.$siteTitle?s("span",{ref:"siteName",staticClass:"site-name",class:{"can-hide":t.$site.themeConfig.logo}},[t._v(t._s(t.$siteTitle))]):t._e()]),t._v(" "),s("div",{staticClass:"links",style:t.linksWrapMaxWidth?{"max-width":t.linksWrapMaxWidth+"px"}:{}},[t.isAlgoliaSearch?s("AlgoliaSearchBox",{attrs:{options:t.algolia}}):!1!==t.$site.themeConfig.search&&!1!==t.$page.frontmatter.search?s("SearchBox"):t._e(),t._v(" "),s("NavLinks",{staticClass:"can-hide"})],1)],1)},[],!1,null,null,null).exports);function b(t,e,s){const n=[];!function t(e,s){for(let n=0,i=e.length;n({isSidebarOpen:!1}),computed:{shouldShowNavbar(){const{themeConfig:t}=this.$site,{frontmatter:e}=this.$page;return!1!==e.navbar&&!1!==t.navbar&&(this.$title||t.logo||t.repo||t.nav||this.$themeLocaleConfig.nav)},shouldShowSidebar(){const{frontmatter:t}=this.$page;return!t.home&&!1!==t.sidebar&&this.sidebarItems.length},sidebarItems(){return Object(n.l)(this.$page,this.$page.regularPath,this.$site,this.$localePath)},pageClasses(){const t=this.$page.frontmatter.pageClass;return[{"no-navbar":!this.shouldShowNavbar,"sidebar-open":this.isSidebarOpen,"no-sidebar":!this.shouldShowSidebar},t]}},mounted(){this.$router.afterEach(()=>{this.isSidebarOpen=!1})},methods:{toggleSidebar(t){this.isSidebarOpen="boolean"==typeof t?t:!this.isSidebarOpen},onTouchStart(t){this.touchStart={x:t.changedTouches[0].clientX,y:t.changedTouches[0].clientY}},onTouchEnd(t){const e=t.changedTouches[0].clientX-this.touchStart.x,s=t.changedTouches[0].clientY-this.touchStart.y;Math.abs(e)>Math.abs(s)&&Math.abs(e)>40&&(e>0&&this.touchStart.x<=80?this.toggleSidebar(!0):this.toggleSidebar(!1))}}}),x=(s(37),Object(a.a)(C,function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("div",{staticClass:"theme-container",class:t.pageClasses,on:{touchstart:t.onTouchStart,touchend:t.onTouchEnd}},[t.shouldShowNavbar?s("Navbar",{on:{"toggle-sidebar":t.toggleSidebar}}):t._e(),t._v(" "),s("div",{staticClass:"sidebar-mask",on:{click:function(e){return t.toggleSidebar(!1)}}}),t._v(" "),s("Sidebar",{attrs:{items:t.sidebarItems},on:{"toggle-sidebar":t.toggleSidebar}},[t._t("sidebar-top",null,{slot:"top"}),t._v(" "),t._t("sidebar-bottom",null,{slot:"bottom"})],2),t._v(" "),t.$page.frontmatter.home?s("Home"):s("Page",{attrs:{"sidebar-items":t.sidebarItems}},[t._t("page-top",null,{slot:"top"}),t._v(" "),t._t("page-bottom",null,{slot:"bottom"})],2)],1)},[],!1,null,null,null));e.default=x.exports}]]); \ No newline at end of file diff --git a/assets/js/20.79b3431f.js b/assets/js/20.79b3431f.js new file mode 100644 index 0000000..e9f2be0 --- /dev/null +++ b/assets/js/20.79b3431f.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[20],{56:function(t,s,a){"use strict";a.r(s);var e=a(0),n=Object(e.a)({},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("p",[t._v("如果下面的内容无法解决你的问题,请查看 "),a("a",{attrs:{href:"https://github.com/eggjs/egg/issues",target:"_blank",rel:"noopener noreferrer"}},[t._v("Egg issues"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("h2",{attrs:{id:"如何高效的反馈问题?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#如何高效的反馈问题?","aria-hidden":"true"}},[t._v("#")]),t._v(" 如何高效的反馈问题?")]),t._v(" "),a("p",[t._v("感谢您向我们反馈问题。")]),t._v(" "),a("ol",[a("li",[t._v("我们推荐如果是小问题(错别字修改,小的 bug fix)直接提交 PR。")]),t._v(" "),a("li",[t._v("如果是一个新需求,请提供:详细需求描述,最好是有伪代码示意。")]),t._v(" "),a("li",[t._v("如果是一个 BUG,请提供:复现步骤,错误日志以及相关配置,并尽量填写下面的模板中的条目。")]),t._v(" "),a("li",[a("strong",[t._v("如果可以,尽可能使用 "),a("code",[t._v("egg-init --type=simple bug")]),t._v(" 提供一个最小可复现的代码仓库,方便我们排查问题。")])]),t._v(" "),a("li",[t._v("不要挤牙膏似的交流,扩展阅读:"),a("a",{attrs:{href:"https://zhuanlan.zhihu.com/p/25795393",target:"_blank",rel:"noopener noreferrer"}},[t._v("如何向开源项目提交无法解答的问题"),a("OutboundLink")],1)])]),t._v(" "),a("p",[t._v("最重要的是,请明白一件事:开源项目的用户和维护者之间并不是甲方和乙方的关系,issue 也不是客服工单。在开 issue 的时候,请抱着一种『一起合作来解决这个问题』的心态,不要期待我们单方面地为你服务。")]),t._v(" "),a("h2",{attrs:{id:"为什么我的配置不生效?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#为什么我的配置不生效?","aria-hidden":"true"}},[t._v("#")]),t._v(" 为什么我的配置不生效?")]),t._v(" "),a("p",[t._v("框架的配置功能比较强大,有不同环境变量,又有框架、插件、应用等很多地方配置。")]),t._v(" "),a("p",[t._v("如果你分析问题时,想知道当前运行时使用的最终配置,可以查看下 "),a("code",[t._v("${root}/run/application_config.json")]),t._v("(worker 进程配置) 和 "),a("code",[t._v("${root}/run/agent_config.json")]),t._v("(agent 进程配置) 这两个文件。("),a("code",[t._v("root")]),t._v(" 为应用根目录,只有在 local 和 unittest 环境下为项目所在目录,其他环境下都为 HOME 目录)")]),t._v(" "),a("p",[t._v("也可参见"),a("a",{attrs:{href:"https://eggjs.org/zh-cn/basics/config.html#%E9%85%8D%E7%BD%AE%E7%BB%93%E6%9E%9C",target:"_blank",rel:"noopener noreferrer"}},[t._v("配置文件"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("p",[t._v("PS:请确保没有写出以下代码:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("someKeys "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'abc'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("appInfo")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" config "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("keys "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'123456'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("h2",{attrs:{id:"线上的日志打印去哪里了?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#线上的日志打印去哪里了?","aria-hidden":"true"}},[t._v("#")]),t._v(" 线上的日志打印去哪里了?")]),t._v(" "),a("p",[t._v("默认配置下,本地开发环境的日志都会打印在应用根目录的 "),a("code",[t._v("logs")]),t._v(" 文件夹下("),a("code",[t._v("${baseDir}/logs")]),t._v(") ,但是在非开发期的环境(非 local 和 unittest 环境),所有的日志都会打印到 "),a("code",[t._v("$HOME/logs")]),t._v(" 文件夹下(例如 "),a("code",[t._v("/home/admin/logs")]),t._v(")。这样可以让本地开发时应用日志互不影响,服务器运行时又有统一的日志输出目录。")]),t._v(" "),a("h2",{attrs:{id:"进程管理为什么没有选型-pm2-?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#进程管理为什么没有选型-pm2-?","aria-hidden":"true"}},[t._v("#")]),t._v(" 进程管理为什么没有选型 PM2 ?")]),t._v(" "),a("ol",[a("li",[t._v("PM2 模块本身复杂度很高,出了问题很难排查。我们认为框架使用的工具复杂度不应该过高,而 PM2 自身的复杂度超越了大部分应用本身。")]),t._v(" "),a("li",[t._v("没法做非常深的优化。")]),t._v(" "),a("li",[t._v("切实的需求问题,一个进程里跑 leader,其他进程代理到 leader 这种模式("),a("router-link",{attrs:{to:"/zh/advanced/framework/cluster-and-ipc.html"}},[t._v("多进程模型")]),t._v("),在企业级开发中对于减少远端连接,降低数据通信压力等都是切实的需求。特别当应用规模大到一定程度,这就会是刚需。egg 本身起源于蚂蚁金服和阿里,我们对标的起点就是大规模企业应用的构建,所以要非常全面。这些特性通过 PM2 很难做到。")],1)]),t._v(" "),a("p",[t._v("进程模型非常重要,会影响到开发模式,运行期间的深度优化等,我们认为可能由框架来控制比较合适。")]),t._v(" "),a("p",[a("strong",[t._v("如何使用 PM2 启动应用?")])]),t._v(" "),a("p",[t._v("尽管我们不推荐使用 PM2 启动,但仍然是可以做到的。")]),t._v(" "),a("p",[t._v("首先,在项目根目录定义启动文件:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// server.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" egg "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" workers "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("Number")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("process"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("argv"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("2")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("||")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'os'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("cpus")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("length"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\negg"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("startCluster")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n workers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n baseDir"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" __dirname"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br")])]),a("p",[t._v("这样,我们就可以通过 PM2 进行启动了:")]),t._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[t._v("pm2 start server.js\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("h2",{attrs:{id:"为什么会有-csrf-报错?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#为什么会有-csrf-报错?","aria-hidden":"true"}},[t._v("#")]),t._v(" 为什么会有 csrf 报错?")]),t._v(" "),a("p",[t._v("通常有两种 csrf 报错:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("missing csrf token")])]),t._v(" "),a("li",[a("code",[t._v("invalid csrf token")])])]),t._v(" "),a("p",[t._v("Egg 内置的 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-security/",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-security"),a("OutboundLink")],1),t._v(" 插件默认对所有『非安全』的方法,例如 "),a("code",[t._v("POST")]),t._v(","),a("code",[t._v("PUT")]),t._v(","),a("code",[t._v("DELETE")]),t._v(" 都进行 CSRF 校验。")]),t._v(" "),a("p",[t._v("请求遇到 csrf 报错通常是因为没有加正确的 csrf token 导致,具体实现方式,请阅读"),a("router-link",{attrs:{to:"/zh/ecosystem/security/csrf.html"}},[t._v("安全威胁 CSRF 的防范")]),t._v("。")],1),t._v(" "),a("h2",{attrs:{id:"本地开发时,修改代码后为什么-worker-进程没有自动重启?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#本地开发时,修改代码后为什么-worker-进程没有自动重启?","aria-hidden":"true"}},[t._v("#")]),t._v(" 本地开发时,修改代码后为什么 worker 进程没有自动重启?")]),t._v(" "),a("p",[t._v("没有自动重启的情况一般是在使用 Jetbrains 旗下软件(IntelliJ IDEA, WebStorm..),并且开启了 Safe Write 选项。")]),t._v(" "),a("p",[t._v("Jetbrains "),a("a",{attrs:{href:"https://www.jetbrains.com/help/webstorm/2016.3/system-settings.html",target:"_blank",rel:"noopener noreferrer"}},[t._v("Safe Write 文档"),a("OutboundLink")],1),t._v("中有提到:")]),t._v(" "),a("blockquote",[a("p",[t._v("If this check box is selected, a changed file is first saved in a temporary file. If the save operation succeeds, the file being saved is replaced with the saved file. (Technically, the original file is deleted and the temporary file is renamed.)")])]),t._v(" "),a("p",[t._v('由于使用了重命名导致文件监听的失效。解决办法是关掉 Safe Write 选项。(Settings | Appearance & Behavior | System Settings | Use "safe write" 路径可能根据版本有所不同)')])])},[],!1,null,null,null);s.default=n.exports}}]); \ No newline at end of file diff --git a/assets/js/21.4be0f224.js b/assets/js/21.4be0f224.js new file mode 100644 index 0000000..32f0fe8 --- /dev/null +++ b/assets/js/21.4be0f224.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[21],{59:function(t,s,a){"use strict";a.r(s);var n=a(0),e=Object(n.a)({},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用场景")]),t._v(" "),a("p",[a("code",[t._v("Helper")]),t._v(" 提供了一些实用的 utility 函数,避免逻辑分散各处,更容易编写测试用例。")]),t._v(" "),a("p",[t._v("框架内置了一些常用的 "),a("code",[t._v("Helper")]),t._v(" 方法,我们也可以编写自定义的 "),a("code",[t._v("Helper")]),t._v(" 方法。")]),t._v(" "),a("h2",{attrs:{id:"访问方式"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#访问方式","aria-hidden":"true"}},[t._v("#")]),t._v(" 访问方式")]),t._v(" "),a("p",[t._v("它是一个 "),a("strong",[t._v("请求级别")]),t._v(" 的对象,可以通过 "),a("code",[t._v("ctx.helper")]),t._v(" 访问到 helper 对象。")]),t._v(" "),a("p",[t._v("在 "),a("router-link",{attrs:{to:"/zh/guide/controller.html"}},[t._v("Controller")]),t._v(" 中使用:")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/user.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("fetch")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" id "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("query"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" user "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("cache"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("helper"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("formatUser")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("p",[t._v("在"),a("router-link",{attrs:{to:"/zh/ecosystem/frontend/template.html"}},[t._v("模板引擎")]),t._v("中使用:")],1),t._v(" "),a("div",{staticClass:"language-html line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-html"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("\x3c!-- app/view/home.tpl --\x3e")]),t._v("\n{{ helper.shtml(value) }}\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br")])]),a("h2",{attrs:{id:"常用的属性和方法"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常用的属性和方法","aria-hidden":"true"}},[t._v("#")]),t._v(" 常用的属性和方法")]),t._v(" "),a("p",[t._v("在 "),a("code",[t._v("Helper")]),t._v(" 上有以下属性:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("this")]),t._v(":"),a("code",[t._v("Helper")]),t._v(" 对象本身,可以用来调用其他 "),a("code",[t._v("Helper")]),t._v(" 方法。")]),t._v(" "),a("li",[a("code",[t._v("this.ctx")]),t._v(":对应的 "),a("router-link",{attrs:{to:"/zh/guide/context.html"}},[t._v("Context")]),t._v(" 对象。")],1),t._v(" "),a("li",[a("code",[t._v("this.app")]),t._v(":对应的 "),a("router-link",{attrs:{to:"/zh/guide/application.html"}},[t._v("Application")]),t._v(" 对象。")],1)]),t._v(" "),a("p",[t._v("框架默认提供以下 "),a("code",[t._v("Helper")]),t._v(" 方法:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("pathFor(name, params)")]),t._v(": 生成对应[路由]的 "),a("code",[t._v("path")]),t._v(" 路径。")]),t._v(" "),a("li",[a("code",[t._v("urlFor(name, params)")]),t._v(": 生成对应[路由]的 "),a("code",[t._v("URL")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("shtml() / sjs() / ...")]),t._v(": 由"),a("router-link",{attrs:{to:"/zh/ecosystem/security/xss.html"}},[t._v("安全组件")]),t._v("提供的安全方法。")],1)]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\napp"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 使用 helper 计算指定 path")]),t._v("\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("helper"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("pathFor")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" limit"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" sort"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'name'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// => /user?limit=10&sort=name")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("h2",{attrs:{id:"如何扩展"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#如何扩展","aria-hidden":"true"}},[t._v("#")]),t._v(" 如何扩展")]),t._v(" "),a("p",[t._v("我们支持开发者通过 "),a("code",[t._v("app/extend/helper.js")]),t._v(" 来扩展 "),a("code",[t._v("Helper")]),t._v("。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/extend/helper.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("foo")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("param")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// this 是 helper 对象,在其中可以调用其他 helper 方法")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// this.ctx => context 对象")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// this.app => application 对象")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("formatUser")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("user")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("only")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'name'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'phone'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br")])]),a("p",[t._v("对应的测试:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// test/app/extend/helper.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" assert "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-mock'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("describe")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'test/app/extend/helper.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'formatUser()'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 创建 ctx")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("mockContext")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("helper"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("formatUser")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'TZ'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" phone"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("123")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" token"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'abcd'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("assert")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("name "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("===")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'TZ'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("assert")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("!")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("token"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br")])]),a("p",[t._v("具体的单元测试运行方式,参见 "),a("router-link",{attrs:{to:"/zh/workflow/development/unittest.html"}},[t._v("研发流程 - 单元测试")]),t._v(" 文档。")],1)])},[],!1,null,null,null);s.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/22.36f8502b.js b/assets/js/22.36f8502b.js new file mode 100644 index 0000000..d7a7daa --- /dev/null +++ b/assets/js/22.36f8502b.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[22],{52:function(t,s,a){"use strict";a.r(s);var n=a(0),e=Object(n.a)({},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("h2",{attrs:{id:"使用背景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用背景","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用背景")]),t._v(" "),a("p",[t._v("互联网时代,无数服务是基于 HTTP 协议进行通信的。")]),t._v(" "),a("p",[t._v("在前面我们了解到的,都是 "),a("code",[t._v("Node.js")]),t._v(" 作为 Web 服务端的相关知识。")]),t._v(" "),a("p",[t._v("其实应用本身作为发起者,来调用后端服务也是一种非常常见的应用场景。")]),t._v(" "),a("p",[t._v("譬如:")]),t._v(" "),a("ul",[a("li",[t._v("调用后端微服务,查询或更新数据。")]),t._v(" "),a("li",[t._v("把日志上报给第三方服务。")]),t._v(" "),a("li",[t._v("上传文件给后端服务。")])]),t._v(" "),a("p",[t._v("因此,框架内置实现了一个 "),a("code",[t._v("HttpClient")]),t._v(",应用可以使用它来非常便捷地完成任何 HTTP 请求。")]),t._v(" "),a("h2",{attrs:{id:"获取方式"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#获取方式","aria-hidden":"true"}},[t._v("#")]),t._v(" 获取方式")]),t._v(" "),a("h3",{attrs:{id:"app-httpclient"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-httpclient","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("app.httpclient")])]),t._v(" "),a("p",[t._v("框架在应用初始化的时候,会自动将 "),a("a",{attrs:{href:"https://github.com/eggjs/egg/blob/master/lib/core/httpclient.js",target:"_blank",rel:"noopener noreferrer"}},[t._v("HttpClient"),a("OutboundLink")],1),t._v(" 初始化到 "),a("code",[t._v("app.httpclient")]),t._v("。")]),t._v(" "),a("p",[t._v("它是基于 "),a("a",{attrs:{href:"https://github.com/node-modules/urllib",target:"_blank",rel:"noopener noreferrer"}},[t._v("urllib"),a("OutboundLink")],1),t._v(" 模块的扩展。")]),t._v(" "),a("h3",{attrs:{id:"app-curl-url-options"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-curl-url-options","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("app.curl(http://github.com/url,%20options)")])]),t._v(" "),a("p",[t._v("框架提供的语法糖,它等价于 "),a("code",[t._v("app.httpclient.request(url, options)")]),t._v("。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" url "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'https://registry.npm.taobao.org/egg/latest'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" dataType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\nconsole"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br")])]),a("h3",{attrs:{id:"ctx-curl-url-options"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-curl-url-options","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ctx.curl(http://github.com/url,%20options)")])]),t._v(" "),a("p",[t._v("框架在 "),a("router-link",{attrs:{to:"/zh/guide/context.html"}},[t._v("Context")]),t._v(" 中同样提供了对应的语法糖,这将是我们最常用的方法。")],1),t._v(" "),a("p",[t._v("它的区别在于,会默认注入 "),a("code",[t._v("options.ctx")]),t._v(",从而在错误处理或打印 "),a("code",[t._v("Trace")]),t._v(" 日志时,可以方便的获取到上游请求的相关信息。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/http.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HttpController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("index")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 示例:请求一个 npm 模块信息")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" url "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'https://registry.npm.taobao.org/egg/latest'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 自动解析 JSON response")]),t._v("\n dataType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 3 秒超时")]),t._v("\n timeout"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("3000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n status"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("status"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("package")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br")])]),a("h2",{attrs:{id:"常用参数及响应"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常用参数及响应","aria-hidden":"true"}},[t._v("#")]),t._v(" 常用参数及响应")]),t._v(" "),a("h3",{attrs:{id:"请求参数"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#请求参数","aria-hidden":"true"}},[t._v("#")]),t._v(" 请求参数")]),t._v(" "),a("p",[t._v("最常用到的 "),a("code",[t._v("Options")]),t._v(" 参数如下:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("options.method")]),t._v(":"),a("a",{attrs:{href:"https://nodejs.org/api/http.html#http_http_methods",target:"_blank",rel:"noopener noreferrer"}},[t._v("HTTP 请求方法"),a("OutboundLink")],1),t._v(",默认为 "),a("code",[t._v("GET")]),t._v(",全大写格式。")]),t._v(" "),a("li",[a("code",[t._v("options.data")]),t._v(":发送的请求体,会根据 "),a("code",[t._v("contentType")]),t._v(" 进行不同的处理。")]),t._v(" "),a("li",[a("code",[t._v("options.contentType")]),t._v(":发送的数据格式,取值 "),a("code",[t._v("json")]),t._v("、"),a("code",[t._v("form")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("options.dataType")]),t._v(":对响应的数据进行格式转换,取值 "),a("code",[t._v("json")]),t._v("、"),a("code",[t._v("text")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("options.headers")]),t._v(":请求头。")])]),t._v(" "),a("p",[t._v("完整的请求参数 "),a("code",[t._v("options")]),t._v(" 说明,参见下文的 "),a("a",{attrs:{href:"#options-%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3"}},[t._v("options 参数详解")]),t._v(" 章节。")]),t._v(" "),a("h3",{attrs:{id:"响应数据"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#响应数据","aria-hidden":"true"}},[t._v("#")]),t._v(" 响应数据")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("result.status")]),t._v(": 响应状态码,如 "),a("code",[t._v("200")]),t._v(", "),a("code",[t._v("302")]),t._v(", "),a("code",[t._v("404")]),t._v(", "),a("code",[t._v("500")]),t._v(" 等等。")]),t._v(" "),a("li",[a("code",[t._v("result.headers")]),t._v(": 响应头,类似 "),a("code",[t._v("{ 'content-type': 'text/html', ... }")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("result.data")]),t._v(": 响应 body 数据,会根据 "),a("code",[t._v("options.dataType")]),t._v(" 进行相应的格式转换。")]),t._v(" "),a("li",[a("code",[t._v("result.res.timing")]),t._v(":请求各阶段的耗时统计,需传递 "),a("code",[t._v("options.timing")]),t._v(" 才会采集。")])]),t._v(" "),a("h2",{attrs:{id:"httpclient-实战"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#httpclient-实战","aria-hidden":"true"}},[t._v("#")]),t._v(" HttpClient 实战")]),t._v(" "),a("p",[t._v("以下示例,我们都使用 https://httpbin.org 提供的服务来测试。")]),t._v(" "),a("h3",{attrs:{id:"发起-get-请求"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#发起-get-请求","aria-hidden":"true"}},[t._v("#")]),t._v(" 发起 GET 请求")]),t._v(" "),a("p",[t._v("读取数据几乎都是使用 "),a("code",[t._v("GET")]),t._v(" 请求,它是 "),a("code",[t._v("HTTP")]),t._v(" 世界最常见的场景,也是最广泛的场景。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/http.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HttpController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'https://httpbin.org/get?foo=bar'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("status "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("status"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br")])]),a("h3",{attrs:{id:"通过-post-发送-json"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#通过-post-发送-json","aria-hidden":"true"}},[t._v("#")]),t._v(" 通过 POST 发送 JSON")]),t._v(" "),a("p",[t._v("微服务间通讯,"),a("code",[t._v("JSON")]),t._v(" 是最常见的协议。")]),t._v(" "),a("p",[t._v("譬如,创建数据的场景一般来说都会使用 "),a("code",[t._v("POST")]),t._v(" 发送 "),a("code",[t._v("JSON")]),t._v(" 数据。")]),t._v(" "),a("p",[t._v("关键配置为:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("method")]),t._v(": 必须配置为 "),a("code",[t._v("POST")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("data")]),t._v(":需要传递的数据对应,Object 类型。")]),t._v(" "),a("li",[a("code",[t._v("contentType: 'json'")]),t._v(":声明以 "),a("code",[t._v("JSON")]),t._v(" 格式发送,框架会自动对其 "),a("code",[t._v("stringify")]),t._v(" 处理。")]),t._v(" "),a("li",[a("code",[t._v("dataType: 'json'")]),t._v(":告知框架应该自动把响应数据解析为 "),a("code",[t._v("JSON")]),t._v(" 对象。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/http.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HttpController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("post")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'https://httpbin.org/post'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 必须指定 method")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 通过 contentType 声明以 JSON 格式发送")]),t._v("\n contentType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n hello"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'world'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n now"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Date"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("now")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 明确告诉 HttpClient 以 JSON 格式处理返回的响应 body")]),t._v("\n dataType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br")])]),a("h3",{attrs:{id:"提交-form-表单"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#提交-form-表单","aria-hidden":"true"}},[t._v("#")]),t._v(" 提交 Form 表单")]),t._v(" "),a("p",[t._v("也有很多接口是面向浏览器设计的,需要通过 "),a("code",[t._v("Form")]),t._v(" 表单方式提交接口。")]),t._v(" "),a("p",[t._v("只需把对应的 "),a("code",[t._v("contentType")]),t._v(" 配置为 "),a("code",[t._v("form")]),t._v(" 即可,框架会自动组装为对应的格式,并通过 "),a("code",[t._v("application/x-www-form-urlencoded")]),t._v(" 提交。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("div",{staticClass:"highlight-lines"},[a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("div",{staticClass:"highlighted"},[t._v(" ")]),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br")]),a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/http.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HttpController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("submit")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'https://httpbin.org/post'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 通过 `form` 格式提交,application/x-www-form-urlencoded")]),t._v("\n contentType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'form'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n now"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Date"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("now")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n foo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'bar'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n dataType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("form"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 响应最终会是类似以下的结果:")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// {")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v('// "foo": "bar",')]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v('// "now": "1483864184348"')]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// }")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br"),a("span",{staticClass:"line-number"},[t._v("22")]),a("br")])]),a("h3",{attrs:{id:"文件上传-multipart"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#文件上传-multipart","aria-hidden":"true"}},[t._v("#")]),t._v(" 文件上传("),a("code",[t._v("Multipart")]),t._v(")")]),t._v(" "),a("p",[t._v("当一个表单提交包含文件的时候,请求数据格式就必须以 "),a("a",{attrs:{href:"http://tools.ietf.org/html/rfc2388",target:"_blank",rel:"noopener noreferrer"}},[t._v("multipart/form-data"),a("OutboundLink")],1),t._v(" 进行提交了。")]),t._v(" "),a("p",[a("a",{attrs:{href:"https://github.com/node-modules/urllib",target:"_blank",rel:"noopener noreferrer"}},[t._v("urllib"),a("OutboundLink")],1),t._v(" 内置了 "),a("a",{attrs:{href:"https://github.com/node-modules/formstream",target:"_blank",rel:"noopener noreferrer"}},[t._v("formstream"),a("OutboundLink")],1),t._v(" 模块来帮助我们生成可以被消费的 "),a("code",[t._v("form")]),t._v(" 对象。")]),t._v(" "),a("p",[t._v("关键配置为:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("files")]),t._v(":需要上传的文件,支持多种形式:\n"),a("ul",[a("li",[t._v("单文件上传:支持直接传递:String 文件路径 / Stream 对象 / Buffer 对象。")]),t._v(" "),a("li",[t._v("多文件上传:数组或 Object 格式,若为后者,则 key 为对应的 fieldName。")])])]),t._v(" "),a("li",[a("code",[t._v("data")]),t._v(":将被转换为对应的 "),a("code",[t._v("form field")]),t._v("。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/http.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HttpController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("upload")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'https://httpbin.org/post'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n dataType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n foo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'bar'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 单文件上传")]),t._v("\n files"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" __filename"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 多文件上传")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// files: {")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// file1: __filename,")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// file2: fs.createReadStream(__filename),")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// file3: Buffer.from('mock file content'),")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// },")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("files"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 响应最终会是类似以下的结果:")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// {")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v('// "file": "\'use strict\';\\n\\nconst For...."')]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// }")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br"),a("span",{staticClass:"line-number"},[t._v("22")]),a("br"),a("span",{staticClass:"line-number"},[t._v("23")]),a("br"),a("span",{staticClass:"line-number"},[t._v("24")]),a("br"),a("span",{staticClass:"line-number"},[t._v("25")]),a("br"),a("span",{staticClass:"line-number"},[t._v("26")]),a("br"),a("span",{staticClass:"line-number"},[t._v("27")]),a("br"),a("span",{staticClass:"line-number"},[t._v("28")]),a("br"),a("span",{staticClass:"line-number"},[t._v("29")]),a("br"),a("span",{staticClass:"line-number"},[t._v("30")]),a("br")])]),a("h3",{attrs:{id:"文件上传-stream"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#文件上传-stream","aria-hidden":"true"}},[t._v("#")]),t._v(" 文件上传("),a("code",[t._v("Stream")]),t._v(")")]),t._v(" "),a("p",[t._v("在 "),a("code",[t._v("Node.js")]),t._v(" 的世界里面,"),a("a",{attrs:{href:"https://nodejs.org/api/stream.html",target:"_blank",rel:"noopener noreferrer"}},[t._v("Stream"),a("OutboundLink")],1),t._v(" 才是主流。")]),t._v(" "),a("p",[t._v("如果服务端支持流式上传,最友好的方式还是直接发送 "),a("code",[t._v("Stream")]),t._v("。")]),t._v(" "),a("p",[a("code",[t._v("Stream")]),t._v(" 实际会以 "),a("code",[t._v("Transfer-Encoding: chunked")]),t._v(" 传输编码格式发送,这个转换是 "),a("a",{attrs:{href:"https://nodejs.org/api/http.html",target:"_blank",rel:"noopener noreferrer"}},[t._v("HTTP"),a("OutboundLink")],1),t._v(" 模块自动实现的。")]),t._v(" "),a("p",[t._v("关键配置为:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("stream")]),t._v(":通过 "),a("code",[t._v("Stream")]),t._v(" 模式发送数据。")]),t._v(" "),a("li",[a("code",[t._v("dataAsQueryString")]),t._v(":可选,需要传递额外的请求参数的场景。")]),t._v(" "),a("li",[a("code",[t._v("data")]),t._v(":可选,会被强制 "),a("code",[t._v("querystring.stringify")]),t._v(" 处理之后拼接到 "),a("code",[t._v("URL")]),t._v(" 的 "),a("code",[t._v("query")]),t._v(" 参数上。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/http.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" fs "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'fs'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" FormStream "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'formstream'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HttpController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("uploadByStream")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 上传当前文件本身用于测试")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" fileStream "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" fs"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("createReadStream")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("__filename"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// httpbin.org 不支持 stream 模式,使用本地 stream 接口代替")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" url "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("protocol"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("://")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("host"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("/stream`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 以 stream 模式提交")]),t._v("\n stream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" fileStream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 额外传递参数")]),t._v("\n dataAsQueryString"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 一般来说都是 access token 之类的权限验证参数")]),t._v("\n accessToken"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'some access token value'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 响应最终会是类似以下的结果:")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v('// {"streamSize":574}')]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br"),a("span",{staticClass:"line-number"},[t._v("22")]),a("br"),a("span",{staticClass:"line-number"},[t._v("23")]),a("br"),a("span",{staticClass:"line-number"},[t._v("24")]),a("br"),a("span",{staticClass:"line-number"},[t._v("25")]),a("br"),a("span",{staticClass:"line-number"},[t._v("26")]),a("br"),a("span",{staticClass:"line-number"},[t._v("27")]),a("br"),a("span",{staticClass:"line-number"},[t._v("28")]),a("br"),a("span",{staticClass:"line-number"},[t._v("29")]),a("br"),a("span",{staticClass:"line-number"},[t._v("30")]),a("br"),a("span",{staticClass:"line-number"},[t._v("31")]),a("br")])]),a("h3",{attrs:{id:"发送-xml"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#发送-xml","aria-hidden":"true"}},[t._v("#")]),t._v(" 发送 XML")]),t._v(" "),a("p",[t._v("此时,可以用 "),a("code",[t._v("content")]),t._v(" 参数代替 "),a("code",[t._v("data")]),t._v(" 参数,框架会原样发送数据。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/http.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HttpController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("xml")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'https://httpbin.org/xml'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 直接发送原始 xml 数据,不需要 HttpClient 做特殊处理")]),t._v("\n content"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'world'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'content-type'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'text/html'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n dataType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br")])]),a("h3",{attrs:{id:"超时时间"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#超时时间","aria-hidden":"true"}},[t._v("#")]),t._v(" 超时时间")]),t._v(" "),a("p",[t._v("请求超时时间,默认是 "),a("code",[t._v("[ 5000, 5000 ]")]),t._v(",即创建连接超时是 5 秒,接收响应超时是 5 秒。")]),t._v(" "),a("p",[t._v("支持 "),a("code",[t._v("Number")]),t._v(" 和 "),a("code",[t._v("[ Number, Number ]")]),t._v(" 格式,前者代表两个时间取同个值。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/http.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HttpController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("timeout")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'https://httpbin.org/timeout'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 创建连接超时 1 秒,接收响应超时 30 秒,用于响应比较大的场景")]),t._v("\n timeout"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("1000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("30000")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n dataType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br")])]),a("h3",{attrs:{id:"处理重定向"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#处理重定向","aria-hidden":"true"}},[t._v("#")]),t._v(" 处理重定向")]),t._v(" "),a("p",[t._v("有些时候,需要对后端的重定向进行跟进处理,框架提供了:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("followRedirect")]),t._v(":是否自动跟进 3xx 的跳转响应,默认是 "),a("code",[t._v("false")]),t._v("。")]),t._v(" "),a("li",[a("code",[t._v("maxRedirects")]),t._v(":最大自动跳转次数,避免死循环,默认是 10 次。 此参数不宜设置过大。")]),t._v(" "),a("li",[a("code",[t._v("formatRedirectUrl(from, to)")]),t._v(":跳转 URL 校正,默认是 "),a("code",[t._v("url.resolve(from, to)")]),t._v("。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/http.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HttpController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("followRedirect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/your_redirect_url'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("formatRedirectUrl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("from")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" to")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 允许跟踪跳转")]),t._v("\n followRedirect"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 最大只允许自动跳转 5 次。")]),t._v("\n maxRedirects"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("5")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 例如可在这里修正跳转不正确的 url")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("to "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("===")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'//foo/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n to "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/foo'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("resolve")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("from")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" to"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br"),a("span",{staticClass:"line-number"},[t._v("22")]),a("br")])]),a("h3",{attrs:{id:"抓包调试"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#抓包调试","aria-hidden":"true"}},[t._v("#")]),t._v(" 抓包调试")]),t._v(" "),a("p",[t._v("有些时候,我们需要抓包来调试对应的 "),a("code",[t._v("HTTP")]),t._v(" 请求。")]),t._v(" "),a("p",[t._v("修改本地开发配置:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.local.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" config "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// add http_proxy to httpclient")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("process"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("env"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("http_proxy"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("httpclient "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n enableProxy"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n rejectUnauthorized"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("false")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n proxy"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" process"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("env"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("http_proxy"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br")])]),a("p",[t._v("使用环境变量启动你的应用:")]),t._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[t._v("$ http_proxy"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v("http://127.0.0.1:8888/"),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("npm")]),t._v(" run dev\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("p",[t._v("然后启动你的抓包工具,如 "),a("a",{attrs:{href:"https://www.charlesproxy.com/",target:"_blank",rel:"noopener noreferrer"}},[t._v("Charles"),a("OutboundLink")],1),t._v(" 或 "),a("a",{attrs:{href:"http://www.telerik.com/fiddler",target:"_blank",rel:"noopener noreferrer"}},[t._v("Fiddler"),a("OutboundLink")],1),t._v(",就可以看到对应的 "),a("code",[t._v("HTTP")]),t._v(" 抓包信息。")]),t._v(" "),a("h3",{attrs:{id:"事件监听"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#事件监听","aria-hidden":"true"}},[t._v("#")]),t._v(" 事件监听")]),t._v(" "),a("p",[t._v("在企业应用场景,常常会有统一 "),a("code",[t._v("Tracer")]),t._v(" 日志的需求。")]),t._v(" "),a("p",[t._v("为了方便在统一监听 "),a("code",[t._v("HttpClient")]),t._v(" 的请求和响应,我们约定了两个事件。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 对请求做拦截,设置一些 trace headers,方便全链路跟踪。")]),t._v("\napp"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("httpclient"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("on")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'request'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("req")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" requestId"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" args"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" req"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("req"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("req"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 仅在 `ctx.curl()` 时才有值,方便记录上游请求信息。")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 例如我们可以设置全局请求 ID,方便日志跟踪")]),t._v("\n req"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'x-request-id'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" uuid"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("v1")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 开启 timing 统计")]),t._v("\n req"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("args"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("timing "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 订阅事件来打印日志")]),t._v("\napp"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("httpclient"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("on")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'response'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("result")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" requestId"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" req"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" res"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" error "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("req"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" res"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("status"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("res"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("timing"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 统计请求各阶段的耗时")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 仅在 `ctx.curl()` 时才有值,方便记录上游请求信息。")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br")])]),a("h2",{attrs:{id:"如何扩展"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#如何扩展","aria-hidden":"true"}},[t._v("#")]),t._v(" 如何扩展")]),t._v(" "),a("p",[t._v("我们跟后端的接口协议,往往会在 "),a("code",[t._v("HTTP")]),t._v(" 上做一层简单的协议封装,如加解密和校验。")]),t._v(" "),a("p",[t._v("如果每次调用 "),a("code",[t._v("HttpClient")]),t._v(" 的时候,都要传递参数和解析协议,未免太麻烦。")]),t._v(" "),a("p",[t._v("此时可以扩展下:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/extend/context.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" rpc "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'../../lib/rpc'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("rpc")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" options")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 提供请求的默认值")]),t._v("\n options "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" Object"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("assign")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n dataType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n contentType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" options"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 发起 HTTP 请求")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" options"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 对后端返回结果进行预处理,如校验、解密等。")]),t._v("\n result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" rpc"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("process")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br")])]),a("p",[t._v("这样,在 "),a("code",[t._v("Controller")]),t._v("、"),a("code",[t._v("Service")]),t._v(" 等地方就可以直接使用了:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("div",{staticClass:"highlight-lines"},[a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("div",{staticClass:"highlighted"},[t._v(" ")]),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br"),a("br")]),a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/http.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HttpController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("post")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 调用对应的扩展方法")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("rpc")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'https://httpbin.org/post'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n hello"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'world'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n now"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Date"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("now")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br")])]),a("h2",{attrs:{id:"编写测试"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写测试","aria-hidden":"true"}},[t._v("#")]),t._v(" 编写测试")]),t._v(" "),a("p",[t._v("对于 "),a("code",[t._v("HttpClient")]),t._v(" 这种关键的请求交互,单元测试就更必不可少。")]),t._v(" "),a("p",[t._v("框架通过 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-mock",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-mock"),a("OutboundLink")],1),t._v(" 提供了 "),a("code",[t._v("app.mockHttpclient(url, method, data)")]),t._v(" 的模拟能力。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token function"}},[t._v("describe")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'GET /httpclient'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should mock httpclient response'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("mockHttpclient")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'https://eggjs.org/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 模拟的参数,可以是 `Buffer/String/JSON`")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 会按照请求时的 `options.dataType` 来做对应的转换")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'mock eggjs.org response'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/httpclient'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'mock eggjs.org response'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br")])]),a("p",[t._v("详见对应的 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-mock#appmockhttpclienturl-method-data",target:"_blank",rel:"noopener noreferrer"}},[t._v("Mock API"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("p",[t._v("具体的单元测试运行方式,参见 "),a("router-link",{attrs:{to:"/zh/workflow/development/unittest.html"}},[t._v("研发流程 - 单元测试")]),t._v(" 文档。")],1),t._v(" "),a("h2",{attrs:{id:"常见错误码"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常见错误码","aria-hidden":"true"}},[t._v("#")]),t._v(" 常见错误码")]),t._v(" "),a("h3",{attrs:{id:"connectiontimeouterror"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#connectiontimeouterror","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ConnectionTimeoutError")])]),t._v(" "),a("ul",[a("li",[t._v("异常名称:"),a("strong",[t._v("创建连接超时")]),t._v(","),a("code",[t._v("ConnectionTimeoutError")])]),t._v(" "),a("li",[t._v("出现场景:通常是 DNS 查询比较慢,或者客户端与服务端之间的网络速度比较慢导致的。")]),t._v(" "),a("li",[t._v("排查建议:请适当增大 "),a("code",[t._v("timeout")]),t._v(" 参数。")])]),t._v(" "),a("h3",{attrs:{id:"responsetimeouterror"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#responsetimeouterror","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ResponseTimeoutError")])]),t._v(" "),a("ul",[a("li",[t._v("异常名称:"),a("strong",[t._v("服务响应超时")]),t._v(","),a("code",[t._v("ResponseTimeoutError")])]),t._v(" "),a("li",[t._v("出现场景:通常是客户端与服务端之间网络速度比较慢,并且响应数据比较大的情况下会发生。")]),t._v(" "),a("li",[t._v("排查建议:请适当增大 "),a("code",[t._v("timeout")]),t._v(" 参数。")])]),t._v(" "),a("h3",{attrs:{id:"econnreset"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#econnreset","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ECONNRESET")])]),t._v(" "),a("ul",[a("li",[t._v("异常名称:"),a("strong",[t._v("服务主动断开连接")]),t._v(","),a("code",[t._v("ResponseError, code: ECONNRESET")])]),t._v(" "),a("li",[t._v("出现场景:通常是服务端主动断开 Socket 连接,导致 HTTP 请求链路异常。")]),t._v(" "),a("li",[t._v("排查建议:请检查当时服务端是否发生网络异常。")])]),t._v(" "),a("h3",{attrs:{id:"econnrefused"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#econnrefused","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ECONNREFUSED")])]),t._v(" "),a("ul",[a("li",[t._v("异常名称:"),a("strong",[t._v("服务不可达")]),t._v(","),a("code",[t._v("RequestError, code: ECONNREFUSED, status: -1")])]),t._v(" "),a("li",[t._v("出现场景:通常是因为请求的 URL 所属 IP 或者端口无法连接成功。")]),t._v(" "),a("li",[t._v("排查建议:请确保 IP 或者端口设置正确,目标网络是通的。")])]),t._v(" "),a("h3",{attrs:{id:"enotfound"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#enotfound","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("ENOTFOUND")])]),t._v(" "),a("ul",[a("li",[t._v("异常名称:"),a("strong",[t._v("域名不存在")]),t._v(","),a("code",[t._v("RequestError, code: ENOTFOUND, status: -1")])]),t._v(" "),a("li",[t._v("出现场景:通常是因为请求的 URL 所在的域名无法通过 DNS 解析成功。")]),t._v(" "),a("li",[t._v("排查建议:请确保域名存在,也需要排查一下 DNS 服务是否配置正确。")])]),t._v(" "),a("h3",{attrs:{id:"jsonresponseformaterror"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#jsonresponseformaterror","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("JSONResponseFormatError")])]),t._v(" "),a("ul",[a("li",[t._v("异常名称:"),a("strong",[t._v("JSON 响应数据解析失败")]),t._v(","),a("code",[t._v("JSONResponseFormatError")])]),t._v(" "),a("li",[t._v("出现场景:设置了 "),a("code",[t._v("dataType=json")]),t._v(",但响应数据不符合 JSON 格式,就会抛出此异常。")]),t._v(" "),a("li",[t._v("排查建议:确保服务端无论在什么情况下都要正确返回 JSON 格式的数据。")])]),t._v(" "),a("p",[t._v("有些 CGI 系统返回的 JSON 数据会包含某些特殊控制字符(U+0000 ~ U+001F),可以通过 "),a("code",[t._v("fixJSONCtlChars")]),t._v(" 参数自动过滤掉它们。")]),t._v(" "),a("h2",{attrs:{id:"options-参数详解"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#options-参数详解","aria-hidden":"true"}},[t._v("#")]),t._v(" Options 参数详解")]),t._v(" "),a("p",[t._v("由于 HTTP 请求的复杂性,导致 "),a("code",[t._v("HttpClient")]),t._v(" 的 "),a("code",[t._v("options")]),t._v(" 参数会非常多。")]),t._v(" "),a("p",[t._v("接下来讲解常用的可选参数的实际用途,更多的参数可以参见 "),a("a",{attrs:{href:"https://github.com/node-modules/urllib",target:"_blank",rel:"noopener noreferrer"}},[t._v("urllib"),a("OutboundLink")],1),t._v(" 文档。")]),t._v(" "),a("h3",{attrs:{id:"默认全局配置"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#默认全局配置","aria-hidden":"true"}},[t._v("#")]),t._v(" 默认全局配置")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("httpclient "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 是否开启本地 DNS 缓存,默认关闭,开启后有两个特性")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 1. 所有的 DNS 查询都会默认优先使用缓存的,即使 DNS 查询错误也不影响应用")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 2. 对同一个域名,在 dnsCacheLookupInterval 的间隔内(默认 10s)只会查询一次")]),t._v("\n enableDNSCache"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("false")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 对同一个域名进行 DNS 查询的最小间隔时间")]),t._v("\n dnsCacheLookupInterval"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("10000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// DNS 同时缓存的最大域名数量,默认 1000")]),t._v("\n dnsCacheMaxLength"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("1000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n\n request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 默认 request 超时时间")]),t._v("\n timeout"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("3000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n\n httpAgent"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 默认开启 http KeepAlive 功能")]),t._v("\n keepAlive"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 空闲的 KeepAlive socket 最长可以存活 4 秒")]),t._v("\n freeSocketTimeout"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("4000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 当 socket 超过 30 秒都没有任何活动,就会被当作超时处理掉")]),t._v("\n timeout"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("30000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 允许创建的最大 socket 数")]),t._v("\n maxSockets"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Number"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("MAX_SAFE_INTEGER")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 最大空闲 socket 数")]),t._v("\n maxFreeSockets"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("256")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n\n httpsAgent"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 默认开启 https KeepAlive 功能")]),t._v("\n keepAlive"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 空闲的 KeepAlive socket 最长可以存活 4 秒")]),t._v("\n freeSocketTimeout"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("4000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 当 socket 超过 30 秒都没有任何活动,就会被当作超时处理掉")]),t._v("\n timeout"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("30000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 允许创建的最大 socket 数")]),t._v("\n maxSockets"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Number"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("MAX_SAFE_INTEGER")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 最大空闲 socket 数")]),t._v("\n maxFreeSockets"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("256")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br"),a("span",{staticClass:"line-number"},[t._v("22")]),a("br"),a("span",{staticClass:"line-number"},[t._v("23")]),a("br"),a("span",{staticClass:"line-number"},[t._v("24")]),a("br"),a("span",{staticClass:"line-number"},[t._v("25")]),a("br"),a("span",{staticClass:"line-number"},[t._v("26")]),a("br"),a("span",{staticClass:"line-number"},[t._v("27")]),a("br"),a("span",{staticClass:"line-number"},[t._v("28")]),a("br"),a("span",{staticClass:"line-number"},[t._v("29")]),a("br"),a("span",{staticClass:"line-number"},[t._v("30")]),a("br"),a("span",{staticClass:"line-number"},[t._v("31")]),a("br"),a("span",{staticClass:"line-number"},[t._v("32")]),a("br"),a("span",{staticClass:"line-number"},[t._v("33")]),a("br"),a("span",{staticClass:"line-number"},[t._v("34")]),a("br"),a("span",{staticClass:"line-number"},[t._v("35")]),a("br"),a("span",{staticClass:"line-number"},[t._v("36")]),a("br"),a("span",{staticClass:"line-number"},[t._v("37")]),a("br"),a("span",{staticClass:"line-number"},[t._v("38")]),a("br"),a("span",{staticClass:"line-number"},[t._v("39")]),a("br"),a("span",{staticClass:"line-number"},[t._v("40")]),a("br"),a("span",{staticClass:"line-number"},[t._v("41")]),a("br"),a("span",{staticClass:"line-number"},[t._v("42")]),a("br")])]),a("p",[t._v("应用可以通过 "),a("code",[t._v("config/config.default.js")]),t._v(" 覆盖此配置。")]),t._v(" "),a("h3",{attrs:{id:"method-string"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#method-string","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("method: String")])]),t._v(" "),a("p",[a("code",[t._v("HTTP")]),t._v(" 请求方法,默认是 "),a("code",[t._v("GET")]),t._v(",全大写格式,支持"),a("a",{attrs:{href:"https://nodejs.org/api/http.html#http_http_methods",target:"_blank",rel:"noopener noreferrer"}},[t._v("所有 HTTP 方法"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("h3",{attrs:{id:"data-object"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#data-object","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("data: Object")])]),t._v(" "),a("p",[t._v("需要发送的请求数据,会根据 "),a("code",[t._v("method")]),t._v(" 自动选择正确的数据处理方式。")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("GET")]),t._v(","),a("code",[t._v("HEAD")]),t._v(":通过 "),a("code",[t._v("querystring.stringify(data)")]),t._v(" 处理后拼接到 "),a("code",[t._v("URL")]),t._v(" 的查询参数上。")]),t._v(" "),a("li",[a("code",[t._v("POST")]),t._v(","),a("code",[t._v("PUT")]),t._v(" 和 "),a("code",[t._v("DELETE")]),t._v(" 等:需要根据 "),a("code",[t._v("contentType")]),t._v(" 做进一步判断处理。\n"),a("ul",[a("li",[a("code",[t._v("contentType = json")]),t._v(":通过 "),a("code",[t._v("JSON.stringify(data)")]),t._v(" 处理,并通过请求 body 发送。")]),t._v(" "),a("li",[t._v("其他:通过 "),a("code",[t._v("querystring.stringify(data)")]),t._v(" 处理,并通过请求 body 发送。")])])])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// GET + Query, `/api/user?foo=bar`")]),t._v("\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" foo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'bar'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// POST + Form + body")]),t._v("\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" foo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'bar'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// POST + JSON + body")]),t._v("\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n contentType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" foo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'bar'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br")])]),a("h3",{attrs:{id:"contenttype-string"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#contenttype-string","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("contentType: String")])]),t._v(" "),a("p",[t._v("设置请求数据格式,支持 "),a("code",[t._v("json")]),t._v(" 和 "),a("code",[t._v("form")]),t._v(",决定了请求数据的序列化格式。")]),t._v(" "),a("p",[t._v("如需要以 JSON 格式发送 "),a("code",[t._v("data")]),t._v(":")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n foo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'bar'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n now"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Date"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("now")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n contentType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br")])]),a("h3",{attrs:{id:"datatype-string"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#datatype-string","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("dataType: String")])]),t._v(" "),a("p",[t._v("设置响应数据格式,默认不对响应数据做任何处理,直接返回原始的 buffer 格式数据。")]),t._v(" "),a("p",[t._v("支持 "),a("code",[t._v("text")]),t._v(" 和 "),a("code",[t._v("json")]),t._v(" 两种取值。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" jsonResult "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n dataType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'json'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\nconsole"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("jsonResult"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" htmlResult "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n dataType"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'text'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\nconsole"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("htmlResult"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("注意")]),t._v(" "),a("p",[t._v("设置成 "),a("code",[t._v("json")]),t._v(" 时,如果响应数据解析失败会抛 "),a("code",[t._v("JSONResponseFormatError")]),t._v(" 异常。")])]),t._v(" "),a("h3",{attrs:{id:"dataasquerystring-boolean"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#dataasquerystring-boolean","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("dataAsQueryString: Boolean")])]),t._v(" "),a("p",[t._v("如果设置为 "),a("code",[t._v("true")]),t._v(",那么即使在 POST 情况下,也会强制将 "),a("code",[t._v("options.data")]),t._v(" 以 "),a("code",[t._v("querystring.stringify")]),t._v(" 处理之后拼接到 "),a("code",[t._v("URL")]),t._v(" 的查询参数上。")]),t._v(" "),a("p",[t._v("可以很好地解决以 "),a("code",[t._v("stream")]),t._v(" 发送数据,且额外的请求参数以 "),a("code",[t._v("URL Query")]),t._v(" 形式传递的应用场景:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n dataAsQueryString"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 一般来说都是 access token 之类的权限验证参数")]),t._v("\n accessToken"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'some access token value'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n stream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" myFileStream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("h3",{attrs:{id:"content-string-buffer"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#content-string-buffer","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("content: String|Buffer")])]),t._v(" "),a("p",[t._v("发送请求正文,如果设置了此参数,那么会直接忽略 "),a("code",[t._v("data")]),t._v(" 参数。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 直接发送原始 xml 数据,不需要 HttpClient 做特殊处理")]),t._v("\n content"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'world'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'content-type'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'text/html'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br")])]),a("h3",{attrs:{id:"headers-object"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#headers-object","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("headers: Object")])]),t._v(" "),a("p",[t._v("自定义请求头。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'x-foo'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'bar'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("h3",{attrs:{id:"timeout-number-array"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#timeout-number-array","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("timeout: Number|Array")])]),t._v(" "),a("p",[t._v("请求超时时间,默认是 "),a("code",[t._v("[ 5000, 5000 ]")]),t._v(",即创建连接超时是 5 秒,接收响应超时是 5 秒。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 创建连接超时 3 秒,接收响应超时 3 秒")]),t._v("\n timeout"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("3000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 创建连接超时 1 秒,接收响应超时 30 秒,用于响应比较大的场景")]),t._v("\n timeout"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("1000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("30000")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("h3",{attrs:{id:"files-mixed"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#files-mixed","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("files: Mixed")])]),t._v(" "),a("p",[t._v("文件上传,支持格式: "),a("code",[t._v("String | ReadStream | Buffer | Array | Object")]),t._v("。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n files"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/path/to/read'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n foo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'other fields'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[t._v("多文件上传:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n files"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n file1"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/path/to/read'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n file2"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" fs"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("createReadStream")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("__filename"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n file3"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" Buffer"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("from")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'mock file content'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n foo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'other fields'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br")])]),a("h3",{attrs:{id:"stream-readstream"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#stream-readstream","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("stream: ReadStream")])]),t._v(" "),a("p",[t._v("设置发送请求正文的可读数据流,一旦设置了此参数,将会忽略 "),a("code",[t._v("data")]),t._v(" 和 "),a("code",[t._v("content")]),t._v("。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n stream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" fs"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("createReadStream")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/path/to/read'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("h3",{attrs:{id:"writestream-writestream"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#writestream-writestream","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("writeStream: WriteStream")])]),t._v(" "),a("p",[t._v("设置接受响应数据的可写数据流,默认是 "),a("code",[t._v("null")]),t._v("。\n一旦设置此参数,那么返回值 "),a("code",[t._v("result.data")]),t._v(" 将会被设置为 "),a("code",[t._v("null")]),t._v(",\n因为数据已经全部写入到 "),a("code",[t._v("writeStream")]),t._v(" 中了。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n writeStream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" fs"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("createWriteStream")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/path/to/store'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("注意事项")]),t._v(" "),a("p",[t._v("请在你充分理解 "),a("code",[t._v("Stream")]),t._v(" 和 "),a("code",[t._v("异步编程")]),t._v(" 的基础上,再使用。")])]),t._v(" "),a("h3",{attrs:{id:"streaming-boolean"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#streaming-boolean","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("streaming: Boolean")])]),t._v(" "),a("p",[t._v("是否直接返回响应流。")]),t._v(" "),a("p",[t._v("开启后会在拿到响应对象 "),a("code",[t._v("res")]),t._v(" 时马上返回,此时 "),a("code",[t._v("headers")]),t._v(" 和 "),a("code",[t._v("status")]),t._v(" 已经可以读取到,但还没有读取 "),a("code",[t._v("data")]),t._v(" 数据。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n streaming"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\nconsole"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("status"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// result.res 是一个 ReadStream 对象")]),t._v("\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("res"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("注意")]),t._v(" "),a("p",[t._v("若 res 不是直接传递给 body,那么我们必须消费这个 stream,并且要做好 error 事件处理。")])]),t._v(" "),a("h3",{attrs:{id:"beforerequest-function-options"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#beforerequest-function-options","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("beforeRequest: Function(options)")])]),t._v(" "),a("p",[t._v("在请求正式发送之前,会尝试调用 "),a("code",[t._v("beforeRequest")]),t._v(" 钩子,允许我们在这里对请求参数做最后一次修改。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("beforeRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("options")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 例如我们可以设置全局请求 id,方便日志跟踪")]),t._v("\n options"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("headers"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'x-request-id'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" uuid"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("v1")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("h3",{attrs:{id:"gzip-boolean"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#gzip-boolean","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("gzip: Boolean")])]),t._v(" "),a("p",[t._v("是否支持 "),a("code",[t._v("gzip")]),t._v(" 响应格式,开启后将自动设置 "),a("code",[t._v("Accept-Encoding: gzip")]),t._v(" 请求头,\n并且会自动解压带 "),a("code",[t._v("Content-Encoding: gzip")]),t._v(" 响应头的数据。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n gzip"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br")])]),a("h3",{attrs:{id:"timing-boolean"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#timing-boolean","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("timing: Boolean")])]),t._v(" "),a("p",[t._v("是否开启请求各阶段的时间测量。")]),t._v(" "),a("p",[t._v("开启后可以通过 "),a("code",[t._v("result.res.timing")]),t._v(" 拿到这次 HTTP 请求各阶段的时间测量值(单位是毫秒)。")]),t._v(" "),a("p",[t._v("通过这些测量值,我们可以非常方便地定位到这次请求最慢的环境发生在那个阶段,效果如同 "),a("code",[t._v("Chrome Network Timing")]),t._v(" 的作用。")]),t._v(" "),a("p",[t._v("各阶段测量值:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("queuing")]),t._v(":分配 "),a("code",[t._v("Socket")]),t._v(" 耗时。")]),t._v(" "),a("li",[a("code",[t._v("dnslookup")]),t._v(":"),a("code",[t._v("DNS")]),t._v(" 查询耗时。")]),t._v(" "),a("li",[a("code",[t._v("connected")]),t._v(":"),a("code",[t._v("Socket")]),t._v(" 三次握手连接成功耗时。")]),t._v(" "),a("li",[a("code",[t._v("requestSent")]),t._v(":请求数据完整发送完毕耗时。")]),t._v(" "),a("li",[a("code",[t._v("waiting")]),t._v(":收到第一个字节的响应数据耗时。")]),t._v(" "),a("li",[a("code",[t._v("contentDownload")]),t._v(":全部响应数据接收完毕耗时。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n timing"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\nconsole"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("res"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("timing"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// {")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v('// "queuing":29,')]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v('// "dnslookup":37,')]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v('// "connected":370,')]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v('// "requestSent":1001,')]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v('// "waiting":1833,')]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v('// "contentDownload":3416')]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// }")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br")])]),a("h3",{attrs:{id:"https-相关参数"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#https-相关参数","aria-hidden":"true"}},[t._v("#")]),t._v(" HTTPS 相关参数")]),t._v(" "),a("p",[t._v("包括 "),a("code",[t._v("key")]),t._v("、"),a("code",[t._v("cert")]),t._v("、"),a("code",[t._v("passphrase")]),t._v(" 等参数,都将透传给 "),a("a",{attrs:{href:"https://nodejs.org/api/https.html",target:"_blank",rel:"noopener noreferrer"}},[t._v("HTTPS"),a("OutboundLink")],1),t._v(" 模块。")]),t._v(" "),a("p",[t._v("其中 "),a("code",[t._v("rejectUnauthorized")]),t._v(" 用于在本地调试时忽略无效的 HTTPS 证书。")]),t._v(" "),a("p",[t._v("具体请查看 "),a("a",{attrs:{href:"https://nodejs.org/api/https.html#https_https_request_options_callback",target:"_blank",rel:"noopener noreferrer"}},[a("code",[t._v("https.request()")]),a("OutboundLink")],1),t._v(" 文档。")]),t._v(" "),a("h2",{attrs:{id:"示例代码"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#示例代码","aria-hidden":"true"}},[t._v("#")]),t._v(" 示例代码")]),t._v(" "),a("p",[t._v("完整示例代码可以在 "),a("a",{attrs:{href:"https://github.com/eggjs/examples/blob/master/httpclient",target:"_blank",rel:"noopener noreferrer"}},[t._v("eggjs/examples/httpclient"),a("OutboundLink")],1),t._v(" 找到。")])])},[],!1,null,null,null);s.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/23.4a400231.js b/assets/js/23.4a400231.js new file mode 100644 index 0000000..88d23f8 --- /dev/null +++ b/assets/js/23.4a400231.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[23],{61:function(s,a,t){"use strict";t.r(a);var n=t(0),e=Object(n.a)({},function(){var s=this,a=s.$createElement,t=s._self._c||a;return t("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[t("h2",{attrs:{id:"使用场景"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用场景")]),s._v(" "),t("p",[s._v("为了方便开发多语言应用,框架内置了国际化("),t("code",[s._v("I18n")]),s._v(")支持,由 "),t("a",{attrs:{href:"https://github.com/eggjs/egg-i18n",target:"_blank",rel:"noopener noreferrer"}},[s._v("egg-i18n"),t("OutboundLink")],1),s._v(" 插件提供。")]),s._v(" "),t("blockquote",[t("p",[t("code",[s._v("i18n")]),s._v(" 是 "),t("code",[s._v("internationalization")]),s._v(" 的缩写,代表 "),t("code",[s._v("i")]),s._v(" 和 "),t("code",[s._v("n")]),s._v(" 之间有 18 个字母。")])]),s._v(" "),t("ul",[t("li",[s._v("开发者需定义多个 "),t("code",[s._v("locale")]),s._v(" 多语言文件。")]),s._v(" "),t("li",[s._v("开发者在 "),t("code",[s._v("Controller")]),s._v(" 或 "),t("code",[s._v("View")]),s._v(" 中使用对应的语法糖渲染字符串。")]),s._v(" "),t("li",[s._v("插件会根据约定,渲染指定的多语言字符串。")])]),s._v(" "),t("h2",{attrs:{id:"定义-locale"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#定义-locale","aria-hidden":"true"}},[s._v("#")]),s._v(" 定义 "),t("code",[s._v("locale")])]),s._v(" "),t("h3",{attrs:{id:"目录规范"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#目录规范","aria-hidden":"true"}},[s._v("#")]),s._v(" 目录规范")]),s._v(" "),t("p",[s._v("多种语言的配置是独立的,统一存放在 "),t("code",[s._v("config/locale/*.js")]),s._v(" 下。")]),s._v(" "),t("div",{staticClass:"language-bash line-numbers-mode"},[t("pre",{pre:!0,attrs:{class:"language-bash"}},[t("code",[s._v("showcase\n└── config\n ├── plugin.js\n ├── config.default.js\n └── locale\n ├── en-US.js\n └── zh-CN.js\n")])]),s._v(" "),t("div",{staticClass:"line-numbers-wrapper"},[t("span",{staticClass:"line-number"},[s._v("1")]),t("br"),t("span",{staticClass:"line-number"},[s._v("2")]),t("br"),t("span",{staticClass:"line-number"},[s._v("3")]),t("br"),t("span",{staticClass:"line-number"},[s._v("4")]),t("br"),t("span",{staticClass:"line-number"},[s._v("5")]),t("br"),t("span",{staticClass:"line-number"},[s._v("6")]),t("br"),t("span",{staticClass:"line-number"},[s._v("7")]),t("br")])]),t("p",[s._v("不仅对于应用目录生效,在框架,插件的 "),t("code",[s._v("config/locale")]),s._v(" 目录下同样生效。")]),s._v(" "),t("div",{staticClass:"warning custom-block"},[t("p",{staticClass:"custom-block-title"},[s._v("友情提示")]),s._v(" "),t("p",[s._v("注意单词拼写,是 locale 不是 locals。")])]),s._v(" "),t("h3",{attrs:{id:"文件格式"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#文件格式","aria-hidden":"true"}},[s._v("#")]),s._v(" 文件格式")]),s._v(" "),t("p",[s._v("支持 "),t("code",[s._v("js")]),s._v(" 和 "),t("code",[s._v("JSON")]),s._v(" 两种格式:")]),s._v(" "),t("div",{staticClass:"language-js line-numbers-mode"},[t("pre",{pre:!0,attrs:{class:"language-js"}},[t("code",[t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/locale/zh-CN.js")]),s._v("\nmodule"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),t("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n Email"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'邮箱'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),t("div",{staticClass:"line-numbers-wrapper"},[t("span",{staticClass:"line-number"},[s._v("1")]),t("br"),t("span",{staticClass:"line-number"},[s._v("2")]),t("br"),t("span",{staticClass:"line-number"},[s._v("3")]),t("br"),t("span",{staticClass:"line-number"},[s._v("4")]),t("br")])]),t("p",[s._v("或")]),s._v(" "),t("div",{staticClass:"language-js line-numbers-mode"},[t("pre",{pre:!0,attrs:{class:"language-js"}},[t("code",[t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/locale/zh-CN.json")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v('"Email"')]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v('"邮箱"')]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),t("div",{staticClass:"line-numbers-wrapper"},[t("span",{staticClass:"line-number"},[s._v("1")]),t("br"),t("span",{staticClass:"line-number"},[s._v("2")]),t("br"),t("span",{staticClass:"line-number"},[s._v("3")]),t("br"),t("span",{staticClass:"line-number"},[s._v("4")]),t("br")])]),t("h3",{attrs:{id:"占位符"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#占位符","aria-hidden":"true"}},[s._v("#")]),s._v(" 占位符")]),s._v(" "),t("p",[s._v("支持类似 "),t("a",{attrs:{href:"https://nodejs.org/api/util.html#util_util_format_format_args",target:"_blank",rel:"noopener noreferrer"}},[t("code",[s._v("util.format()")]),t("OutboundLink")],1),s._v(" 的 "),t("code",[s._v("%s")]),s._v(","),t("code",[s._v("%j")]),s._v(" 等占位符语法。")]),s._v(" "),t("div",{staticClass:"language-js line-numbers-mode"},[t("pre",{pre:!0,attrs:{class:"language-js"}},[t("code",[t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/locale/zh-CN.js")]),s._v("\nmodule"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),t("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Welcome back, %s!'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'欢迎回来,%s!'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\nctx"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),t("span",{pre:!0,attrs:{class:"token function"}},[s._v("__")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Welcome back, %s!'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Shawn'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// zh-CN => 欢迎回来,Shawn!")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// en-US => Welcome back, Shawn!")]),s._v("\n")])]),s._v(" "),t("div",{staticClass:"line-numbers-wrapper"},[t("span",{staticClass:"line-number"},[s._v("1")]),t("br"),t("span",{staticClass:"line-number"},[s._v("2")]),t("br"),t("span",{staticClass:"line-number"},[s._v("3")]),t("br"),t("span",{staticClass:"line-number"},[s._v("4")]),t("br"),t("span",{staticClass:"line-number"},[s._v("5")]),t("br"),t("span",{staticClass:"line-number"},[s._v("6")]),t("br"),t("span",{staticClass:"line-number"},[s._v("7")]),t("br"),t("span",{staticClass:"line-number"},[s._v("8")]),t("br")])]),t("p",[s._v("同时支持数组下标占位符方式,例如:")]),s._v(" "),t("div",{staticClass:"language-js line-numbers-mode"},[t("pre",{pre:!0,attrs:{class:"language-js"}},[t("code",[t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/locale/zh-CN.js")]),s._v("\nmodule"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),t("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Hello {0}! My name is {1}.'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'你好 {0}! 我的名字叫 {1}。'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\nctx"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),t("span",{pre:!0,attrs:{class:"token function"}},[s._v("__")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Hello {0}! My name is {1}.'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'foo'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'bar'")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// zh-CN => 你好 foo!我的名字叫 bar。")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// en-US => Hello foo! My name is bar.")]),s._v("\n")])]),s._v(" "),t("div",{staticClass:"line-numbers-wrapper"},[t("span",{staticClass:"line-number"},[s._v("1")]),t("br"),t("span",{staticClass:"line-number"},[s._v("2")]),t("br"),t("span",{staticClass:"line-number"},[s._v("3")]),t("br"),t("span",{staticClass:"line-number"},[s._v("4")]),t("br"),t("span",{staticClass:"line-number"},[s._v("5")]),t("br"),t("span",{staticClass:"line-number"},[s._v("6")]),t("br"),t("span",{staticClass:"line-number"},[s._v("7")]),t("br"),t("span",{staticClass:"line-number"},[s._v("8")]),t("br")])]),t("h2",{attrs:{id:"使用-i18n"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#使用-i18n","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用 "),t("code",[s._v("i18n")])]),s._v(" "),t("h3",{attrs:{id:"ctx-key-values"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#ctx-key-values","aria-hidden":"true"}},[s._v("#")]),s._v(" "),t("code",[s._v("ctx.__(key, ...values)")])]),s._v(" "),t("p",[s._v("插件提供了 "),t("code",[s._v("ctx.__(key, ...values)")]),s._v(" 来获取语言配置。")]),s._v(" "),t("p",[s._v("它等价于 "),t("code",[s._v("ctx.gettext(key, ...values)")]),s._v("。")]),s._v(" "),t("div",{staticClass:"language-js line-numbers-mode"},[t("pre",{pre:!0,attrs:{class:"language-js"}},[t("code",[t("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("HomeController")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token function"}},[s._v("index")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" ctx "),t("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("ctx"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),t("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// zh-CN => 邮箱")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// en-US => Email")]),s._v("\n email"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" ctx"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),t("span",{pre:!0,attrs:{class:"token function"}},[s._v("__")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Email'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n\n "),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// zh-CN => 欢迎回来,Shawn!")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// en-US => Welcome back, Shawn!")]),s._v("\n message"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" ctx"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),t("span",{pre:!0,attrs:{class:"token function"}},[s._v("__")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Welcome back, %s!'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Shawn'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n\n "),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// zh-CN => 你好 foo!我的名字叫 bar。")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// en-US => Hello foo! My name is bar.")]),s._v("\n descriptions"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" ctx"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),t("span",{pre:!0,attrs:{class:"token function"}},[s._v("__")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Hello {0}! My name is {1}.'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'foo'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'bar'")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),t("div",{staticClass:"line-numbers-wrapper"},[t("span",{staticClass:"line-number"},[s._v("1")]),t("br"),t("span",{staticClass:"line-number"},[s._v("2")]),t("br"),t("span",{staticClass:"line-number"},[s._v("3")]),t("br"),t("span",{staticClass:"line-number"},[s._v("4")]),t("br"),t("span",{staticClass:"line-number"},[s._v("5")]),t("br"),t("span",{staticClass:"line-number"},[s._v("6")]),t("br"),t("span",{staticClass:"line-number"},[s._v("7")]),t("br"),t("span",{staticClass:"line-number"},[s._v("8")]),t("br"),t("span",{staticClass:"line-number"},[s._v("9")]),t("br"),t("span",{staticClass:"line-number"},[s._v("10")]),t("br"),t("span",{staticClass:"line-number"},[s._v("11")]),t("br"),t("span",{staticClass:"line-number"},[s._v("12")]),t("br"),t("span",{staticClass:"line-number"},[s._v("13")]),t("br"),t("span",{staticClass:"line-number"},[s._v("14")]),t("br"),t("span",{staticClass:"line-number"},[s._v("15")]),t("br"),t("span",{staticClass:"line-number"},[s._v("16")]),t("br"),t("span",{staticClass:"line-number"},[s._v("17")]),t("br"),t("span",{staticClass:"line-number"},[s._v("18")]),t("br")])]),t("h3",{attrs:{id:"在-view-中使用"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#在-view-中使用","aria-hidden":"true"}},[s._v("#")]),s._v(" 在 View 中使用")]),s._v(" "),t("p",[s._v("插件也同时把对应的语法糖注入到 "),t("code",[s._v("ctx.locals")]),s._v(" 上,因此也可以直接在模板引擎里面使用:")]),s._v(" "),t("div",{staticClass:"language-html line-numbers-mode"},[t("pre",{pre:!0,attrs:{class:"language-html"}},[t("code",[t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("<")]),s._v("li")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(">")])]),s._v("{{ __('Email') }}: {{ user.email }}"),t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("")])]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("<")]),s._v("li")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(">")])]),s._v("\n {{ __('Welcome back, %s!', user.name) }}\n"),t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("")])]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("<")]),s._v("li")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(">")])]),s._v("\n {{ __('Hello {0}! My name is {1}.', ['foo', 'bar']) }}\n"),t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token tag"}},[t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("")])]),s._v("\n")])]),s._v(" "),t("div",{staticClass:"line-numbers-wrapper"},[t("span",{staticClass:"line-number"},[s._v("1")]),t("br"),t("span",{staticClass:"line-number"},[s._v("2")]),t("br"),t("span",{staticClass:"line-number"},[s._v("3")]),t("br"),t("span",{staticClass:"line-number"},[s._v("4")]),t("br"),t("span",{staticClass:"line-number"},[s._v("5")]),t("br"),t("span",{staticClass:"line-number"},[s._v("6")]),t("br"),t("span",{staticClass:"line-number"},[s._v("7")]),t("br")])]),t("h2",{attrs:{id:"切换语言"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#切换语言","aria-hidden":"true"}},[s._v("#")]),s._v(" 切换语言")]),s._v(" "),t("h3",{attrs:{id:"默认语言"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#默认语言","aria-hidden":"true"}},[s._v("#")]),s._v(" 默认语言")]),s._v(" "),t("p",[s._v("默认语言是 "),t("code",[s._v("en-US")]),s._v("。假设我们想修改默认语言为简体中文:")]),s._v(" "),t("div",{staticClass:"language-js line-numbers-mode"},[t("pre",{pre:!0,attrs:{class:"language-js"}},[t("code",[t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nexports"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("i18n "),t("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n defaultLocale"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'zh-CN'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),t("div",{staticClass:"line-numbers-wrapper"},[t("span",{staticClass:"line-number"},[s._v("1")]),t("br"),t("span",{staticClass:"line-number"},[s._v("2")]),t("br"),t("span",{staticClass:"line-number"},[s._v("3")]),t("br"),t("span",{staticClass:"line-number"},[s._v("4")]),t("br")])]),t("h3",{attrs:{id:"切换语言-2"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#切换语言-2","aria-hidden":"true"}},[s._v("#")]),s._v(" 切换语言")]),s._v(" "),t("p",[s._v("插件也会根据请求参数不同,自动选择指定的语言。")]),s._v(" "),t("p",[s._v("修改后会记录到 "),t("code",[s._v("locale")]),s._v(" 这个 "),t("code",[s._v("Cookie")]),s._v(",下次请求直接用设定好的语言。")]),s._v(" "),t("p",[s._v("优先级从高到低:")]),s._v(" "),t("ol",[t("li",[t("code",[s._v("Query")]),s._v(": "),t("code",[s._v("/?locale=en-US")])]),s._v(" "),t("li",[t("code",[s._v("Cookie")]),s._v(": "),t("code",[s._v("locale=zh-TW")])]),s._v(" "),t("li",[t("code",[s._v("Header")]),s._v(": "),t("code",[s._v("Accept-Language: zh-CN,zh;q=0.5")])])]),s._v(" "),t("p",[s._v("如果想修改 "),t("code",[s._v("Query")]),s._v(" 或者 "),t("code",[s._v("Cookie")]),s._v(" 参数名称:")]),s._v(" "),t("div",{staticClass:"language-js line-numbers-mode"},[t("pre",{pre:!0,attrs:{class:"language-js"}},[t("code",[t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nexports"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("i18n "),t("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n queryField"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'locale'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n cookieField"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'locale'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),t("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// Cookie 默认一年后过期, 如果设置为 Number,则单位为 ms")]),s._v("\n cookieMaxAge"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),t("span",{pre:!0,attrs:{class:"token string"}},[s._v("'1y'")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),t("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),t("div",{staticClass:"line-numbers-wrapper"},[t("span",{staticClass:"line-number"},[s._v("1")]),t("br"),t("span",{staticClass:"line-number"},[s._v("2")]),t("br"),t("span",{staticClass:"line-number"},[s._v("3")]),t("br"),t("span",{staticClass:"line-number"},[s._v("4")]),t("br"),t("span",{staticClass:"line-number"},[s._v("5")]),t("br"),t("span",{staticClass:"line-number"},[s._v("6")]),t("br"),t("span",{staticClass:"line-number"},[s._v("7")]),t("br")])]),t("h2",{attrs:{id:"局限性"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#局限性","aria-hidden":"true"}},[s._v("#")]),s._v(" 局限性")]),s._v(" "),t("p",[s._v("一般来说,国际化是需要有配套的运营后台的,该插件只是一个简化的实现,开发者根据具体情况选择使用。")])])},[],!1,null,null,null);a.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/24.e0d88e74.js b/assets/js/24.e0d88e74.js new file mode 100644 index 0000000..f3c5dbe --- /dev/null +++ b/assets/js/24.e0d88e74.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[24],{63:function(s,t,a){"use strict";a.r(t);var n=a(0),e=Object(n.a)({},function(){var s=this,t=s.$createElement,a=s._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用场景")]),s._v(" "),a("p",[s._v("我们常常需要在应用启动期间进行一些初始化工作,在本文我们将一起理解下框架的生命周期。")]),s._v(" "),a("p",[s._v("框架约定可以通过 "),a("code",[s._v("app.js")]),s._v(" 来编写 "),a("code",[s._v("Boot")]),s._v(" 类来注入 "),a("code",[s._v("Hook")]),s._v("。")]),s._v(" "),a("p",[s._v("提供了以下"),a("code",[s._v("生命周期")]),s._v(" 的 "),a("code",[s._v("Hook")]),s._v(":")]),s._v(" "),a("ul",[a("li",[a("code",[s._v("configWillLoad")]),s._v(":配置文件即将加载,这是最后动态修改配置的时机。")]),s._v(" "),a("li",[a("code",[s._v("configDidLoad")]),s._v(":配置文件加载完成。")]),s._v(" "),a("li",[a("code",[s._v("didLoad")]),s._v(":文件加载完成。")]),s._v(" "),a("li",[a("code",[s._v("willReady")]),s._v(":插件启动完毕,用于定义前置操作。")]),s._v(" "),a("li",[a("code",[s._v("didReady")]),s._v(":应用启动完毕。")]),s._v(" "),a("li",[a("code",[s._v("serverDidReady")]),s._v(":"),a("code",[s._v("Server")]),s._v(" 启动完毕,可以开始导入流量。")]),s._v(" "),a("li",[a("code",[s._v("beforeClose")]),s._v(":应用即将关闭。")])]),s._v(" "),a("h2",{attrs:{id:"定义生命周期"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#定义生命周期","aria-hidden":"true"}},[s._v("#")]),s._v(" 定义生命周期")]),s._v(" "),a("p",[s._v("我们可以通过 "),a("code",[s._v("app.js")]),s._v(" 来挂载各个点的 "),a("code",[s._v("Hook")]),s._v("。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("AppBootHook")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("constructor")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 配置文件已读取合并但还未生效,修改配置的最后时机,仅支持同步操作。")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("configWillLoad")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 所有配置已经加载完毕,用于自定义 Loader 挂载。")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("configDidLoad")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 插件的初始化")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("didLoad")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 所有插件启动完毕,用于做应用启动成功前的一些必须的前置操作。")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("willReady")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 应用已经启动完毕,可以用于做一些初始化工作。")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("didReady")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// Server 已经启动成功,可以开始导入流量,处理外部请求。")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("serverDidReady")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 应用即将关闭前")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("beforeClose")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br"),a("span",{staticClass:"line-number"},[s._v("15")]),a("br"),a("span",{staticClass:"line-number"},[s._v("16")]),a("br"),a("span",{staticClass:"line-number"},[s._v("17")]),a("br"),a("span",{staticClass:"line-number"},[s._v("18")]),a("br"),a("span",{staticClass:"line-number"},[s._v("19")]),a("br"),a("span",{staticClass:"line-number"},[s._v("20")]),a("br"),a("span",{staticClass:"line-number"},[s._v("21")]),a("br"),a("span",{staticClass:"line-number"},[s._v("22")]),a("br"),a("span",{staticClass:"line-number"},[s._v("23")]),a("br"),a("span",{staticClass:"line-number"},[s._v("24")]),a("br"),a("span",{staticClass:"line-number"},[s._v("25")]),a("br"),a("span",{staticClass:"line-number"},[s._v("26")]),a("br"),a("span",{staticClass:"line-number"},[s._v("27")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("注意")]),s._v(" "),a("p",[s._v("在自定义生命周期函数中不建议做太耗时的操作,框架会有启动的超时检测。")])]),s._v(" "),a("h2",{attrs:{id:"详解生命周期"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#详解生命周期","aria-hidden":"true"}},[s._v("#")]),s._v(" 详解生命周期")]),s._v(" "),a("h3",{attrs:{id:"configwillload"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#configwillload","aria-hidden":"true"}},[s._v("#")]),s._v(" "),a("code",[s._v("configWillLoad()")])]),s._v(" "),a("p",[s._v("此时"),a("router-link",{attrs:{to:"/zh/guide/config.html"}},[s._v("配置文件")]),s._v("已经被读取并合并,但是还并未生效,"),a("strong",[s._v("这是应用层修改配置的最后时机")]),s._v("。")],1),s._v(" "),a("p",[s._v("使用场景举例:")]),s._v(" "),a("ul",[a("li",[s._v("对配置中的秘钥进行解密。")]),s._v(" "),a("li",[s._v("修改框架内置中间件顺序。")])]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("AppBootHook")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("constructor")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 注意:此函数只支持同步调用")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("configWillLoad")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 此时 config 文件已经被读取并合并,但是还并未生效,这是修改配置的最后时机")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 例如:参数中的密码是加密的,在此处进行解密")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("mysql"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("password "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("decrypt")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("mysql"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("password"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("注意事项")]),s._v(" "),a("p",[s._v("此 "),a("code",[s._v("Hook")]),s._v(" 现在只支持同步调用。")])]),s._v(" "),a("h3",{attrs:{id:"configdidload"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#configdidload","aria-hidden":"true"}},[s._v("#")]),s._v(" "),a("code",[s._v("configDidLoad()")])]),s._v(" "),a("p",[s._v("所有的配置已经加载完毕,此 "),a("code",[s._v("Hook")]),s._v(" 可以用来加载应用自定义的文件,启动自定义的服务。")]),s._v(" "),a("p",[s._v("使用场景举例:")]),s._v(" "),a("ul",[a("li",[s._v("初始化自定义的模块。")]),s._v(" "),a("li",[s._v("自定义 Loader 加载规范。")]),s._v(" "),a("li",[s._v("插入一个中间件到框架的 "),a("code",[s._v("coreMiddleware")]),s._v(" 之间。")])]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("AppBootHook")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("constructor")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("configDidLoad")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 所有的配置已经加载完毕,可以用来加载应用自定义的文件,初始化自定义的服务")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("loader"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("loadToContext")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("path"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("join")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("__dirname"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'app/tasks'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'tasks'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n fieldClass"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'tasksClasses'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 例如:插入一个中间件到框架的 coreMiddleware 之间")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" statusIndex "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("coreMiddleware"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("indexOf")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'status'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("coreMiddleware"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("splice")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("statusIndex "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("+")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("1")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("0")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'limit'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br"),a("span",{staticClass:"line-number"},[s._v("15")]),a("br"),a("span",{staticClass:"line-number"},[s._v("16")]),a("br"),a("span",{staticClass:"line-number"},[s._v("17")]),a("br")])]),a("h3",{attrs:{id:"async-didload"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#async-didload","aria-hidden":"true"}},[s._v("#")]),s._v(" "),a("code",[s._v("async didLoad()")])]),s._v(" "),a("p",[s._v("此 "),a("code",[s._v("Hook")]),s._v(" 可以用来插件的初始化。")]),s._v(" "),a("p",[s._v("把初始化逻辑拆分为 "),a("code",[s._v("configDidLoad")]),s._v(" 和 "),a("code",[s._v("didLoad")]),s._v(" 两个阶段的考虑在于:"),a("code",[s._v("插件之间可能有服务依赖")]),s._v("。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("AppBootHook")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("constructor")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("configDidLoad")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 初始化自定义服务")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("queue "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("new")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Queue")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("queue"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("didLoad")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 启动自定义的服务")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("queue"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("init")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br"),a("span",{staticClass:"line-number"},[s._v("15")]),a("br"),a("span",{staticClass:"line-number"},[s._v("16")]),a("br")])]),a("h3",{attrs:{id:"async-willready"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#async-willready","aria-hidden":"true"}},[s._v("#")]),s._v(" "),a("code",[s._v("async willReady()")])]),s._v(" "),a("p",[s._v("所有的插件都已启动完毕,但是应用整体还未 "),a("code",[s._v("Ready")]),s._v("。")]),s._v(" "),a("p",[s._v("在该 "),a("code",[s._v("Hook")]),s._v(" 可以做一些"),a("strong",[s._v("必须")]),s._v("的前置操作,这些操作成功才会启动应用。")]),s._v(" "),a("ul",[a("li",[s._v("如做一些数据初始化等操作。")])]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("AppBootHook")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("constructor")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("willReady")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 所有的插件都已启动完毕,但是应用整体还未 Ready")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 可以做一些数据初始化等操作,这些操作成功才会启动应用")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 例如:从数据库加载数据到内存缓存")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("cacheData "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("model"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("query")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'select * from QUERY_CACHE_SQL'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("注意")]),s._v(" "),a("p",[s._v("在自定义生命周期函数中不建议做太耗时的操作,框架会有启动的超时检测。")])]),s._v(" "),a("h3",{attrs:{id:"async-didready"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#async-didready","aria-hidden":"true"}},[s._v("#")]),s._v(" "),a("code",[s._v("async didReady()")])]),s._v(" "),a("p",[s._v("应用已经启动完毕,可以用于做一些初始化工作。")]),s._v(" "),a("p",[s._v("与 "),a("code",[s._v("willReady()")]),s._v(" 的区别在于: 该 "),a("code",[s._v("Hook")]),s._v(" 的操作是可选的,失败不会阻塞应用启动。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("AppBootHook")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("constructor")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("didReady")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 应用已经启动完毕")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 该操作是可选的,失败也不影响应用对外服务。")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("createAnonymousContext")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("Biz"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("request")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br")])]),a("h3",{attrs:{id:"async-serverdidready"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#async-serverdidready","aria-hidden":"true"}},[s._v("#")]),s._v(" "),a("code",[s._v("async serverDidReady()")])]),s._v(" "),a("p",[a("code",[s._v("HTTP/HTTPS Server")]),s._v(" 已经启动成功,可以开始导入流量,处理外部请求。")]),s._v(" "),a("p",[s._v("此时可以拿到 "),a("code",[s._v("app.server")]),s._v(" 实例。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("AppBootHook")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("constructor")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("serverDidReady")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("server"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("on")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'timeout'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("socket")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// handle socket timeout")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br")])]),a("h3",{attrs:{id:"async-beforeclose"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#async-beforeclose","aria-hidden":"true"}},[s._v("#")]),s._v(" "),a("code",[s._v("async beforeClose()")])]),s._v(" "),a("p",[s._v("应用即将关闭前的处理 "),a("code",[s._v("Hook")]),s._v(",一般用于资源的释放操作。")]),s._v(" "),a("p",[a("strong",[s._v("注意:该 "),a("code",[s._v("Hook")]),s._v(" 将按注册的逆序执行。")])]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("AppBootHook")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("constructor")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("app "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("beforeClose")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// do sth before app close")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("注意事项")]),s._v(" "),a("p",[s._v("框架默认最多只会等到 "),a("code",[s._v("5s")]),s._v(" 就会退出,不保证会等待所有的该 "),a("code",[s._v("Hook")]),s._v(" 执行完毕。")])])])},[],!1,null,null,null);t.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/25.e176e978.js b/assets/js/25.e176e978.js new file mode 100644 index 0000000..7810300 --- /dev/null +++ b/assets/js/25.e176e978.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[25],{44:function(t,s,a){"use strict";a.r(s);var n=a(0),e=Object(n.a)({},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用场景")]),t._v(" "),a("p",[t._v("日志对于 Web 开发的重要性毋庸置疑,对应用的运行状态监控、问题排查等都有非常重要的意义。")]),t._v(" "),a("p",[t._v("框架内置了强大的企业级日志支持,由 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-logger",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-logger"),a("OutboundLink")],1),t._v(" 模块提供。")]),t._v(" "),a("p",[a("strong",[t._v("主要特性:")])]),t._v(" "),a("ul",[a("li",[t._v("日志分级")]),t._v(" "),a("li",[t._v("统一错误日志")]),t._v(" "),a("li",[t._v("启动日志和运行日志分离")]),t._v(" "),a("li",[t._v("多进程日志")]),t._v(" "),a("li",[t._v("自动切割日志")]),t._v(" "),a("li",[t._v("高性能")]),t._v(" "),a("li",[t._v("可扩展,支持自定义日志")])]),t._v(" "),a("h2",{attrs:{id:"打印日志"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#打印日志","aria-hidden":"true"}},[t._v("#")]),t._v(" 打印日志")]),t._v(" "),a("p",[t._v("在绝大部分的地方,你都可以获取到 "),a("code",[t._v("Logger")]),t._v(" 实例。")]),t._v(" "),a("p",[t._v("以下介绍几个常用的获取方式,它们的对应的日志都会写入到 "),a("code",[t._v("${appInfo.name}-web.log")]),t._v(" 文件。")]),t._v(" "),a("h3",{attrs:{id:"app-logger"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#app-logger","aria-hidden":"true"}},[t._v("#")]),t._v(" app.logger")]),t._v(" "),a("p",[t._v("应用级别的日志,记录一些业务上与请求无关的信息,如启动阶段。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/middleware/static.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("options"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" app")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("logger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("info")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`[egg-static] mount ")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("options"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("dir"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v(" as static root`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("function")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("static")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("h3",{attrs:{id:"ctx-logger"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#ctx-logger","aria-hidden":"true"}},[t._v("#")]),t._v(" ctx.logger")]),t._v(" "),a("p",[t._v("用于记录请求相关的日志。")]),t._v(" "),a("p",[t._v("它打印的日志都会在前面带上一些当前请求相关的信息。")]),t._v(" "),a("p",[t._v("如 "),a("code",[t._v("[${userId}/${ip}/${traceId}/${cost}ms ${method} ${url}]")]),t._v("。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/user.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 打印日志")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("logger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("info")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'ctx.logger'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'TZ'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("p",[t._v("对应的日志输出为:")]),t._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[t._v("2019-02-03 11:18:56,157 INFO 46536 "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("-/127.0.0.1/-/5ms GET /api/user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" ctx.logger\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("h3",{attrs:{id:"this-logger"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#this-logger","aria-hidden":"true"}},[t._v("#")]),t._v(" this.logger")]),t._v(" "),a("p",[t._v("在 "),a("router-link",{attrs:{to:"/zh/guide/controller.html"}},[t._v("Controller")]),t._v("、"),a("router-link",{attrs:{to:"/zh/guide/service.html"}},[t._v("Service")]),t._v(" 等实例中可以获取该对象。")],1),t._v(" "),a("p",[t._v("类似 "),a("code",[t._v("ctx.logger")]),t._v(",不同之处是它会额外加上该日志的文件路径,以便快速定位日志打印位置。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/user.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("logger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("info")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'ctx.logger'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 打印日志,会添加路径")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("logger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("info")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'this.logger'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'TZ'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br")])]),a("p",[t._v("对应的日志输出为:")]),t._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[t._v("2019-02-03 11:18:56,157 INFO 46536 "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("-/127.0.0.1/-/5ms GET /api/user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" ctx.logger\n2019-02-03 11:18:56,158 INFO 46536 "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("-/127.0.0.1/-/5ms GET /api/user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("controller.user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" this.logger\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br")])]),a("h2",{attrs:{id:"日志级别"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#日志级别","aria-hidden":"true"}},[t._v("#")]),t._v(" 日志级别")]),t._v(" "),a("p",[t._v("日志分为 "),a("code",[t._v("NONE")]),t._v(","),a("code",[t._v("DEBUG")]),t._v(","),a("code",[t._v("INFO")]),t._v(","),a("code",[t._v("WARN")]),t._v(" 和 "),a("code",[t._v("ERROR")]),t._v(" 5 个级别。")]),t._v(" "),a("p",[t._v("分别对应于:"),a("code",[t._v("logger.debug()")]),t._v(" / "),a("code",[t._v("logger.info()")]),t._v(" / "),a("code",[t._v("logger.warn()")]),t._v(" / "),a("code",[t._v("logger.error()")]),t._v("。")]),t._v(" "),a("p",[t._v("默认只会输出 "),a("code",[t._v("INFO")]),t._v(" 及以上级别,可以通过对应的 "),a("code",[t._v("logger.level")]),t._v(" 来配置。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nconfig"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("logger "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n level"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'INFO'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("h2",{attrs:{id:"错误日志"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#错误日志","aria-hidden":"true"}},[t._v("#")]),t._v(" 错误日志")]),t._v(" "),a("p",[a("strong",[t._v("为了更方便的进行错误追踪,框架默认会把所有 "),a("code",[t._v("Logger")]),t._v(" 的 "),a("code",[t._v("ERROR")]),t._v(" 日志统一输出到 "),a("code",[t._v("common-error.log")]),t._v(" 文件")]),t._v("。")]),t._v(" "),a("p",[t._v("另外,为了保证异常可追踪,请输出 "),a("code",[t._v("Error")]),t._v(" 类型,从而获取到堆栈信息。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("logger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("error")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("new")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Error")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'whoops'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("p",[t._v("将输出:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token number"}},[t._v("2019")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("-")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("02")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("-")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("03")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("14")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("23")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("25")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("481")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token constant"}},[t._v("ERROR")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("93655")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("-")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("127.0")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v(".0")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v(".1")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("-")]),a("span",{pre:!0,attrs:{class:"token regex"}},[t._v("/6ms GET /")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" nodejs"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("Error"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" whoops\n at HomeController"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("index")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("Users"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("tz"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("Workspaces"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("coding"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("github"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("com"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("atian25"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("egg"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("-")]),t._v("showcase"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("app"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("controller"),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("/")]),t._v("home"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("js"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("13")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("23")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br")])]),a("h2",{attrs:{id:"输出方式"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#输出方式","aria-hidden":"true"}},[t._v("#")]),t._v(" 输出方式")]),t._v(" "),a("h3",{attrs:{id:"文件日志"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#文件日志","aria-hidden":"true"}},[t._v("#")]),t._v(" 文件日志")]),t._v(" "),a("p",[t._v("日志文件默认都放在 "),a("code",[t._v("${appInfo.root}/logs/${appInfo.name}")]),t._v(" 目录下。")]),t._v(" "),a("p",[a("strong",[t._v("值得注意的是:"),a("code",[t._v("appInfo.root")]),t._v(" 会根据运行环境自动适配根目录。")])]),t._v(" "),a("ul",[a("li",[a("code",[t._v("local")]),t._v(" 和 "),a("code",[t._v("unittest")]),t._v(" 环境下为 "),a("code",[t._v("baseDir")]),t._v(",即项目源码的根目录。")]),t._v(" "),a("li",[a("code",[t._v("prod")]),t._v(" 和其他运行环境,都为 "),a("code",[t._v("HOME")]),t._v(",即用户目录,如 "),a("code",[t._v("/home/admin")]),t._v("。")])]),t._v(" "),a("p",[t._v("这是一个优雅的适配,因为:")]),t._v(" "),a("ul",[a("li",[t._v("为了统一管控,线上环境都统一写入用户目录,如 "),a("code",[t._v("/home/admin/logs/${appInfo.name}")]),t._v("。")]),t._v(" "),a("li",[t._v("本地开发时,为了避免冲突,不想污染用户目录,会倾向于直接打印在项目源码的 "),a("code",[t._v("logs")]),t._v(" 目录。")])]),t._v(" "),a("h3",{attrs:{id:"终端日志"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#终端日志","aria-hidden":"true"}},[t._v("#")]),t._v(" 终端日志")]),t._v(" "),a("p",[t._v("日志打印到文件中的同时,为了方便开发,也会同时打印到终端中。")]),t._v(" "),a("p",[t._v("开发环境下默认只会输出 "),a("code",[t._v("INFO")]),t._v(" 及以上级别,可以通过对应的 "),a("code",[t._v("logger.consoleLevel")]),t._v(" 来配置。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nconfig"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("logger "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n consoleLevel"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'INFO'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("注意事项")]),t._v(" "),a("p",[a("strong",[t._v("基于性能的考虑,在正式环境下,默认会关闭终端日志输出。")])])]),t._v(" "),a("h2",{attrs:{id:"正式环境"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#正式环境","aria-hidden":"true"}},[t._v("#")]),t._v(" 正式环境")]),t._v(" "),a("p",[t._v("基于性能和统一管控的考虑,正式环境的日志配置,有以下默认约定。")]),t._v(" "),a("h3",{attrs:{id:"落盘方式"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#落盘方式","aria-hidden":"true"}},[t._v("#")]),t._v(" 落盘方式")]),t._v(" "),a("p",[t._v("通常 Web 访问是高频访问,每次打印日志都写磁盘会造成频繁磁盘 IO。")]),t._v(" "),a("p",[t._v("为了提高性能,我们采用的文件日志写入策略是:")]),t._v(" "),a("p",[a("strong",[t._v("日志同步写入内存,异步每隔一段时间(默认 1 秒)刷盘。")])]),t._v(" "),a("p",[t._v("更多详细请参考 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-logger",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-logger"),a("OutboundLink")],1),t._v(" 和 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-logrotator",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-logrotator"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("h3",{attrs:{id:"日志文件输出位置"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#日志文件输出位置","aria-hidden":"true"}},[t._v("#")]),t._v(" 日志文件输出位置")]),t._v(" "),a("p",[t._v("为了统一管控,一般要求线上环境都统一写入用户目录,如 "),a("code",[t._v("/home/admin/logs/${appInfo.name}")]),t._v("。")]),t._v(" "),a("p",[t._v("具体参见上面的 "),a("a",{attrs:{href:"#%E6%96%87%E4%BB%B6%E6%97%A5%E5%BF%97"}},[t._v("文件日志")]),t._v(" 章节相关描述。")]),t._v(" "),a("h3",{attrs:{id:"禁止输出-debug-日志"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#禁止输出-debug-日志","aria-hidden":"true"}},[t._v("#")]),t._v(" 禁止输出 "),a("code",[t._v("DEBUG")]),t._v(" 日志")]),t._v(" "),a("p",[t._v("在生产环境,为了避免一些插件的调试日志打印导致性能问题,\b默认禁止打印 "),a("code",[t._v("DEBUG")]),t._v(" 日志。")]),t._v(" "),a("p",[t._v("如果确实有需求,需要打开 "),a("code",[t._v("allowDebugAtProd")]),t._v(" 配置项。("),a("strong",[t._v("不推荐")]),t._v(")")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("logger "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n level"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'DEBUG'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n allowDebugAtProd"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("h3",{attrs:{id:"禁止输出终端日志"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#禁止输出终端日志","aria-hidden":"true"}},[t._v("#")]),t._v(" 禁止输出终端日志")]),t._v(" "),a("p",[t._v("基于性能的考虑,在正式环境下,默认会关闭终端日志输出。")]),t._v(" "),a("p",[t._v("如有需要,你可以通过下面的配置开启。("),a("strong",[t._v("不推荐")]),t._v(")")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("logger "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n disableConsoleAfterReady"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("false")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("h2",{attrs:{id:"自定义日志"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#自定义日志","aria-hidden":"true"}},[t._v("#")]),t._v(" 自定义日志")]),t._v(" "),a("p",[a("strong",[t._v("一般应用无需自己配置自定义日志")]),t._v(",因为日志打太多或太分散都会导致关注度分散,反而难以管理和难以排查发现问题。")]),t._v(" "),a("h3",{attrs:{id:"框架内置日志"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#框架内置日志","aria-hidden":"true"}},[t._v("#")]),t._v(" 框架内置日志")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("${appInfo.name}-web.log")]),t._v(":应用输出的日志,通过上述的 "),a("code",[t._v("ctx.logger")]),t._v(" 等打印。")]),t._v(" "),a("li",[a("code",[t._v("egg-web.log")]),t._v(": 用于框架内核、插件日志,通过 "),a("code",[t._v("app.coreLogger")]),t._v(" 打印。")]),t._v(" "),a("li",[a("code",[t._v("common-error.log")]),t._v(":所有 Logger 的错误日志会统一汇集到该文件。")]),t._v(" "),a("li",[t._v("还有很多内置插件输出的 Tracer 日志,详见对应的文档。")])]),t._v(" "),a("h3",{attrs:{id:"增加自定义日志"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#增加自定义日志","aria-hidden":"true"}},[t._v("#")]),t._v(" 增加自定义日志")]),t._v(" "),a("p",[t._v("你也可以通过以下配置,增加自定义日志:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" path "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'path'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("appInfo")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" config "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 自定义日志")]),t._v("\n config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("customLogger "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n oneLogger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'one.log'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br")])]),a("p",[t._v("如果配置为文件名,则会自动转换为 "),a("code",[t._v("path.join(this.app.config.logger.dir, file)")]),t._v("。")]),t._v(" "),a("p",[t._v("然后可通过 "),a("code",[t._v("app.getLogger('oneLogger')")]),t._v(" / "),a("code",[t._v("ctx.getLogger('oneLogger')")]),t._v(" 获取,获取到的 logger 会使用对应的 "),a("code",[t._v("Logger")]),t._v(" 配置,并以 "),a("code",[t._v("config.logger")]),t._v(" 为默认值。")]),t._v(" "),a("div",{staticClass:"tip custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("注意")]),t._v(" "),a("p",[a("code",[t._v("app.getLogger")]),t._v(" 和 "),a("code",[t._v("ctx.getLogger")]),t._v(" 获取到的 logger 实例是有区别的,前者拿到是应用级别的日志实例( 参考 "),a("a",{attrs:{href:"#app-logger"}},[t._v("app.logger")]),t._v(" ),后者拿到的是请求级别的日志实例( 参考 "),a("a",{attrs:{href:"#ctx-logger"}},[t._v("ctx.logger")]),t._v(" ),如果需要自定义日志中也有请求信息( 比如 userId、traceId 等 ),请选择 "),a("code",[t._v("ctx.getLogger")]),t._v(",否则选择 "),a("code",[t._v("app.getLogger")]),t._v(",请根据项目的日志实际使用场景选择合理的方法。")])]),t._v(" "),a("h3",{attrs:{id:"日志输出格式"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#日志输出格式","aria-hidden":"true"}},[t._v("#")]),t._v(" 日志输出格式")]),t._v(" "),a("p",[t._v("你也可以通过自定义 "),a("code",[t._v("formatter")]),t._v(" 和 "),a("code",[t._v("contextFormatter")]),t._v(" 来自定义日志输出格式。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nconfig"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("customLogger "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n oneLogger"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'one.log'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("formatter")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("meta")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" level"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" date"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" pid"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" message "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" meta"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`[")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("date"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("] [")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("level"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("] [")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("pid"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("] ")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("message"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("contextFormatter")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("meta")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" level"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" date"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" pid"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" message "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" meta"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`[")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("date"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("] [")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("level"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("] [")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("pid"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("] [")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("meta"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("href"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("] ")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("message"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("]`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br")])]),a("h3",{attrs:{id:"高级自定义日志"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#高级自定义日志","aria-hidden":"true"}},[t._v("#")]),t._v(" 高级自定义日志")]),t._v(" "),a("p",[t._v("日志默认是打印到日志文件中,当本地开发时同时会打印到终端。")]),t._v(" "),a("p",[t._v("但是,有时候我们需要把日志上报到第三方服务,这时候我们就需要自定义日志的 "),a("code",[t._v("Transport")]),t._v("。")]),t._v(" "),a("p",[a("code",[t._v("Transport")]),t._v(" 是一种传输通道,一个 "),a("code",[t._v("Logger")]),t._v(" 可包含多个传输通道。")]),t._v(" "),a("p",[t._v("默认的 "),a("code",[t._v("Logger")]),t._v(" 均有 "),a("code",[t._v("File")]),t._v(" 和 "),a("code",[t._v("Console")]),t._v(" 两个通道,分别负责打印到文件和终端。")]),t._v(" "),a("p",[t._v("举个例子,我们不仅需要把错误日志打印到 "),a("code",[t._v("common-error.log")]),t._v(",还需要上报给第三方服务。")]),t._v(" "),a("p",[t._v("首先我们定义一个日志的 "),a("code",[t._v("Transport")]),t._v(",代表第三方日志服务。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// lib/remote_transport.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" util "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'util'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" Transport "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-logger'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("Transport"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("RemoteErrorTransport")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Transport")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 定义 log 方法,在此方法中把日志上报给远端服务")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("level"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" args")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" log"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("args"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("instanceof")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Error")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" err "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" args"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n log "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" util"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("format")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'%s: %s\\n%s\\npid: %s\\n'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("message"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("stack"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" process"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("pid"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("else")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n log "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" util"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("format")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("...")]),t._v("args"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("options"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("curl")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'http://url/to/remote/error/log/service/logs'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" log"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n method"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'POST'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("catch")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("error"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br")])]),a("p",[t._v("然后再对 "),a("code",[t._v("Logger")]),t._v(" 添加 "),a("code",[t._v("Transport")]),t._v(",这样每条日志就会同时打印到这个 "),a("code",[t._v("Transport")]),t._v(" 了。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app.js")]),t._v("\napp"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("getLogger")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'errorLogger'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("set")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'remote'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("new")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("RemoteErrorTransport")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" level"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'ERROR'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" app "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br")])]),a("p",[t._v("上面的例子比较简单,实际情况中我们需要考虑性能,很可能采取先打印到内存,再定时上传的策略,以提高性能。")]),t._v(" "),a("h2",{attrs:{id:"日志切割"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#日志切割","aria-hidden":"true"}},[t._v("#")]),t._v(" 日志切割")]),t._v(" "),a("p",[t._v("企业级日志一个最常见的需求之一是对日志进行自动切割,以方便管理。")]),t._v(" "),a("p",[t._v("框架内置了 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-logrotator",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-logrotator"),a("OutboundLink")],1),t._v(" 插件来提供支持。")]),t._v(" "),a("h3",{attrs:{id:"按天切割"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#按天切割","aria-hidden":"true"}},[t._v("#")]),t._v(" 按天切割")]),t._v(" "),a("p",[t._v("这是框架的默认日志切割方式,在每日 "),a("code",[t._v("00:01")]),t._v(" 按照 "),a("code",[t._v(".log.YYYY-MM-DD")]),t._v(" 文件名进行切割。")]),t._v(" "),a("p",[t._v("譬如当前写入的日志为 "),a("code",[t._v("example-app-web.log")]),t._v(",当凌晨 "),a("code",[t._v("00:00")]),t._v(" 时,会对日志进行切割,把过去一天的日志按 "),a("code",[t._v("example-app-web.log.YYYY-MM-DD")]),t._v(" 的形式切割为单独的文件。")]),t._v(" "),a("h3",{attrs:{id:"按照文件大小切割"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#按照文件大小切割","aria-hidden":"true"}},[t._v("#")]),t._v(" 按照文件大小切割")]),t._v(" "),a("p",[t._v("我们也可以按照文件大小进行切割。例如,当文件超过 2G 时进行切割。")]),t._v(" "),a("p",[t._v("譬如,我们需要把 "),a("code",[t._v("egg-web.log")]),t._v(" 按照大小进行切割:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" path "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'path'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("appInfo")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" config "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("logrotator "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n filesRotateBySize"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-web.log'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n maxFileSize"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("2")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("*")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("1024")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("*")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("1024")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("*")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("1024")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br")])]),a("p",[t._v("添加到 "),a("code",[t._v("filesRotateBySize")]),t._v(" 的日志文件不再按天进行切割。")]),t._v(" "),a("p",[t._v("如果配置为文件名,则会自动转换为 "),a("code",[t._v("path.join(this.app.config.logger.dir, file)")]),t._v("。")]),t._v(" "),a("h3",{attrs:{id:"按照小时切割"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#按照小时切割","aria-hidden":"true"}},[t._v("#")]),t._v(" 按照小时切割")]),t._v(" "),a("p",[t._v("我们也可以选择按照小时进行切割,这和默认的按天切割非常类似,只是时间缩短到每小时。")]),t._v(" "),a("p",[t._v("例如,我们需要把 "),a("code",[t._v("common-error.log")]),t._v(" 按照小时进行切割:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.${env}.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" path "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'path'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("appInfo")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n logrotator"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n filesRotateByHour"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'common-error.log'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br")])]),a("p",[t._v("添加到 "),a("code",[t._v("filesRotateByHour")]),t._v(" 的日志文件不再被按天进行切割。")]),t._v(" "),a("p",[t._v("如果配置为文件名,则会自动转换为 "),a("code",[t._v("path.join(this.app.config.logger.dir, file)")]),t._v("。")]),t._v(" "),a("h2",{attrs:{id:"编写测试"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写测试","aria-hidden":"true"}},[t._v("#")]),t._v(" 编写测试")]),t._v(" "),a("p",[t._v("框架提供了 "),a("code",[t._v("expectLog()")]),t._v(" 和 "),a("code",[t._v("mockLog()")]),t._v(" 来简化测试工作。")]),t._v(" "),a("p",[t._v("后者会把对应的日志保留一份在缓存中,避免 IO 较高时,写入延迟导致的校验失败。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token function"}},[t._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'should work'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("mockLog")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("httpRequest")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'hello world'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("200")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expectLog")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'foo in logger'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expectLog")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token regex"}},[t._v("/foo in coreLogger/")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'coreLogger'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("expectLog")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'foo in myCustomLogger'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'myCustomLogger'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br")])])])},[],!1,null,null,null);s.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/26.e04f1b85.js b/assets/js/26.e04f1b85.js new file mode 100644 index 0000000..2283c66 --- /dev/null +++ b/assets/js/26.e04f1b85.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[26],{67:function(s,t,a){"use strict";a.r(t);var n=a(0),e=Object(n.a)({},function(){var s=this,t=s.$createElement,a=s._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用场景")]),s._v(" "),a("p",[a("strong",[s._v("插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。")])]),s._v(" "),a("p",[s._v("我们在使用 "),a("code",[s._v("Koa")]),s._v(" 中间件过程中发现了下面一些问题:")]),s._v(" "),a("ol",[a("li",[a("strong",[s._v("中间件是有先后顺序的,需要统一管控")]),s._v(",但是它自身却无法管理这种顺序,只能交给使用者。这样其实非常不友好,一旦顺序不对,结果可能有天壤之别。")]),s._v(" "),a("li",[a("strong",[s._v("中间件的定位是拦截用户请求")]),s._v(",并在它前后做一些事情,例如:鉴权、安全检查、访问日志等等。但实际情况是,"),a("strong",[s._v("有些功能是和请求无关的")]),s._v(",例如:定时任务、消息订阅、后台逻辑等等。")]),s._v(" "),a("li",[s._v("一些"),a("strong",[s._v("复杂的初始化逻辑")]),s._v(",需要在应用启动的时候完成,这显然也不适合放到中间件中去实现。")])]),s._v(" "),a("p",[s._v("综上所述,我们需要一套更加强大的机制,来管理、编排那些相对独立的业务逻辑。")]),s._v(" "),a("h2",{attrs:{id:"使用插件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用插件","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用插件")]),s._v(" "),a("p",[s._v("举个例子,我们想引入 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-validate",target:"_blank",rel:"noopener noreferrer"}},[s._v("egg-validate"),a("OutboundLink")],1),s._v(" 这个插件。")]),s._v(" "),a("h3",{attrs:{id:"安装依赖"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#安装依赖","aria-hidden":"true"}},[s._v("#")]),s._v(" 安装依赖")]),s._v(" "),a("p",[s._v("插件一般通过 "),a("code",[s._v("npm")]),s._v(" 模块的方式进行复用:")]),s._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[s._v("$ "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("npm")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("install")]),s._v(" egg-validate --save\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br")])]),a("p",[a("strong",[s._v("注意:我们建议通过 "),a("code",[s._v("^")]),s._v(" 的方式引入依赖,并且强烈不建议锁定版本。")])]),s._v(" "),a("div",{staticClass:"tip custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("友情提示")]),s._v(" "),a("p",[s._v("有些插件是内置到框架中,但默认不开启的,此时无需手动安装依赖。详见下文。")])]),s._v(" "),a("h3",{attrs:{id:"挂载插件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#挂载插件","aria-hidden":"true"}},[s._v("#")]),s._v(" 挂载插件")]),s._v(" "),a("p",[s._v("在 "),a("code",[s._v("config/plugin.js")]),s._v(" 中声明:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/plugin.js")]),s._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("validate "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n enable"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("package")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg-validate'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br")])]),a("h3",{attrs:{id:"使用插件-2"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用插件-2","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用插件")]),s._v(" "),a("p",[s._v("然后就可以使用插件提供的功能:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/controller/user.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("UserController")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("create")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" rule "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'string'")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("validate")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("rule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// ...")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br")])]),a("h2",{attrs:{id:"了解插件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#了解插件","aria-hidden":"true"}},[s._v("#")]),s._v(" 了解插件")]),s._v(" "),a("p",[a("strong",[s._v("一个插件其实就是一个『迷你的应用』,和应用几乎一模一样")]),s._v("。")]),s._v(" "),a("h3",{attrs:{id:"目录结构"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#目录结构","aria-hidden":"true"}},[s._v("#")]),s._v(" 目录结构")]),s._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[s._v("my-plugin\n├── app\n│ ├── "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("service")]),s._v("\n│ "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── user.js\n│ ├── middleware\n│ "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── response_time.js\n│ └── extend\n│ ├── application.js\n│ ├── context.js\n│ └── helper.js\n├── config\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" ├── config.default.js\n│ ├── config.prod.js\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" ├── config.local.js\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── config.unittest.js\n├── "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("test")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("service")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("|")]),s._v(" └── user.test.js\n└── package.json\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br"),a("span",{staticClass:"line-number"},[s._v("15")]),a("br"),a("span",{staticClass:"line-number"},[s._v("16")]),a("br"),a("span",{staticClass:"line-number"},[s._v("17")]),a("br"),a("span",{staticClass:"line-number"},[s._v("18")]),a("br"),a("span",{staticClass:"line-number"},[s._v("19")]),a("br")])]),a("h3",{attrs:{id:"service"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#service","aria-hidden":"true"}},[s._v("#")]),s._v(" Service")]),s._v(" "),a("p",[s._v("插件可以包含 "),a("router-link",{attrs:{to:"/zh/guide/service.html"}},[s._v("Service")]),s._v(",框架会自动挂载。")],1),s._v(" "),a("h3",{attrs:{id:"config"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#config","aria-hidden":"true"}},[s._v("#")]),s._v(" Config")]),s._v(" "),a("p",[s._v("插件可以包含 "),a("router-link",{attrs:{to:"/zh/guide/config.html"}},[s._v("配置")]),s._v("。")],1),s._v(" "),a("p",[s._v("插件一般会包含自己的默认配置,应用开发者可以自由覆盖对应的配置:")]),s._v(" "),a("p",[s._v("譬如 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-static",target:"_blank",rel:"noopener noreferrer"}},[s._v("egg-static"),a("OutboundLink")],1),s._v(" 插件默认的 "),a("code",[s._v("prefix")]),s._v(" 为 "),a("code",[s._v("/public/")]),s._v("。")]),s._v(" "),a("p",[s._v("你可以在应用的配置里面覆盖掉它:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nconfig"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("static "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n prefix"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/static/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br")])]),a("p",[s._v("具体合并规则可以参见"),a("router-link",{attrs:{to:"/zh/guide/config.html"}},[s._v("配置")]),s._v("。")],1),s._v(" "),a("h3",{attrs:{id:"middleware"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#middleware","aria-hidden":"true"}},[s._v("#")]),s._v(" Middleware")]),s._v(" "),a("p",[s._v("插件可以包含 "),a("router-link",{attrs:{to:"/zh/guide/middleware.html"}},[s._v("中间件")]),s._v("。")],1),s._v(" "),a("p",[s._v("框架把插件的 "),a("code",[s._v("app/middleware")]),s._v(" 目录下的文件,同样加载到 "),a("code",[s._v("app.middleware")]),s._v(" 上。")]),s._v(" "),a("p",[s._v("大部分情况下,插件开发者会自动挂载中间件到对应的地方,无需应用开发者处理。")]),s._v(" "),a("p",[s._v("但某些情况下,插件仅提供了中间件定义,并不帮应用开发者决定挂载顺序。")]),s._v(" "),a("p",[s._v("此时,应用开发者只需遵循 "),a("router-link",{attrs:{to:"/zh/guide/middleware.html"}},[s._v("中间件")]),s._v(" 文档来使用即可。")],1),s._v(" "),a("h3",{attrs:{id:"extend"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#extend","aria-hidden":"true"}},[s._v("#")]),s._v(" Extend")]),s._v(" "),a("p",[s._v("插件可以提供 "),a("router-link",{attrs:{to:"/zh/guide/context.html#如何扩展"}},[s._v("Context")]),s._v("、"),a("router-link",{attrs:{to:"/zh/guide/application.html#如何扩展"}},[s._v("Application")]),s._v("、"),a("router-link",{attrs:{to:"/zh/guide/helper.html#如何扩展"}},[s._v("Helper")]),s._v(" 等的扩展。")],1),s._v(" "),a("p",[s._v("譬如在插件里面提供以下扩展,对应的逻辑就可以共享给其他应用。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// {plugin_root}/app/extend/context.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token constant"}},[s._v("UA")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("Symbol")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Context#ua'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" useragent "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'useragent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("get")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("ua")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("if")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("!")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),a("span",{pre:!0,attrs:{class:"token constant"}},[s._v("UA")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// this 就是 ctx 对象,在其中可以调用 ctx 上的其他方法,或访问属性")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" uaString "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'user-agent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),a("span",{pre:!0,attrs:{class:"token constant"}},[s._v("UA")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" useragent"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("parse")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("uaString"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),a("span",{pre:!0,attrs:{class:"token constant"}},[s._v("UA")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br")])]),a("h3",{attrs:{id:"不支持的特性"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#不支持的特性","aria-hidden":"true"}},[s._v("#")]),s._v(" 不支持的特性")]),s._v(" "),a("ul",[a("li",[s._v("没有 "),a("router-link",{attrs:{to:"/zh/guide/router.html"}},[s._v("Router")]),s._v(" 和 "),a("router-link",{attrs:{to:"/zh/guide/controller.html"}},[s._v("Controller")]),s._v("。")],1),s._v(" "),a("li",[s._v("没有 "),a("code",[s._v("plugin.js")]),s._v(",只能声明跟其他插件的依赖,而"),a("strong",[s._v("不能决定")]),s._v("其他插件的开启与否。")])]),s._v(" "),a("h2",{attrs:{id:"插件配置"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#插件配置","aria-hidden":"true"}},[s._v("#")]),s._v(" 插件配置")]),s._v(" "),a("h3",{attrs:{id:"参数介绍"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#参数介绍","aria-hidden":"true"}},[s._v("#")]),s._v(" 参数介绍")]),s._v(" "),a("p",[s._v("应用开发者通过 "),a("code",[s._v("config/plugin.js")]),s._v(" 来声明插件的挂载。")]),s._v(" "),a("p",[s._v("除了上面我们使用到的 "),a("code",[s._v("enable")]),s._v(" 和 "),a("code",[s._v("package")]),s._v(" 外,其他参数如下:")]),s._v(" "),a("ul",[a("li",[a("code",[s._v("enable")]),s._v(" - 是否开启此插件,默认为 "),a("code",[s._v("true")]),s._v("。")]),s._v(" "),a("li",[a("code",[s._v("package")]),s._v(" - "),a("code",[s._v("npm")]),s._v(" 模块名称,通过 "),a("code",[s._v("npm")]),s._v(" 模块形式引入插件。")]),s._v(" "),a("li",[a("code",[s._v("path")]),s._v(" - 插件绝对路径,跟 "),a("code",[s._v("package")]),s._v(" 配置互斥。")]),s._v(" "),a("li",[a("code",[s._v("env")]),s._v(" - 数组,仅在指定运行环境才开启,会覆盖插件自身 "),a("code",[s._v("package.json")]),s._v(" 中的配置。")])]),s._v(" "),a("p",[s._v("插件本身的 "),a("code",[s._v("package.json")]),s._v(" 里面也会有一个 "),a("code",[s._v("eggPlugin")]),s._v(" 属性来声明默认的属性。")]),s._v(" "),a("h3",{attrs:{id:"开启框架内置插件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#开启框架内置插件","aria-hidden":"true"}},[s._v("#")]),s._v(" 开启框架内置插件")]),s._v(" "),a("p",[s._v("框架一般也会内置一些插件,它们有可能默认是开启或关闭的。")]),s._v(" "),a("p",[s._v("此时,应用无需配置 "),a("code",[s._v("package")]),s._v(",直接配置 "),a("code",[s._v("enable")]),s._v(" 即可:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/plugin.js")]),s._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("cors "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n enable"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 也可以简写为:")]),s._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("validate "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br")])]),a("h3",{attrs:{id:"package-和-path"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#package-和-path","aria-hidden":"true"}},[s._v("#")]),s._v(" "),a("code",[s._v("package")]),s._v(" 和 "),a("code",[s._v("path")])]),s._v(" "),a("ul",[a("li",[a("code",[s._v("package")]),s._v(":通过 "),a("code",[s._v("npm")]),s._v(" 方式引入,也是最常见的引入方式。")]),s._v(" "),a("li",[a("code",[s._v("path")]),s._v(":通过绝对路径引入。")]),s._v(" "),a("li",[s._v("后者主要场景是:应用内部抽象了一个插件,但还没达到可以发布独立插件的阶段临时使用。")]),s._v(" "),a("li",[s._v("关于这两种方式的使用场景,可以参见"),a("router-link",{attrs:{to:"/zh/workflow/progressive.html"}},[s._v("渐进式开发")]),s._v("。")],1)]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/plugin.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" path "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'path'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("mysql "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n enable"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n path"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" path"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("join")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("__dirname"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'../lib/plugin/egg-mysql'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br")])]),a("h3",{attrs:{id:"根据环境配置"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#根据环境配置","aria-hidden":"true"}},[s._v("#")]),s._v(" 根据环境配置")]),s._v(" "),a("p",[s._v("同时,我们还支持 "),a("code",[s._v("plugin.{env}.js")]),s._v(" 这种模式,会根据"),a("router-link",{attrs:{to:"/zh/guide/config.html#运行环境"}},[s._v("运行环境")]),s._v("加载插件配置。")],1),s._v(" "),a("p",[s._v("比如定义了一个开发环境使用的插件 "),a("code",[s._v("egg-dev")]),s._v(",只希望在本地环境加载,可以安装到 "),a("code",[s._v("devDependencies")]),s._v("。")]),s._v(" "),a("p",[s._v("譬如 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-development-proxyagent",target:"_blank",rel:"noopener noreferrer"}},[s._v("egg-development-proxyagent"),a("OutboundLink")],1),s._v(" 这个插件,只会在开发环境使用。")]),s._v(" "),a("p",[s._v("则我们可以只安装到 "),a("code",[s._v("devDependencies")]),s._v(":")]),s._v(" "),a("div",{staticClass:"language-bash line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-bash"}},[a("code",[s._v("$ "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("npm")]),s._v(" i egg-dev --save-dev\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br")])]),a("p",[s._v("然后在 "),a("code",[s._v("plugin.local.js")]),s._v(" 中声明:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/plugin.local.js")]),s._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("proxyagent "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n enable"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("package")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg-development-proxyagent'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br")])]),a("p",[s._v("这样在生产环境可以 "),a("code",[s._v("npm i --production")]),s._v(" 不需要下载 "),a("code",[s._v("egg-development-proxyagent")]),s._v(" 的包了。")]),s._v(" "),a("p",[a("strong",[s._v("注意:")])]),s._v(" "),a("ul",[a("li",[s._v("不存在 "),a("code",[s._v("plugin.default.js")])]),s._v(" "),a("li",[a("strong",[s._v("只能在应用层使用,在框架层请勿使用。")])])]),s._v(" "),a("h2",{attrs:{id:"常见问题"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常见问题","aria-hidden":"true"}},[s._v("#")]),s._v(" 常见问题")]),s._v(" "),a("h3",{attrs:{id:"如何开发一个插件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#如何开发一个插件","aria-hidden":"true"}},[s._v("#")]),s._v(" 如何开发一个插件")]),s._v(" "),a("p",[s._v("恭喜你迈出这一步,可以回馈社区。")]),s._v(" "),a("p",[s._v("具体可以参见文档:")]),s._v(" "),a("ul",[a("li",[a("router-link",{attrs:{to:"/zh/advanced/framework/plugin.html"}},[s._v("插件开发")]),s._v("。")],1),s._v(" "),a("li",[a("router-link",{attrs:{to:"/zh/workflow/progressive.html"}},[s._v("渐进式开发")]),s._v("。")],1)]),s._v(" "),a("h3",{attrs:{id:"插件太多,每个应用都要开启怎么办?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#插件太多,每个应用都要开启怎么办?","aria-hidden":"true"}},[s._v("#")]),s._v(" 插件太多,每个应用都要开启怎么办?")]),s._v(" "),a("p",[s._v("此时应该考虑包装为一个"),a("router-link",{attrs:{to:"/zh/advanced/framework/framework.html"}},[s._v("上层框架")]),s._v("。")],1)])},[],!1,null,null,null);t.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/27.46820c07.js b/assets/js/27.46820c07.js new file mode 100644 index 0000000..38bb7d8 --- /dev/null +++ b/assets/js/27.46820c07.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[27],{57:function(t,s,a){"use strict";a.r(s);var n=a(0),e=Object(n.a)({},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用场景")]),t._v(" "),a("p",[a("code",[t._v("Router")]),t._v(" 也称之为 "),a("code",[t._v("路由")]),t._v(",用于描述请求 "),a("code",[t._v("URL")]),t._v(" 和具体承担执行动作的 "),a("router-link",{attrs:{to:"/zh/guide/controller.html"}},[t._v("Controller")]),t._v(" 的对应关系。")],1),t._v(" "),a("p",[t._v("框架通过 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-router",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-router"),a("OutboundLink")],1),t._v(" 来提供相关支持。")]),t._v(" "),a("h2",{attrs:{id:"编写路由"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写路由","aria-hidden":"true"}},[t._v("#")]),t._v(" 编写路由")]),t._v(" "),a("p",[t._v("我们约定 "),a("code",[t._v("app/router.js")]),t._v(" 文件用于统一所有路由规则。")]),t._v(" "),a("p",[t._v("通过统一的配置,可以避免路由规则逻辑散落在多个地方,从而出现未知的冲突,可以更方便的来查看全局的路由规则。")]),t._v(" "),a("p",[t._v("假设有以下 "),a("code",[t._v("Controller")]),t._v(" 定义:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/user.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("info")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`hello ")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("params"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("p",[t._v("则我们可以定义对应的路由如下:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// GET /user/123")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user/:id'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("info"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("p",[t._v("这样就完成了一个最简单的 "),a("code",[t._v("Router")]),t._v(" 定义,当用户访问 "),a("code",[t._v("GET /user/123")]),t._v(" 时,这个 "),a("code",[t._v("UserController")]),t._v(" 里面的 "),a("code",[t._v("info")]),t._v(" 方法就会执行。")]),t._v(" "),a("h2",{attrs:{id:"路由定义"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#路由定义","aria-hidden":"true"}},[t._v("#")]),t._v(" 路由定义")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("verb")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/some-path'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("action"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("h3",{attrs:{id:"路由方法"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#路由方法","aria-hidden":"true"}},[t._v("#")]),t._v(" 路由方法")]),t._v(" "),a("p",[t._v("即为上面的 "),a("code",[t._v("verb")]),t._v(",代表用户触发动作,支持 GET、POST 等所有 HTTP 方法。")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("router.head")]),t._v(" - 对应 "),a("code",[t._v("HTTP HEAD")]),t._v(" 方法。")]),t._v(" "),a("li",[a("code",[t._v("router.get")]),t._v(" - 对应 "),a("code",[t._v("HTTP GET")]),t._v(" 方法。")]),t._v(" "),a("li",[a("code",[t._v("router.put")]),t._v(" - 对应 "),a("code",[t._v("HTTP PUT")]),t._v(" 方法。")]),t._v(" "),a("li",[a("code",[t._v("router.post")]),t._v(" - 对应 "),a("code",[t._v("HTTP POST")]),t._v(" 方法。")]),t._v(" "),a("li",[a("code",[t._v("router.patch")]),t._v(" - 对应 "),a("code",[t._v("HTTP PATCH")]),t._v(" 方法。")]),t._v(" "),a("li",[a("code",[t._v("router.delete")]),t._v(" - 对应 "),a("code",[t._v("HTTP DELETE")]),t._v(" 方法。")]),t._v(" "),a("li",[a("code",[t._v("router.del")]),t._v(" - 由于 "),a("code",[t._v("delete")]),t._v(" 是保留字,故一般会用 "),a("code",[t._v("router.del")]),t._v(" 别名。")]),t._v(" "),a("li",[a("code",[t._v("router.options")]),t._v(" - 对应 "),a("code",[t._v("HTTP OPTIONS")]),t._v(" 方法。")])]),t._v(" "),a("p",[t._v("除此之外,还提供了:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("router.redirect")]),t._v(" - 可以对 URL 进行重定向处理,比如把用户访问的根目录路由到某个主页。")]),t._v(" "),a("li",[a("code",[t._v("router.all")]),t._v(" - 对所有的 HTTP 方法都挂载。")])]),t._v(" "),a("h3",{attrs:{id:"路由路径"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#路由路径","aria-hidden":"true"}},[t._v("#")]),t._v(" 路由路径")]),t._v(" "),a("p",[t._v("即为上面的 "),a("code",[t._v("/some-path")]),t._v(",并支持命名参数。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/home'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("home"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("index"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 支持命名参数,通过 `ctx.params.id` 可以取出。")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user/:id'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("detail"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[t._v("也支持正则式:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 可以通过 `ctx.params[0]` 获取到对应的正则分组信息。")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token regex"}},[t._v("/^\\/package\\/([\\w-.]+\\/[\\w-.]+)$/")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("package"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("detail"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[t._v("如果你有一个通配的路由映射,需注意顺序,放在后面,如:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user/manager'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("manager"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\nrouter"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user/:id'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("detail"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br")])]),a("p",[t._v("路径解析使用了 "),a("a",{attrs:{href:"https://github.com/pillarjs/path-to-regexp",target:"_blank",rel:"noopener noreferrer"}},[t._v("path-to-regexp"),a("OutboundLink")],1),t._v(" 模块,更多规则可以参见其文档。")]),t._v(" "),a("h3",{attrs:{id:"路由中间件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#路由中间件","aria-hidden":"true"}},[t._v("#")]),t._v(" 路由中间件")]),t._v(" "),a("p",[t._v("支持对特定路由挂载中间件。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("verb")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/some-path'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" middleware1"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("...")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" middlewareN"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("action"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br")])]),a("p",[t._v("如下示例:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" middleware "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 初始化")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" responseTime "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" middleware"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("responseTime")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" headerKey"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'X-Time'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 仅挂载到指定的路由上")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/test'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" responseTime"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("test"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br")])]),a("h3",{attrs:{id:"路由别名"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#路由别名","aria-hidden":"true"}},[t._v("#")]),t._v(" 路由别名")]),t._v(" "),a("p",[t._v("支持对路由定义别名,用于生成路由链接。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("verb")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'router-name'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/some-path'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("action"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\nrouter"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("verb")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'router-name'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/some-path'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" middleware1"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("...")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" middlewareN"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("action"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br")])]),a("p",[t._v("然后可以通过 "),a("router-link",{attrs:{to:"/zh/guide/helper.html"}},[t._v("Helper")]),t._v(" 提供的辅助函数 "),a("code",[t._v("pathFor")]),t._v(" 和 "),a("code",[t._v("urlFor")]),t._v(" 来生成链接。")],1),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nrouter"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 使用 helper 计算指定 path")]),t._v("\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("helper"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("pathFor")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" limit"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" sort"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'name'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// => /user?limit=10&sort=name")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("p",[t._v("你可以通过 "),a("code",[t._v("ctx.routerName")]),t._v(" 获取到当前命中的路由别名。")]),t._v(" "),a("h2",{attrs:{id:"restful-风格的-url-定义"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#restful-风格的-url-定义","aria-hidden":"true"}},[t._v("#")]),t._v(" RESTful 风格的 URL 定义")]),t._v(" "),a("p",[a("code",[t._v("RESTful")]),t._v(" 是非常经典的 Web API 设计规范,如 "),a("a",{attrs:{href:"https://en.wikipedia.org/wiki/Create,_read,_update_and_delete",target:"_blank",rel:"noopener noreferrer"}},[t._v("CRUD"),a("OutboundLink")],1),t._v(" 的路由结构。")]),t._v(" "),a("p",[t._v("我们提供了 "),a("code",[t._v("app.resources('routerName', 'pathMatch', controller)")]),t._v(" 来简化开发。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("resources")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'posts'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/posts'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("posts"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("resources")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'users'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/api/v1/users'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("v1"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("users"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/v1/users.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("p",[t._v("如上,我们对 "),a("code",[t._v("/posts")]),t._v(" 路径设置了映射到 "),a("code",[t._v("app/controller/posts.js")]),t._v("。")]),t._v(" "),a("p",[t._v("然后,你只需要在 "),a("code",[t._v("Controller")]),t._v(" 里面按需提供对应的方法即可,框架会自动映射。")]),t._v(" "),a("table",[a("thead",[a("tr",[a("th",[t._v("Method")]),t._v(" "),a("th",[t._v("Path")]),t._v(" "),a("th",[t._v("Route Name")]),t._v(" "),a("th",[t._v("Controller.Action")])])]),t._v(" "),a("tbody",[a("tr",[a("td",[t._v("GET")]),t._v(" "),a("td",[t._v("/posts")]),t._v(" "),a("td",[t._v("posts")]),t._v(" "),a("td",[t._v("controller.posts.index")])]),t._v(" "),a("tr",[a("td",[t._v("GET")]),t._v(" "),a("td",[t._v("/posts/new")]),t._v(" "),a("td",[t._v("new_post")]),t._v(" "),a("td",[t._v("controller.posts.new")])]),t._v(" "),a("tr",[a("td",[t._v("GET")]),t._v(" "),a("td",[t._v("/posts/:id")]),t._v(" "),a("td",[t._v("post")]),t._v(" "),a("td",[t._v("controller.posts.show")])]),t._v(" "),a("tr",[a("td",[t._v("GET")]),t._v(" "),a("td",[t._v("/posts/:id/edit")]),t._v(" "),a("td",[t._v("edit_post")]),t._v(" "),a("td",[t._v("controller.posts.edit")])]),t._v(" "),a("tr",[a("td",[t._v("POST")]),t._v(" "),a("td",[t._v("/posts")]),t._v(" "),a("td",[t._v("posts")]),t._v(" "),a("td",[t._v("controller.posts.create")])]),t._v(" "),a("tr",[a("td",[t._v("PUT")]),t._v(" "),a("td",[t._v("/posts/:id")]),t._v(" "),a("td",[t._v("post")]),t._v(" "),a("td",[t._v("controller.posts.update")])]),t._v(" "),a("tr",[a("td",[t._v("DELETE")]),t._v(" "),a("td",[t._v("/posts/:id")]),t._v(" "),a("td",[t._v("post")]),t._v(" "),a("td",[t._v("controller.posts.destroy")])])])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/posts.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("PostController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("index")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("new")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("create")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("show")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("edit")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("update")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("destroy")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br")])]),a("p",[t._v("具体示例,可以参考 "),a("router-link",{attrs:{to:"/zh/tutorials/restful.html"}},[t._v("实现 RESTful API")]),t._v(" 文档。")],1),t._v(" "),a("h2",{attrs:{id:"router-实战"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#router-实战","aria-hidden":"true"}},[t._v("#")]),t._v(" Router 实战")]),t._v(" "),a("p",[t._v("下面通过更多实际的例子,来说明 Router 的用法。")]),t._v(" "),a("h3",{attrs:{id:"获取查询参数"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#获取查询参数","aria-hidden":"true"}},[t._v("#")]),t._v(" 获取查询参数")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user/list'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("list"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/user.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("list")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// curl http://127.0.0.1:7001/user/list?name=tz")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`name: ")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("query"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("name"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br")])]),a("h3",{attrs:{id:"获取命名参数"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#获取命名参数","aria-hidden":"true"}},[t._v("#")]),t._v(" 获取命名参数")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/user/:id/:name'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("detail"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/user.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UserController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("detail")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// curl http://127.0.0.1:7001/user/123/tz")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token template-string"}},[a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`user: ")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("params"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("id"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v(", ")]),a("span",{pre:!0,attrs:{class:"token interpolation"}},[a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("${")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("params"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("name"),a("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[t._v("}")])]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("`")])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br")])]),a("h3",{attrs:{id:"重定向"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#重定向","aria-hidden":"true"}},[t._v("#")]),t._v(" 重定向")]),t._v(" "),a("p",[t._v("使用方式:"),a("code",[t._v("router.redirect(source, destination, [code])")]),t._v("。")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("source")]),t._v(" 和 "),a("code",[t._v("destination")]),t._v(" 可以是路径,也可以是路径别名。")]),t._v(" "),a("li",[a("code",[t._v("code")]),t._v(" 默认 301,可选参数。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'index'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/home/index'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("home"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("index"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("redirect")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/home/index'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("302")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/home.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("HomeController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("index")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// curl -L http://localhost:7001")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'hello controller'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br")])]),a("h2",{attrs:{id:"常见问题"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常见问题","aria-hidden":"true"}},[t._v("#")]),t._v(" 常见问题")]),t._v(" "),a("h3",{attrs:{id:"路由映射太多?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#路由映射太多?","aria-hidden":"true"}},[t._v("#")]),t._v(" 路由映射太多?")]),t._v(" "),a("p",[t._v("一般来说,我们并不推荐把路由规则逻辑散落在多个地方,这会给排查问题带来困扰。")]),t._v(" "),a("p",[t._v("若确实有需求,可以如下拆分:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'./router/news'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'./router/admin'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router/news.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/news/list'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("news"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("list"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/news/detail'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("news"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("detail"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/router/admin.js")]),t._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function-variable function"}},[t._v("exports")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token parameter"}},[t._v("app")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=>")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/admin/user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("admin"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n router"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("get")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'/admin/log'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("admin"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("log"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br")])]),a("p",[t._v("也可直接使用 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-router-plus",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-router-plus"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("p",[t._v("另外,框架会在启动期把最终的路由映射 dump 到 "),a("code",[t._v("run/router.json")]),t._v(" 中。")]),t._v(" "),a("h3",{attrs:{id:"自动映射路由?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#自动映射路由?","aria-hidden":"true"}},[t._v("#")]),t._v(" 自动映射路由?")]),t._v(" "),a("p",[t._v("一般来说,如果符合 "),a("code",[t._v("RESTful")]),t._v(" 风格的路由,直接用上述的 "),a("code",[t._v("router.resource()")]),t._v(" 配置即可。")]),t._v(" "),a("p",[t._v("如果你的业务场景中,有其他约定的规则,则可以参考对应的 "),a("code",[t._v("resource")]),t._v(" 源码,扩展自己的方法,封装为插件。")]),t._v(" "),a("h3",{attrs:{id:"通过装饰器映射?"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#通过装饰器映射?","aria-hidden":"true"}},[t._v("#")]),t._v(" 通过装饰器映射?")]),t._v(" "),a("p",[t._v("装饰器目前还不是 ECMA 的正式规范,框架未提供该功能。")]),t._v(" "),a("p",[t._v("开发者可以自行通过 "),a("code",[t._v("TypeScript")]),t._v(" 或 "),a("code",[t._v("Babel")]),t._v(" 转义对应的自定义装饰器。")])])},[],!1,null,null,null);s.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/28.56abde8a.js b/assets/js/28.56abde8a.js new file mode 100644 index 0000000..69b3946 --- /dev/null +++ b/assets/js/28.56abde8a.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[28],{69:function(s,t,a){"use strict";a.r(t);var n=a(0),e=Object(n.a)({},function(){var s=this,t=s.$createElement,a=s._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用场景")]),s._v(" "),a("p",[a("code",[s._v("Service")]),s._v(" 是在复杂业务场景下用于做业务逻辑封装的一个抽象层:")]),s._v(" "),a("ul",[a("li",[s._v("保持 "),a("code",[s._v("Controller")]),s._v(" 中的逻辑更加简洁。")]),s._v(" "),a("li",[s._v("保持业务逻辑的独立性,抽象出来的 "),a("code",[s._v("Service")]),s._v(" 可以被多个 "),a("code",[s._v("Controller")]),s._v(" 重复调用。")]),s._v(" "),a("li",[s._v("将逻辑和展现分离,更容易编写测试用例。")])]),s._v(" "),a("p",[s._v("场景举例:")]),s._v(" "),a("ul",[a("li",[s._v("复杂数据的处理,如从数据库获取信息后,需经过一定的规则计算,才能返回用户显示。")]),s._v(" "),a("li",[s._v("第三方服务的调用,如调用后端微服务的接口。")])]),s._v(" "),a("h2",{attrs:{id:"编写-service"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写-service","aria-hidden":"true"}},[s._v("#")]),s._v(" 编写 Service")]),s._v(" "),a("p",[s._v("我们约定把 "),a("code",[s._v("Service")]),s._v(" 放置在 "),a("code",[s._v("app/service")]),s._v(" 目录下:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/service/user.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" Service "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("UserService")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Service")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("find")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("uid")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" user "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("db"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("query")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'select * from user where uid = ?'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" uid"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" UserService"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br")])]),a("h2",{attrs:{id:"使用-service"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用-service","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用 Service")]),s._v(" "),a("p",[s._v("框架会默认挂载到 "),a("code",[s._v("ctx.service")]),s._v(" 上,对应的 Key 为文件名的驼峰格式。")]),s._v(" "),a("p",[s._v("如上面的 "),a("code",[s._v("Service")]),s._v(" 会挂载为 "),a("code",[s._v("ctx.service.user")]),s._v("。")]),s._v(" "),a("p",[s._v("然后就可以在 "),a("code",[s._v("Controller")]),s._v(" 里调用:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/controller/user.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" Controller "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("UserController")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("info")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" userId "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("params"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("id"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" userInfo "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("find")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("userId"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" userInfo"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" UserController"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br")])]),a("h2",{attrs:{id:"生命周期"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#生命周期","aria-hidden":"true"}},[s._v("#")]),s._v(" 生命周期")]),s._v(" "),a("p",[a("code",[s._v("Service")]),s._v(" 不是单例,是 "),a("strong",[s._v("请求级别")]),s._v(" 的对象,它挂载在 "),a("code",[s._v("Context")]),s._v(" 上的。")]),s._v(" "),a("p",[a("code",[s._v("Service")]),s._v(" 是延迟实例化的,仅在每一次请求中,首次调用到该 "),a("code",[s._v("Service")]),s._v(" 的时候,才会实例化。")]),s._v(" "),a("p",[s._v("因此,无需担心实例化的性能损耗,经过我们大规模的实践证明,可以忽略不计。")]),s._v(" "),a("h2",{attrs:{id:"挂载规则"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#挂载规则","aria-hidden":"true"}},[s._v("#")]),s._v(" 挂载规则")]),s._v(" "),a("p",[s._v("约定放置在 "),a("code",[s._v("app/service")]),s._v(" 目录下,支持多级目录,"),a("strong",[s._v("对应的文件名会转换为驼峰格式")]),s._v("。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[s._v("app"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("biz"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("js")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("biz"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("user\napp"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("sync_user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("js")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("syncUser\napp"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("HackerNews"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("js")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("hackerNews\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br")])]),a("h2",{attrs:{id:"常用属性和方法"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#常用属性和方法","aria-hidden":"true"}},[s._v("#")]),s._v(" 常用属性和方法")]),s._v(" "),a("p",[a("code",[s._v("Service")]),s._v(" 实例继承 "),a("code",[s._v("egg.Service")]),s._v(",提供以下属性:")]),s._v(" "),a("ul",[a("li",[a("code",[s._v("this.ctx")]),s._v(": 当前请求的上下文 "),a("router-link",{attrs:{to:"/zh/guide/context.html"}},[s._v("Context")]),s._v(" 的实例,可以拿到各种便捷属性和方法。")],1),s._v(" "),a("li",[a("code",[s._v("this.app")]),s._v(": 当前应用 "),a("router-link",{attrs:{to:"/zh/guide/application.html"}},[s._v("Application")]),s._v(" 的实例,可以拿到全局对象和方法。")],1),s._v(" "),a("li",[a("code",[s._v("this.service")]),s._v(":应用定义的 "),a("router-link",{attrs:{to:"/zh/guide/service.html"}},[s._v("Service")]),s._v(",可以调用其他 "),a("code",[s._v("Service")]),s._v("。")],1),s._v(" "),a("li",[a("code",[s._v("this.config")]),s._v(":应用运行时的"),a("router-link",{attrs:{to:"/zh/guide/config.html"}},[s._v("配置项")]),s._v("。")],1),s._v(" "),a("li",[a("code",[s._v("this.logger")]),s._v(":logger 对象,使用方法类似 "),a("router-link",{attrs:{to:"/zh/guide/logger.html#ctx-logger"}},[s._v("Context Logger")]),s._v(",不同之处是通过这个 Logger 对象记录的日志,会额外加上该日志的文件路径,以便快速定位日志打印位置。")],1)]),s._v(" "),a("h2",{attrs:{id:"编写测试"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#编写测试","aria-hidden":"true"}},[s._v("#")]),s._v(" 编写测试")]),s._v(" "),a("p",[s._v("可以通过 "),a("code",[s._v("app.mockContext()")]),s._v(" 获取到 "),a("code",[s._v("Context")]),s._v(" 实例来测试。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// test/service/user.test.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" mock"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" assert "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg-mock'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("describe")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'test/service/user.test.js'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("it")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'should get exists user'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 创建 ctx")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("mockContext")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 通过 ctx 访问到 service.user")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" user "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("find")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'TZ'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("assert")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("assert")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("name "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("===")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'TZ'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br")])]),a("p",[s._v("具体的单元测试运行方式,参见 "),a("router-link",{attrs:{to:"/zh/workflow/development/unittest.html"}},[s._v("研发流程 - 单元测试")]),s._v(" 文档。")],1)])},[],!1,null,null,null);t.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/29.4e4c2114.js b/assets/js/29.4e4c2114.js new file mode 100644 index 0000000..1d7c4b3 --- /dev/null +++ b/assets/js/29.4e4c2114.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[29],{68:function(s,t,a){"use strict";a.r(t);var n=a(0),e=Object(n.a)({},function(){var s=this,t=s.$createElement,a=s._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用场景")]),s._v(" "),a("p",[s._v("在 Web 应用中经常用 "),a("router-link",{attrs:{to:"/zh/guide/cookie.html"}},[s._v("Cookie")]),s._v(" 来承担标识请求方身份的功能,但浏览器给每个站点分配的空间是很有限的。")],1),s._v(" "),a("p",[s._v("从而提出了 "),a("code",[s._v("Session")]),s._v(" 的概念,用于用户身份识别,以及会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)。")]),s._v(" "),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("最佳实践")]),s._v(" "),a("p",[a("strong",[s._v("对于 Egg 的用户来说,请不要直接操作 "),a("code",[s._v("ctx.session")])]),s._v(",而应该:")]),s._v(" "),a("ul",[a("li",[s._v("使用 "),a("router-link",{attrs:{to:"/zh/ecosystem/userservice/"}},[s._v("用户系统")]),s._v(" 提供的统一登录方式,由它来操作 "),a("code",[s._v("Session")]),s._v("。")],1),s._v(" "),a("li",[s._v("如果你有额外的用户信息需要存储,直接操作 "),a("router-link",{attrs:{to:"/zh/ecosystem/data/zcache.html"}},[s._v("ZCache(Tair)")]),s._v(" 提供的 API。")],1)])]),s._v(" "),a("h2",{attrs:{id:"使用-session"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用-session","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用 Session")]),s._v(" "),a("p",[s._v("框架内置了 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-session",target:"_blank",rel:"noopener noreferrer"}},[s._v("Session"),a("OutboundLink")],1),s._v(" 插件,给我们提供了 "),a("code",[s._v("ctx.session")]),s._v(" 来访问或者修改当前用户 "),a("code",[s._v("Session")]),s._v(" 。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/controller/home.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("HomeController")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("fetchPosts")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 获取 Session 上的内容")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" userId "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("userId"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" posts "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("service"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("post"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("fetch")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("userId"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 修改 Session 的值")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("visited "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("visited "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("?")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("Number")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("visited"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("+")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("1")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("1")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n success"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n posts"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br"),a("span",{staticClass:"line-number"},[s._v("15")]),a("br")])]),a("p",[a("code",[s._v("Session")]),s._v(" 的使用方法非常直观,直接读取或修改它就可以了,如果要删除,直接赋值为 "),a("code",[s._v("null")]),s._v(":")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("null")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br")])]),a("h2",{attrs:{id:"禁止使用的-key-值"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#禁止使用的-key-值","aria-hidden":"true"}},[s._v("#")]),s._v(" 禁止使用的 Key 值")]),s._v(" "),a("p",[s._v("需要 "),a("strong",[s._v("特别注意")]),s._v(" 的是:设置 "),a("code",[s._v("session")]),s._v(" 属性时需要避免:")]),s._v(" "),a("ul",[a("li",[s._v("不要以 "),a("code",[s._v("_")]),s._v(" 开头")]),s._v(" "),a("li",[s._v("不能为 "),a("code",[s._v("isNew")])])]),s._v(" "),a("p",[s._v("否则会造成字段丢失,详见 "),a("a",{attrs:{href:"https://github.com/koajs/session/blob/master/lib/session.js#L37-L47",target:"_blank",rel:"noopener noreferrer"}},[s._v("koa-session"),a("OutboundLink")],1),s._v(" 源码。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// ❌ 错误的用法")]),s._v("\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("_visited "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("1")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// --\x3e 该字段会在下一次请求时丢失")]),s._v("\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("isNew "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'HeHe'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// --\x3e 为内部关键字, 不应该去更改")]),s._v("\n\n"),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// ✔️ 正确的用法")]),s._v("\nctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("visited "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("1")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// --\x3e 此处没有问题")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br")])]),a("h2",{attrs:{id:"存储方式"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#存储方式","aria-hidden":"true"}},[s._v("#")]),s._v(" 存储方式")]),s._v(" "),a("h3",{attrs:{id:"cookie"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#cookie","aria-hidden":"true"}},[s._v("#")]),s._v(" Cookie")]),s._v(" "),a("p",[s._v("默认配置下,会把用户的 "),a("code",[s._v("Session")]),s._v(" 加密后直接存储在 "),a("code",[s._v("Cookie")]),s._v(" 中的一个字段中,浏览器每次请求时会带上这个 "),a("code",[s._v("Cookie")]),s._v(",我们在服务端解密后使用。")]),s._v(" "),a("p",[a("code",[s._v("Session")]),s._v(" 写入 "),a("code",[s._v("Cookie")]),s._v(" 的默认配置如下:")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[s._v("config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n key"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'EGG_SESS'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 存储 `Session` 的 `Cookie` 键值对的 key")]),s._v("\n maxAge"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("24")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("*")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("3600")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("*")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[s._v("1000")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 1 天")]),s._v("\n httpOnly"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n encrypt"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br")])]),a("p",[s._v("可以看到,默认配置下,存放 "),a("code",[s._v("Session")]),s._v(" 的 "),a("code",[s._v("Cookie")]),s._v(" 将会加密存储、不可被前端 js 访问,这样可以保证用户数据是安全的。")]),s._v(" "),a("h3",{attrs:{id:"redis"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#redis","aria-hidden":"true"}},[s._v("#")]),s._v(" Redis")]),s._v(" "),a("p",[s._v("默认存储在 "),a("code",[s._v("Cookie")]),s._v(" 时,如果 "),a("code",[s._v("Session")]),s._v(" 对象过于庞大,就会导致:")]),s._v(" "),a("ul",[a("li",[s._v("浏览器通常都有限制最大的 "),a("code",[s._v("Cookie")]),s._v(" 长度,当设置的 "),a("code",[s._v("Session")]),s._v(" 过大时,浏览器可能拒绝保存。")]),s._v(" "),a("li",[s._v("当 "),a("code",[s._v("Session")]),s._v(" 过大时,每次请求都要额外带上庞大的 "),a("code",[s._v("Cookie")]),s._v(" 信息,影响性能。")])]),s._v(" "),a("p",[s._v("对于社区的用户,可以使用 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-session-redis",target:"_blank",rel:"noopener noreferrer"}},[s._v("egg-session-redis"),a("OutboundLink")],1),s._v(" 插件来配置存储。")]),s._v(" "),a("p",[s._v("你需要:")]),s._v(" "),a("ul",[a("li",[s._v("参考 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-redis",target:"_blank",rel:"noopener noreferrer"}},[s._v("egg-redis"),a("OutboundLink")],1),s._v(" 插件的文档,来配置对应的 "),a("code",[s._v("Redis")]),s._v(" 地址信息。")]),s._v(" "),a("li",[s._v("安装并开启对应的插件。")])]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// plugin.js")]),s._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("redis "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n enable"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("package")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg-redis'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("sessionRedis "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n enable"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("package")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg-session-redis'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br")])]),a("h3",{attrs:{id:"注意事项"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#注意事项","aria-hidden":"true"}},[s._v("#")]),s._v(" 注意事项")]),s._v(" "),a("p",[s._v("一旦选择了将 "),a("code",[s._v("Session")]),s._v(" 存入到外部存储中,就意味着系统将强依赖于这个外部存储,当它挂了的时候,我们就完全无法使用 "),a("code",[s._v("Session")]),s._v(" 相关的功能了。")]),s._v(" "),a("p",[s._v("一般来说,建议只将必要的信息存储在 "),a("code",[s._v("Session")]),s._v(" 中,保持 "),a("code",[s._v("Session")]),s._v(" 的精简并使用默认的 "),a("code",[s._v("Cookie")]),s._v(" 存储,用户级别的缓存不要存储在 "),a("code",[s._v("Session")]),s._v(" 中。")]),s._v(" "),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[s._v("注意事项")]),s._v(" "),a("p",[s._v("再次提醒,对于 Egg 的用户来说,请不要直接读取和写入 "),a("code",[s._v("ctx.session")]),s._v("。")]),s._v(" "),a("p",[s._v("应该使用 "),a("router-link",{attrs:{to:"/zh/ecosystem/userservice/"}},[s._v("用户系统")]),s._v(" 提供的统一登录方式,读取 "),a("code",[s._v("ctx.user")]),s._v("。")],1)]),s._v(" "),a("h2",{attrs:{id:"session-实战"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#session-实战","aria-hidden":"true"}},[s._v("#")]),s._v(" Session 实战")]),s._v(" "),a("h3",{attrs:{id:"删除-session"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#删除-session","aria-hidden":"true"}},[s._v("#")]),s._v(" 删除 Session")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("null")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br")])]),a("h3",{attrs:{id:"修改失效时间"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#修改失效时间","aria-hidden":"true"}},[s._v("#")]),s._v(" 修改失效时间")]),s._v(" "),a("p",[s._v("虽然在 "),a("code",[s._v("Session")]),s._v(" 的配置中有一项是 "),a("code",[s._v("maxAge")]),s._v(",但是它只能全局设置 "),a("code",[s._v("Session")]),s._v(" 的有效期。")]),s._v(" "),a("p",[s._v("我们经常可以在一些网站的登陆页上看到有 "),a("strong",[s._v("记住我")]),s._v(" 的选项框,勾选之后可以让登陆用户的 "),a("code",[s._v("Session")]),s._v(" 有效期更长。")]),s._v(" "),a("p",[s._v("这种针对特定用户的 "),a("code",[s._v("Session")]),s._v(" 有效时间设置我们可以通过 "),a("code",[s._v("ctx.session.maxAge=")]),s._v(" 来实现。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/controller/user.js")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" ms "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'ms'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("UserController")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("login")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" username"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" password"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" rememberMe "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" user "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("loginAndGetUser")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("username"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" password"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 设置 Session")]),s._v("\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("user "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" user"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 如果用户勾选了 `记住我`,设置 30 天的过期时间")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("if")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("rememberMe"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("maxAge "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[s._v("ms")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[s._v("'30d'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br"),a("span",{staticClass:"line-number"},[s._v("7")]),a("br"),a("span",{staticClass:"line-number"},[s._v("8")]),a("br"),a("span",{staticClass:"line-number"},[s._v("9")]),a("br"),a("span",{staticClass:"line-number"},[s._v("10")]),a("br"),a("span",{staticClass:"line-number"},[s._v("11")]),a("br"),a("span",{staticClass:"line-number"},[s._v("12")]),a("br"),a("span",{staticClass:"line-number"},[s._v("13")]),a("br"),a("span",{staticClass:"line-number"},[s._v("14")]),a("br")])]),a("h3",{attrs:{id:"延长有效期"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#延长有效期","aria-hidden":"true"}},[s._v("#")]),s._v(" 延长有效期")]),s._v(" "),a("p",[s._v("默认情况下,当用户请求没有导致 "),a("code",[s._v("Session")]),s._v(" 被修改时,框架都不会延长 "),a("code",[s._v("Session")]),s._v(" 的有效期。")]),s._v(" "),a("p",[s._v("但是在有些场景下,我们希望用户如果长时间都在访问我们的站点,则延长他们的 "),a("code",[s._v("Session")]),s._v(" 有效期,不让用户退出登录态。")]),s._v(" "),a("p",[s._v("框架提供了一个 "),a("code",[s._v("renew")]),s._v(" 配置项用于实现此功能,它会在发现当用户 "),a("code",[s._v("Session")]),s._v(" 的有效期仅剩下最大有效期一半的时候,重置 "),a("code",[s._v("Session")]),s._v(" 的有效期。")]),s._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n session"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n renew"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[s._v("1")]),a("br"),a("span",{staticClass:"line-number"},[s._v("2")]),a("br"),a("span",{staticClass:"line-number"},[s._v("3")]),a("br"),a("span",{staticClass:"line-number"},[s._v("4")]),a("br"),a("span",{staticClass:"line-number"},[s._v("5")]),a("br"),a("span",{staticClass:"line-number"},[s._v("6")]),a("br")])])])},[],!1,null,null,null);t.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/3.e44b477d.js b/assets/js/3.e44b477d.js new file mode 100644 index 0000000..9799d7e --- /dev/null +++ b/assets/js/3.e44b477d.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[3],{23:function(t,e,n){},41:function(t,e,n){"use strict";var i=n(23);n.n(i).a},50:function(t,e,n){"use strict";n.r(e);var i={functional:!0,props:{type:{type:String,default:"tip"},text:String,vertical:{type:String,default:"top"}},render:(t,{props:e,slots:n})=>t("span",{class:["badge",e.type],style:{verticalAlign:e.vertical}},e.text||n().default)},a=(n(41),n(0)),p=Object(a.a)(i,void 0,void 0,!1,null,"c13ee5b0",null);e.default=p.exports}}]); \ No newline at end of file diff --git a/assets/js/30.dcc0f8a4.js b/assets/js/30.dcc0f8a4.js new file mode 100644 index 0000000..59fecda --- /dev/null +++ b/assets/js/30.dcc0f8a4.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[30],{55:function(t,s,a){"use strict";a.r(s);var n=a(0),e=Object(n.a)({},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("h2",{attrs:{id:"使用场景"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[t._v("#")]),t._v(" 使用场景")]),t._v(" "),a("p",[t._v("文件上传,是 Web 应用的一个常见的功能。")]),t._v(" "),a("p",[t._v("框架内置了 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-multipart",target:"_blank",rel:"noopener noreferrer"}},[t._v("Multipart"),a("OutboundLink")],1),t._v(" 插件:")]),t._v(" "),a("ul",[a("li",[t._v("解析浏览器上传的 "),a("code",[t._v("multipart/form-data")]),t._v(" 的数据。")]),t._v(" "),a("li",[t._v("提供 "),a("code",[t._v("file")]),t._v(" 和 "),a("code",[t._v("stream")]),t._v(" 两种处理接口供开发者选择。")]),t._v(" "),a("li",[t._v("默认提供了安全的限制。")])]),t._v(" "),a("p",[t._v("获取到用户上传的数据后,开发者可以:")]),t._v(" "),a("ul",[a("li",[t._v("存储为本地文件。")]),t._v(" "),a("li",[t._v("提交给第三方服务,参见 "),a("router-link",{attrs:{to:"/zh/guide/httpclient.html"}},[t._v("通过 HttpClient 上传文件")]),t._v("。")],1),t._v(" "),a("li",[t._v("大部分情况下,我们会转存给"),a("strong",[t._v("云存储服务")]),t._v(",在本文中我们也会一并介绍到。")])]),t._v(" "),a("h2",{attrs:{id:"file-模式"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#file-模式","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("File")]),t._v(" 模式")]),t._v(" "),a("p",[t._v("虽然在 "),a("code",[t._v("Node.js")]),t._v(" 的世界里面,"),a("a",{attrs:{href:"https://nodejs.org/api/stream.html",target:"_blank",rel:"noopener noreferrer"}},[t._v("Stream"),a("OutboundLink")],1),t._v(" 才是主流。")]),t._v(" "),a("p",[t._v("但对于一般开发者来说,"),a("code",[t._v("Stream")]),t._v(" 并不是很容易掌握,尤其是错误处理环节。")]),t._v(" "),a("p",[t._v("因此,框架提供了 "),a("code",[t._v("File")]),t._v(" 模式来简化开发。")]),t._v(" "),a("p",[t._v("相关的示例代码参见:"),a("a",{attrs:{href:"https://github.com/eggjs/examples/tree/master/multipart-file-mode",target:"_blank",rel:"noopener noreferrer"}},[t._v("eggjs/example/multipart-file-mode"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("h3",{attrs:{id:"配置"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#配置","aria-hidden":"true"}},[t._v("#")]),t._v(" 配置")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nconfig"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("multipart "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n mode"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'file'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br")])]),a("h3",{attrs:{id:"前端代码"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#前端代码","aria-hidden":"true"}},[t._v("#")]),t._v(" 前端代码")]),t._v(" "),a("p",[t._v("前端可以通过 "),a("code",[t._v("Form")]),t._v(" 或 "),a("code",[t._v("AJAX")]),t._v(" 等方式来上传文件。")]),t._v(" "),a("p",[t._v("譬如:")]),t._v(" "),a("div",{staticClass:"language-html line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-html"}},[a("code",[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("form")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("method")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("POST"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("action")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("/upload?_csrf={{ ctx.csrf | safe }}"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("enctype")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("multipart/form-data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("\n title: "),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("input")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("name")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("title"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("/>")])]),t._v("\n file1: "),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("input")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("name")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("file1"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("/>")])]),t._v("\n file2: "),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("input")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("name")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("file2"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("/>")])]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("button")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("submit"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("Upload"),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("")])]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("")])]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br")])]),a("div",{staticClass:"tip custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("注意事项")]),t._v(" "),a("p",[t._v("文件上传需要通过 "),a("code",[t._v("POST")]),t._v(" 协议,因此会受到 "),a("router-link",{attrs:{to:"/zh/ecosystem/security/csrf.html"}},[t._v("CSRF")]),t._v(" 安全的管控,具体参见对应文档。")],1)]),t._v(" "),a("h3",{attrs:{id:"获取上传的文件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#获取上传的文件","aria-hidden":"true"}},[t._v("#")]),t._v(" 获取上传的文件")]),t._v(" "),a("p",[t._v("框架在 "),a("code",[t._v("File")]),t._v(" 模式下,会把获取到的文件挂载到 "),a("code",[t._v("ctx.request.files")]),t._v(" 数组上。")]),t._v(" "),a("p",[t._v("关键代码:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("ctx.request.files")]),t._v(": 获取到的文件列表。")]),t._v(" "),a("li",[a("code",[t._v("ctx.oss.put()")]),t._v(":示例代码,此处为上传到 OSS 云存储,下文会介绍到。")]),t._v(" "),a("li",[a("code",[t._v("ctx.cleanupRequestFiles()")]),t._v(":处理完毕后,清理临时文件。")])]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// app/controller/upload.js")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UploadController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("upload")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'got %d files'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("files"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("length"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("try")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 遍历处理多个文件")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("for")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" file "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("of")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("request"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("files"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'field: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("fieldname"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'filename: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("filename"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'encoding: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("encoding"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'mime: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("mime"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'tmp filepath: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("filepath"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 处理文件,比如上传到云端")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("oss"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("put")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-multipart-test/'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("filename"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("filepath"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("finally")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 需要删除临时文件")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("cleanupRequestFiles")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br"),a("span",{staticClass:"line-number"},[t._v("22")]),a("br"),a("span",{staticClass:"line-number"},[t._v("23")]),a("br"),a("span",{staticClass:"line-number"},[t._v("24")]),a("br"),a("span",{staticClass:"line-number"},[t._v("25")]),a("br"),a("span",{staticClass:"line-number"},[t._v("26")]),a("br")])]),a("h2",{attrs:{id:"stream-模式"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#stream-模式","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("Stream")]),t._v(" 模式")]),t._v(" "),a("p",[t._v("如果你对于 "),a("code",[t._v("Node.js")]),t._v(" 中的 "),a("code",[t._v("Stream")]),t._v(" 模式非常熟悉,那么你可以选择此模式。")]),t._v(" "),a("p",[t._v("相关的示例代码参见:"),a("a",{attrs:{href:"https://github.com/eggjs/examples/tree/master/multipart",target:"_blank",rel:"noopener noreferrer"}},[t._v("eggjs/example/multipart"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("h3",{attrs:{id:"上传单个文件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#上传单个文件","aria-hidden":"true"}},[t._v("#")]),t._v(" 上传单个文件")]),t._v(" "),a("p",[t._v("框架同样提供了简化开发的语法糖:")]),t._v(" "),a("ul",[a("li",[a("code",[t._v("ctx.getFileStream()")]),t._v(":获取上传的文件流,仅支持上传一个文件的情况。")]),t._v(" "),a("li",[a("code",[t._v("stream.fields")]),t._v(" 获取其他表单字段。")])]),t._v(" "),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("注意事项")]),t._v(" "),a("p",[t._v("由于表单解析是有时序的,因此前端代码中,"),a("code",[t._v("文件 fileds")]),t._v(" 必须在最后面。")]),t._v(" "),a("p",[t._v("否则在拿到文件流时,"),a("code",[t._v("stream.fields")]),t._v(" 还没解析完,从而获取不到。")])]),t._v(" "),a("p",[t._v("因此对应的前端代码:")]),t._v(" "),a("div",{staticClass:"language-html line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-html"}},[a("code",[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("form")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("method")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("POST"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("action")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("/upload?_csrf={{ ctx.csrf | safe }}"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("enctype")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("multipart/form-data"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("\n title: "),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("input")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("name")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("title"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("/>")])]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("\x3c!-- 只能有一个 File,且必须放在最后--\x3e")]),t._v("\n file: "),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("input")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("name")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("/>")])]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("<")]),t._v("button")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token attr-name"}},[t._v("type")]),a("span",{pre:!0,attrs:{class:"token attr-value"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("=")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')]),t._v("submit"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v('"')])]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(">")])]),t._v("Upload"),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("")])]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token tag"}},[a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("")])]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br")])]),a("p",[t._v("对应的后端代码:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" path "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'path'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" sendToWormhole "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'stream-wormhole'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" Controller "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("Controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UploadController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("upload")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" stream "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("getFileStream")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" name "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-multipart-test/'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" path"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("basename")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("stream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("filename"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 文件处理,上传到云存储等等")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("try")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("oss"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("put")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" stream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("catch")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 必须将上传的文件流消费掉,要不然浏览器响应会卡死")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("sendToWormhole")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("stream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("throw")]),t._v(" err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n\n ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("body "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 所有表单字段都能通过 `stream.fields` 获取到")]),t._v("\n fields"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" stream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("fields"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br"),a("span",{staticClass:"line-number"},[t._v("22")]),a("br"),a("span",{staticClass:"line-number"},[t._v("23")]),a("br"),a("span",{staticClass:"line-number"},[t._v("24")]),a("br"),a("span",{staticClass:"line-number"},[t._v("25")]),a("br"),a("span",{staticClass:"line-number"},[t._v("26")]),a("br")])]),a("h3",{attrs:{id:"上传多个文件"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#上传多个文件","aria-hidden":"true"}},[t._v("#")]),t._v(" 上传多个文件")]),t._v(" "),a("p",[t._v("同时上传多个文件的场景,不能通过 "),a("code",[t._v("ctx.getFileStream()")]),t._v(" 来获取,只能通过以下方式:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" sendToWormhole "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'stream-wormhole'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" Controller "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("require")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("Controller"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n\n"),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UploadController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("upload")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" ctx "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" parts "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("multipart")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// parts() 返回 promise 对象")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("while")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("part "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("parts")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("!=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("null")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("length"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 这是 busboy 的字段")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'field: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("0")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'value: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("1")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'valueTruncated: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("2")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'fieldnameTruncated: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("3")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("else")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("if")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("!")]),t._v("part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("filename"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 这时是用户没有选择文件就点击了上传(part 是 file stream,但是 part.filename 为空)")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 需要做出处理,例如给出错误提示消息")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("return")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// part 是上传的文件流")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'field: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("fieldname"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'filename: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("filename"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'encoding: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("encoding"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'mime: '")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("mime"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 文件处理,上传到云存储等等")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("let")]),t._v(" result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("try")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n result "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("oss"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("put")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'egg-multipart-test/'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("+")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("filename"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("catch")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 必须将上传的文件流消费掉,要不然浏览器响应会卡死")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("sendToWormhole")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("part"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("throw")]),t._v(" err"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("result"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("log")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'and we are done parsing the form!'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br"),a("span",{staticClass:"line-number"},[t._v("14")]),a("br"),a("span",{staticClass:"line-number"},[t._v("15")]),a("br"),a("span",{staticClass:"line-number"},[t._v("16")]),a("br"),a("span",{staticClass:"line-number"},[t._v("17")]),a("br"),a("span",{staticClass:"line-number"},[t._v("18")]),a("br"),a("span",{staticClass:"line-number"},[t._v("19")]),a("br"),a("span",{staticClass:"line-number"},[t._v("20")]),a("br"),a("span",{staticClass:"line-number"},[t._v("21")]),a("br"),a("span",{staticClass:"line-number"},[t._v("22")]),a("br"),a("span",{staticClass:"line-number"},[t._v("23")]),a("br"),a("span",{staticClass:"line-number"},[t._v("24")]),a("br"),a("span",{staticClass:"line-number"},[t._v("25")]),a("br"),a("span",{staticClass:"line-number"},[t._v("26")]),a("br"),a("span",{staticClass:"line-number"},[t._v("27")]),a("br"),a("span",{staticClass:"line-number"},[t._v("28")]),a("br"),a("span",{staticClass:"line-number"},[t._v("29")]),a("br"),a("span",{staticClass:"line-number"},[t._v("30")]),a("br"),a("span",{staticClass:"line-number"},[t._v("31")]),a("br"),a("span",{staticClass:"line-number"},[t._v("32")]),a("br"),a("span",{staticClass:"line-number"},[t._v("33")]),a("br"),a("span",{staticClass:"line-number"},[t._v("34")]),a("br"),a("span",{staticClass:"line-number"},[t._v("35")]),a("br"),a("span",{staticClass:"line-number"},[t._v("36")]),a("br"),a("span",{staticClass:"line-number"},[t._v("37")]),a("br"),a("span",{staticClass:"line-number"},[t._v("38")]),a("br"),a("span",{staticClass:"line-number"},[t._v("39")]),a("br"),a("span",{staticClass:"line-number"},[t._v("40")]),a("br"),a("span",{staticClass:"line-number"},[t._v("41")]),a("br"),a("span",{staticClass:"line-number"},[t._v("42")]),a("br")])]),a("h3",{attrs:{id:"错误处理"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#错误处理","aria-hidden":"true"}},[t._v("#")]),t._v(" 错误处理")]),t._v(" "),a("p",[a("code",[t._v("Stream")]),t._v(" 模式下,在异常处理里面,"),a("strong",[t._v("必须将上传的文件流消费掉,要不然浏览器响应会卡死")]),t._v("。")]),t._v(" "),a("p",[t._v("如上示例,你可以使用 "),a("a",{attrs:{href:"https://github.com/node-modules/stream-wormhole",target:"_blank",rel:"noopener noreferrer"}},[t._v("stream-wormhole"),a("OutboundLink")],1),t._v(" 和 "),a("a",{attrs:{href:"https://github.com/node-modules/mz-modules",target:"_blank",rel:"noopener noreferrer"}},[t._v("mz-modules/pump"),a("OutboundLink")],1),t._v(" 模块来处理。")]),t._v(" "),a("div",{staticClass:"warning custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("友情提示")]),t._v(" "),a("p",[t._v("如果你对 "),a("code",[t._v("Stream")]),t._v(" 没有足够了解的时候,建议直接使用 "),a("code",[t._v("File")]),t._v(" 模式。")])]),t._v(" "),a("h2",{attrs:{id:"安全限制"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#安全限制","aria-hidden":"true"}},[t._v("#")]),t._v(" 安全限制")]),t._v(" "),a("h3",{attrs:{id:"文件大小"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#文件大小","aria-hidden":"true"}},[t._v("#")]),t._v(" 文件大小")]),t._v(" "),a("p",[t._v("为了避免恶意的攻击,框架默认对文件上传接口,限制了 "),a("code",[t._v("File")]),t._v(" 和 "),a("code",[t._v("Field")]),t._v(" 的个数和大小。")]),t._v(" "),a("p",[t._v("默认配置如下,开发者可以根据需求修改对应的配置。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("config"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("multipart "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 表单 Field 文件名长度限制")]),t._v("\n fieldNameSize"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("100")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 表单 Field 内容大小")]),t._v("\n fieldSize"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'100kb'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 表单 Field 最大个数")]),t._v("\n fields"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 单个文件大小")]),t._v("\n fileSize"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'10mb'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 允许上传的最大文件数")]),t._v("\n files"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token number"}},[t._v("10")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br"),a("span",{staticClass:"line-number"},[t._v("13")]),a("br")])]),a("p",[t._v("其中,"),a("code",[t._v("fileSize")]),t._v(" 支持 "),a("code",[t._v("10mb")]),t._v(" 这种人性化的方式,具体参见 "),a("a",{attrs:{href:"https://github.com/node-modules/humanize-bytes",target:"_blank",rel:"noopener noreferrer"}},[t._v("humanize-bytes"),a("OutboundLink")],1),t._v(" 模块。")]),t._v(" "),a("h3",{attrs:{id:"文件类型"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#文件类型","aria-hidden":"true"}},[t._v("#")]),t._v(" 文件类型")]),t._v(" "),a("p",[t._v("为了保证文件上传的安全,框架限制了支持的文件格式。默认的后缀白名单参见"),a("a",{attrs:{href:"https://github.com/eggjs/egg-multipart/blob/master/app.js#L23",target:"_blank",rel:"noopener noreferrer"}},[t._v("源码"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("p",[t._v("开发者可以通过配置 "),a("code",[t._v("fileExtensions")]),t._v(" 来新增允许的类型:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("module"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n multipart"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n fileExtensions"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'.apk'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 增加对 apk 扩展名的文件支持")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br")])]),a("p",[t._v("如果你希望覆盖框架内置的白名单,可以配置 "),a("code",[t._v("whitelist")]),t._v(" 属性:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[t._v("module"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("exports "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n multipart"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 覆盖整个白名单,只允许上传 '.png' 格式")]),t._v("\n whitelist"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("[")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'.png'")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("]")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// 也支持函数格式")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// whitelist: (filename) => [ '.png' ].includes(path.extname(filename) || ''),")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br")])]),a("div",{staticClass:"tip custom-block"},[a("p",{staticClass:"custom-block-title"},[t._v("友情提示")]),t._v(" "),a("p",[t._v("当重写了 "),a("code",[t._v("whitelist")]),t._v(" 时,"),a("code",[t._v("fileExtensions")]),t._v(" 不生效。")])]),t._v(" "),a("h2",{attrs:{id:"云存储"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#云存储","aria-hidden":"true"}},[t._v("#")]),t._v(" 云存储")]),t._v(" "),a("p",[t._v("当获得上传的文件之后,我们一般会转存到云存储服务,尤其是在集群的情况下。")]),t._v(" "),a("p",[t._v("常用的服务有:")]),t._v(" "),a("ul",[a("li",[a("a",{attrs:{href:"https://cn.aliyun.com/product/oss",target:"_blank",rel:"noopener noreferrer"}},[t._v("OSS"),a("OutboundLink")],1),t._v("。")])]),t._v(" "),a("h3",{attrs:{id:"oss"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#oss","aria-hidden":"true"}},[t._v("#")]),t._v(" OSS")]),t._v(" "),a("p",[t._v("框架内置了 "),a("a",{attrs:{href:"https://github.com/eggjs/egg-oss",target:"_blank",rel:"noopener noreferrer"}},[t._v("egg-oss"),a("OutboundLink")],1),t._v(" 插件,默认未开启。")]),t._v(" "),a("h4",{attrs:{id:"配置-2"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#配置-2","aria-hidden":"true"}},[t._v("#")]),t._v(" 配置")]),t._v(" "),a("p",[t._v("首先需要开启插件:")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/plugin.js")]),t._v("\nexports"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("oss "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token boolean"}},[t._v("true")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br")])]),a("p",[t._v("然后配置一下你的 "),a("code",[t._v("OSS")]),t._v(" 的 "),a("code",[t._v("bucket")]),t._v(", "),a("code",[t._v("accessKeyId")]),t._v(", "),a("code",[t._v("accessKeySecret")]),t._v(" 等必要信息。")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// config/config.default.js")]),t._v("\nconfig"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("oss "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n client"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n accessKeyId"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'your access key'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n accessKeySecret"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'your access secret'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n bucket"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'your bucket name'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n endpoint"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'oss-cn-hongkong.aliyun.com'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n timeout"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(":")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token string"}},[t._v("'60s'")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// accessKeyId 和 accessKeySecret 是否经过 egg-bin 加密的")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// encryptPassword: false,")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br"),a("span",{staticClass:"line-number"},[t._v("10")]),a("br"),a("span",{staticClass:"line-number"},[t._v("11")]),a("br"),a("span",{staticClass:"line-number"},[t._v("12")]),a("br")])]),a("p",[t._v("然后通过 "),a("code",[t._v("ctx.oss.put()")]),t._v(" 方法即可上传,支持 "),a("code",[t._v("File")]),t._v(" 和 "),a("code",[t._v("Stream")]),t._v(" 两种模式。")]),t._v(" "),a("h4",{attrs:{id:"file-模式-2"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#file-模式-2","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("File")]),t._v(" 模式")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UploadController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("upload")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// ...")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// file 是拿到的上传的文件对象")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" url "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("oss"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("put")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" file"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("filepath"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("info")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// url 即为上传后的文件链接")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("h4",{attrs:{id:"stream-模式-2"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#stream-模式-2","aria-hidden":"true"}},[t._v("#")]),t._v(" "),a("code",[t._v("Stream")]),t._v(" 模式")]),t._v(" "),a("div",{staticClass:"language-js line-numbers-mode"},[a("pre",{pre:!0,attrs:{class:"language-js"}},[a("code",[a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("class")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("UploadController")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("extends")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token class-name"}},[t._v("Controller")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("async")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("upload")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// ...")]),t._v("\n\n "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// stream 是拿到的上传的文件流对象")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("const")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("{")]),t._v(" url "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token operator"}},[t._v("=")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("await")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token keyword"}},[t._v("this")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("ctx"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),t._v("oss"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("put")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("name"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(",")]),t._v(" stream"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v("\n console"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(".")]),a("span",{pre:!0,attrs:{class:"token function"}},[t._v("info")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("(")]),t._v("url"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(")")]),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v(";")]),t._v(" "),a("span",{pre:!0,attrs:{class:"token comment"}},[t._v("// url 即为上传后的文件链接")]),t._v("\n "),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n"),a("span",{pre:!0,attrs:{class:"token punctuation"}},[t._v("}")]),t._v("\n")])]),t._v(" "),a("div",{staticClass:"line-numbers-wrapper"},[a("span",{staticClass:"line-number"},[t._v("1")]),a("br"),a("span",{staticClass:"line-number"},[t._v("2")]),a("br"),a("span",{staticClass:"line-number"},[t._v("3")]),a("br"),a("span",{staticClass:"line-number"},[t._v("4")]),a("br"),a("span",{staticClass:"line-number"},[t._v("5")]),a("br"),a("span",{staticClass:"line-number"},[t._v("6")]),a("br"),a("span",{staticClass:"line-number"},[t._v("7")]),a("br"),a("span",{staticClass:"line-number"},[t._v("8")]),a("br"),a("span",{staticClass:"line-number"},[t._v("9")]),a("br")])]),a("h3",{attrs:{id:"前端直接上传-oss"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#前端直接上传-oss","aria-hidden":"true"}},[t._v("#")]),t._v(" 前端直接上传 OSS")]),t._v(" "),a("p",[t._v("还有一种常见的需求:前端直接上传文件到 "),a("code",[t._v("OSS")]),t._v(",不经过我们的 "),a("code",[t._v("Web")]),t._v(" 应用。")]),t._v(" "),a("p",[t._v("OSS 提供了 "),a("a",{attrs:{href:"https://help.aliyun.com/document_detail/100624.html?spm=a2c4g.11186623.2.26.2a76342biQgZBM#concept-xzh-nzk-2gb",target:"_blank",rel:"noopener noreferrer"}},[t._v("STS 临时授权方式"),a("OutboundLink")],1),t._v("。")]),t._v(" "),a("p",[t._v("上述的 "),a("code",[t._v("egg-oss")]),t._v(" 插件的底层是 "),a("a",{attrs:{href:"https://github.com/ali-sdk/ali-oss",target:"_blank",rel:"noopener noreferrer"}},[t._v("ali-oss"),a("OutboundLink")],1),t._v(" 模块,也提供了对应的支持,具体参见文档。")])])},[],!1,null,null,null);s.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/31.7a987d06.js b/assets/js/31.7a987d06.js new file mode 100644 index 0000000..58db0dc --- /dev/null +++ b/assets/js/31.7a987d06.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[31],{66:function(t,e,n){"use strict";n.r(e);var s=n(0),l=Object(s.a)({},function(){var t=this.$createElement,e=this._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}},[e("p",[this._v("快速开始")])])},[],!1,null,null,null);e.default=l.exports}}]); \ No newline at end of file diff --git a/assets/js/32.28875310.js b/assets/js/32.28875310.js new file mode 100644 index 0000000..dde3833 --- /dev/null +++ b/assets/js/32.28875310.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[32],{9:function(n,w,o){}}]); \ No newline at end of file diff --git a/assets/js/4.d58994b3.js b/assets/js/4.d58994b3.js new file mode 100644 index 0000000..0d925db --- /dev/null +++ b/assets/js/4.d58994b3.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[4],{38:function(s,t,a){s.exports=a.p+"assets/img/onion.2972bdca.png"},39:function(s,t,a){s.exports=a.p+"assets/img/middleware.5fabc0c7.gif"},65:function(s,t,a){"use strict";a.r(t);var n=a(0),e=Object(n.a)({},function(){var s=this,t=s.$createElement,n=s._self._c||t;return n("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[n("h2",{attrs:{id:"使用场景"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#使用场景","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用场景")]),s._v(" "),n("p",[s._v("一个 HTTP 请求进来后,会执行一系列的处理,然后返回响应给用户。")]),s._v(" "),n("p",[s._v("这个过程就像一条管道,管道的每一个切面逻辑,我们称之为 "),n("code",[s._v("Middleware")]),s._v(",也叫 "),n("code",[s._v("中间件")]),s._v("。")]),s._v(" "),n("p",[s._v("框架继承于 "),n("code",[s._v("Koa")]),s._v(",在 "),n("code",[s._v("Koa")]),s._v(" 里面有个更形象的术语:"),n("code",[s._v("洋葱模型")]),s._v("。")]),s._v(" "),n("p",[n("img",{attrs:{src:a(38),alt:""}})]),s._v(" "),n("p",[n("code",[s._v("Koa")]),s._v(" 中间件执行顺序:")]),s._v(" "),n("p",[n("img",{attrs:{src:a(39),alt:""}})]),s._v(" "),n("h2",{attrs:{id:"编写中间件"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#编写中间件","aria-hidden":"true"}},[s._v("#")]),s._v(" 编写中间件")]),s._v(" "),n("p",[s._v("我们约定把中间件放置在 "),n("code",[s._v("app/middleware")]),s._v(" 目录下:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/middleware/response_time.js")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function-variable function"}},[s._v("exports")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("function")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("responseTime")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" next")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" start "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" Date"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("now")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("next")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" cost "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" Date"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("now")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("-")]),s._v(" start"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("set")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'X-Response-Time'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token template-string"}},[n("span",{pre:!0,attrs:{class:"token string"}},[s._v("`")]),n("span",{pre:!0,attrs:{class:"token interpolation"}},[n("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[s._v("${")]),s._v("cost"),n("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[s._v("}")])]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("ms`")])]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br")])]),n("p",[s._v("如上,需 exports 一个普通的 function,返回一个标准的 Koa Middleware 函数。")]),s._v(" "),n("h2",{attrs:{id:"加载规则"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#加载规则","aria-hidden":"true"}},[s._v("#")]),s._v(" 加载规则")]),s._v(" "),n("p",[s._v("框架会把 "),n("code",[s._v("app/middleware")]),s._v(" 目录下的文件挂载到 "),n("code",[s._v("app.middleware")]),s._v(" 上。")]),s._v(" "),n("p",[s._v("支持多级目录,注意:"),n("strong",[s._v("对应的文件名会转换为驼峰格式")]),s._v("。")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[s._v("app"),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("middleware"),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("api"),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("auth"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("js")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("middleware"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("api"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("auth\napp"),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("middleware"),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("response_time"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("js")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("middleware"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("responseTime\napp"),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("middleware"),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("/")]),s._v("BlockBot"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("js")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("middleware"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("blockBot\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br")])]),n("h2",{attrs:{id:"使用中间件"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#使用中间件","aria-hidden":"true"}},[s._v("#")]),s._v(" 使用中间件")]),s._v(" "),n("p",[s._v("由于中间件是洋葱模型的一部分,因此"),n("strong",[s._v("需要应用开发者显式挂载,决定它们的顺序")]),s._v("。")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 注意是驼峰格式")]),s._v("\n middleware"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'responseTime'")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br")])]),n("h2",{attrs:{id:"自定义配置"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#自定义配置","aria-hidden":"true"}},[s._v("#")]),s._v(" 自定义配置")]),s._v(" "),n("p",[s._v("一般来说中间件也会有自己的配置。")]),s._v(" "),n("p",[s._v("我们可以把之前的中间件改造如下:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/middleware/response_time.js")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function-variable function"}},[s._v("exports")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("options"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" app")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("function")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("responseTime")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" next")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" start "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" Date"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("now")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("next")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" cost "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" Date"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("now")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("-")]),s._v(" start"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// `options.headerKey` 等价于 `app.config.responseTime.headerKey`")]),s._v("\n ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("set")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("options"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("headerKey"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token template-string"}},[n("span",{pre:!0,attrs:{class:"token string"}},[s._v("`")]),n("span",{pre:!0,attrs:{class:"token interpolation"}},[n("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[s._v("${")]),s._v("cost"),n("span",{pre:!0,attrs:{class:"token interpolation-punctuation punctuation"}},[s._v("}")])]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("ms`")])]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br"),n("span",{staticClass:"line-number"},[s._v("10")]),n("br")])]),n("p",[s._v("如上,接受两个参数:")]),s._v(" "),n("ul",[n("li",[n("code",[s._v("options")]),s._v(": 中间件的配置项,框架会将 "),n("code",[s._v("app.config[${middlewareName}]")]),s._v(" 传递进来。")]),s._v(" "),n("li",[n("code",[s._v("app")]),s._v(": 当前应用 Application 的实例。")])]),s._v(" "),n("p",[s._v("对应的配置:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n middleware"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'responseTime'")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// key 为驼峰格式")]),s._v("\n responseTime"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n headerKey"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'X-Response-Time'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br")])]),n("h2",{attrs:{id:"通用配置"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#通用配置","aria-hidden":"true"}},[s._v("#")]),s._v(" 通用配置")]),s._v(" "),n("p",[s._v("中间件支持以下几个通用的配置项:")]),s._v(" "),n("ul",[n("li",[n("code",[s._v("enable")]),s._v(":控制中间件是否开启。")]),s._v(" "),n("li",[n("code",[s._v("match")]),s._v(":设置只有符合某些规则的请求才会经过这个中间件。")]),s._v(" "),n("li",[n("code",[s._v("ignore")]),s._v(":设置符合某些规则的请求不经过这个中间件。")])]),s._v(" "),n("h3",{attrs:{id:"enable"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#enable","aria-hidden":"true"}},[s._v("#")]),s._v(" enable")]),s._v(" "),n("p",[s._v("如果我们的应用并不需要默认的 "),n("code",[s._v("bodyParser")]),s._v(" 中间件来进行请求体的解析,此时我们可以通过配置来关闭它。")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[s._v("module"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n bodyParser"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n enable"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("false")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br")])]),n("h3",{attrs:{id:"match-和-ignore"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#match-和-ignore","aria-hidden":"true"}},[s._v("#")]),s._v(" match 和 ignore")]),s._v(" "),n("p",[s._v("如果我们想让 "),n("code",[s._v("responseTime")]),s._v(" 只针对 API 请求开启,我们可以配置:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[s._v("module"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n responseTime"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n match"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/api'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br")])]),n("p",[n("code",[s._v("match")]),s._v(" 和 "),n("code",[s._v("ignore")]),s._v(" 支持多种类型的配置方式,两者互斥不允许同时配置。")]),s._v(" "),n("ul",[n("li",[n("code",[s._v("字符串")]),s._v(":当参数为字符串类型时,配置的是一个 URL 的路径前缀,所有以配置的字符串作为前缀的 URL 都会匹配上。当然,你也可以直接使用字符串数组。")]),s._v(" "),n("li",[n("code",[s._v("正则")]),s._v(":当参数为正则时,直接匹配满足正则验证的 URL 的路径。")]),s._v(" "),n("li",[n("code",[s._v("函数")]),s._v(":当参数为一个函数时,会将请求上下文传递给这个函数,最终取函数返回的结果(true/false)来判断是否匹配。")])]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[s._v("module"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n responseTime"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("match")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("ctx")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("url"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("startsWith")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/api'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br")])]),n("p",[s._v("详见 "),n("a",{attrs:{href:"https://github.com/eggjs/egg-path-matching",target:"_blank",rel:"noopener noreferrer"}},[s._v("egg-path-matching"),n("OutboundLink")],1),s._v("。")]),s._v(" "),n("h2",{attrs:{id:"修改内置中间件的配置"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#修改内置中间件的配置","aria-hidden":"true"}},[s._v("#")]),s._v(" 修改内置中间件的配置")]),s._v(" "),n("p",[s._v("除了应用层加载中间件之外,框架自身和其他的插件也会加载许多中间件。")]),s._v(" "),n("p",[s._v("如果开发者期望自定义对应的配置,可以修改同名配置项进行覆盖。")]),s._v(" "),n("p",[s._v("如框架内置的 "),n("code",[s._v("bodyParser")]),s._v(" 中间件,可以自定义配置如下:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n bodyParser"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n jsonLimit"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'10mb'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br")])]),n("h2",{attrs:{id:"路由中间件"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#路由中间件","aria-hidden":"true"}},[s._v("#")]),s._v(" 路由中间件")]),s._v(" "),n("p",[s._v("如果 "),n("code",[s._v("match")]),s._v(" / "),n("code",[s._v("ignore")]),s._v(" 不能满足你的需求,如你期望在不同的路由中使用不同的配置。")]),s._v(" "),n("p",[s._v("则可以在"),n("router-link",{attrs:{to:"/zh/guide/router.html"}},[s._v("路由")]),s._v("中单独初始化和挂载:")],1),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/router.js")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function-variable function"}},[s._v("exports")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 初始化")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" responseTime "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("middleware"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("responseTime")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" headerKey"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'X-Time'")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 仅挂载到指定的路由上")]),s._v("\n app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("router"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("get")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/test'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" responseTime"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("controller"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("test"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br")])]),n("h2",{attrs:{id:"引入-koa-生态"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#引入-koa-生态","aria-hidden":"true"}},[s._v("#")]),s._v(" 引入 Koa 生态")]),s._v(" "),n("p",[s._v("我们也可以非常容易的引入 Koa 中间件生态。")]),s._v(" "),n("p",[s._v("以 "),n("a",{attrs:{href:"https://github.com/koajs/compress",target:"_blank",rel:"noopener noreferrer"}},[s._v("koa-compress"),n("OutboundLink")],1),s._v(" 为例,在 Koa 中使用时:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" koa "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'koa'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" compress "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'koa-compress'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" app "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("koa")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" options "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" threshold"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token number"}},[s._v("2048")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\napp"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("use")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("compress")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("options"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br")])]),n("p",[s._v("在我们的应用中,会更简单一些,只需:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/middleware/compress.js")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// koa-compress 暴露的接口 `(options) => middleware` 和框架要求一致")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'koa-compress'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br")])]),n("p",[s._v("对应的配置:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n middleware"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'compress'")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n compress"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n threshold"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token number"}},[s._v("2048")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br")])]),n("p",[s._v("如果使用到的 Koa 中间件不符合入参规范,则可以自行处理下:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n webpack"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n compiler"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n others"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/middleware/webpack.js")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" webpackMiddleware "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'some-koa-middleware'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function-variable function"}},[s._v("exports")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("options"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" app")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("webpackMiddleware")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("options"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("compiler"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" options"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("others"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br"),n("span",{staticClass:"line-number"},[s._v("10")]),n("br"),n("span",{staticClass:"line-number"},[s._v("11")]),n("br"),n("span",{staticClass:"line-number"},[s._v("12")]),n("br"),n("span",{staticClass:"line-number"},[s._v("13")]),n("br"),n("span",{staticClass:"line-number"},[s._v("14")]),n("br")])]),n("h2",{attrs:{id:"编写测试"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#编写测试","aria-hidden":"true"}},[s._v("#")]),s._v(" 编写测试")]),s._v(" "),n("p",[s._v("类似于 "),n("router-link",{attrs:{to:"/zh/guide/controller.html"}},[s._v("Controller")]),s._v(" 的测试,通过 "),n("code",[s._v("app.httpRequest")]),s._v(" 来测试。")],1),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// test/controller/home.test.js")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" mock"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" assert "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg-mock'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("describe")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'test/middleware/response_time.test.js'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("it")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'should response header'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("httpRequest")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("get")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/api/test'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("expect")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'X-Response-Time'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token regex"}},[s._v("/\\d+ms/")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br"),n("span",{staticClass:"line-number"},[s._v("10")]),n("br")])]),n("p",[s._v("具体的单元测试运行方式,参见 "),n("router-link",{attrs:{to:"/zh/workflow/development/unittest.html"}},[s._v("研发流程 - 单元测试")]),s._v(" 文档。")],1)])},[],!1,null,null,null);t.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/5.10b31697.js b/assets/js/5.10b31697.js new file mode 100644 index 0000000..e19a491 --- /dev/null +++ b/assets/js/5.10b31697.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[5],{40:function(s,t,a){s.exports=a.p+"assets/img/todomvc.c40dbbd3.png"},64:function(s,t,a){"use strict";a.r(t);var n=a(0),e=Object(n.a)({},function(){var s=this,t=s.$createElement,n=s._self._c||t;return n("ContentSlotsDistributor",{attrs:{"slot-key":s.$parent.slotKey}},[n("p",[s._v("在本章中我们先来学习如何写一个简单的 "),n("code",[s._v("Egg")]),s._v(" 应用,通过它来了解一些基本的概念和术语。")]),s._v(" "),n("div",{staticClass:"tip custom-block"},[n("p",{staticClass:"custom-block-title"},[s._v("友情提示")]),s._v(" "),n("p",[s._v("需注意的是,本文介绍的是 "),n("code",[s._v("Egg")]),s._v(" 的基础使用。\n对于 "),n("code",[s._v("Egg")]),s._v(" 的开发者而言,很多插件无需自行安装,已经内置到框架,直接开启即可。\n更多内容,在"),n("router-link",{attrs:{to:"/zh/guide/"}},[s._v("开发指南")]),s._v("中可以了解到。")],1)]),s._v(" "),n("h2",{attrs:{id:"典型场景"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#典型场景","aria-hidden":"true"}},[s._v("#")]),s._v(" 典型场景")]),s._v(" "),n("p",[s._v("我们以 "),n("a",{attrs:{href:"http://todomvc.com/",target:"_blank",rel:"noopener noreferrer"}},[s._v("TodoMVC"),n("OutboundLink")],1),s._v(" 这个典型的前端应用场景为例,一步步从零开始搭建。")]),s._v(" "),n("p",[s._v("完整的源码参见 "),n("a",{attrs:{href:"https://github.com/eggjs/examples/tree/master/todomvc",target:"_blank",rel:"noopener noreferrer"}},[s._v("eggjs/examples/todomvc"),n("OutboundLink")],1),s._v("。")]),s._v(" "),n("p",[n("img",{attrs:{src:a(40),alt:""}})]),s._v(" "),n("h2",{attrs:{id:"逐步搭建"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#逐步搭建","aria-hidden":"true"}},[s._v("#")]),s._v(" 逐步搭建")]),s._v(" "),n("h3",{attrs:{id:"环境准备"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#环境准备","aria-hidden":"true"}},[s._v("#")]),s._v(" 环境准备")]),s._v(" "),n("ul",[n("li",[s._v("操作系统:支持 "),n("code",[s._v("macOS")]),s._v(","),n("code",[s._v("Linux")]),s._v(","),n("code",[s._v("Windows")]),s._v(",推荐本地开发用 "),n("code",[s._v("macOS")]),s._v("。")]),s._v(" "),n("li",[s._v("运行环境:仅需要 "),n("a",{attrs:{href:"https://nodejs.org/",target:"_blank",rel:"noopener noreferrer"}},[s._v("Node.js"),n("OutboundLink")],1),s._v(",对应的安装参见"),n("router-link",{attrs:{to:"/zh/quickstart/prepare.html"}},[s._v("文档")]),s._v("。")],1)]),s._v(" "),n("h3",{attrs:{id:"初始化项目"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#初始化项目","aria-hidden":"true"}},[s._v("#")]),s._v(" 初始化项目")]),s._v(" "),n("p",[s._v("通过骨架来"),n("router-link",{attrs:{to:"/zh/workflow/development/init.html"}},[s._v("初始化")]),s._v(":")],1),s._v(" "),n("div",{staticClass:"language-bash line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-bash"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 使用 `Egg` 的 `simple` 骨架来初始化")]),s._v("\n$ "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("mkdir")]),s._v(" demo "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("&&")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("cd")]),s._v(" demo\n$ "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("npm")]),s._v(" init egg --type"),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v("simple\n$ "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("npm")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("install")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br")])]),n("h3",{attrs:{id:"目录结构"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#目录结构","aria-hidden":"true"}},[s._v("#")]),s._v(" 目录结构")]),s._v(" "),n("p",[s._v("框架奉行『约定优于配置』,所以我们首先来看看生成的目录结构,更多可以参见"),n("router-link",{attrs:{to:"/zh/guide/directory.html"}},[s._v("目录规范")]),s._v("。")],1),s._v(" "),n("div",{staticClass:"language-bash line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-bash"}},[n("code",[s._v("demo\n├── app\n│ ├── controller "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 控制器")]),s._v("\n│ │ └── home.js\n│ └── router.js "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 路由映射")]),s._v("\n├── config "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 配置文件")]),s._v("\n│ ├── config.default.js\n│ └── plugin.js\n├── "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("test")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("# 单元测试")]),s._v("\n├── README.md\n└── package.json\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br"),n("span",{staticClass:"line-number"},[s._v("10")]),n("br"),n("span",{staticClass:"line-number"},[s._v("11")]),n("br")])]),n("h3",{attrs:{id:"controller"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#controller","aria-hidden":"true"}},[s._v("#")]),s._v(" "),n("code",[s._v("Controller")])]),s._v(" "),n("p",[n("router-link",{attrs:{to:"/zh/guide/controller.html"}},[s._v("Controller")]),s._v(" 负责"),n("strong",[s._v("解析用户的输入,处理后返回相应的结果")]),s._v("。")],1),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/controller/home.js")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" Controller "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("HomeController")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("index")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" ctx "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'hi, egg'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("exports "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" HomeController"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br"),n("span",{staticClass:"line-number"},[s._v("10")]),n("br"),n("span",{staticClass:"line-number"},[s._v("11")]),n("br")])]),n("p",[s._v("接着配置 "),n("router-link",{attrs:{to:"/zh/guide/router.html"}},[s._v("路由")]),s._v(" 映射到对应的 "),n("code",[s._v("URL")]),s._v(" 上。")],1),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/router.js")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("/**\n * @param {Egg.Application} app - egg application\n */")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function-variable function"}},[s._v("exports")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" router"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" controller "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n router"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("get")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" controller"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("home"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("index"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br")])]),n("h3",{attrs:{id:"本地开发"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#本地开发","aria-hidden":"true"}},[s._v("#")]),s._v(" 本地开发")]),s._v(" "),n("p",[s._v("框架提供了"),n("router-link",{attrs:{to:"/zh/workflow/development/development.html"}},[s._v("本地开发")]),s._v("的辅助工具。")],1),s._v(" "),n("ul",[n("li",[s._v("辅助本地启动应用,监控代码变更自动重启。")]),s._v(" "),n("li",[s._v("自动生成 "),n("code",[s._v("d.ts")]),s._v(" 文件,提供 "),n("code",[s._v("智能提示")]),s._v(" 和 "),n("code",[s._v("代码跳转")]),s._v(" 等能力。")])]),s._v(" "),n("p",[s._v("通过命令启动应用:")]),s._v(" "),n("div",{staticClass:"language-bash line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-bash"}},[n("code",[s._v("$ "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("npm")]),s._v(" run dev\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br")])]),n("p",[s._v("然后就可以访问 "),n("code",[s._v("http://127.0.0.1:7001/")]),s._v("。")]),s._v(" "),n("h3",{attrs:{id:"模板渲染"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#模板渲染","aria-hidden":"true"}},[s._v("#")]),s._v(" 模板渲染")]),s._v(" "),n("p",[s._v("绝大多数情况,我们都需要读取数据后渲染模板,然后呈现给用户。")]),s._v(" "),n("p",[s._v("但 "),n("code",[s._v("Egg")]),s._v(" 并不强制你使用某种模板引擎,故我们需要引入对应的『插件』。")]),s._v(" "),n("div",{staticClass:"tip custom-block"},[n("p",{staticClass:"custom-block-title"},[s._v("术语讲堂")]),s._v(" "),n("p",[s._v("插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。\n详见"),n("router-link",{attrs:{to:"/zh/guide/plugin.html"}},[s._v("开发指南 - 插件")]),s._v("文档。")],1)]),s._v(" "),n("p",[s._v("在本章中,我们使用 "),n("a",{attrs:{href:"https://mozilla.github.io/nunjucks/",target:"_blank",rel:"noopener noreferrer"}},[s._v("Nunjucks"),n("OutboundLink")],1),s._v(" 来渲染,先安装对应的插件 "),n("a",{attrs:{href:"https://github.com/eggjs/egg-view-nunjucks",target:"_blank",rel:"noopener noreferrer"}},[s._v("egg-view-nunjucks"),n("OutboundLink")],1),s._v(" :")]),s._v(" "),n("div",{staticClass:"language-bash line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-bash"}},[n("code",[s._v("$ "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("npm")]),s._v(" i egg-view-nunjucks --save\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br")])]),n("p",[s._v("开启插件:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/plugin.js")]),s._v("\nexports"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("nunjucks "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n enable"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("true")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("package")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg-view-nunjucks'")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br")])]),n("p",[s._v("按照约定,在 "),n("code",[s._v("app/view")]),s._v(" 目录下添加对应的模板文件:")]),s._v(" "),n("div",{staticClass:"language-html line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-html"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("\x3c!-- app/view/home.tpl --\x3e")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token tag"}},[n("span",{pre:!0,attrs:{class:"token tag"}},[n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("<")]),s._v("html")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(">")])]),s._v("\n ...\n "),n("span",{pre:!0,attrs:{class:"token tag"}},[n("span",{pre:!0,attrs:{class:"token tag"}},[n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("<")]),s._v("script")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token attr-name"}},[s._v("src")]),n("span",{pre:!0,attrs:{class:"token attr-value"}},[n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("=")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v('"')]),s._v("/public/main.js"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v('"')])]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(">")])]),n("span",{pre:!0,attrs:{class:"token script"}}),n("span",{pre:!0,attrs:{class:"token tag"}},[n("span",{pre:!0,attrs:{class:"token tag"}},[n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("")])]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token tag"}},[n("span",{pre:!0,attrs:{class:"token tag"}},[n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("")])]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br")])]),n("p",[s._v("对应的 "),n("code",[s._v("Controller")]),s._v(" 改为:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("HomeController")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("index")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" ctx "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// 渲染模板 `app/view/home.tpl`")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("render")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'home.tpl'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br")])]),n("h3",{attrs:{id:"静态资源"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#静态资源","aria-hidden":"true"}},[s._v("#")]),s._v(" 静态资源")]),s._v(" "),n("p",[s._v("前端代码的发布,一般有:")]),s._v(" "),n("ul",[n("li",[s._v("构建后发布到 "),n("code",[s._v("CDN")]),s._v("。(推荐)")]),s._v(" "),n("li",[s._v("直接在应用中托管。")])]),s._v(" "),n("p",[n("code",[s._v("Egg")]),s._v(" 内置了 "),n("a",{attrs:{href:"https://github.com/eggjs/egg-static",target:"_blank",rel:"noopener noreferrer"}},[s._v("egg-static"),n("OutboundLink")],1),s._v(" 插件,对后者提供了支持。")]),s._v(" "),n("p",[s._v("默认会把 "),n("code",[s._v("app/public")]),s._v(" 目录映射到 "),n("code",[s._v("/public")]),s._v(" 路由上。")]),s._v(" "),n("p",[s._v("在本例中,我们使用 "),n("code",[s._v("Vue")]),s._v(" 来写对应的前端逻辑,可以直接参见示例代码。")]),s._v(" "),n("div",{staticClass:"warning custom-block"},[n("p",{staticClass:"custom-block-title"},[s._v("注意事项")]),s._v(" "),n("ul",[n("li",[n("code",[s._v("static")]),s._v(" 插件,线上会默认设置一年的 "),n("code",[s._v("magAge")]),s._v("。")]),s._v(" "),n("li",[s._v("框架默认开启了 "),n("router-link",{attrs:{to:"/zh/ecosystem/security/csrf.html"}},[s._v("CSRF 防护")]),s._v(",故 "),n("code",[s._v("AJAX")]),s._v(" 请求需要带上对应的 "),n("code",[s._v("token")]),s._v(":")],1)]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/public/main.js")]),s._v("\naxios"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("defaults"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("headers"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("common"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("[")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'x-csrf-token'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("]")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" Cookies"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("get")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'csrfToken'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br")])])]),s._v(" "),n("h3",{attrs:{id:"配置文件"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#配置文件","aria-hidden":"true"}},[s._v("#")]),s._v(" 配置文件")]),s._v(" "),n("p",[s._v("写业务的时候,不可避免的需要有"),n("router-link",{attrs:{to:"/zh/guide/config.html"}},[s._v("配置文件")]),s._v("。")],1),s._v(" "),n("p",[s._v("框架提供了强大的配置合并管理功能。")]),s._v(" "),n("p",[s._v("如上述的 "),n("code",[s._v("nunjucks")]),s._v(" 插件,添加对应的配置:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// config/config.default.js")]),s._v("\nconfig"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("view "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n defaultViewEngine"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'nunjucks'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n mapping"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'.tpl'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'nunjucks'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'.html'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'nunjucks'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br")])]),n("div",{staticClass:"warning custom-block"},[n("p",{staticClass:"custom-block-title"},[s._v("注意事项")]),s._v(" "),n("p",[s._v("是 "),n("code",[s._v("config")]),s._v(" 目录,不是 "),n("code",[s._v("app/config")]),s._v("!")])]),s._v(" "),n("h3",{attrs:{id:"service"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#service","aria-hidden":"true"}},[s._v("#")]),s._v(" "),n("code",[s._v("Service")])]),s._v(" "),n("p",[s._v("我们的业务逻辑一般会写在 "),n("router-link",{attrs:{to:"/zh/guide/service.html"}},[s._v("Service")]),s._v(" 里,然后供 "),n("code",[s._v("Controller")]),s._v(" 调用。")],1),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/service/todo.js")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" Service "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("TodoService")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Service")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("/**\n * create todo\n * @param {Todo} todo - todo info without `id`, but `title` required\n */")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("create")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("todo")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// validate")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("if")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("!")]),s._v("todo"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("title"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("throw")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token number"}},[s._v("422")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'task title required'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// normalize")]),s._v("\n todo"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("id "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" Date"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("now")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("toString")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n todo"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("completed "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("false")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("store"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("push")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("todo"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" todo"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br"),n("span",{staticClass:"line-number"},[s._v("10")]),n("br"),n("span",{staticClass:"line-number"},[s._v("11")]),n("br"),n("span",{staticClass:"line-number"},[s._v("12")]),n("br"),n("span",{staticClass:"line-number"},[s._v("13")]),n("br"),n("span",{staticClass:"line-number"},[s._v("14")]),n("br"),n("span",{staticClass:"line-number"},[s._v("15")]),n("br"),n("span",{staticClass:"line-number"},[s._v("16")]),n("br"),n("span",{staticClass:"line-number"},[s._v("17")]),n("br"),n("span",{staticClass:"line-number"},[s._v("18")]),n("br"),n("span",{staticClass:"line-number"},[s._v("19")]),n("br"),n("span",{staticClass:"line-number"},[s._v("20")]),n("br")])]),n("p",[s._v("对应的 "),n("code",[s._v("Controller")]),s._v(" 如下:")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/controller/todo.js")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("TodoController")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("create")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" service "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("this")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// params validate, need `egg-validate` plugin")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// ctx.validate({ title: { type: 'string' } });")]),s._v("\n\n ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("status "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token number"}},[s._v("201")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("await")]),s._v(" service"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("todo"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("create")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("ctx"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("request"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br"),n("span",{staticClass:"line-number"},[s._v("10")]),n("br"),n("span",{staticClass:"line-number"},[s._v("11")]),n("br"),n("span",{staticClass:"line-number"},[s._v("12")]),n("br")])]),n("h3",{attrs:{id:"restful"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#restful","aria-hidden":"true"}},[s._v("#")]),s._v(" "),n("code",[s._v("RESTful")])]),s._v(" "),n("p",[n("code",[s._v("Egg")]),s._v(" 对 "),n("code",[s._v("RESTful")]),s._v(" 这种常见的场景提供了"),n("router-link",{attrs:{to:"/zh/guide/router.html#RESTful-风格的-URL-定义"}},[s._v("内建的支持")]),s._v(":")],1),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/router.js")]),s._v("\nmodule"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function-variable function"}},[s._v("exports")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("app")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" router"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" controller "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// RESTful 映射")]),s._v("\n router"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("resources")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/api/todo'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" controller"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("todo"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br")])]),n("p",[s._v("对应的 "),n("code",[s._v("Controller")]),s._v(":")]),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// app/controller/todo.js")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("class")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("TodoController")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("extends")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token class-name"}},[s._v("Controller")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// `GET /api/todo`")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("index")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// `POST /api/todo`")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("create")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// `PUT /api/todo`")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("update")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n\n "),n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// `DELETE /api/todo`")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("async")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("destroy")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br"),n("span",{staticClass:"line-number"},[s._v("10")]),n("br"),n("span",{staticClass:"line-number"},[s._v("11")]),n("br"),n("span",{staticClass:"line-number"},[s._v("12")]),n("br"),n("span",{staticClass:"line-number"},[s._v("13")]),n("br"),n("span",{staticClass:"line-number"},[s._v("14")]),n("br")])]),n("h3",{attrs:{id:"单元测试"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#单元测试","aria-hidden":"true"}},[s._v("#")]),s._v(" 单元测试")]),s._v(" "),n("p",[s._v("Web 应用中的单元测试非常重要,框架也提供了对应的"),n("router-link",{attrs:{to:"/zh/workflow/development/unittest.html"}},[s._v("单元测试能力支持")]),s._v("。")],1),s._v(" "),n("div",{staticClass:"language-js line-numbers-mode"},[n("pre",{pre:!0,attrs:{class:"language-js"}},[n("code",[n("span",{pre:!0,attrs:{class:"token comment"}},[s._v("// test/app/controller/todo.test.js")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("const")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" mock"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" assert "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("require")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'egg-mock/bootstrap'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n\n"),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("describe")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'test/app/controller/todo.test.js'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("it")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'should add todo'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token keyword"}},[s._v("return")]),s._v(" app"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("httpRequest")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("post")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'/api/todo'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("send")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v(" title"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(":")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Add one'")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("expect")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Content-Type'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token regex"}},[s._v("/json/")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("expect")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'X-Response-Time'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(",")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token regex"}},[s._v("/\\d+ms/")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("expect")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token number"}},[s._v("201")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("expect")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),n("span",{pre:!0,attrs:{class:"token parameter"}},[s._v("res")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("=>")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("{")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("assert")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("res"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("id"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("assert")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("res"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("title "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("===")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token string"}},[s._v("'Add one'")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token function"}},[s._v("assert")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("(")]),s._v("res"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("body"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(".")]),s._v("completed "),n("span",{pre:!0,attrs:{class:"token operator"}},[s._v("===")]),s._v(" "),n("span",{pre:!0,attrs:{class:"token boolean"}},[s._v("false")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n "),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n"),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v("}")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(")")]),n("span",{pre:!0,attrs:{class:"token punctuation"}},[s._v(";")]),s._v("\n")])]),s._v(" "),n("div",{staticClass:"line-numbers-wrapper"},[n("span",{staticClass:"line-number"},[s._v("1")]),n("br"),n("span",{staticClass:"line-number"},[s._v("2")]),n("br"),n("span",{staticClass:"line-number"},[s._v("3")]),n("br"),n("span",{staticClass:"line-number"},[s._v("4")]),n("br"),n("span",{staticClass:"line-number"},[s._v("5")]),n("br"),n("span",{staticClass:"line-number"},[s._v("6")]),n("br"),n("span",{staticClass:"line-number"},[s._v("7")]),n("br"),n("span",{staticClass:"line-number"},[s._v("8")]),n("br"),n("span",{staticClass:"line-number"},[s._v("9")]),n("br"),n("span",{staticClass:"line-number"},[s._v("10")]),n("br"),n("span",{staticClass:"line-number"},[s._v("11")]),n("br"),n("span",{staticClass:"line-number"},[s._v("12")]),n("br"),n("span",{staticClass:"line-number"},[s._v("13")]),n("br"),n("span",{staticClass:"line-number"},[s._v("14")]),n("br"),n("span",{staticClass:"line-number"},[s._v("15")]),n("br"),n("span",{staticClass:"line-number"},[s._v("16")]),n("br"),n("span",{staticClass:"line-number"},[s._v("17")]),n("br"),n("span",{staticClass:"line-number"},[s._v("18")]),n("br")])])])},[],!1,null,null,null);t.default=e.exports}}]); \ No newline at end of file diff --git a/assets/js/6.3a86b35b.js b/assets/js/6.3a86b35b.js new file mode 100644 index 0000000..95b8bd5 --- /dev/null +++ b/assets/js/6.3a86b35b.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[6],{43:function(t,e,s){"use strict";s.r(e);const o=["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links."];var n={methods:{getMsg:()=>o[Math.floor(Math.random()*o.length)]}},h=s(0),i=Object(h.a)(n,function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"theme-container"},[e("div",{staticClass:"theme-default-content"},[e("h1",[this._v("404")]),this._v(" "),e("blockquote",[this._v(this._s(this.getMsg()))]),this._v(" "),e("router-link",{attrs:{to:"/"}},[this._v("Take me home.")])],1)])},[],!1,null,null,null);e.default=i.exports}}]); \ No newline at end of file diff --git a/assets/js/7.bb785290.js b/assets/js/7.bb785290.js new file mode 100644 index 0000000..6a97f85 --- /dev/null +++ b/assets/js/7.bb785290.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[7],{49:function(t,e,n){"use strict";n.r(e);var s=n(0),l=Object(s.a)({},function(){var t=this.$createElement;return(this._self._c||t)("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}})},[],!1,null,null,null);e.default=l.exports}}]); \ No newline at end of file diff --git a/assets/js/8.80b3eee6.js b/assets/js/8.80b3eee6.js new file mode 100644 index 0000000..a416c80 --- /dev/null +++ b/assets/js/8.80b3eee6.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[8],{48:function(t,e,n){"use strict";n.r(e);var s=n(0),i=Object(s.a)({},function(){var t=this.$createElement,e=this._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}},[e("p",[this._v("guide")])])},[],!1,null,null,null);e.default=i.exports}}]); \ No newline at end of file diff --git a/assets/js/9.3f5434be.js b/assets/js/9.3f5434be.js new file mode 100644 index 0000000..b40241a --- /dev/null +++ b/assets/js/9.3f5434be.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[9],{47:function(t,e,n){"use strict";n.r(e);var s=n(0),r=Object(s.a)({},function(){var t=this.$createElement,e=this._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}},[e("p",[this._v("QuickStart")])])},[],!1,null,null,null);e.default=r.exports}}]); \ No newline at end of file diff --git a/assets/js/app.317272b7.js b/assets/js/app.317272b7.js new file mode 100644 index 0000000..b0cdebd --- /dev/null +++ b/assets/js/app.317272b7.js @@ -0,0 +1,14 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[0],[]]);!function(e){function t(t){for(var r,a,l=t[0],s=t[1],u=t[2],f=0,p=[];f
        '};function i(e,t,n){return en?n:e}function o(e){return 100*(-1+e)}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(r[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=i(e,r.minimum,1),n.status=1===e?null:e;var s=n.render(!t),u=s.querySelector(r.barSelector),c=r.speed,f=r.easing;return s.offsetWidth,a(function(t){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),l(u,function(e,t,n){var i;return(i="translate3d"===r.positionUsing?{transform:"translate3d("+o(e)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+o(e)+"%,0)"}:{"margin-left":o(e)+"%"}).transition="all "+t+"ms "+n,i}(e,c,f)),1===e?(l(s,{transition:"none",opacity:1}),s.offsetWidth,setTimeout(function(){l(s,{transition:"all "+c+"ms linear",opacity:0}),setTimeout(function(){n.remove(),t()},c)},c)):setTimeout(t,c)}),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout(function(){n.status&&(n.trickle(),e())},r.trickleSpeed)};return r.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*i(Math.random()*t,.1,.95)),t=i(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},e=0,t=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===t&&n.start(),e++,t++,r.always(function(){0==--t?(e=0,n.done()):n.set((e-t)/e)}),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");u(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=r.template;var i,a=t.querySelector(r.barSelector),s=e?"-100":o(n.status||0),c=document.querySelector(r.parent);return l(a,{transition:"all 0 linear",transform:"translate3d("+s+"%,0,0)"}),r.showSpinner||(i=t.querySelector(r.spinnerSelector))&&p(i),c!=document.body&&u(c,"nprogress-custom-parent"),c.appendChild(t),t},n.remove=function(){c(document.documentElement,"nprogress-busy"),c(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&p(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var a=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),l=function(){var e=["Webkit","O","Moz","ms"],t={};function n(n){return n=n.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(e,t){return t.toUpperCase()}),t[n]||(t[n]=function(t){var n=document.body.style;if(t in n)return t;for(var r,i=e.length,o=t.charAt(0).toUpperCase()+t.slice(1);i--;)if((r=e[i]+o)in n)return r;return t}(n))}function r(e,t,r){t=n(t),e.style[t]=r}return function(e,t){var n,i,o=arguments;if(2==o.length)for(n in t)void 0!==(i=t[n])&&t.hasOwnProperty(n)&&r(e,n,i);else r(e,o[1],o[2])}}();function s(e,t){var n="string"==typeof e?e:f(e);return n.indexOf(" "+t+" ")>=0}function u(e,t){var n=f(e),r=n+t;s(n,t)||(e.className=r.substring(1))}function c(e,t){var n,r=f(e);s(e,t)&&(n=r.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function f(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function p(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n})?r.call(t,n,t,e):r)||(e.exports=i)},function(e,t){var n="Expected a function",r=NaN,i="[object Symbol]",o=/^\s+|\s+$/g,a=/^[-+]0x[0-9a-f]+$/i,l=/^0b[01]+$/i,s=/^0o[0-7]+$/i,u=parseInt,c="object"==typeof global&&global&&global.Object===Object&&global,f="object"==typeof self&&self&&self.Object===Object&&self,p=c||f||Function("return this")(),d=Object.prototype.toString,h=Math.max,v=Math.min,g=function(){return p.Date.now()};function m(e,t,r){var i,o,a,l,s,u,c=0,f=!1,p=!1,d=!0;if("function"!=typeof e)throw new TypeError(n);function m(t){var n=i,r=o;return i=o=void 0,c=t,l=e.apply(r,n)}function _(e){var n=e-u;return void 0===u||n>=t||n<0||p&&e-c>=a}function w(){var e=g();if(_(e))return x(e);s=setTimeout(w,function(e){var n=t-(e-u);return p?v(n,a-(e-c)):n}(e))}function x(e){return s=void 0,d&&i?m(e):(i=o=void 0,l)}function k(){var e=g(),n=_(e);if(i=arguments,o=this,u=e,n){if(void 0===s)return function(e){return c=e,s=setTimeout(w,t),f?m(e):l}(u);if(p)return s=setTimeout(w,t),m(u)}return void 0===s&&(s=setTimeout(w,t)),l}return t=b(t)||0,y(r)&&(f=!!r.leading,a=(p="maxWait"in r)?h(b(r.maxWait)||0,t):a,d="trailing"in r?!!r.trailing:d),k.cancel=function(){void 0!==s&&clearTimeout(s),c=0,i=u=o=s=void 0},k.flush=function(){return void 0===s?l:x(g())},k}function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function b(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&d.call(e)==i}(e))return r;if(y(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=y(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(o,"");var n=l.test(e);return n||s.test(e)?u(e.slice(2),n?2:8):a.test(e)?r:+e}e.exports=function(e,t,r){var i=!0,o=!0;if("function"!=typeof e)throw new TypeError(n);return y(r)&&(i="leading"in r?!!r.leading:i,o="trailing"in r?!!r.trailing:o),m(e,t,{leading:i,maxWait:t,trailing:o})}},function(e,t,n){e.exports=n(8)},function(e,t,n){Promise.all([n.e(0),n.e(32)]).then(n.t.bind(null,9,7))},function(e,t,n){},function(e,t,n){"use strict";var r=n(1);n.n(r).a},function(e,t,n){"use strict";n.r(t); +/*! + * Vue.js v2.6.10 + * (c) 2014-2019 Evan You + * Released under the MIT License. + */ +var r=Object.freeze({});function i(e){return null==e}function o(e){return null!=e}function a(e){return!0===e}function l(e){return"string"==typeof e||"number"==typeof e||"symbol"==typeof e||"boolean"==typeof e}function s(e){return null!==e&&"object"==typeof e}var u=Object.prototype.toString;function c(e){return"[object Object]"===u.call(e)}function f(e){return"[object RegExp]"===u.call(e)}function p(e){var t=parseFloat(String(e));return t>=0&&Math.floor(t)===t&&isFinite(e)}function d(e){return o(e)&&"function"==typeof e.then&&"function"==typeof e.catch}function h(e){return null==e?"":Array.isArray(e)||c(e)&&e.toString===u?JSON.stringify(e,null,2):String(e)}function v(e){var t=parseFloat(e);return isNaN(t)?e:t}function g(e,t){for(var n=Object.create(null),r=e.split(","),i=0;i-1)return e.splice(n,1)}}var b=Object.prototype.hasOwnProperty;function _(e,t){return b.call(e,t)}function w(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}var x=/-(\w)/g,k=w(function(e){return e.replace(x,function(e,t){return t?t.toUpperCase():""})}),C=w(function(e){return e.charAt(0).toUpperCase()+e.slice(1)}),$=/\B([A-Z])/g,S=w(function(e){return e.replace($,"-$1").toLowerCase()});var E=Function.prototype.bind?function(e,t){return e.bind(t)}:function(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n};function O(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function A(e,t){for(var n in t)e[n]=t[n];return e}function T(e){for(var t={},n=0;n0,Y=J&&J.indexOf("edge/")>0,Z=(J&&J.indexOf("android"),J&&/iphone|ipad|ipod|ios/.test(J)||"ios"===G),ee=(J&&/chrome\/\d+/.test(J),J&&/phantomjs/.test(J),J&&J.match(/firefox\/(\d+)/)),te={}.watch,ne=!1;if(W)try{var re={};Object.defineProperty(re,"passive",{get:function(){ne=!0}}),window.addEventListener("test-passive",null,re)}catch(e){}var ie=function(){return void 0===H&&(H=!W&&!K&&"undefined"!=typeof global&&(global.process&&"server"===global.process.env.VUE_ENV)),H},oe=W&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function ae(e){return"function"==typeof e&&/native code/.test(e.toString())}var le,se="undefined"!=typeof Symbol&&ae(Symbol)&&"undefined"!=typeof Reflect&&ae(Reflect.ownKeys);le="undefined"!=typeof Set&&ae(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return!0===this.set[e]},e.prototype.add=function(e){this.set[e]=!0},e.prototype.clear=function(){this.set=Object.create(null)},e}();var ue=P,ce=0,fe=function(){this.id=ce++,this.subs=[]};fe.prototype.addSub=function(e){this.subs.push(e)},fe.prototype.removeSub=function(e){y(this.subs,e)},fe.prototype.depend=function(){fe.target&&fe.target.addDep(this)},fe.prototype.notify=function(){var e=this.subs.slice();for(var t=0,n=e.length;t-1)if(o&&!_(i,"default"))a=!1;else if(""===a||a===S(e)){var s=Ue(String,i.type);(s<0||l0&&(ct((u=e(u,(n||"")+"_"+s))[0])&&ct(f)&&(r[c]=ye(f.text+u[0].text),u.shift()),r.push.apply(r,u)):l(u)?ct(f)?r[c]=ye(f.text+u):""!==u&&r.push(ye(u)):ct(u)&&ct(f)?r[c]=ye(f.text+u.text):(a(t._isVList)&&o(u.tag)&&i(u.key)&&o(n)&&(u.key="__vlist"+n+"_"+s+"__"),r.push(u)));return r}(e):void 0}function ct(e){return o(e)&&o(e.text)&&!1===e.isComment}function ft(e,t){if(e){for(var n=Object.create(null),r=se?Reflect.ownKeys(e):Object.keys(e),i=0;i0,a=e?!!e.$stable:!o,l=e&&e.$key;if(e){if(e._normalized)return e._normalized;if(a&&n&&n!==r&&l===n.$key&&!o&&!n.$hasNormal)return n;for(var s in i={},e)e[s]&&"$"!==s[0]&&(i[s]=vt(t,s,e[s]))}else i={};for(var u in t)u in i||(i[u]=gt(t,u));return e&&Object.isExtensible(e)&&(e._normalized=i),F(i,"$stable",a),F(i,"$key",l),F(i,"$hasNormal",o),i}function vt(e,t,n){var r=function(){var e=arguments.length?n.apply(null,arguments):n({});return(e=e&&"object"==typeof e&&!Array.isArray(e)?[e]:ut(e))&&(0===e.length||1===e.length&&e[0].isComment)?void 0:e};return n.proxy&&Object.defineProperty(e,t,{get:r,enumerable:!0,configurable:!0}),r}function gt(e,t){return function(){return e[t]}}function mt(e,t){var n,r,i,a,l;if(Array.isArray(e)||"string"==typeof e)for(n=new Array(e.length),r=0,i=e.length;rdocument.createEvent("Event").timeStamp&&(cn=function(){return fn.now()})}function pn(){var e,t;for(un=cn(),ln=!0,nn.sort(function(e,t){return e.id-t.id}),sn=0;snsn&&nn[n].id>e.id;)n--;nn.splice(n+1,0,e)}else nn.push(e);an||(an=!0,tt(pn))}}(this)},hn.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||s(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(e){Fe(e,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},hn.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},hn.prototype.depend=function(){for(var e=this.deps.length;e--;)this.deps[e].depend()},hn.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||y(this.vm._watchers,this);for(var e=this.deps.length;e--;)this.deps[e].removeSub(this);this.active=!1}};var vn={enumerable:!0,configurable:!0,get:P,set:P};function gn(e,t,n){vn.get=function(){return this[t][n]},vn.set=function(e){this[t][n]=e},Object.defineProperty(e,n,vn)}function mn(e){e._watchers=[];var t=e.$options;t.props&&function(e,t){var n=e.$options.propsData||{},r=e._props={},i=e.$options._propKeys=[];e.$parent&&Ce(!1);var o=function(o){i.push(o);var a=De(o,t,n,e);Ee(r,o,a),o in e||gn(e,"_props",o)};for(var a in t)o(a);Ce(!0)}(e,t.props),t.methods&&function(e,t){e.$options.props;for(var n in t)e[n]="function"!=typeof t[n]?P:E(t[n],e)}(e,t.methods),t.data?function(e){var t=e.$options.data;c(t=e._data="function"==typeof t?function(e,t){de();try{return e.call(t,t)}catch(e){return Fe(e,t,"data()"),{}}finally{he()}}(t,e):t||{})||(t={});var n=Object.keys(t),r=e.$options.props,i=(e.$options.methods,n.length);for(;i--;){var o=n[i];0,r&&_(r,o)||(a=void 0,36!==(a=(o+"").charCodeAt(0))&&95!==a&&gn(e,"_data",o))}var a;Se(t,!0)}(e):Se(e._data={},!0),t.computed&&function(e,t){var n=e._computedWatchers=Object.create(null),r=ie();for(var i in t){var o=t[i],a="function"==typeof o?o:o.get;0,r||(n[i]=new hn(e,a||P,P,yn)),i in e||bn(e,i,o)}}(e,t.computed),t.watch&&t.watch!==te&&function(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;i-1:"string"==typeof e?e.split(",").indexOf(t)>-1:!!f(e)&&e.test(t)}function An(e,t){var n=e.cache,r=e.keys,i=e._vnode;for(var o in n){var a=n[o];if(a){var l=En(a.componentOptions);l&&!t(l)&&Tn(n,o,r,i)}}}function Tn(e,t,n,r){var i=e[t];!i||r&&i.tag===r.tag||i.componentInstance.$destroy(),e[t]=null,y(n,t)}$n.prototype._init=function(e){var t=this;t._uid=kn++,t._isVue=!0,e&&e._isComponent?function(e,t){var n=e.$options=Object.create(e.constructor.options),r=t._parentVnode;n.parent=t.parent,n._parentVnode=r;var i=r.componentOptions;n.propsData=i.propsData,n._parentListeners=i.listeners,n._renderChildren=i.children,n._componentTag=i.tag,t.render&&(n.render=t.render,n.staticRenderFns=t.staticRenderFns)}(t,e):t.$options=Ie(Cn(t.constructor),e||{},t),t._renderProxy=t,t._self=t,function(e){var t=e.$options,n=t.parent;if(n&&!t.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(e)}e.$parent=n,e.$root=n?n.$root:e,e.$children=[],e.$refs={},e._watcher=null,e._inactive=null,e._directInactive=!1,e._isMounted=!1,e._isDestroyed=!1,e._isBeingDestroyed=!1}(t),function(e){e._events=Object.create(null),e._hasHookEvent=!1;var t=e.$options._parentListeners;t&&Qt(e,t)}(t),function(e){e._vnode=null,e._staticTrees=null;var t=e.$options,n=e.$vnode=t._parentVnode,i=n&&n.context;e.$slots=pt(t._renderChildren,i),e.$scopedSlots=r,e._c=function(t,n,r,i){return Ut(e,t,n,r,i,!1)},e.$createElement=function(t,n,r,i){return Ut(e,t,n,r,i,!0)};var o=n&&n.data;Ee(e,"$attrs",o&&o.attrs||r,null,!0),Ee(e,"$listeners",t._parentListeners||r,null,!0)}(t),tn(t,"beforeCreate"),function(e){var t=ft(e.$options.inject,e);t&&(Ce(!1),Object.keys(t).forEach(function(n){Ee(e,n,t[n])}),Ce(!0))}(t),mn(t),function(e){var t=e.$options.provide;t&&(e._provided="function"==typeof t?t.call(e):t)}(t),tn(t,"created"),t.$options.el&&t.$mount(t.$options.el)},function(e){var t={get:function(){return this._data}},n={get:function(){return this._props}};Object.defineProperty(e.prototype,"$data",t),Object.defineProperty(e.prototype,"$props",n),e.prototype.$set=Oe,e.prototype.$delete=Ae,e.prototype.$watch=function(e,t,n){if(c(t))return xn(this,e,t,n);(n=n||{}).user=!0;var r=new hn(this,e,t,n);if(n.immediate)try{t.call(this,r.value)}catch(e){Fe(e,this,'callback for immediate watcher "'+r.expression+'"')}return function(){r.teardown()}}}($n),function(e){var t=/^hook:/;e.prototype.$on=function(e,n){var r=this;if(Array.isArray(e))for(var i=0,o=e.length;i1?O(n):n;for(var r=O(arguments,1),i='event handler for "'+e+'"',o=0,a=n.length;oparseInt(this.max)&&Tn(a,l[0],l,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}}};!function(e){var t={get:function(){return q}};Object.defineProperty(e,"config",t),e.util={warn:ue,extend:A,mergeOptions:Ie,defineReactive:Ee},e.set=Oe,e.delete=Ae,e.nextTick=tt,e.observable=function(e){return Se(e),e},e.options=Object.create(null),D.forEach(function(t){e.options[t+"s"]=Object.create(null)}),e.options._base=e,A(e.options.components,jn),function(e){e.use=function(e){var t=this._installedPlugins||(this._installedPlugins=[]);if(t.indexOf(e)>-1)return this;var n=O(arguments,1);return n.unshift(this),"function"==typeof e.install?e.install.apply(e,n):"function"==typeof e&&e.apply(null,n),t.push(e),this}}(e),function(e){e.mixin=function(e){return this.options=Ie(this.options,e),this}}(e),Sn(e),function(e){D.forEach(function(t){e[t]=function(e,n){return n?("component"===t&&c(n)&&(n.name=n.name||e,n=this.options._base.extend(n)),"directive"===t&&"function"==typeof n&&(n={bind:n,update:n}),this.options[t+"s"][e]=n,n):this.options[t+"s"][e]}})}(e)}($n),Object.defineProperty($n.prototype,"$isServer",{get:ie}),Object.defineProperty($n.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty($n,"FunctionalRenderContext",{value:jt}),$n.version="2.6.10";var zn=g("style,class"),Ln=g("input,textarea,option,select,progress"),Rn=g("contenteditable,draggable,spellcheck"),In=g("events,caret,typing,plaintext-only"),Mn=function(e,t){return Fn(t)||"false"===t?"false":"contenteditable"===e&&In(t)?t:"true"},Dn=g("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),Nn="http://www.w3.org/1999/xlink",qn=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},Un=function(e){return qn(e)?e.slice(6,e.length):""},Fn=function(e){return null==e||!1===e};function Bn(e){for(var t=e.data,n=e,r=e;o(r.componentInstance);)(r=r.componentInstance._vnode)&&r.data&&(t=Hn(r.data,t));for(;o(n=n.parent);)n&&n.data&&(t=Hn(t,n.data));return function(e,t){if(o(e)||o(t))return Vn(e,Wn(t));return""}(t.staticClass,t.class)}function Hn(e,t){return{staticClass:Vn(e.staticClass,t.staticClass),class:o(e.class)?[e.class,t.class]:t.class}}function Vn(e,t){return e?t?e+" "+t:e:t||""}function Wn(e){return Array.isArray(e)?function(e){for(var t,n="",r=0,i=e.length;r-1?vr(e,t,n):Dn(t)?Fn(n)?e.removeAttribute(t):(n="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,n)):Rn(t)?e.setAttribute(t,Mn(t,n)):qn(t)?Fn(n)?e.removeAttributeNS(Nn,Un(t)):e.setAttributeNS(Nn,t,n):vr(e,t,n)}function vr(e,t,n){if(Fn(n))e.removeAttribute(t);else{if(Q&&!X&&"TEXTAREA"===e.tagName&&"placeholder"===t&&""!==n&&!e.__ieph){var r=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",r)};e.addEventListener("input",r),e.__ieph=!0}e.setAttribute(t,n)}}var gr={create:dr,update:dr};function mr(e,t){var n=t.elm,r=t.data,a=e.data;if(!(i(r.staticClass)&&i(r.class)&&(i(a)||i(a.staticClass)&&i(a.class)))){var l=Bn(t),s=n._transitionClasses;o(s)&&(l=Vn(l,Wn(s))),l!==n._prevClass&&(n.setAttribute("class",l),n._prevClass=l)}}var yr,br={create:mr,update:mr},_r="__r",wr="__c";function xr(e,t,n){var r=yr;return function i(){var o=t.apply(null,arguments);null!==o&&$r(e,i,n,r)}}var kr=Ke&&!(ee&&Number(ee[1])<=53);function Cr(e,t,n,r){if(kr){var i=un,o=t;t=o._wrapper=function(e){if(e.target===e.currentTarget||e.timeStamp>=i||e.timeStamp<=0||e.target.ownerDocument!==document)return o.apply(this,arguments)}}yr.addEventListener(e,t,ne?{capture:n,passive:r}:n)}function $r(e,t,n,r){(r||yr).removeEventListener(e,t._wrapper||t,n)}function Sr(e,t){if(!i(e.data.on)||!i(t.data.on)){var n=t.data.on||{},r=e.data.on||{};yr=t.elm,function(e){if(o(e[_r])){var t=Q?"change":"input";e[t]=[].concat(e[_r],e[t]||[]),delete e[_r]}o(e[wr])&&(e.change=[].concat(e[wr],e.change||[]),delete e[wr])}(n),at(n,r,Cr,$r,xr,t.context),yr=void 0}}var Er,Or={create:Sr,update:Sr};function Ar(e,t){if(!i(e.data.domProps)||!i(t.data.domProps)){var n,r,a=t.elm,l=e.data.domProps||{},s=t.data.domProps||{};for(n in o(s.__ob__)&&(s=t.data.domProps=A({},s)),l)n in s||(a[n]="");for(n in s){if(r=s[n],"textContent"===n||"innerHTML"===n){if(t.children&&(t.children.length=0),r===l[n])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===n&&"PROGRESS"!==a.tagName){a._value=r;var u=i(r)?"":String(r);Tr(a,u)&&(a.value=u)}else if("innerHTML"===n&&Jn(a.tagName)&&i(a.innerHTML)){(Er=Er||document.createElement("div")).innerHTML=""+r+"";for(var c=Er.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;c.firstChild;)a.appendChild(c.firstChild)}else if(r!==l[n])try{a[n]=r}catch(e){}}}}function Tr(e,t){return!e.composing&&("OPTION"===e.tagName||function(e,t){var n=!0;try{n=document.activeElement!==e}catch(e){}return n&&e.value!==t}(e,t)||function(e,t){var n=e.value,r=e._vModifiers;if(o(r)){if(r.number)return v(n)!==v(t);if(r.trim)return n.trim()!==t.trim()}return n!==t}(e,t))}var Pr={create:Ar,update:Ar},jr=w(function(e){var t={},n=/:(.+)/;return e.split(/;(?![^(]*\))/g).forEach(function(e){if(e){var r=e.split(n);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t});function zr(e){var t=Lr(e.style);return e.staticStyle?A(e.staticStyle,t):t}function Lr(e){return Array.isArray(e)?T(e):"string"==typeof e?jr(e):e}var Rr,Ir=/^--/,Mr=/\s*!important$/,Dr=function(e,t,n){if(Ir.test(t))e.style.setProperty(t,n);else if(Mr.test(n))e.style.setProperty(S(t),n.replace(Mr,""),"important");else{var r=qr(t);if(Array.isArray(n))for(var i=0,o=n.length;i-1?t.split(Br).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function Vr(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(Br).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{for(var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?e.setAttribute("class",n):e.removeAttribute("class")}}function Wr(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&A(t,Kr(e.name||"v")),A(t,e),t}return"string"==typeof e?Kr(e):void 0}}var Kr=w(function(e){return{enterClass:e+"-enter",enterToClass:e+"-enter-to",enterActiveClass:e+"-enter-active",leaveClass:e+"-leave",leaveToClass:e+"-leave-to",leaveActiveClass:e+"-leave-active"}}),Gr=W&&!X,Jr="transition",Qr="animation",Xr="transition",Yr="transitionend",Zr="animation",ei="animationend";Gr&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(Xr="WebkitTransition",Yr="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Zr="WebkitAnimation",ei="webkitAnimationEnd"));var ti=W?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(e){return e()};function ni(e){ti(function(){ti(e)})}function ri(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n.indexOf(t)<0&&(n.push(t),Hr(e,t))}function ii(e,t){e._transitionClasses&&y(e._transitionClasses,t),Vr(e,t)}function oi(e,t,n){var r=li(e,t),i=r.type,o=r.timeout,a=r.propCount;if(!i)return n();var l=i===Jr?Yr:ei,s=0,u=function(){e.removeEventListener(l,c),n()},c=function(t){t.target===e&&++s>=a&&u()};setTimeout(function(){s0&&(n=Jr,c=a,f=o.length):t===Qr?u>0&&(n=Qr,c=u,f=s.length):f=(n=(c=Math.max(a,u))>0?a>u?Jr:Qr:null)?n===Jr?o.length:s.length:0,{type:n,timeout:c,propCount:f,hasTransform:n===Jr&&ai.test(r[Xr+"Property"])}}function si(e,t){for(;e.length1}function hi(e,t){!0!==t.data.show&&ci(t)}var vi=function(e){var t,n,r={},s=e.modules,u=e.nodeOps;for(t=0;th?b(e,i(n[m+1])?null:n[m+1].elm,n,d,m,r):d>m&&w(0,t,p,h)}(p,g,m,n,c):o(m)?(o(e.text)&&u.setTextContent(p,""),b(p,null,m,0,m.length-1,n)):o(g)?w(0,g,0,g.length-1):o(e.text)&&u.setTextContent(p,""):e.text!==t.text&&u.setTextContent(p,t.text),o(h)&&o(d=h.hook)&&o(d=d.postpatch)&&d(e,t)}}}function $(e,t,n){if(a(n)&&o(e.parent))e.parent.data.pendingInsert=t;else for(var r=0;r-1,a.selected!==o&&(a.selected=o);else if(L(_i(a),r))return void(e.selectedIndex!==l&&(e.selectedIndex=l));i||(e.selectedIndex=-1)}}function bi(e,t){return t.every(function(t){return!L(t,e)})}function _i(e){return"_value"in e?e._value:e.value}function wi(e){e.target.composing=!0}function xi(e){e.target.composing&&(e.target.composing=!1,ki(e.target,"input"))}function ki(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}function Ci(e){return!e.componentInstance||e.data&&e.data.transition?e:Ci(e.componentInstance._vnode)}var $i={model:gi,show:{bind:function(e,t,n){var r=t.value,i=(n=Ci(n)).data&&n.data.transition,o=e.__vOriginalDisplay="none"===e.style.display?"":e.style.display;r&&i?(n.data.show=!0,ci(n,function(){e.style.display=o})):e.style.display=r?o:"none"},update:function(e,t,n){var r=t.value;!r!=!t.oldValue&&((n=Ci(n)).data&&n.data.transition?(n.data.show=!0,r?ci(n,function(){e.style.display=e.__vOriginalDisplay}):fi(n,function(){e.style.display="none"})):e.style.display=r?e.__vOriginalDisplay:"none")},unbind:function(e,t,n,r,i){i||(e.style.display=e.__vOriginalDisplay)}}},Si={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function Ei(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?Ei(Wt(t.children)):e}function Oi(e){var t={},n=e.$options;for(var r in n.propsData)t[r]=e[r];var i=n._parentListeners;for(var o in i)t[k(o)]=i[o];return t}function Ai(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{props:t.componentOptions.propsData})}var Ti=function(e){return e.tag||Vt(e)},Pi=function(e){return"show"===e.name},ji={name:"transition",props:Si,abstract:!0,render:function(e){var t=this,n=this.$slots.default;if(n&&(n=n.filter(Ti)).length){0;var r=this.mode;0;var i=n[0];if(function(e){for(;e=e.parent;)if(e.data.transition)return!0}(this.$vnode))return i;var o=Ei(i);if(!o)return i;if(this._leaving)return Ai(e,i);var a="__transition-"+this._uid+"-";o.key=null==o.key?o.isComment?a+"comment":a+o.tag:l(o.key)?0===String(o.key).indexOf(a)?o.key:a+o.key:o.key;var s=(o.data||(o.data={})).transition=Oi(this),u=this._vnode,c=Ei(u);if(o.data.directives&&o.data.directives.some(Pi)&&(o.data.show=!0),c&&c.data&&!function(e,t){return t.key===e.key&&t.tag===e.tag}(o,c)&&!Vt(c)&&(!c.componentInstance||!c.componentInstance._vnode.isComment)){var f=c.data.transition=A({},s);if("out-in"===r)return this._leaving=!0,lt(f,"afterLeave",function(){t._leaving=!1,t.$forceUpdate()}),Ai(e,i);if("in-out"===r){if(Vt(o))return u;var p,d=function(){p()};lt(s,"afterEnter",d),lt(s,"enterCancelled",d),lt(f,"delayLeave",function(e){p=e})}}return i}}},zi=A({tag:String,moveClass:String},Si);function Li(e){e.elm._moveCb&&e.elm._moveCb(),e.elm._enterCb&&e.elm._enterCb()}function Ri(e){e.data.newPos=e.elm.getBoundingClientRect()}function Ii(e){var t=e.data.pos,n=e.data.newPos,r=t.left-n.left,i=t.top-n.top;if(r||i){e.data.moved=!0;var o=e.elm.style;o.transform=o.WebkitTransform="translate("+r+"px,"+i+"px)",o.transitionDuration="0s"}}delete zi.mode;var Mi={Transition:ji,TransitionGroup:{props:zi,beforeMount:function(){var e=this,t=this._update;this._update=function(n,r){var i=Yt(e);e.__patch__(e._vnode,e.kept,!1,!0),e._vnode=e.kept,i(),t.call(e,n,r)}},render:function(e){for(var t=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,i=this.$slots.default||[],o=this.children=[],a=Oi(this),l=0;l-1?Xn[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:Xn[e]=/HTMLUnknownElement/.test(t.toString())},A($n.options.directives,$i),A($n.options.components,Mi),$n.prototype.__patch__=W?vi:P,$n.prototype.$mount=function(e,t){return function(e,t,n){var r;return e.$el=t,e.$options.render||(e.$options.render=me),tn(e,"beforeMount"),r=function(){e._update(e._render(),n)},new hn(e,r,P,{before:function(){e._isMounted&&!e._isDestroyed&&tn(e,"beforeUpdate")}},!0),n=!1,null==e.$vnode&&(e._isMounted=!0,tn(e,"mounted")),e}(this,e=e&&W?function(e){if("string"==typeof e){var t=document.querySelector(e);return t||document.createElement("div")}return e}(e):void 0,t)},W&&setTimeout(function(){q.devtools&&oe&&oe.emit("init",$n)},0);var Di=$n; +/*! + * vue-router v3.0.7 + * (c) 2019 Evan You + * @license MIT + */function Ni(e){return Object.prototype.toString.call(e).indexOf("Error")>-1}function qi(e,t){for(var n in t)e[n]=t[n];return e}var Ui={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render:function(e,t){var n=t.props,r=t.children,i=t.parent,o=t.data;o.routerView=!0;for(var a=i.$createElement,l=n.name,s=i.$route,u=i._routerViewCache||(i._routerViewCache={}),c=0,f=!1;i&&i._routerRoot!==i;){var p=i.$vnode&&i.$vnode.data;p&&(p.routerView&&c++,p.keepAlive&&i._inactive&&(f=!0)),i=i.$parent}if(o.routerViewDepth=c,f)return a(u[l],o,r);var d=s.matched[c];if(!d)return u[l]=null,a();var h=u[l]=d.components[l];o.registerRouteInstance=function(e,t){var n=d.instances[l];(t&&n!==e||!t&&n===e)&&(d.instances[l]=t)},(o.hook||(o.hook={})).prepatch=function(e,t){d.instances[l]=t.componentInstance},o.hook.init=function(e){e.data.keepAlive&&e.componentInstance&&e.componentInstance!==d.instances[l]&&(d.instances[l]=e.componentInstance)};var v=o.props=function(e,t){switch(typeof t){case"undefined":return;case"object":return t;case"function":return t(e);case"boolean":return t?e.params:void 0;default:0}}(s,d.props&&d.props[l]);if(v){v=o.props=qi({},v);var g=o.attrs=o.attrs||{};for(var m in v)h.props&&m in h.props||(g[m]=v[m],delete v[m])}return a(h,o,r)}};var Fi=/[!'()*]/g,Bi=function(e){return"%"+e.charCodeAt(0).toString(16)},Hi=/%2C/g,Vi=function(e){return encodeURIComponent(e).replace(Fi,Bi).replace(Hi,",")},Wi=decodeURIComponent;function Ki(e){var t={};return(e=e.trim().replace(/^(\?|#|&)/,""))?(e.split("&").forEach(function(e){var n=e.replace(/\+/g," ").split("="),r=Wi(n.shift()),i=n.length>0?Wi(n.join("=")):null;void 0===t[r]?t[r]=i:Array.isArray(t[r])?t[r].push(i):t[r]=[t[r],i]}),t):t}function Gi(e){var t=e?Object.keys(e).map(function(t){var n=e[t];if(void 0===n)return"";if(null===n)return Vi(t);if(Array.isArray(n)){var r=[];return n.forEach(function(e){void 0!==e&&(null===e?r.push(Vi(t)):r.push(Vi(t)+"="+Vi(e)))}),r.join("&")}return Vi(t)+"="+Vi(n)}).filter(function(e){return e.length>0}).join("&"):null;return t?"?"+t:""}var Ji=/\/?$/;function Qi(e,t,n,r){var i=r&&r.options.stringifyQuery,o=t.query||{};try{o=Xi(o)}catch(e){}var a={name:t.name||e&&e.name,meta:e&&e.meta||{},path:t.path||"/",hash:t.hash||"",query:o,params:t.params||{},fullPath:eo(t,i),matched:e?Zi(e):[]};return n&&(a.redirectedFrom=eo(n,i)),Object.freeze(a)}function Xi(e){if(Array.isArray(e))return e.map(Xi);if(e&&"object"==typeof e){var t={};for(var n in e)t[n]=Xi(e[n]);return t}return e}var Yi=Qi(null,{path:"/"});function Zi(e){for(var t=[];e;)t.unshift(e),e=e.parent;return t}function eo(e,t){var n=e.path,r=e.query;void 0===r&&(r={});var i=e.hash;return void 0===i&&(i=""),(n||"/")+(t||Gi)(r)+i}function to(e,t){return t===Yi?e===t:!!t&&(e.path&&t.path?e.path.replace(Ji,"")===t.path.replace(Ji,"")&&e.hash===t.hash&&no(e.query,t.query):!(!e.name||!t.name)&&(e.name===t.name&&e.hash===t.hash&&no(e.query,t.query)&&no(e.params,t.params)))}function no(e,t){if(void 0===e&&(e={}),void 0===t&&(t={}),!e||!t)return e===t;var n=Object.keys(e),r=Object.keys(t);return n.length===r.length&&n.every(function(n){var r=e[n],i=t[n];return"object"==typeof r&&"object"==typeof i?no(r,i):String(r)===String(i)})}var ro,io=[String,Object],oo=[String,Array],ao={name:"RouterLink",props:{to:{type:io,required:!0},tag:{type:String,default:"a"},exact:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,event:{type:oo,default:"click"}},render:function(e){var t=this,n=this.$router,r=this.$route,i=n.resolve(this.to,r,this.append),o=i.location,a=i.route,l=i.href,s={},u=n.options.linkActiveClass,c=n.options.linkExactActiveClass,f=null==u?"router-link-active":u,p=null==c?"router-link-exact-active":c,d=null==this.activeClass?f:this.activeClass,h=null==this.exactActiveClass?p:this.exactActiveClass,v=o.path?Qi(null,o,null,n):a;s[h]=to(r,v),s[d]=this.exact?s[h]:function(e,t){return 0===e.path.replace(Ji,"/").indexOf(t.path.replace(Ji,"/"))&&(!t.hash||e.hash===t.hash)&&function(e,t){for(var n in t)if(!(n in e))return!1;return!0}(e.query,t.query)}(r,v);var g=function(e){lo(e)&&(t.replace?n.replace(o):n.push(o))},m={click:lo};Array.isArray(this.event)?this.event.forEach(function(e){m[e]=g}):m[this.event]=g;var y={class:s};if("a"===this.tag)y.on=m,y.attrs={href:l};else{var b=function e(t){if(t)for(var n,r=0;r=0&&(t=e.slice(r),e=e.slice(0,r));var i=e.indexOf("?");return i>=0&&(n=e.slice(i+1),e=e.slice(0,i)),{path:e,query:n,hash:t}}(i.path||""),s=t&&t.path||"/",u=l.path?uo(l.path,s,n||i.append):s,c=function(e,t,n){void 0===t&&(t={});var r,i=n||Ki;try{r=i(e||"")}catch(e){r={}}for(var o in t)r[o]=t[o];return r}(l.query,i.query,r&&r.options.parseQuery),f=i.hash||l.hash;return f&&"#"!==f.charAt(0)&&(f="#"+f),{_normalized:!0,path:u,query:c,hash:f}}function zo(e,t){var n=To(e),r=n.pathList,i=n.pathMap,o=n.nameMap;function a(e,n,a){var l=jo(e,n,!1,t),u=l.name;if(u){var c=o[u];if(!c)return s(null,l);var f=c.regex.keys.filter(function(e){return!e.optional}).map(function(e){return e.name});if("object"!=typeof l.params&&(l.params={}),n&&"object"==typeof n.params)for(var p in n.params)!(p in l.params)&&f.indexOf(p)>-1&&(l.params[p]=n.params[p]);return l.path=Ao(c.path,l.params),s(c,l,a)}if(l.path){l.params={};for(var d=0;d=e.length?n():e[i]?t(e[i],function(){r(i+1)}):r(i+1)};r(0)}function Yo(e){return function(t,n,r){var i=!1,o=0,a=null;Zo(e,function(e,t,n,l){if("function"==typeof e&&void 0===e.cid){i=!0,o++;var s,u=na(function(t){var i;((i=t).__esModule||ta&&"Module"===i[Symbol.toStringTag])&&(t=t.default),e.resolved="function"==typeof t?t:ro.extend(t),n.components[l]=t,--o<=0&&r()}),c=na(function(e){var t="Failed to resolve async component "+l+": "+e;a||(a=Ni(e)?e:new Error(t),r(a))});try{s=e(u,c)}catch(e){c(e)}if(s)if("function"==typeof s.then)s.then(u,c);else{var f=s.component;f&&"function"==typeof f.then&&f.then(u,c)}}}),i||r()}}function Zo(e,t){return ea(e.map(function(e){return Object.keys(e.components).map(function(n){return t(e.components[n],e.instances[n],e,n)})}))}function ea(e){return Array.prototype.concat.apply([],e)}var ta="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function na(e){var t=!1;return function(){for(var n=[],r=arguments.length;r--;)n[r]=arguments[r];if(!t)return t=!0,e.apply(this,n)}}var ra=function(e,t){this.router=e,this.base=function(e){if(!e)if(so){var t=document.querySelector("base");e=(e=t&&t.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else e="/";"/"!==e.charAt(0)&&(e="/"+e);return e.replace(/\/$/,"")}(t),this.current=Yi,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[]};function ia(e,t,n,r){var i=Zo(e,function(e,r,i,o){var a=function(e,t){"function"!=typeof e&&(e=ro.extend(e));return e.options[t]}(e,t);if(a)return Array.isArray(a)?a.map(function(e){return n(e,r,i,o)}):n(a,r,i,o)});return ea(r?i.reverse():i)}function oa(e,t){if(t)return function(){return e.apply(t,arguments)}}ra.prototype.listen=function(e){this.cb=e},ra.prototype.onReady=function(e,t){this.ready?e():(this.readyCbs.push(e),t&&this.readyErrorCbs.push(t))},ra.prototype.onError=function(e){this.errorCbs.push(e)},ra.prototype.transitionTo=function(e,t,n){var r=this,i=this.router.match(e,this.current);this.confirmTransition(i,function(){r.updateRoute(i),t&&t(i),r.ensureURL(),r.ready||(r.ready=!0,r.readyCbs.forEach(function(e){e(i)}))},function(e){n&&n(e),e&&!r.ready&&(r.ready=!0,r.readyErrorCbs.forEach(function(t){t(e)}))})},ra.prototype.confirmTransition=function(e,t,n){var r=this,i=this.current,o=function(e){Ni(e)&&(r.errorCbs.length?r.errorCbs.forEach(function(t){t(e)}):console.error(e)),n&&n(e)};if(to(e,i)&&e.matched.length===i.matched.length)return this.ensureURL(),o();var a=function(e,t){var n,r=Math.max(e.length,t.length);for(n=0;n-1?decodeURI(e.slice(0,r))+e.slice(r):decodeURI(e)}else n>-1&&(e=decodeURI(e.slice(0,n))+e.slice(n));return e}function fa(e){var t=window.location.href,n=t.indexOf("#");return(n>=0?t.slice(0,n):t)+"#"+e}function pa(e){Ho?Jo(fa(e)):window.location.hash=e}function da(e){Ho?Qo(fa(e)):window.location.replace(fa(e))}var ha=function(e){function t(t,n){e.call(this,t,n),this.stack=[],this.index=-1}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.push=function(e,t,n){var r=this;this.transitionTo(e,function(e){r.stack=r.stack.slice(0,r.index+1).concat(e),r.index++,t&&t(e)},n)},t.prototype.replace=function(e,t,n){var r=this;this.transitionTo(e,function(e){r.stack=r.stack.slice(0,r.index).concat(e),t&&t(e)},n)},t.prototype.go=function(e){var t=this,n=this.index+e;if(!(n<0||n>=this.stack.length)){var r=this.stack[n];this.confirmTransition(r,function(){t.index=n,t.updateRoute(r)})}},t.prototype.getCurrentLocation=function(){var e=this.stack[this.stack.length-1];return e?e.fullPath:"/"},t.prototype.ensureURL=function(){},t}(ra),va=function(e){void 0===e&&(e={}),this.app=null,this.apps=[],this.options=e,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=zo(e.routes||[],this);var t=e.mode||"hash";switch(this.fallback="history"===t&&!Ho&&!1!==e.fallback,this.fallback&&(t="hash"),so||(t="abstract"),this.mode=t,t){case"history":this.history=new aa(this,e.base);break;case"hash":this.history=new sa(this,e.base,this.fallback);break;case"abstract":this.history=new ha(this,e.base);break;default:0}},ga={currentRoute:{configurable:!0}};function ma(e,t){return e.push(t),function(){var n=e.indexOf(t);n>-1&&e.splice(n,1)}}va.prototype.match=function(e,t,n){return this.matcher.match(e,t,n)},ga.currentRoute.get=function(){return this.history&&this.history.current},va.prototype.init=function(e){var t=this;if(this.apps.push(e),e.$once("hook:destroyed",function(){var n=t.apps.indexOf(e);n>-1&&t.apps.splice(n,1),t.app===e&&(t.app=t.apps[0]||null)}),!this.app){this.app=e;var n=this.history;if(n instanceof aa)n.transitionTo(n.getCurrentLocation());else if(n instanceof sa){var r=function(){n.setupListeners()};n.transitionTo(n.getCurrentLocation(),r,r)}n.listen(function(e){t.apps.forEach(function(t){t._route=e})})}},va.prototype.beforeEach=function(e){return ma(this.beforeHooks,e)},va.prototype.beforeResolve=function(e){return ma(this.resolveHooks,e)},va.prototype.afterEach=function(e){return ma(this.afterHooks,e)},va.prototype.onReady=function(e,t){this.history.onReady(e,t)},va.prototype.onError=function(e){this.history.onError(e)},va.prototype.push=function(e,t,n){this.history.push(e,t,n)},va.prototype.replace=function(e,t,n){this.history.replace(e,t,n)},va.prototype.go=function(e){this.history.go(e)},va.prototype.back=function(){this.go(-1)},va.prototype.forward=function(){this.go(1)},va.prototype.getMatchedComponents=function(e){var t=e?e.matched?e:this.resolve(e).route:this.currentRoute;return t?[].concat.apply([],t.matched.map(function(e){return Object.keys(e.components).map(function(t){return e.components[t]})})):[]},va.prototype.resolve=function(e,t,n){var r=jo(e,t=t||this.history.current,n,this),i=this.match(r,t),o=i.redirectedFrom||i.fullPath;return{location:r,route:i,href:function(e,t,n){var r="hash"===n?"#"+t:t;return e?co(e+"/"+r):r}(this.history.base,o,this.mode),normalizedTo:r,resolved:i}},va.prototype.addRoutes=function(e){this.matcher.addRoutes(e),this.history.current!==Yi&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(va.prototype,ga),va.install=function e(t){if(!e.installed||ro!==t){e.installed=!0,ro=t;var n=function(e){return void 0!==e},r=function(e,t){var r=e.$options._parentVnode;n(r)&&n(r=r.data)&&n(r=r.registerRouteInstance)&&r(e,t)};t.mixin({beforeCreate:function(){n(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),t.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,r(this,this)},destroyed:function(){r(this)}}),Object.defineProperty(t.prototype,"$router",{get:function(){return this._routerRoot._router}}),Object.defineProperty(t.prototype,"$route",{get:function(){return this._routerRoot._route}}),t.component("RouterView",Ui),t.component("RouterLink",ao);var i=t.config.optionMergeStrategies;i.beforeRouteEnter=i.beforeRouteLeave=i.beforeRouteUpdate=i.created}},va.version="3.0.7",so&&window.Vue&&window.Vue.use(va);var ya=va;var ba={NotFound:()=>n.e(6).then(n.bind(null,43)),Layout:()=>Promise.all([n.e(0),n.e(2)]).then(n.bind(null,42))},_a={"v-1605d0f3":()=>n.e(7).then(n.bind(null,49)),"v-97a66c4e":()=>n.e(8).then(n.bind(null,48)),"v-3ea02d83":()=>n.e(9).then(n.bind(null,47)),"v-77fd80de":()=>n.e(10).then(n.bind(null,58)),"v-dd3f763a":()=>n.e(11).then(n.bind(null,45)),"v-3a272843":()=>n.e(12).then(n.bind(null,46)),"v-555b843c":()=>n.e(13).then(n.bind(null,70)),"v-01665048":()=>n.e(14).then(n.bind(null,51)),"v-bea6a0f0":()=>n.e(17).then(n.bind(null,53)),"v-61e12502":()=>n.e(18).then(n.bind(null,71)),"v-5138afa0":()=>n.e(20).then(n.bind(null,56)),"v-2634a070":()=>n.e(21).then(n.bind(null,59)),"v-a25f7170":()=>n.e(23).then(n.bind(null,61)),"v-1879f308":()=>n.e(24).then(n.bind(null,63)),"v-15b3b548":()=>n.e(4).then(n.bind(null,65)),"v-2f089be8":()=>n.e(26).then(n.bind(null,67)),"v-a9bc6b9c":()=>n.e(28).then(n.bind(null,69)),"v-9c1bf3a0":()=>n.e(29).then(n.bind(null,68)),"v-c73d89b6":()=>n.e(31).then(n.bind(null,66)),"v-13a2f408":()=>n.e(5).then(n.bind(null,64)),"v-45c3b484":()=>n.e(15).then(n.bind(null,62)),"v-2ca41536":()=>n.e(19).then(n.bind(null,60)),"v-26f90708":()=>n.e(25).then(n.bind(null,44)),"v-1f1ad7a8":()=>n.e(27).then(n.bind(null,57)),"v-07b4bab0":()=>n.e(30).then(n.bind(null,55)),"v-471e5888":()=>n.e(16).then(n.bind(null,54)),"v-25adca68":()=>n.e(22).then(n.bind(null,52))};function wa(e){const t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}const xa=/-(\w)/g,ka=wa(e=>e.replace(xa,(e,t)=>t?t.toUpperCase():"")),Ca=/\B([A-Z])/g,$a=wa(e=>e.replace(Ca,"-$1").toLowerCase()),Sa=wa(e=>e.charAt(0).toUpperCase()+e.slice(1));function Ea(e,t){if(!t)return;if(e(t))return e(t);return t.includes("-")?e(Sa(ka(t))):e(Sa(t))||e($a(t))}const Oa=Object.assign({},ba,_a),Aa=e=>Oa[e],Ta=e=>_a[e],Pa=e=>ba[e],ja=e=>Di.component(e);function za(e){return Ea(Ta,e)}function La(e){return Ea(Pa,e)}function Ra(e){return Ea(Aa,e)}function Ia(e){return Ea(ja,e)}function Ma(...e){return Promise.all(e.filter(e=>e).map(async e=>{if(!Ia(e)&&Ra(e)){const t=await Ra(e)();Di.component(e,t.default)}}))}function Da(e,t){"undefined"!=typeof window&&window.__VUEPRESS__&&(window.__VUEPRESS__[e]=t)}var Na={created(){this.$ssrContext&&(this.$ssrContext.title=this.$title,this.$ssrContext.lang=this.$lang,this.$ssrContext.description=this.$page.description||this.$description)},mounted(){this.currentMetaTags=new Set,this.updateMeta()},methods:{updateMeta(){document.title=this.$title,document.documentElement.lang=this.$lang;const e=this.$page.frontmatter.meta||[],t=e.slice(0);0===e.filter(e=>"description"===e.name).length&&t.push({name:"description",content:this.$description});const n=document.querySelectorAll('meta[name="description"]');n.length&&n.forEach(e=>this.currentMetaTags.add(e)),this.currentMetaTags=new Set(qa(t,this.currentMetaTags))}},watch:{$page(){this.updateMeta()}},beforeDestroy(){qa(null,this.currentMetaTags)}};function qa(e,t){if(t&&[...t].forEach(e=>{document.head.removeChild(e)}),e)return e.map(e=>{const t=document.createElement("meta");return Object.keys(e).forEach(n=>{t.setAttribute(n,e[n])}),document.head.appendChild(t),t})}var Ua=n(3),Fa={mounted(){window.addEventListener("scroll",this.onScroll)},methods:{onScroll:n.n(Ua)()(function(){this.setActiveHash()},300),setActiveHash(){const e=[].slice.call(document.querySelectorAll(".sidebar-link")),t=[].slice.call(document.querySelectorAll(".header-anchor")).filter(t=>e.some(e=>e.hash===t.hash)),n=Math.max(window.pageYOffset,document.documentElement.scrollTop,document.body.scrollTop),r=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),i=window.innerHeight+n;for(let e=0;e=o.parentElement.offsetTop+10&&(!a||n{this.$nextTick(()=>{this.$vuepress.$set("disableScrollBehavior",!1)})})}}}},beforeDestroy(){window.removeEventListener("scroll",this.onScroll)}},Ba=n(2),Ha=n.n(Ba),Va=[Na,Fa,{mounted(){Ha.a.configure({showSpinner:!1}),this.$router.beforeEach((e,t,n)=>{e.path===t.path||Di.component(e.name)||Ha.a.start(),n()}),this.$router.afterEach(()=>{Ha.a.done(),this.isSidebarOpen=!1})}}],Wa={methods:{getLayout(){if(this.$page.path){const e=this.$page.frontmatter.layout;return e&&(this.$vuepress.getLayoutAsyncComponent(e)||this.$vuepress.getVueComponent(e))?e:"Layout"}return"NotFound"}},computed:{layout(){const e=this.getLayout();return Da("layout",e),Di.component(e)}}},Ka=n(0),Ga=Object(Ka.a)(Wa,function(){var e=this.$createElement;return(this._self._c||e)(this.layout,{tag:"component"})},[],!1,null,null,null).exports;!function(e,t,n){switch(t){case"components":e[t]||(e[t]={}),Object.assign(e[t],n);break;case"mixins":e[t]||(e[t]=[]),e[t].push(...n);break;default:throw new Error("Unknown option name.")}}(Ga,"mixins",Va);const Ja=[{name:"v-1605d0f3",path:"/",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-1605d0f3").then(n)}},{path:"/index.html",redirect:"/"},{name:"v-97a66c4e",path:"/guide/",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-97a66c4e").then(n)}},{path:"/guide/index.html",redirect:"/guide/"},{name:"v-3ea02d83",path:"/quickstart/",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-3ea02d83").then(n)}},{path:"/quickstart/index.html",redirect:"/quickstart/"},{name:"v-77fd80de",path:"/quickstart/egg.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-77fd80de").then(n)}},{name:"v-dd3f763a",path:"/zh/",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-dd3f763a").then(n)}},{path:"/zh/index.html",redirect:"/zh/"},{name:"v-3a272843",path:"/zh/guide/",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-3a272843").then(n)}},{path:"/zh/guide/index.html",redirect:"/zh/guide/"},{name:"v-555b843c",path:"/zh/guide/application.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-555b843c").then(n)}},{name:"v-01665048",path:"/zh/guide/config.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-01665048").then(n)}},{name:"v-bea6a0f0",path:"/zh/guide/cookie.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-bea6a0f0").then(n)}},{name:"v-61e12502",path:"/zh/guide/directory.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-61e12502").then(n)}},{name:"v-5138afa0",path:"/zh/guide/faq.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-5138afa0").then(n)}},{name:"v-2634a070",path:"/zh/guide/helper.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-2634a070").then(n)}},{name:"v-a25f7170",path:"/zh/guide/i18n.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-a25f7170").then(n)}},{name:"v-1879f308",path:"/zh/guide/lifecycle.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-1879f308").then(n)}},{name:"v-15b3b548",path:"/zh/guide/middleware.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-15b3b548").then(n)}},{name:"v-2f089be8",path:"/zh/guide/plugin.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-2f089be8").then(n)}},{name:"v-a9bc6b9c",path:"/zh/guide/service.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-a9bc6b9c").then(n)}},{name:"v-9c1bf3a0",path:"/zh/guide/session.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-9c1bf3a0").then(n)}},{name:"v-c73d89b6",path:"/zh/quickstart/",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-c73d89b6").then(n)}},{path:"/zh/quickstart/index.html",redirect:"/zh/quickstart/"},{name:"v-13a2f408",path:"/zh/quickstart/egg.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-13a2f408").then(n)}},{name:"v-45c3b484",path:"/zh/guide/context.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-45c3b484").then(n)}},{name:"v-2ca41536",path:"/zh/guide/error_handler.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-2ca41536").then(n)}},{name:"v-26f90708",path:"/zh/guide/logger.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-26f90708").then(n)}},{name:"v-1f1ad7a8",path:"/zh/guide/router.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-1f1ad7a8").then(n)}},{name:"v-07b4bab0",path:"/zh/guide/upload.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-07b4bab0").then(n)}},{name:"v-471e5888",path:"/zh/guide/controller.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-471e5888").then(n)}},{name:"v-25adca68",path:"/zh/guide/httpclient.html",component:Ga,beforeEnter:(e,t,n)=>{Ma("Layout","v-25adca68").then(n)}},{path:"*",component:Ga}],Qa={title:"Egg",description:"Egg 文档",base:"/",pages:[{title:"Home",frontmatter:{home:!0,heroImage:"/icon.svg",heroText:"EggJs",tagline:"Born to build better enterprise frameworks and apps with Node.js & Koa",actionText:"QuickStart →",actionLink:"/zh/guide/",features:[{title:"Egg",details:"Egg Detail"}],footer:"MIT Licensed"},regularPath:"/",relativePath:"README.md",key:"v-1605d0f3",path:"/"},{title:"Guide",frontmatter:{title:"Guide",navTitle:"Egg Guide",toc:!1},regularPath:"/guide/",relativePath:"guide/README.md",key:"v-97a66c4e",path:"/guide/"},{title:"QuickStart",frontmatter:{title:"QuickStart"},regularPath:"/quickstart/",relativePath:"quickstart/README.md",key:"v-3ea02d83",path:"/quickstart/"},{title:"Simple Egg Application",frontmatter:{title:"Simple Egg Application"},regularPath:"/quickstart/egg.html",relativePath:"quickstart/egg.md",key:"v-77fd80de",path:"/quickstart/egg.html"},{title:"Home",frontmatter:{home:!0,heroImage:"/icon.svg",heroText:"EggJs",tagline:"为企业级框架和应用而生",actionText:"快速上手 →",actionLink:"/zh/guide/",features:[{title:"Egg",details:"Egg Detail"}],footer:"MIT Licensed"},regularPath:"/zh/",relativePath:"zh/README.md",key:"v-dd3f763a",path:"/zh/"},{title:"概述",frontmatter:{title:"概述",navTitle:"Egg 指南",toc:!1},regularPath:"/zh/guide/",relativePath:"zh/guide/README.md",key:"v-3a272843",path:"/zh/guide/",headers:[{level:2,title:"Web 模型",slug:"web-模型"},{level:2,title:"功能模块",slug:"功能模块"}]},{title:"Application",frontmatter:{title:"Application"},regularPath:"/zh/guide/application.html",relativePath:"zh/guide/application.md",key:"v-555b843c",path:"/zh/guide/application.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"获取方式",slug:"获取方式"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:3,title:"app.config",slug:"app-config"},{level:3,title:"app.router",slug:"app-router"},{level:3,title:"app.controller",slug:"app-controller"},{level:3,title:"app.logger",slug:"app-logger"},{level:3,title:"app.middleware",slug:"app-middleware"},{level:3,title:"app.server",slug:"app-server"},{level:3,title:"app.curl()",slug:"app-curl"},{level:3,title:"app.createAnonymousContext()",slug:"app-createanonymouscontext"},{level:2,title:"如何扩展",slug:"如何扩展"},{level:3,title:"方法扩展",slug:"方法扩展"},{level:3,title:"属性扩展",slug:"属性扩展"},{level:3,title:"编写测试",slug:"编写测试"},{level:3,title:"按照环境进行扩展",slug:"按照环境进行扩展"}]},{title:"配置",frontmatter:{title:"配置"},regularPath:"/zh/guide/config.html",relativePath:"zh/guide/config.md",key:"v-01665048",path:"/zh/guide/config.html",headers:[{level:2,title:"方案选型",slug:"方案选型"},{level:2,title:"运行环境",slug:"运行环境"},{level:3,title:"env",slug:"env"},{level:2,title:"配置文件",slug:"配置文件"},{level:2,title:"配置定义",slug:"配置定义"},{level:2,title:"AppInfo",slug:"appinfo"},{level:2,title:"加载规则",slug:"加载规则"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"为什么我的配置不生效?",slug:"为什么我的配置不生效?"},{level:3,title:"如何查看最终的配置?",slug:"如何查看最终的配置?"}]},{title:"Cookie",frontmatter:{title:"Cookie"},regularPath:"/zh/guide/cookie.html",relativePath:"zh/guide/cookie.md",key:"v-bea6a0f0",path:"/zh/guide/cookie.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"使用 Cookie",slug:"使用-cookie"},{level:2,title:"术语解释",slug:"术语解释"},{level:3,title:"过期时间",slug:"过期时间"},{level:3,title:"作用域",slug:"作用域"},{level:3,title:"安全",slug:"安全"},{level:3,title:"加签 && 加密",slug:"加签-加密"},{level:2,title:"API 说明",slug:"api-说明"},{level:3,title:"set(key, value, options)",slug:"set-key-value-options"},{level:3,title:"get(key, options)",slug:"get-key-options"},{level:3,title:"options",slug:"options"},{level:3,title:"配置秘钥",slug:"配置秘钥"},{level:2,title:"Cookie 实战",slug:"cookie-实战"},{level:3,title:"读取前端写入的 Cookie",slug:"读取前端写入的-cookie"},{level:3,title:"允许前端读取 Cookie",slug:"允许前端读取-cookie"},{level:3,title:"不允许浏览器看到明文内容",slug:"不允许浏览器看到明文内容"},{level:3,title:"删除 Cookie",slug:"删除-cookie"},{level:2,title:"编写测试",slug:"编写测试"},{level:2,title:"注意事项",slug:"注意事项"}]},{title:"目录规范",frontmatter:{title:"目录规范"},regularPath:"/zh/guide/directory.html",relativePath:"zh/guide/directory.md",key:"v-61e12502",path:"/zh/guide/directory.html"},{title:"FAQ",frontmatter:{title:"FAQ",sidebarDepth:1},regularPath:"/zh/guide/faq.html",relativePath:"zh/guide/faq.md",key:"v-5138afa0",path:"/zh/guide/faq.html",headers:[{level:2,title:"如何高效的反馈问题?",slug:"如何高效的反馈问题?"},{level:2,title:"为什么我的配置不生效?",slug:"为什么我的配置不生效?"},{level:2,title:"线上的日志打印去哪里了?",slug:"线上的日志打印去哪里了?"},{level:2,title:"进程管理为什么没有选型 PM2 ?",slug:"进程管理为什么没有选型-pm2-?"},{level:2,title:"为什么会有 csrf 报错?",slug:"为什么会有-csrf-报错?"},{level:2,title:"本地开发时,修改代码后为什么 worker 进程没有自动重启?",slug:"本地开发时,修改代码后为什么-worker-进程没有自动重启?"}]},{title:"Helper",frontmatter:{title:"Helper"},regularPath:"/zh/guide/helper.html",relativePath:"zh/guide/helper.md",key:"v-2634a070",path:"/zh/guide/helper.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"访问方式",slug:"访问方式"},{level:2,title:"常用的属性和方法",slug:"常用的属性和方法"},{level:2,title:"如何扩展",slug:"如何扩展"}]},{title:"国际化",frontmatter:{title:"国际化"},regularPath:"/zh/guide/i18n.html",relativePath:"zh/guide/i18n.md",key:"v-a25f7170",path:"/zh/guide/i18n.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"定义 locale",slug:"定义-locale"},{level:3,title:"目录规范",slug:"目录规范"},{level:3,title:"文件格式",slug:"文件格式"},{level:3,title:"占位符",slug:"占位符"},{level:2,title:"使用 i18n",slug:"使用-i18n"},{level:3,title:"ctx.__(key, ...values)",slug:"ctx-key-values"},{level:3,title:"在 View 中使用",slug:"在-view-中使用"},{level:2,title:"切换语言",slug:"切换语言"},{level:3,title:"默认语言",slug:"默认语言"},{level:3,title:"切换语言",slug:"切换语言-2"},{level:2,title:"局限性",slug:"局限性"}]},{title:"生命周期",frontmatter:{title:"生命周期"},regularPath:"/zh/guide/lifecycle.html",relativePath:"zh/guide/lifecycle.md",key:"v-1879f308",path:"/zh/guide/lifecycle.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"定义生命周期",slug:"定义生命周期"},{level:2,title:"详解生命周期",slug:"详解生命周期"},{level:3,title:"configWillLoad()",slug:"configwillload"},{level:3,title:"configDidLoad()",slug:"configdidload"},{level:3,title:"async didLoad()",slug:"async-didload"},{level:3,title:"async willReady()",slug:"async-willready"},{level:3,title:"async didReady()",slug:"async-didready"},{level:3,title:"async serverDidReady()",slug:"async-serverdidready"},{level:3,title:"async beforeClose()",slug:"async-beforeclose"}]},{title:"Middleware",frontmatter:{title:"Middleware"},regularPath:"/zh/guide/middleware.html",relativePath:"zh/guide/middleware.md",key:"v-15b3b548",path:"/zh/guide/middleware.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写中间件",slug:"编写中间件"},{level:2,title:"加载规则",slug:"加载规则"},{level:2,title:"使用中间件",slug:"使用中间件"},{level:2,title:"自定义配置",slug:"自定义配置"},{level:2,title:"通用配置",slug:"通用配置"},{level:3,title:"enable",slug:"enable"},{level:3,title:"match 和 ignore",slug:"match-和-ignore"},{level:2,title:"修改内置中间件的配置",slug:"修改内置中间件的配置"},{level:2,title:"路由中间件",slug:"路由中间件"},{level:2,title:"引入 Koa 生态",slug:"引入-koa-生态"},{level:2,title:"编写测试",slug:"编写测试"}]},{title:"使用插件",frontmatter:{title:"使用插件"},regularPath:"/zh/guide/plugin.html",relativePath:"zh/guide/plugin.md",key:"v-2f089be8",path:"/zh/guide/plugin.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"使用插件",slug:"使用插件"},{level:3,title:"安装依赖",slug:"安装依赖"},{level:3,title:"挂载插件",slug:"挂载插件"},{level:3,title:"使用插件",slug:"使用插件-2"},{level:2,title:"了解插件",slug:"了解插件"},{level:3,title:"目录结构",slug:"目录结构"},{level:3,title:"Service",slug:"service"},{level:3,title:"Config",slug:"config"},{level:3,title:"Middleware",slug:"middleware"},{level:3,title:"Extend",slug:"extend"},{level:3,title:"不支持的特性",slug:"不支持的特性"},{level:2,title:"插件配置",slug:"插件配置"},{level:3,title:"参数介绍",slug:"参数介绍"},{level:3,title:"开启框架内置插件",slug:"开启框架内置插件"},{level:3,title:"package 和 path",slug:"package-和-path"},{level:3,title:"根据环境配置",slug:"根据环境配置"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"如何开发一个插件",slug:"如何开发一个插件"},{level:3,title:"插件太多,每个应用都要开启怎么办?",slug:"插件太多,每个应用都要开启怎么办?"}]},{title:"Service",frontmatter:{title:"Service"},regularPath:"/zh/guide/service.html",relativePath:"zh/guide/service.md",key:"v-a9bc6b9c",path:"/zh/guide/service.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写 Service",slug:"编写-service"},{level:2,title:"使用 Service",slug:"使用-service"},{level:2,title:"生命周期",slug:"生命周期"},{level:2,title:"挂载规则",slug:"挂载规则"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:2,title:"编写测试",slug:"编写测试"}]},{title:"Session",frontmatter:{title:"Session"},regularPath:"/zh/guide/session.html",relativePath:"zh/guide/session.md",key:"v-9c1bf3a0",path:"/zh/guide/session.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"使用 Session",slug:"使用-session"},{level:2,title:"禁止使用的 Key 值",slug:"禁止使用的-key-值"},{level:2,title:"存储方式",slug:"存储方式"},{level:3,title:"Cookie",slug:"cookie"},{level:3,title:"Redis",slug:"redis"},{level:3,title:"注意事项",slug:"注意事项"},{level:2,title:"Session 实战",slug:"session-实战"},{level:3,title:"删除 Session",slug:"删除-session"},{level:3,title:"修改失效时间",slug:"修改失效时间"},{level:3,title:"延长有效期",slug:"延长有效期"}]},{title:"快速开始",frontmatter:{title:"快速开始"},regularPath:"/zh/quickstart/",relativePath:"zh/quickstart/README.md",key:"v-c73d89b6",path:"/zh/quickstart/"},{title:"简单的 Egg 应用",frontmatter:{title:"简单的 Egg 应用"},regularPath:"/zh/quickstart/egg.html",relativePath:"zh/quickstart/egg.md",key:"v-13a2f408",path:"/zh/quickstart/egg.html",headers:[{level:2,title:"典型场景",slug:"典型场景"},{level:2,title:"逐步搭建",slug:"逐步搭建"},{level:3,title:"环境准备",slug:"环境准备"},{level:3,title:"初始化项目",slug:"初始化项目"},{level:3,title:"目录结构",slug:"目录结构"},{level:3,title:"Controller",slug:"controller"},{level:3,title:"本地开发",slug:"本地开发"},{level:3,title:"模板渲染",slug:"模板渲染"},{level:3,title:"静态资源",slug:"静态资源"},{level:3,title:"配置文件",slug:"配置文件"},{level:3,title:"Service",slug:"service"},{level:3,title:"RESTful",slug:"restful"},{level:3,title:"单元测试",slug:"单元测试"}]},{title:"Context",frontmatter:{title:"Context"},regularPath:"/zh/guide/context.html",relativePath:"zh/guide/context.md",key:"v-45c3b484",path:"/zh/guide/context.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"获取方式",slug:"获取方式"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:3,title:"ctx.app",slug:"ctx-app"},{level:3,title:"ctx.service",slug:"ctx-service"},{level:3,title:"ctx.logger",slug:"ctx-logger"},{level:3,title:"ctx.curl()",slug:"ctx-curl"},{level:3,title:"ctx.runInBackground()",slug:"ctx-runinbackground"},{level:3,title:"ctx.query",slug:"ctx-query"},{level:3,title:"ctx.queries",slug:"ctx-queries"},{level:3,title:"ctx.params",slug:"ctx-params"},{level:3,title:"ctx.routerPath",slug:"ctx-routerpath"},{level:3,title:"ctx.routerName",slug:"ctx-routername"},{level:3,title:"ctx.request.body",slug:"ctx-request-body"},{level:3,title:"ctx.request.files",slug:"ctx-request-files"},{level:3,title:"ctx.get(name)",slug:"ctx-get-name"},{level:3,title:"ctx.cookies",slug:"ctx-cookies"},{level:3,title:"ctx.status =",slug:"ctx-status"},{level:3,title:"ctx.body =",slug:"ctx-body"},{level:3,title:"ctx.set(name, value)",slug:"ctx-set-name-value"},{level:3,title:"ctx.type =",slug:"ctx-type"},{level:3,title:"ctx.render()",slug:"ctx-render"},{level:3,title:"ctx.redirect()",slug:"ctx-redirect"},{level:3,title:"ctx.request",slug:"ctx-request"},{level:3,title:"ctx.response",slug:"ctx-response"},{level:3,title:"更多",slug:"更多"},{level:2,title:"如何扩展",slug:"如何扩展"},{level:3,title:"属性扩展",slug:"属性扩展"},{level:3,title:"编写测试",slug:"编写测试"}]},{title:"异常处理",frontmatter:{title:"异常处理"},regularPath:"/zh/guide/error_handler.html",relativePath:"zh/guide/error_handler.md",key:"v-2ca41536",path:"/zh/guide/error_handler.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"Node.js 异常处理",slug:"node-js-异常处理"},{level:2,title:"框架内置支持",slug:"框架内置支持"},{level:2,title:"业务错误处理",slug:"业务错误处理"},{level:2,title:"框架兜底处理",slug:"框架兜底处理"},{level:3,title:"errorPageUrl",slug:"errorpageurl"},{level:3,title:"自定义统一异常处理",slug:"自定义统一异常处理"},{level:2,title:"404",slug:"_404"},{level:3,title:"默认返回值",slug:"默认返回值"},{level:3,title:"重定向",slug:"重定向"},{level:3,title:"自定义 404 响应",slug:"自定义-404-响应"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"该不该 Catch",slug:"该不该-catch"},{level:3,title:"回调错误无法捕获",slug:"回调错误无法捕获"}]},{title:"日志",frontmatter:{title:"日志"},regularPath:"/zh/guide/logger.html",relativePath:"zh/guide/logger.md",key:"v-26f90708",path:"/zh/guide/logger.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"打印日志",slug:"打印日志"},{level:3,title:"app.logger",slug:"app-logger"},{level:3,title:"ctx.logger",slug:"ctx-logger"},{level:3,title:"this.logger",slug:"this-logger"},{level:2,title:"日志级别",slug:"日志级别"},{level:2,title:"错误日志",slug:"错误日志"},{level:2,title:"输出方式",slug:"输出方式"},{level:3,title:"文件日志",slug:"文件日志"},{level:3,title:"终端日志",slug:"终端日志"},{level:2,title:"正式环境",slug:"正式环境"},{level:3,title:"落盘方式",slug:"落盘方式"},{level:3,title:"日志文件输出位置",slug:"日志文件输出位置"},{level:3,title:"禁止输出 DEBUG 日志",slug:"禁止输出-debug-日志"},{level:3,title:"禁止输出终端日志",slug:"禁止输出终端日志"},{level:2,title:"自定义日志",slug:"自定义日志"},{level:3,title:"框架内置日志",slug:"框架内置日志"},{level:3,title:"增加自定义日志",slug:"增加自定义日志"},{level:3,title:"日志输出格式",slug:"日志输出格式"},{level:3,title:"高级自定义日志",slug:"高级自定义日志"},{level:2,title:"日志切割",slug:"日志切割"},{level:3,title:"按天切割",slug:"按天切割"},{level:3,title:"按照文件大小切割",slug:"按照文件大小切割"},{level:3,title:"按照小时切割",slug:"按照小时切割"},{level:2,title:"编写测试",slug:"编写测试"}]},{title:"Router",frontmatter:{title:"Router"},regularPath:"/zh/guide/router.html",relativePath:"zh/guide/router.md",key:"v-1f1ad7a8",path:"/zh/guide/router.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写路由",slug:"编写路由"},{level:2,title:"路由定义",slug:"路由定义"},{level:3,title:"路由方法",slug:"路由方法"},{level:3,title:"路由路径",slug:"路由路径"},{level:3,title:"路由中间件",slug:"路由中间件"},{level:3,title:"路由别名",slug:"路由别名"},{level:2,title:"RESTful 风格的 URL 定义",slug:"restful-风格的-url-定义"},{level:2,title:"Router 实战",slug:"router-实战"},{level:3,title:"获取查询参数",slug:"获取查询参数"},{level:3,title:"获取命名参数",slug:"获取命名参数"},{level:3,title:"重定向",slug:"重定向"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"路由映射太多?",slug:"路由映射太多?"},{level:3,title:"自动映射路由?",slug:"自动映射路由?"},{level:3,title:"通过装饰器映射?",slug:"通过装饰器映射?"}]},{title:"文件上传",frontmatter:{title:"文件上传"},regularPath:"/zh/guide/upload.html",relativePath:"zh/guide/upload.md",key:"v-07b4bab0",path:"/zh/guide/upload.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"File 模式",slug:"file-模式"},{level:3,title:"配置",slug:"配置"},{level:3,title:"前端代码",slug:"前端代码"},{level:3,title:"获取上传的文件",slug:"获取上传的文件"},{level:2,title:"Stream 模式",slug:"stream-模式"},{level:3,title:"上传单个文件",slug:"上传单个文件"},{level:3,title:"上传多个文件",slug:"上传多个文件"},{level:3,title:"错误处理",slug:"错误处理"},{level:2,title:"安全限制",slug:"安全限制"},{level:3,title:"文件大小",slug:"文件大小"},{level:3,title:"文件类型",slug:"文件类型"},{level:2,title:"云存储",slug:"云存储"},{level:3,title:"OSS",slug:"oss"},{level:3,title:"前端直接上传 OSS",slug:"前端直接上传-oss"}]},{title:"Controller",frontmatter:{title:"Controller"},regularPath:"/zh/guide/controller.html",relativePath:"zh/guide/controller.md",key:"v-471e5888",path:"/zh/guide/controller.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写 Controller",slug:"编写-controller"},{level:2,title:"生命周期",slug:"生命周期"},{level:2,title:"挂载规则",slug:"挂载规则"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:2,title:"Controller 实战",slug:"controller-实战"},{level:3,title:"HTTP 基础",slug:"http-基础"},{level:3,title:"获取请求参数",slug:"获取请求参数"},{level:3,title:"获取请求 body",slug:"获取请求-body"},{level:3,title:"解析 JSON / Form 请求",slug:"解析-json-form-请求"},{level:3,title:"解析 XML 请求",slug:"解析-xml-请求"},{level:3,title:"解析自定义类型",slug:"解析自定义类型"},{level:3,title:"文件上传",slug:"文件上传"},{level:3,title:"获取 Header",slug:"获取-header"},{level:3,title:"代理服务器",slug:"代理服务器"},{level:3,title:"读写 Cookie",slug:"读写-cookie"},{level:3,title:"参数校验",slug:"参数校验"},{level:3,title:"调用 Service",slug:"调用-service"},{level:3,title:"发送 HTTP 响应",slug:"发送-http-响应"},{level:3,title:"模板渲染",slug:"模板渲染"},{level:3,title:"JSONP",slug:"jsonp"},{level:3,title:"重定向",slug:"重定向"},{level:2,title:"编写测试",slug:"编写测试"},{level:3,title:"测试 GET 请求",slug:"测试-get-请求"},{level:3,title:"测试 POST 请求",slug:"测试-post-请求"},{level:3,title:"测试文件上传",slug:"测试文件上传"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"missing csrf token",slug:"missing-csrf-token"},{level:3,title:"redirection is prohibited",slug:"redirection-is-prohibited"}]},{title:"HttpClient",frontmatter:{title:"HttpClient"},regularPath:"/zh/guide/httpclient.html",relativePath:"zh/guide/httpclient.md",key:"v-25adca68",path:"/zh/guide/httpclient.html",headers:[{level:2,title:"使用背景",slug:"使用背景"},{level:2,title:"获取方式",slug:"获取方式"},{level:3,title:"app.httpclient",slug:"app-httpclient"},{level:3,title:"app.curl(http://github.com/url,%20options)",slug:"app-curl-url-options"},{level:3,title:"ctx.curl(http://github.com/url,%20options)",slug:"ctx-curl-url-options"},{level:2,title:"常用参数及响应",slug:"常用参数及响应"},{level:3,title:"请求参数",slug:"请求参数"},{level:3,title:"响应数据",slug:"响应数据"},{level:2,title:"HttpClient 实战",slug:"httpclient-实战"},{level:3,title:"发起 GET 请求",slug:"发起-get-请求"},{level:3,title:"通过 POST 发送 JSON",slug:"通过-post-发送-json"},{level:3,title:"提交 Form 表单",slug:"提交-form-表单"},{level:3,title:"文件上传(Multipart)",slug:"文件上传-multipart"},{level:3,title:"文件上传(Stream)",slug:"文件上传-stream"},{level:3,title:"发送 XML",slug:"发送-xml"},{level:3,title:"超时时间",slug:"超时时间"},{level:3,title:"处理重定向",slug:"处理重定向"},{level:3,title:"抓包调试",slug:"抓包调试"},{level:3,title:"事件监听",slug:"事件监听"},{level:2,title:"如何扩展",slug:"如何扩展"},{level:2,title:"编写测试",slug:"编写测试"},{level:2,title:"常见错误码",slug:"常见错误码"},{level:3,title:"ConnectionTimeoutError",slug:"connectiontimeouterror"},{level:3,title:"ResponseTimeoutError",slug:"responsetimeouterror"},{level:3,title:"ECONNRESET",slug:"econnreset"},{level:3,title:"ECONNREFUSED",slug:"econnrefused"},{level:3,title:"ENOTFOUND",slug:"enotfound"},{level:3,title:"JSONResponseFormatError",slug:"jsonresponseformaterror"},{level:2,title:"Options 参数详解",slug:"options-参数详解"},{level:3,title:"默认全局配置",slug:"默认全局配置"},{level:3,title:"method: String",slug:"method-string"},{level:3,title:"data: Object",slug:"data-object"},{level:3,title:"contentType: String",slug:"contenttype-string"},{level:3,title:"dataType: String",slug:"datatype-string"},{level:3,title:"dataAsQueryString: Boolean",slug:"dataasquerystring-boolean"},{level:3,title:"content: String|Buffer",slug:"content-string-buffer"},{level:3,title:"headers: Object",slug:"headers-object"},{level:3,title:"timeout: Number|Array",slug:"timeout-number-array"},{level:3,title:"files: Mixed",slug:"files-mixed"},{level:3,title:"stream: ReadStream",slug:"stream-readstream"},{level:3,title:"writeStream: WriteStream",slug:"writestream-writestream"},{level:3,title:"streaming: Boolean",slug:"streaming-boolean"},{level:3,title:"beforeRequest: Function(options)",slug:"beforerequest-function-options"},{level:3,title:"gzip: Boolean",slug:"gzip-boolean"},{level:3,title:"timing: Boolean",slug:"timing-boolean"},{level:3,title:"HTTPS 相关参数",slug:"https-相关参数"},{level:2,title:"示例代码",slug:"示例代码"}]}],themeConfig:{locales:{"/":{selectText:"Languages",label:"English",editLinkText:"Edit this page on GitHub",serviceWorker:{updatePopup:{message:"New content is available.",buttonText:"Refresh"}},nav:[{text:"Home",link:"/"},{text:"QuickStart",link:"/quickstart/"},{text:"Guide",link:"/guide/"}],sidebar:{"/quickstart/":[["./","QuickStart"],"egg"],"/guide/":[["./","Description"]]}},"/zh/":{selectText:"选择语言",label:"简体中文",editLinkText:"在 GitHub 上编辑此页",serviceWorker:{updatePopup:{message:"发现新内容可用.",buttonText:"刷新"}},nav:[{text:"首页",link:"/zh/"},{text:"快速开始",link:"/zh/quickstart/"},{text:"教程",link:"/zh/guide/"}],sidebar:{"/zh/quickstart/":[["./","快速开始"],"egg"],"/zh/guide/":[["./","概述"],"directory","middleware","controller","router","service","application","context","config","logger","cookie","session","helper","httpclient","error_handler","lifecycle","plugin","upload","i18n"]}}}},locales:{"/":{lang:"en-US",title:"Egg",description:"Born to build better enterprise frameworks and apps with Node.js & Koa",path:"/"},"/zh/":{lang:"zh-CN",title:"Egg",description:"为企业级框架和应用而生",path:"/zh/"}}};n(5);Di.component("Badge",()=>Promise.all([n.e(0),n.e(3)]).then(n.bind(null,50)));n(6);var Xa=[{},({Vue:e})=>{e.mixin({computed:{$dataBlock(){return this.$options.__data__block__}}})},{},{}],Ya=[];class Za{constructor(){this.store=new Di({data:{state:{}}})}$get(e){return this.store.state[e]}$set(e,t){Di.set(this.store.state,e,t)}$emit(...e){this.store.$emit(...e)}$on(...e){this.store.$on(...e)}}class el extends Za{}Object.assign(el.prototype,{getPageAsyncComponent:za,getLayoutAsyncComponent:La,getAsyncComponent:Ra,getVueComponent:Ia});var tl={install(e){const t=new el;e.$vuepress=t,e.prototype.$vuepress=t}};function nl(e,t){return e.options.routes.filter(e=>e.path.toLowerCase()===t.toLowerCase()).length>0}var rl={props:{pageKey:String,slotKey:{type:String,default:"default"}},render(e){const t=this.pageKey||this.$parent.$page.key;return Da("pageKey",t),Di.component(t)||Di.component(t,za(t)),e(t||"")}},il={functional:!0,props:{slotKey:String,required:!0},render:(e,{props:t,slots:n})=>e("div",{class:[`content__${t.slotKey}`]},n()[t.slotKey])},ol=(n(7),Object(Ka.a)({},function(e,t){var n=t._c;return n("svg",{staticClass:"icon outbound",attrs:{xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"}},[n("path",{attrs:{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}}),t._v(" "),n("polygon",{attrs:{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"}})])},[],!0,null,null,null).exports),al={functional:!0,render(e,{parent:t,children:n}){if(t._isMounted)return n;t.$once("hook:mounted",()=>{t.$forceUpdate()})}};Di.config.productionTip=!1,Di.use(ya),Di.use(tl),Di.mixin(function(e,t,n=Di){!function(e){e.locales&&Object.keys(e.locales).forEach(t=>{e.locales[t].path=t}),Object.freeze(e)}(t),n.$vuepress.$set("siteData",t);const r=new(e(n.$vuepress.$get("siteData"))),i=Object.getOwnPropertyDescriptors(Object.getPrototypeOf(r)),o={};return Object.keys(i).reduce((e,t)=>(t.startsWith("$")&&(e[t]=i[t].get),e),o),{computed:o}}(e=>(class{setPage(e){this.__page=e}get $site(){return e}get $themeConfig(){return this.$site.themeConfig}get $frontmatter(){return this.$page.frontmatter}get $localeConfig(){const{locales:e={}}=this.$site;let t,n;for(const r in e)"/"===r?n=e[r]:0===this.$page.path.indexOf(r)&&(t=e[r]);return t||n||{}}get $siteTitle(){return this.$localeConfig.title||this.$site.title||""}get $title(){const e=this.$page,{metaTitle:t}=this.$page.frontmatter;if("string"==typeof t)return t;const n=this.$siteTitle,r=e.frontmatter.home?null:e.frontmatter.title||e.title;return n?r?r+" | "+n:n:r||"VuePress"}get $description(){const e=function(e){if(e){const t=e.filter(e=>"description"===e.name)[0];if(t)return t.content}}(this.$page.frontmatter.meta);return e||this.$page.frontmatter.description||this.$localeConfig.description||this.$site.description||""}get $lang(){return this.$page.frontmatter.lang||this.$localeConfig.lang||"en-US"}get $localePath(){return this.$localeConfig.path||"/"}get $themeLocaleConfig(){return(this.$site.themeConfig.locales||{})[this.$localePath]||{}}get $page(){return this.__page?this.__page:function(e,t){for(let n=0;nn||(e.hash?!Di.$vuepress.$get("disableScrollBehavior")&&{selector:e.hash}:{x:0,y:0})});!function(e){e.beforeEach((t,n,r)=>{if(nl(e,t.path))r();else if(/(\/|\.html)$/.test(t.path))if(/\/$/.test(t.path)){const n=t.path.replace(/\/$/,"")+".html";nl(e,n)?r(n):r()}else r();else{const n=t.path+"/",i=t.path+".html";nl(e,i)?r(i):nl(e,n)?r(n):r()}})}(n);const r={};try{Xa.forEach(t=>{"function"==typeof t&&t({Vue:Di,options:r,router:n,siteData:Qa,isServer:e})})}catch(e){console.error(e)}return{app:new Di(Object.assign(r,{router:n,render:e=>e("div",{attrs:{id:"app"}},[e("router-view",{ref:"layout"}),e("div",{class:"global-ui"},Ya.map(t=>e(t)))])})),router:n}}(!1);window.__VUEPRESS__={version:"1.0.2",hash:"8fae391"},sl.onReady(()=>{ll.$mount("#app")})}]); \ No newline at end of file diff --git a/guide/index.html b/guide/index.html new file mode 100644 index 0000000..d5dcbf9 --- /dev/null +++ b/guide/index.html @@ -0,0 +1,17 @@ + + + + + + Guide | Egg + + + + + + + +
        + + + diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..505ad78 --- /dev/null +++ b/icon.svg @@ -0,0 +1,94 @@ + + + + Group 8 + Created with Sketch Beta. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..b53e405 --- /dev/null +++ b/index.html @@ -0,0 +1,21 @@ + + + + + + Egg + + + + + + + +
        hero

        EggJs

        + Born to build better enterprise frameworks and apps with Node.js & Koa +

        QuickStart →

        Egg

        Egg Detail

        + + + diff --git a/quickstart/egg.html b/quickstart/egg.html new file mode 100644 index 0000000..7ff32a7 --- /dev/null +++ b/quickstart/egg.html @@ -0,0 +1,21 @@ + + + + + + Simple Egg Application | Egg + + + + + + + + + + + diff --git a/quickstart/index.html b/quickstart/index.html new file mode 100644 index 0000000..645f72d --- /dev/null +++ b/quickstart/index.html @@ -0,0 +1,21 @@ + + + + + + QuickStart | Egg + + + + + + + + + + + diff --git a/zh/guide/application.html b/zh/guide/application.html new file mode 100644 index 0000000..3ae3d35 --- /dev/null +++ b/zh/guide/application.html @@ -0,0 +1,81 @@ + + + + + + Application | Egg + + + + + + + +

        使用场景

        Application 是全局应用对象,继承于 Koa.Application,可以用于扩展全局的方法和对象。

        在一个应用中,一个进程只会实例化一个 Application 实例。

        注意事项

        Node.js 进程间是无法共享对象的,因此每个进程都会有一个 Application 实例。

        获取方式

        Application 对象几乎可以在编写应用时的任何一个地方获取到:

        ControllerService 等可以通过 this.app,或者所有 Context 对象上的 ctx.app

        // app/controller/home.js
        +class HomeController extends Controller {
        +  async index() {
        +    // 从 `Controller/Service` 基类继承的属性: `this.app`
        +    console.log(this.app.config.name);
        +    // 从 ctx 对象上获取
        +    console.log(this.ctx.app.config.name);
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        几乎所有被框架 Loader 加载的文件,都可以 export 一个函数,并接收 app 作为参数:

        Router

        // app/router.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +  router.get('/', controller.home.index);
        +};
        +
        1
        2
        3
        4
        5

        Middleware

        // app/middleware/response_time.js
        +module.exports = (options, app) => {
        +  // 加载期传递 app 实例
        +  console.log(app);
        +
        +  return async function responseTime(ctx, next) {};
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        常用属性和方法

        app.config

        应用的配置

        app.router

        对应的路由对象。

        app.controller

        对应的 Controller 对象。

        app.logger

        用于应用级别的日志记录,如记录启动阶段的一些数据信息,记录一些业务上与请求无关的信息。

        更多参见 日志 文档。

        app.middleware

        挂载后的所有 Middleware 对象。

        app.server

        对应的 HTTP ServerHTTPS Server 实例。

        可以在 生命周期serverDidReady 事件之后获取到。

        app.curl()

        通过 HttpClient 发起请求。

        app.createAnonymousContext()

        在某些非用户请求的场景下,我们也需要访问到 Context,此时该方法获取:

        const ctx = app.createAnonymousContext();
        +await ctx.service.user.list();
        +
        1
        2

        如何扩展

        我们支持开发者通过 app/extend/application.js 来扩展 Application

        方法扩展

        // app/extend/application.js
        +module.exports = {
        +  foo(param) {
        +    // this 就是 app 对象,在其中可以调用 app 上的其他方法,或访问属性
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6

        属性扩展

        一般来说属性的计算只需要进行一次,否则在多次访问属性时会计算多次,降低应用性能。

        推荐的方式是使用 Symbol + Getter 的模式来实现缓存。

        例如,增加一个 app.nunjucks 属性:

        // app/extend/application.js
        +const NUNJUCKS = Symbol('Application#nunjucks');
        +const nunjuck = require('nunjuck');
        +
        +module.exports = {
        +  get nunjucks() {
        +    if (!this[NUNJUCKS]) {
        +      // this 就是 app 对象,可以获取到 app 上的其他属性
        +      this[NUNJUCKS] = new nunjucks.Environment(this.config.nunjucks);
        +    }
        +    return this[NUNJUCKS];
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        编写测试

        对于扩展的逻辑,我们一般需要通过单元测试来保证代码质量。

        // test/app/extend/application.js
        +const { app, assert } = require('egg-mock');
        +
        +describe('test/app/extend/application.js', () => {
        +  it('should export nunjucks', () => {
        +    assert(app.nunjucks);
        +    assert(app.nunjucks.renderString('{{ name }}', { name: 'TZ' }) === 'TZ');
        +  });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        按照环境进行扩展

        另外,还可以根据运行环境进行有选择的扩展。

        app/extend/application.unittest.js 定义的扩展,只在 unittest 环境生效。

        // app/extend/application.unittest.js
        +module.exports = {
        +  mockXX(k, v) {
        +  },
        +};
        +
        1
        2
        3
        4
        5

        这个文件只会在 unittest 环境加载。

        同理,对于下文中的 ApplicationContextRequestResponseHelper 都可以使用这种方式针对某个环境进行扩展。

        + + + diff --git a/zh/guide/config.html b/zh/guide/config.html new file mode 100644 index 0000000..be6484a --- /dev/null +++ b/zh/guide/config.html @@ -0,0 +1,79 @@ + + + + + + 配置 | Egg + + + + + + + +

        方案选型

        配置的管理有多种方案,以下列一些常见的方案:

        • 使用平台管理配置,应用构建时将当前环境的配置放入包内,启动时指定该配置。但应用就无法一次构建多次部署,而且本地开发环境想使用配置会变的很麻烦。
        • 使用平台管理配置,在启动时将当前环境的配置通过环境变量传入,这是比较优雅的方式,但框架对运维的要求会比较高,需要部署平台支持,同时开发环境也有相同痛点。
        • 使用代码管理配置,在代码中添加多个环境的配置,在启动时传入当前环境的参数即可。但无法全局配置,必须修改代码。

        我们选择了最后一种配置方案,配置即代码,配置的变更也应该经过 Review 后才能发布。应用包本身是可以部署在多个环境的,只需要指定运行环境即可。

        运行环境

        Egg 应用是一次构建多地部署,所以 Egg 会根据外部传入的一些配置来决定如何运行。

        env

        应用开发者可以通过 app.config.env 获取当前运行环境。

        以下为框架支持的运行环境:

        serverEnv NODE_ENV 说明
        local - 本地开发环境
        unittest test 单元测试环境
        prod production 生产环境

        运行环境会决定插件是否开启,选择默认的配置项,对开发者非常友好。

        配置文件

        框架会根据不同的运行环境来加载不同的配置文件。

        showcase
        +├── app
        +└── config
        +    ├── config.default.js
        +    ├── config.prod.js
        +    ├── config.unittest.js
        +    ├── config.default.js
        +    └── config.local.js
        +
        1
        2
        3
        4
        5
        6
        7
        8
        • config.default.js 为默认的配置文件,所有环境都会加载它,绝大部分配置应该写在这里
        • 然后会根据运行环境加载对应的配置,并覆盖默认配置的同名配置。 +
          • prod 环境会加载 config.prod.jsconfig.default.js 文件。
          • 然后 config.prod.js 会覆盖 config.default.js 的同名配置。

        具体的运行环境与配置文件的加载规则,参见应用部署文档相关章节。

        配置定义

        配置文件返回的是一个 Object 对象,支持三种写法,请根据具体场合选择合适的写法。

        // config/config.default.js
        +module.exports = {
        +  logger: {
        +    dir: '/home/admin/logs/demoapp',
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6

        配置文件也可以简化的写成 exports.key = value 形式。

        // config/config.default.js
        +exports.keys = 'my-cookie-secret-key';
        +exports.logger = {
        +  level: 'DEBUG',
        +};
        +
        1
        2
        3
        4
        5

        也可以是一个 function,入参为 appInfo

        // config/config.default.js
        +const path = require('path');
        +
        +module.exports = appInfo => {
        +  const config = {};
        +
        +  config.logger = {
        +    dir: path.join(appInfo.root, 'logs', appInfo.name),
        +  };
        +
        +  return config;
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        友情提示

        一些插件文档里面,描述配置时,可能会使用 exports.pluginName = {} 的方式。

        复制时,请根据你的具体配置写法进行修正。

        AppInfo

        内置的 appInfo 有:

        appInfo 说明
        pkg package.json
        name 应用名,同 pkg.name
        baseDir 应用的代码根目录。
        HOME 用户目录,如 admin 账户为 /home/admin
        root 应用根目录,localunittest 环境下为 baseDir,其他都为 HOME

        注意事项

        值得注意的是:appInfo.root 是一个优雅的适配。

        比如在服务器环境我们会使用 /home/admin/logs 作为日志目录,而本地开发时又不想污染用户目录,这样的适配就很好解决这个问题。

        加载规则

        应用、插件、框架都可以定义这些配置,而且目录结构都是一致的。

        但存在优先级(应用 > 框架 > 插件),相对于此运行环境的优先级会更高。

        框架会按加载顺序使用 extend2 模块进行深度拷贝。

        比如在 prod 环境加载一个配置的加载顺序如下,后加载的会覆盖前面的同名配置。

        -> 插件 config.default.js
        +-> 框架 config.default.js
        +-> 应用 config.default.js
        +-> 插件 config.prod.js
        +-> 框架 config.prod.js
        +-> 应用 config.prod.js
        +
        1
        2
        3
        4
        5
        6

        注意事项

        合并配置时,对于数组的处理是直接覆盖而不是合并。

        const a = {
        +  arr: [ 1, 2 ],
        +};
        +const b = {
        +  arr: [ 3 ],
        +};
        +extend(true, a, b);
        +// => { arr: [ 3 ] }
        +
        1
        2
        3
        4
        5
        6
        7
        8

        根据上面的例子,框架直接覆盖数组而不是进行合并。

        常见问题

        为什么我的配置不生效?

        首先,要确保不会犯以下的低级错误:

        // config/config.default.js
        +exports.someKeys = 'abc';
        +
        +module.exports = appInfo => {
        +  const config = {};
        +  config.keys = '123456';
        +  return config;
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8

        其次,参考下一条 FAQ 来排查问题。

        如何查看最终的配置?

        框架的配置功能比较强大,有不同环境变量,又有框架、插件、应用等很多地方配置。

        如果你分析问题时,想知道当前运行时使用的最终配置,框架提供了:

        • run/application_config.json 文件:最终的配置合并结果,可以用来分析问题。
        • run/application_config_meta.json 文件:用来排查属性的来源。

        另外,基于安全的考虑,dump 出的文件中会对一些字段进行脱敏处理,主要包括两类:

        • 如密码、密钥等安全字段,可以通过 config.dump.ignore 配置。
        • 如函数、Buffer 等类型,JSON.stringify 后的内容特别大。

        友情提示

        注意:run 目录是每次启动期都会 dump 的信息,用于问题排查。

        开发者修改该目录的文件将不会有任何效果,应该把该目录加到 gitignore 中。

        + + + diff --git a/zh/guide/context.html b/zh/guide/context.html new file mode 100644 index 0000000..63878a2 --- /dev/null +++ b/zh/guide/context.html @@ -0,0 +1,277 @@ + + + + + + Context | Egg + + + + + + + +

        使用场景

        Context 是一个 请求级别 的对象,继承自 Koa.Context

        在每一次收到用户请求时都会实例化一个 Context 对象,它封装了该次请求的相关信息,并提供了许多便捷的方法来获取请求参数或者设置响应信息。

        框架会将所有的 Service 挂载到 Context 实例上,某些插件也会将挂载一些其他的方法和对象。

        获取方式

        最常见的 Context 实例获取方式是在 Middleware, Controller 以及 Service 中。

        ControllerService 等可以通过 this.ctx 获取:

        // app/controller/home.js
        +class HomeController extends Controller {
        +  async index() {
        +    const { ctx } = this;
        +    ctx.body = ctx.query('name');
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7

        MiddlewareKoa 框架保持一致:

        // app/middleware/response_time.js
        +module.exports = () => {
        +  return async function responseTime(ctx, next) {
        +    const start = Date.now();
        +    await next();
        +    const cost = Date.now() - start;
        +    ctx.set('X-Response-Time', `${cost}ms`);
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        在某些非用户请求的场景下,我们也需要访问到 Context,此时可以通过 ApplicationcreateAnonymousContext() 方法获取:

        const ctx = app.createAnonymousContext();
        +await ctx.service.user.list();
        +
        1
        2

        定时任务 也接收 Context 实例作为参数,以便执行一些定时的业务逻辑:

        // app/schedule/refresh.js
        +exports.task = async ctx => {
        +  await ctx.service.posts.refresh();
        +};
        +
        1
        2
        3
        4

        常用属性和方法

        ctx.app

        对应的 Application 实例。

        ctx.service

        对应的 Service 实例。

        ctx.logger

        与请求相关的 ContextLogger 实例。

        它打印的日志都会在前面带上一些当前请求相关的信息。

        [$userId/$ip/$traceId/${cost}ms $method $url]

        通过这些信息,我们可以从日志快速定位请求,并串联一次请求中的所有的日志。

        更多参见 日志 文档。

        ctx.curl()

        通过 HttpClient 发起请求。

        ctx.runInBackground()

        有些时候,我们在处理完用户请求后,希望立即返回响应,但同时需要异步执行一些操作。

        // app/controller/trade.js
        +class TradeController extends Controller {
        +  async buy () {
        +    const goods = {};
        +    const result = await ctx.service.trade.buy(goods);
        +
        +    // 下单后需要进行一次核对,且不阻塞当前请求
        +    ctx.runInBackground(async () => {
        +      // 这里面的异常都会统统被 Backgroud 捕获掉,并打印错误日志
        +      await ctx.service.trade.check(result);
        +    });
        +
        +    ctx.body = { msg: '已下单' };
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        ctx.query

        在 URL 中 ? 后面的部分是一个 Query String,这一部分经常用于 GET 请求中传递参数。

        // GET /api/user/list?limit=10&sort=name
        +class UserController extends Controller {
        +  async list() {
        +    console.log(this.ctx.query);
        +    // { limit: '10', sort: 'name' }
        +    ctx.body = 'hi, egg';
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8

        对应的测试:

        // test/controller/home.test.js
        +const { app, mock, assert } = require('egg-mock');
        +
        +describe('test/controller/home.test.js', () => {
        +  it('should GET /', () => {
        +    return app.httpRequest()
        +      .get('/')
        +      .set('User-Agent', 'egg-unittest')
        +      .query({ limit: '10', sort: 'name' })
        +      .expect(200);
        +  });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        友情提示

        鉴于 HTTP 协议的约定,在请求中获取到的查询参数,均为字符串,如有需要需自行转型。

        值得注意的是,ctx.query 对重复的 key 只取第一个值,后面将被忽略。

        /api/user?sort=name&id=2&id=3query.id === '2'

        这样处理的原因是为了保持统一性,由于通常情况下我们都不会设计让用户传递相同的 key,所以我们经常会写类似下面的代码:

        const key = ctx.query.key || '';
        +if (key.startsWith('egg')) {
        +  // do something
        +}
        +
        1
        2
        3
        4

        而如果有人故意发起请求带上重复的 key 就会引发系统异常。因此框架保证了从 ctx.query 上获取的参数一旦存在,一定是字符串类型。

        ctx.queries

        如果你的系统设计允许用户传递相同的 key(不推荐),可以使用 ctx.queries

        // GET /api/user?sort=name&id=2&id=3
        +class UserController extends Controller {
        +  async list() {
        +    console.log(this.ctx.queries);
        +    // { sort: [ 'name' ], id: [ '2', '3' ] }
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        • queries.id === [ '2', '3']
        • ctx.queries 的属性一定是数组类型,如 queries.name === [ 'sort' ]
        • 如果你确定只会传递一个,则应该使用 query.sort 而不是 queries.sort

        ctx.params

        获取 Router 命名参数。

        ctx.routerPath

        获取当前命中的 Router 路径。

        ctx.routerName

        获取当前命中的 Router 别名。

        ctx.request.body

        框架内置了 bodyParser,用于获取 POST 等的 请求 body

        class UserController extends Controller {
        +  async create() {
        +    // 获取请求信息 `{ name: 'TZ' }`
        +    console.log(this.ctx.request.body);
        +    // ...
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7

        对应的测试:

        // test/controller/home.test.js
        +it('should POST form', () => {
        +
        +  // 跳过 `CSRF` 校验
        +  app.mockCsrf();
        +
        +  return app.httpRequest()
        +    .post('/user/create')
        +    .type('form')
        +    .send({ name: 'TZ' })
        +    .expect(200);
        +});
        +
        +it('should POST JSON', () => {
        +  app.mockCsrf();
        +  return app.httpRequest()
        +    .post('/user/create')
        +    .type('json')
        +    .send({ name: 'TZ' })
        +    .expect(200);
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        ctx.request.files

        获取 file 模式上传的文件对象,参见 文件上传 文档。

        ctx.get(name)

        获取请求 Header 信息。

        由于 HTTP 协议中 Header 是忽略大小写的,因此 ctx.headers 中的 Key 一律转为小写。

        一般我们推荐使用 ctx.get(name) 来获取对应的 Header,它会忽略大小写。

        ctx.get('User-Agent');
        +
        +ctx.headers['user-agent'];
        +
        +// 取不到值
        +ctx.headers['User-Agent'];
        +
        1
        2
        3
        4
        5
        6

        ctx.cookies

        读取 Cookie 对象,参见 Cookie 文档。

        ctx.status =

        HTTP 设计了非常多的状态码

        正确地设置状态码,可以让响应更符合语义,参考 List of HTTP status codes

        框架提供了一个便捷的 Setter 来进行状态码的设置:

        class UserController extends Controller {
        +  async create() {
        +    // 设置状态码为 201
        +    this.ctx.status = 201;
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6

        对应的测试:

        it('should POST /user', () => {
        +  return app.httpRequest()
        +    .post('/user')
        +    .expect(201);
        +});
        +
        1
        2
        3
        4
        5

        ctx.body =

        HTTP 请求的绝大部分数据都是通过 body 发送给请求方的。

        • 作为 API 接口,通常直接赋值一个 Object 对象。
        • 作为 HTML 页面,通常返回 HTML 字符串。
        • 作为文件下载等场景,还可以直接赋值为 Stream
        // app/controller/home.js
        +class HomeController extends Controller {
        +  // GET /
        +  async index() {
        +    this.ctx.type = 'html';
        +    this.ctx.body = '<html><h1>Hello</h1></html>';
        +  }
        +
        +  // GET /api/info
        +  async info() {
        +    this.ctx.body = {
        +      name: 'egg',
        +      category: 'framework',
        +      language: 'Node.js',
        +    };
        +  }
        +
        +  // GET /api/proxy
        +  async proxy() {
        +    const { ctx } = this;
        +    const result = await ctx.curl(url, {
        +      streaming: true,
        +    });
        +    ctx.set(result.header);
        +    // result.res 是一个 stream
        +    ctx.body = result.res;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28

        对应的测试:

        it('should response html', () => {
        +  return app.httpRequest()
        +    .get('/')
        +    .expect('<html><h1>Hello</h1></html>')
        +    .expect(/Hello/);
        +});
        +
        +it('should response json', () => {
        +  return app.httpRequest()
        +    .get('/api/info')
        +    .expect({
        +      name: 'egg',
        +      category: 'framework',
        +      language: 'Node.js',
        +    })
        +    .expect(res => {
        +      assert(res.body.name === 'egg');
        +    });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        ctx.set(name, value)

        除了 状态码响应体 外,还可以通过响应 Header 设置一些扩展信息。

        • ctx.set(key, value):可以设置一个 Header
        • ctx.set(headers):可以同时设置多个 Header
        // app/controller/proxy.js
        +class ProxyController extends Controller {
        +  async show() {
        +    const { ctx } = this;
        +    const start = Date.now();
        +    ctx.body = await ctx.service.post.get();
        +    const cost = Date.now() - start;
        +    // 设置一个响应头
        +    ctx.set('x-response-time', `${cost}ms`);
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        对应的测试:

        it('should send response header', () => {
        +  return app.httpRequest()
        +    .post('/api/post')
        +    .expect('X-Response-Time', /\d+ms/);
        +});
        +
        1
        2
        3
        4
        5

        ctx.type =

        和请求中的 body 一样,在响应也需要对应的 Content-Type 告知客户端如何对数据进行解析。

        框架提供了该语法糖,等价于 ctx.set('Content-Type', mime)

        • json:对应于 API 接口的 application/json
        • html:对应于 HTML 页面的 text/html
        • 更多参见 mime-types

        一般可以省略,框架会自动根据取值,来赋值对应的 Content-Type

        // app/controller/user.js
        +class UserController extends Controller {
        +  async list() {
        +    // 一般可以省略,框架会自动根据取值
        +    this.ctx.body = { name: 'egg' };
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        对应的测试:

        it('should response json', () => {
        +  return app.httpRequest()
        +    .get('/api/user')
        +    .expect('Content-Type', /json/);
        +});
        +
        1
        2
        3
        4
        5

        ctx.render()

        通常来说,我们不会手写 HTML 页面,而是会通过模板引擎进行生成。

        我们可以通过使用模板插件,来提供渲染能力。

        class HomeController extends Controller {
        +  async index() {
        +    const ctx = this.ctx;
        +    await ctx.render('home.tpl', { name: 'egg' });
        +    // ctx.body = await ctx.renderString('hi, {{ name }}', { name: 'egg' });
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        具体示例可以查看模板引擎

        ctx.redirect()

        重定向请求,默认为 302,如果需要,可以设置 ctx.status = 301

        class UserController extends Controller {
        +  async logout() {
        +    const { ctx } = this;
        +
        +    ctx.logout();
        +    ctx.redirect(ctx.get('referer') || '/');
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8

        对应的测试:

        it('should logout', () => {
        +  return app.httpRequest()
        +    .get('/user/logout')
        +    .expect('Location', '/')
        +    .expect(302);
        +});
        +
        1
        2
        3
        4
        5
        6

        安全提示

        基于安全考虑,默认只允许重定向处于白名单的域名。

        更多参见 安全链接 文档。

        ctx.request

        由于 Node.js 原生的 HTTP Request 对象比较底层。

        因此 Koa 做了一层薄薄的 Koa.Request 封装,提供了一系列方法获取 HTTP 请求相关信息。

        一般你不需要直接调用它,Context 已经代理了它们的大部分方法和属性,如上文所述。

        唯一的例外是:获取 POST 的 body 应该使用 ctx.request.body,而不是 ctx.body

        // app/controller/user.js
        +class UserController extends Controller {
        +  async update() {
        +    const { app, ctx } = this;
        +    // 等价于 ctx.query 这个 getter
        +    const id = ctx.request.query.id;
        +
        +    // 唯一的不同,获取 post body
        +    const postBody = ctx.request.body;
        +
        +    // 等价于 ctx.body 这个 setter
        +    ctx.response.body = await app.service.update(id, postBody);
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        ctx.response

        由于 Node.js 原生的 HTTP Response 对象比较底层。

        因此 Koa 做了一层薄薄的 Koa.Response 封装,提供了一系列方法设置 HTTP 响应。

        一般你不需要直接调用它,Context 已经代理了它们的大部分方法和属性,如上文所述。

        更多

        更多语法糖,请参见 Koa Aliases 文档。

        如何扩展

        我们支持开发者通过:

        • 通过 app/extend/context.js 来扩展 Context
        • 通过 app/extend/request.js 来扩展 Request
        • 通过 app/extend/response.js 来扩展 Response
        • 同样也支持在 app/extend/context.unittest.js 来根据运行环境扩展。

        属性扩展

        一般来说属性的计算只需要进行一次,否则在多次访问属性时会计算多次,降低应用性能。

        推荐的方式是使用 Symbol + Getter 的模式来实现缓存。

        // app/extend/context.js
        +const UA = Symbol('Context#ua');
        +const useragent = require('useragent');
        +
        +module.exports = {
        +  get ua() {
        +    if (!this[UA]) {
        +      // this 就是 ctx 对象,在其中可以调用 ctx 上的其他方法,或访问属性
        +      const uaString = this.get('user-agent');
        +      this[UA] = useragent.parse(uaString);
        +    }
        +    return this[UA];
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        编写测试

        // test/app/extend/context.js
        +const { app, assert } = require('egg-mock');
        +
        +describe('test/app/extend/contex.js', () => {
        +  it('should parse ua', () => {
        +    // 创建 ctx
        +    const ctx = app.mockContext({
        +      headers: {
        +        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) Chrome/15.0.874.24',
        +      },
        +    });
        +
        +    assert(ctx.ua.chrome);
        +  });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        + + + diff --git a/zh/guide/controller.html b/zh/guide/controller.html new file mode 100644 index 0000000..a211e1d --- /dev/null +++ b/zh/guide/controller.html @@ -0,0 +1,229 @@ + + + + + + Controller | Egg + + + + + + + +

        使用场景

        Controller 负责解析用户的输入,处理后返回相应的结果

        Controller 其实就是一个特殊的 Middleware,它在洋葱模型的最里面。

        场景举例:

        • 提供 AJAX 接口,接收用户的参数,查找数据库返回给用户或将用户的请求更新到数据库中。
        • 根据用户访问的 URL,渲染对应的模板返回 HTML 给浏览器渲染。
        • 作为代理服务器时,将用户的请求转发到其他服务上,并将处理结果返回给用户。

        最佳实践

        Controller 仅负责 HTTP 层的相关处理逻辑,不要包含太多业务逻辑。

        1. 获取用户通过 HTTP 传递过来的请求参数。
        2. 校验、组装参数。
        3. 调用 Service 进行业务处理。
        4. 必要时处理转换 Service 的返回结果,如渲染模板。
        5. 通过 HTTP 将结果响应给用户。

        编写 Controller

        我们约定把 Controller 放置在 app/controller 目录下:

        // app/controller/user.js
        +const { Controller } = require('egg');
        +
        +class UserController extends Controller {
        +  async create() {
        +    const { ctx, service } = this;
        +
        +    // 获取请求信息
        +    const userInfo = ctx.request.body;
        +
        +    // 校验参数
        +    ctx.assert(userInfo && userInfo.name, 422, 'user name is required.');
        +
        +    // 调用 Service 进行业务处理
        +    const result = await service.user.create(userInfo);
        +
        +    // 响应内容和响应码
        +    ctx.body = result;
        +    ctx.status = 201;
        +  }
        +}
        +module.exports = UserController;
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

        然后通过路由配置 URL 请求映射:

        // app/router.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +  router.post('/api/user', controller.user.create);
        +};
        +
        1
        2
        3
        4
        5

        然后通过 POST /api/user 即可访问。

        生命周期

        Controller 类会被挂载到 app.Controller 上,用于在 路由 配置 URL 映射。

        但处理用户请求时,每一个请求都会实例化一个 Controller 实例。

        Controller 是延迟实例化的,仅在请求调用到该 Controller 的时候,才会实例化。

        因此,无需担心实例化的性能损耗,经过我们大规模的实践证明,可以忽略不计。

        挂载规则

        约定放置在 app/controller 目录下,支持多级目录,对应的文件名会转换为驼峰格式

        app/controller/biz/user.js => app.controller.biz.user
        +app/controller/sync_user.js => app.controller.syncUser
        +app/controller/HackerNews.js => app.controller.hackerNews
        +
        1
        2
        3

        常用属性和方法

        Controller 实例继承 egg.Controller,提供以下属性:

        • this.ctx: 当前请求的上下文 Context 的实例,可以拿到各种便捷属性和方法。
        • this.app: 当前应用 Application 的实例,可以拿到全局对象和方法。
        • this.service:应用定义的 Service,可以调用业务逻辑层。
        • this.config:应用运行时的配置项
        • this.logger:logger 对象,使用方法类似 Context Logger,不同之处是通过这个 Logger 对象记录的日志,会额外加上该日志的文件路径,以便快速定位日志打印位置。

        Controller 实战

        HTTP 基础

        由于 Controller 基本上是业务开发中唯一和 HTTP 协议打交道的地方,在继续往下了解之前,我们首先简单的看一下 HTTP 协议是怎样的。

        如果我们发起一个 HTTP 请求来访问前面写的的 Controller

        $ curl -X POST http://localhost:7001/api/user -d '{"name":"TZ"}' -H 'Content-Type:application/json; charset=UTF-8'
        +
        1

        通过 curl 发出的 HTTP 请求的内容就会是下面这样的:

        POST /api/user HTTP/1.1
        +Host: localhost:7001
        +Content-Type:application/json; charset=UTF-8
        +
        +{"name":"TZ"}
        +
        1
        2
        3
        4
        5

        请求的第一行包含了三个信息,我们比较常用的是前面两个:

        • method:HTTP 方法,此处为 POST
        • path:HTTP 路径,此处为 /api/user,如果用户的请求中包含 query,也会在这里出现。

        从第二行开始直到空行位置,都是请求的 Headers 部分:

        • Host:我们在浏览器发起请求的时候,域名会用来通过 DNS 解析找到服务的 IP 地址,但是浏览器也会将域名和端口号放在 Host 头中一并发送给服务端。
        • Content-Type:当我们的请求有 body 的时候,都会有 Content-Type 来标明我们的请求体是什么格式的。

        之后的内容全部都是请求的 body,当请求是 POST, PUT 等方法的时候,可以带上请求体,服务端会根据 Content-Type 来解析请求体。

        在服务端处理完这个请求后,会发送一个 HTTP 响应给客户端:

        HTTP/1.1 201 Created
        +Content-Type: application/json; charset=utf-8
        +Content-Length: 13
        +Date: Mon, 09 Jan 2019 08:40:28 GMT
        +Connection: keep-alive
        +
        +{"id":1,"name":"TZ"}
        +
        1
        2
        3
        4
        5
        6
        7

        第一行中也包含了三段,其中我们常用的主要是响应状态码,这个例子中它的值是 201,它的含义是在服务端成功创建了一条资源。

        和请求一样,从第二行开始到下一个空行之间都是响应头,这里的 Content-Type, Content-Length 表示这个响应的格式是 JSON,长度为 13 个字节。

        最后剩下的部分就是这次响应真正的内容。

        获取请求参数

        在 URL 中 ? 后面的部分是一个 Query String,这一部分经常用于 GET 请求中传递参数。

        • ctx.query:解析查询参数,转换为 Object,属性为字符串。
        • ctx.queries:同上,但支持同名的多个参数解析,属性为数组。
        • ctx.params:获取 Router 命名参数。
        // GET /api/user/list?limit=10&sort=name
        +class UserController extends Controller {
        +  async list() {
        +    console.log(this.ctx.query);
        +    // { limit: '10', sort: 'name' }
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7

        友情提示

        鉴于 HTTP 协议的约定,在请求中获取到的查询参数,均为字符串,如有需要需自行转型。

        具体使用参见 Context 文档。

        获取请求 body

        虽然我们可以通过 URL 传递参数,但是还是有诸多限制:

        • 浏览器中会对 URL 的长度有所限制,如果需要传递的参数过多就会无法传递。
        • 访问的 URL 往往会被记录到日志或浏览器中,有一些敏感数据通过 URL 传递会不安全。
        • GET 请求可能会被缓存,导致非预期的意外。

        框架内置了 bodyParser,开发者可以通过 ctx.request.body 获取到对应的数据。

        class UserController extends Controller {
        +  async create() {
        +    // 获取请求信息 `{ name: 'TZ' }`
        +    console.log(this.ctx.request.body);
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6

        友情提示

        一个常见的错误是把 ctx.request.bodyctx.body 混淆,后者其实是 ctx.response.body 的简写。

        解析 JSON / Form 请求

        一般通过 Content-Type 来声明请求 body 的格式,常见的格式有 JSONForm

        • application/json:按 JSON 格式进行解析。
        • application/x-www-form-urlencoded:按 Form 格式进行解析。

        框架默认限制 body 的大小为 100kb,如果你需要上传更大的内容,需配置:

        // config/config.default.js
        +module.exports = {
        +  bodyParser: {
        +    jsonLimit: '1mb',
        +    formLimit: '1mb',
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        • 如果 body 超过了最大长度配置,会抛出一个状态码为 413 的异常。
        • 如果 body 解析失败(错误的 JSON),会抛出一个状态码为 400 的异常。
        • 支持 10mb 这种人性化的方式,具体参见 humanize-bytes 模块。

        友情提示

        如果我们应用前面还有一层反向代理(Nginx),则也需要调整它的配置,以确保反向代理也支持同样长度的请求 body。

        解析 XML 请求

        有些时候,我们需要解析 XML 协议,可配置:

        // config/config.default.js
        +exports.bodyParser = {
        +  enableTypes: [ 'json', 'form', 'text' ],
        +  extendTypes: {
        +    text: [ 'application/xml' ],
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        然后可以自行使用 XML 解析库分析 ctx.request.body 的原始字符串。

        const { xml2js } = require('xml-js');
        +const xmlContent = xml2js(ctx.request.body);
        +
        1
        2

        解析自定义类型

        如需自定义协议,如 application/custom-rpc,内容一样为 JSON,则可以配置:

        // config/config.default.js
        +exports.bodyParser = {
        +  extendTypes: {
        +    json: 'application/custom-rpc',
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6

        文件上传

        请求 body 还可以通过 multipart/form-data 格式来实现文件上传。

        框架内置了 egg-multipart 来支持该特性。

        支持 filestream 模式,本文仅介绍前者,更多用法请阅读文件上传文档。

        先启用 file 模式:

        // config/config.default.js
        +exports.multipart = {
        +  mode: 'file',
        +};
        +
        1
        2
        3
        4

        然后接收文件:

        // app/controller/upload.js
        +class UploadController extends Controller {
        +  async upload() {
        +    const { ctx } = this;
        +    const file = ctx.request.files[0];
        +    const name = 'egg-multipart-test/' + path.basename(file.filename);
        +    // 然后可以对文件进行处理,如上传 OSS 之类的
        +    // ...
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        获取 Header

        框架提供了 ctx.get(name) 方法来获取请求头,具体参见 Context 文档。

        class HomeController extends Controller {
        +  async index() {
        +    console.log(this.ctx.get('user-agent'));
        +  }
        +}
        +
        1
        2
        3
        4
        5

        代理服务器

        大部分情况下,我们的 Web 服务都是在代理服务器(如Nginx) 后面,此时需要配置 config.proxy = true,框架对应的 Getter 会对应的增加处理逻辑。

        • ctx.ips:获取请求经过所有的中间设备 IP 地址列表。
        • ctx.ip:获取请求发起方的 IP 地址,对应的代理 HeaderX-Forwarded-For
        • ctx.host:获取 HOST,对应的代理 HeaderX-Forwarded-Host

        另外,代理服务器处理 HTTPS 请求时,我们的 Web 服务收到的是内部的 HTTP 请求。

        开发者可以通过 ctx.protocol 来获取客户端访问的协议,框架会解析 X-Forwarded-Prot

        详细参见源码实现

        通过 ctx.cookies,我们可以在 Controller 中便捷、安全的设置和读取 Cookie

        具体可参见 Cookie 文档。

        参数校验

        在获取到用户请求的参数后,不可避免的要对参数进行一些校验。

        在上面的示例中,我们简单的使用 ctx.assert 进行了校验。

        实际业务中,会需要更复杂的校验,可以查看 egg-validate 等插件的文档。

        调用 Service

        不建议 Controller 中实现太多业务逻辑,一般通过 Service 层进行业务逻辑的封装。

        这不仅能提高代码的复用性,同时可以让我们的业务逻辑更好测试。

        发送 HTTP 响应

        当业务逻辑完成之后,Controller 的最后一个职责就是将处理结果通过 HTTP 响应给用户。

        • ctx.body=:设置响应 body。
        • ctx.type=:设置响应的 Content-Type
        • ctx.status=:设置响应的状态码。
        • ctx.set(name, header):设置响应 Header
        // app/controller/home.js
        +class HomeController extends Controller {
        +  async index() {
        +    const { ctx } = this;
        +
        +    ctx.set('powered-by', 'egg');
        +    ctx.body = {
        +      name: 'egg',
        +      category: 'framework',
        +      language: 'Node.js',
        +    };
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        具体可以参见 Context 文档。

        模板渲染

        通常来说,我们不会手写 HTML 页面,而是会通过模板引擎进行生成。

        我们可以通过使用模板插件,来提供渲染能力。

        class HomeController extends Controller {
        +  async index() {
        +    const ctx = this.ctx;
        +    await ctx.render('home.tpl', { name: 'egg' });
        +    // ctx.body = await ctx.renderString('hi, {{ name }}', { name: 'egg' });
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        具体示例可以查看模板引擎

        JSONP

        有时我们需要给非本域的页面提供接口服务,又由于一些历史原因无法通过 CORS 实现,可以通过 JSONP 来进行响应。

        框架内置了 egg-jsonp 插件,提供了 app.jsonp() 来支持响应 JSONP 格式的数据。

        使用

        先通过路由中间件的方式来局部开启:

        // app/router.js
        +module.exports = app => {
        +  const jsonp = app.jsonp();
        +  app.router.get('/api/posts/:id', jsonp, app.controller.posts.show);
        +  app.router.get('/api/posts', jsonp, app.controller.posts.list);
        +};
        +
        1
        2
        3
        4
        5
        6

        然后在 Controller 中,只需要正常编写即可:

        // app/controller/posts.js
        +class PostController extends Controller {
        +  async show() {
        +    this.ctx.body = {
        +      name: 'egg',
        +      category: 'framework',
        +      language: 'Node.js',
        +    };
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        用户请求对应的 URL 时带上 _callback=fn 查询参数,将会返回 JSONP 格式的数据。

        配置

        框架默认支持方法名为 callback_callback,并限制长度小于 50 字符。

        如有需要,可以自定义配置:

        // config/config.default.js
        +exports.jsonp = {
        +  callback: 'cb', // 识别 query 中的 `cb` 参数
        +  limit: 100, // 函数名最长为 100 个字符
        +};
        +
        1
        2
        3
        4
        5

        通过上面的方式配置之后,如果用户通过 /api/posts/1?cb=fn 请求 JSONP

        也可以在 app.jsonp() 创建中间件时覆盖默认的配置,以达到不同路由使用不同配置的目的:

        // app/router.js
        +module.exports = app => {
        +  const { router, controller, jsonp } = app;
        +  router.get('/api/posts', jsonp({ callback: 'cb' }), controller.posts.list);
        +};
        +
        1
        2
        3
        4
        5

        安全

        JSONP 如果使用不当会导致非常多的安全问题,可以将 JSONP 接口分为三种类型:

        1. 查询非敏感数据,例如获取一个论坛的公开文章列表。
        2. 查询敏感数据,例如获取一个用户的交易记录。
        3. 提交数据并修改数据库,例如给某一个用户创建一笔订单。

        如果我们的 JSONP 接口提供下面两类服务,在不做任何跨站防御的情况下,可能泄露用户敏感数据甚至导致用户被钓鱼。

        因此框架给 JSONP 默认提供了 CSRF 校验referrer 校验,具体参见 JSONP XSS 相关的安全防范 文档。

        // config/config.default.js
        +module.exports = {
        +  jsonp: {
        +    csrf: true,
        +    whiteList: /^https?:\/\/test.com\//,
        +    // whiteList: '.test.com',
        +    // whiteList: 'sub.test.com',
        +    // whiteList: [ 'sub.test.com', 'sub2.test.com' ],
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        提示

        当 CSRF 和 referrer 校验同时开启时,请求发起方只需要满足任意一个条件即可通过 JSONP 的安全校验。

        重定向

        使用

        可以通过 ctx.redirect(url) 来重定向请求。

        默认为 302,如果需要,可以设置 ctx.status = 301

        class UserController extends Controller {
        +  async logout() {
        +    const ctx = this.ctx;
        +
        +    ctx.logout();
        +    ctx.redirect(ctx.get('referer') || '/');
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8

        安全域名

        框架通过 egg-security 插件覆盖了 Koa 原生的 ctx.redirect 实现,以提供更加安全的重定向。

        • ctx.redirect(url) 如果不在配置的白名单域名内,则禁止跳转。
        • ctx.unsafeRedirect(url) 不判断域名,直接跳转,一般不建议使用,明确了解可能带来的风险后使用。

        security.domainWhiteList数组内为空,则默认会对所有跳转请求放行,即等同于ctx.unsafeRedirect(url)

        安全提示

        基于安全管控的原因,我们不推荐在应用层直接覆盖该属性,而是应该提交 Merge Request,除非该域名非阿里所属。

        更多参见 安全插件 文档。

        编写测试

        框架集成了 SuperTest 用于 HTTP 测试。

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        测试 GET 请求

        // test/controller/home.test.js
        +const { app, mock, assert } = require('egg-mock');
        +
        +describe('test/controller/home.test.js', () => {
        +  it('should GET /', () => {
        +    return app.httpRequest()
        +      .get('/')
        +      .set('User-Agent', 'unittest')
        +      .query({ limit: '10' })
        +      .expect('hi, egg')
        +      .expect('X-Response-Time', /\d+ms/)
        +      .expect(200);
        +  });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        测试 POST 请求

        可以通过 app.mockCsrf() 来跳过 CSRF 校验。

        // test/controller/home.test.js
        +it('should POST form', () => {
        +  app.mockCsrf();
        +  return app.httpRequest()
        +    .post('/api/body')
        +    .type('form')
        +    .send({ name: 'TZ' })
        +    .expect(200);
        +});
        +
        +it('should POST JSON', () => {
        +  app.mockCsrf();
        +  return app.httpRequest()
        +    .post('/api/body')
        +    .type('json')
        +    .send({ name: 'TZ' })
        +    .expect(200);
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18

        测试文件上传

        // test/controller/home.test.js
        +it('should upload file', () => {
        +  app.mockCsrf();
        +  return app.httpRequest()
        +    .post('/api/upload')
        +    .field('name', 'just a test')
        +    .attach('file', path.join(__dirname, 'egg.png'))
        +    .expect(200);
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        常见问题

        missing csrf token

        框架默认开启了 CSRF 安全限制。

        因此新手开发者在 Postman 测试前端发起 AJAX单元测试 时经常遇到的一个报错:

        nodejs.ForbiddenError: missing csrf token
        +
        1

        如何处理可以阅读上述文档。

        redirection is prohibited

        nodejs.InternalServerError: a security problem has been detected for url "http://www.baidu.com/", redirection is prohibited.
        +
        1

        如上所述,不允许重定向到非白名单的域名,具体处理参见安全域名

        + + + diff --git a/zh/guide/cookie.html b/zh/guide/cookie.html new file mode 100644 index 0000000..6f7eadd --- /dev/null +++ b/zh/guide/cookie.html @@ -0,0 +1,76 @@ + + + + + + Cookie | Egg + + + + + + + +

        使用场景

        HTTP 请求都是无状态的,但是我们的 Web 应用通常都需要知道发起请求的人是谁。

        为了解决这个问题,HTTP 协议设计了一个特殊的请求头:Cookie

        服务端可以通过响应头将少量数据响应给客户端,浏览器会遵循协议将数据保存,并在下次请求同一个服务的时候带上对应的数据。

        Cookie 主要用于:

        • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
        • 个性化设置(如用户自定义设置、主题等)
        • 浏览器行为跟踪(如跟踪分析用户行为等)

        服务器使用 Set-Cookie 响应头部向用户浏览器发送 Cookie 信息:

        HTTP/1.0 200 OK
        +Content-type: text/html
        +Set-Cookie: uid=123456
        +Set-Cookie: user=tz
        +
        1
        2
        3
        4

        后续对该服务发起的每一次新请求,浏览器都会将之前保存的信息通过 Cookie 请求头回传:

        GET /user HTTP/1.1
        +Host: www.example.org
        +Cookie: uid=123456; user=tz
        +
        1
        2
        3

        框架内置了 egg-cookies 插件,提供了 ctx.cookies,用于便捷、安全的读写 Cookie

        // app/controller/home.js
        +class HomeController extends Controller {
        +  async add() {
        +    const { ctx } = this;
        +    let count = ctx.cookies.get('count');
        +    count = count ? Number(count) : 0;
        +    ctx.cookies.set('count', ++count);
        +    ctx.body = count;
        +  }
        +  async remove() {
        +    const { ctx } = this;
        +    ctx.cookies.set('count', null);
        +    ctx.status = 204;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        友情提示

        在使用 Cookie 时我们需要思考清楚它的场景:

        • 需要被浏览器保存多久?
        • 是否可以被 js 获取到?
        • 是否可以被前端修改?

        框架默认配置下, Cookie 是加签不加密的,浏览器可以看到明文,js 不能访问,不能被客户端(手工)篡改。

        术语解释

        过期时间

        ExpiresMax-Age 用于定义 Cookie 对应的键值对的持久化时间。

        Expires 优先级低于 Max-Age,如果两者都没设置,则将会在关闭浏览器时失效。

        作用域

        DomainPath 标识定义了 Cookie 的作用域:即 Cookie 应该发送给哪些 URL。

        安全

        • SecureCookie 只有在 HTTPS 协议下才会发送给服务端。
        • HttpOnlyCookie 将无法被 JavaScript 访问,从而避免 XSS 攻击。

        加签 && 加密

        • 加签:对 Cookie 进行签名,避免前端篡改。不会修改原键值,而是新增一个 ${key}.sig 的键值。
        • 加密:对 Cookie 进行加密,避免 Cookie 明文写入,泄露给恶意用户。

        API 说明

        set(key, value, options)

        框架提供了 ctx.set(key, value, options) 来向用户发送 Cookie 信息。

        其中,keyvalue 称之为一个 键值对。配置参数 options下文

        get(key, options)

        Cookie 是通过同一个 Header 中传输过来的,因此需要通过该方法解析并获取对应的值。

        值得注意的是,获取时的 options.signedoptions.encrypt 要和 set() 的时候保持一致。

        options

        术语 一一对应,支持以下参数配置:

        • maxAge: {Number} 在浏览器的最长保存时间。
        • expires: {Date} 失效时间。优先级低于 maxAge。如果两者都没设置,则将会在关闭浏览器时失效。
        • path: {String} 生效的 URL 路径,默认为 /,即当前域名下均可访问这个 Cookie。
        • domain: {String} 对生效的域名,默认没有配置,可以配置成只在指定域名才能访问。
        • httpOnly: {Boolean} 是否可以被 js 访问,默认为 true,不允许被 js 访问
        • secure: {Boolean} 框架会自动判断当前请求是否为 HTTPS,从而自动赋值。
        • signed: {Boolean}:是否加签,默认为 true。
        • encrypt: {Boolean} 是否加密,默认为 false。

        此外,还扩展了:

        • overwrite {Boolean}:相同的 Key 的处理逻辑,为 true,则后设置的值会覆盖前面设置的,否则将会发送两个 Set-Cookie 响应头。

        配置秘钥

        由于我们在 Cookie 中需要用到加解密验签,所以需要配置一个秘钥供加密使用。

        // config/config.default.js
        +module.exports = {
        +  keys: 'key1,key2',
        +};
        +
        1
        2
        3
        4

        如果你没配置该属性,则在访问时会报错:

        ERROR 17996 [-/::1/-/7ms GET /] nodejs.Error: Please set config.keysfirst
        +
        1

        keys 配置成一个字符串,可以按照逗号分隔配置多个 key。

        Cookie 在使用这个配置进行加解密时:

        • 加密加签时只会使用第一个秘钥。
        • 解密验签时会遍历 keys 进行解密。

        如果我们想要更新 Cookie 的秘钥,但是又不希望之前设置到用户浏览器上的 Cookie 失效,可以将新的秘钥配置到 keys 最前面,等过一段时间之后再删去不需要的秘钥即可。

        如果要获取前端或者其他系统设置的 Cookie,需要指定参数 signedfalse,避免对它做验签导致获取不到 Cookie 的值。

        ctx.cookies.get('frontend-cookie', {
        +  signed: false,
        +});
        +
        1
        2
        3

        如果想要 Cookie 在浏览器端可以被 js 访问并修改:

        ctx.cookies.set(key, value, {
        +  httpOnly: false,
        +  signed: false,
        +});
        +
        1
        2
        3
        4

        不允许浏览器看到明文内容

        如果想要 Cookie 在浏览器端不能被修改,不能看到明文:

        ctx.cookies.set(key, value, {
        +  httpOnly: true, // 默认就是 true
        +  encrypt: true, // 加密传输
        +});
        +
        1
        2
        3
        4
        ctx.cookies.set(key, null);
        +
        1

        编写测试

        类似 Controller 的测试。

        需注意的是:模拟 Cookies 可能需要加上对应的 sig 加签信息。

        // test/controller/cookies.test.js
        +const { app, mock, assert } = require('egg-mock');
        +
        +describe('test/controller/cookies.test.js', () => {
        +  it('should GET /', () => {
        +    return app.httpRequest()
        +      .get('/cookies')
        +      .set('cookie', [ 'name=tz; path=/; httponly,name.sig=KdTywxAfCA4vHc1fmNipTZ9zPhBatn1br5tXWomvO14; path=/; httponly' ])
        +      .expect('set-cookie', /uid=123;/)
        +      .expect(200);
        +  });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        注意事项

        1. 由于浏览器和其他客户端实现的不确定性,为了保证 Cookie 可以写入成功,建议 value 通过 base64 编码或者其他形式 encode 之后再写入。
        2. 由于浏览器对 Cookie 有长度限制限制,所以尽量不要设置太长的 Cookie。一般来说不要超过 4093 bytes。当设置的 Cookie value 大于这个值时,框架会打印一条警告日志。
        3. 尽可能少写入数据到 Cookie
        + + + diff --git a/zh/guide/directory.html b/zh/guide/directory.html new file mode 100644 index 0000000..c84c2b2 --- /dev/null +++ b/zh/guide/directory.html @@ -0,0 +1,49 @@ + + + + + + 目录规范 | Egg + + + + + + + +

        对于一个团队框架来说,『约定优于配置』,按照一套统一的约定进行应用开发,可以极大地减少开发人员的沟通成本。

        框架通过 Loader 机制来自动挂载文件,应用开发者只需要添加文件到对应的目录即可。

        showcase
        +├── app
        +|   ├── router.js
        +│   ├── controller
        +│   |   └── home.js
        +│   ├── service
        +│   |   └── user.js
        +│   ├── middleware
        +│   |   └── response_time.js
        +│   └── view
        +│       └── home.tpl
        +├── config
        +|   ├── plugin.js
        +|   ├── config.default.js
        +│   ├── config.prod.js
        +|   ├── config.local.js
        +|   └── config.unittest.js
        +├── test
        +|   ├── controller
        +|   |   └── home.test.js
        +|   └── service
        +|       └── user.test.js
        +└── package.json
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23

        如上,为一个常见的应用目录结构:

        • app: 为主要的逻辑代码目录。 +
          • 常规 MVC 如: app/controllerapp/serviceapp/router.js 等。
          • 某些插件也会自定义加载规范,如 app/rpc 等目录的自动挂载。
        • config: 为配置目录,包含不同环境的配置文件,以及插件挂载声明。
        • test: 为单元测试目录。
        • run:每次启动期都会 dump 的相关信息,用于问题排查,建议加入 gitignore

        文件挂载如下:

        • app/controller/home.js 会被自动挂载到 app.controller.home
        • app/service/user.js 会被自动挂载到 ctx.service.user

        注意事项

        需要注意的是,加载文件时会进行驼峰转换,因此文件名和挂载的属性名可能会存在差异:

        • 默认情况下,连字符和下划线均会被转换为驼峰格式。
        • app/middleware/response_time.js 挂载为 app.middleware.responseTime
        • 部分插件,如 mongoose 插件有特殊约定,会挂载为类格式,如 app.model.User

        在后面的章节中,我们会逐步介绍具体的目录约定。

        如果需要自定义加载规则,可以参见 Loader 相关文档。

        + + + diff --git a/zh/guide/error_handler.html b/zh/guide/error_handler.html new file mode 100644 index 0000000..7ce12ef --- /dev/null +++ b/zh/guide/error_handler.html @@ -0,0 +1,167 @@ + + + + + + 异常处理 | Egg + + + + + + + +

        使用场景

        健壮性,是一个应用的基本要求。如何正确的处理错误是非常重要的一件事。

        实际开发中,错误可以分为几类:

        • 非期望的入参,如函数要求传递的是数值,却传递了字符串。
        • 意料之中的错误,如 Http 网络断开文件不存在等。
        • 完全意料之外的异常,譬如业务进程被外部杀死。

        错误的处理也有一些通用的实践:

        • 需要记录错误的信息,位置,堆栈和上下文。
        • 根据内容协商来返回不同的响应格式。
        • 正式环境下,不能把详细的错误信息和堆栈抛到用户侧。

        Node.js 异常处理

        Node.js 里,对异常的处理非常重要,如果有未捕获异常会直接导致进程退出。

        在早期的 Node.js 里, Error-first callbacks 是用的比较广泛的一种错误处理的约定。

        但嵌套层次一多起来,就需要一层层的往上抛出,非常容易遗漏和出现问题。

        因此,在 Async Function 异步编程模型出来后,通过 try..catch 来捕获错误,就直观了很多。

        async create(data) {
        +  try {
        +    return await this.service.user.create(data);
        +  } catch (err) {
        +    this.logger.error('create user fail', err);
        +    return {};
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8

        注意事项

        避免使用 callback,它抛出的错误,无法被 try 直接捕获,详见 Node.js Error 文档。

        框架内置支持

        框架内置了 onerror 插件,提供了统一的错误处理机制。

        对一个请求处理过程中的 MiddlewareControllerService 等抛出的任何异常都会被它捕获。

        业务错误处理

        如果你需要对业务错误进行统一处理,可以如下:

        // app/middleware/error_handler.js
        +module.exports = () => {
        +  return async function errorHandler(ctx, next) {
        +    try {
        +      await next();
        +    } catch (err) {
        +      const { app } = ctx;
        +      // 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
        +      app.emit('error', err, ctx);
        +
        +      const status = err.status || 500;
        +
        +      // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
        +      const error = status === 500 && app.config.env === 'prod' ? 'Internal Server Error' : err.message;
        +
        +      // 仅供参考,需按自己的业务逻辑处理。
        +      ctx.body = { error };
        +      ctx.status = status;
        +    }
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20

        挂载中间件:

        // config/config.default.js
        +module.exports = {
        +  middleware: [ 'errorHandler' ],
        +  errorHandler: {
        +    // 仅对该路径下的接口处理
        +    match: '/api',
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8

        框架兜底处理

        框架通过 onerror 插件提供了统一的错误处理机制。

        对一个请求的所有处理方法(MiddlewareControllerService)中抛出的任何异常都会被它捕获。

        并自动根据请求想要获取的类型返回不同类型的错误(基于 Content Negotiation)。

        请求需求的格式 环境 errorPageUrl 是否配置 返回内容
        HTML & TEXT local & unittest - onerror 自带的错误页面,展示详细的错误信息
        HTML & TEXT 其他 重定向到 errorPageUrl
        HTML & TEXT 其他 onerror 自带的没有错误信息的简单错误页(不推荐)
        JSON & JSONP local & unittest - JSON 对象或对应的 JSONP 格式响应,带详细的错误信息
        JSON & JSONP 其他 - JSON 对象或对应的 JSONP 格式响应,不带详细的错误信息

        errorPageUrl

        onerror 插件支持 errorPageUrl 配置,当配置了 errorPageUrl 时,一旦用户请求线上应用的 HTML 页面异常,就会重定向到这个地址。

        config/config.default.js

        // config/config.default.js
        +module.exports = {
        +  onerror: {
        +    // 线上页面发生异常时,重定向到这个页面上
        +    errorPageUrl: '/50x.html',
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        自定义统一异常处理

        尽管框架提供了默认的统一异常处理机制,但是应用开发中经常需要对异常时的响应做自定义,特别是在做一些接口开发的时候。框架自带的 onerror 插件支持自定义配置错误处理方法,可以覆盖默认的错误处理方法。

        // config/config.default.js
        +module.exports = {
        +  onerror: {
        +    all(err, ctx) {
        +      // 在此处定义针对所有响应类型的错误处理方法
        +      // 注意,定义了 config.all 之后,其他错误处理方法不会再生效
        +      ctx.body = 'error';
        +      ctx.status = 500;
        +    },
        +    html(err, ctx) {
        +      // html hander
        +      ctx.body = '<h3>error</h3>';
        +      ctx.status = 500;
        +    },
        +    json(err, ctx) {
        +      // json hander
        +      ctx.body = { message: 'error' };
        +      ctx.status = 500;
        +    },
        +    jsonp(err, ctx) {
        +      // 一般来说,不需要特殊针对 jsonp 进行错误定义,jsonp 的错误处理会自动调用 json 错误处理,并包装成 jsonp 的响应格式
        +    },
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24

        404

        404 - NOT FOUND 是我们比较熟悉的一种错误。

        框架并不是把它视为是一种异常,并在上面的兜底流程做处理,而是另行提供了处理逻辑。

        默认返回值

        如果一次用户请求,经过了 MiddlewareController 处理后,对应的 ctx.bodyctx.status 都未被赋值时,框架会视为 404

        此时框架会默认根据 Accepet 头来响应对应的值:

        // Accpet: application/json
        +{ "message": "Not Found" }
        +
        +// Accept: text/html
        +<h1>404 Not Found</h1>
        +
        1
        2
        3
        4
        5

        重定向

        框架也支持通过配置,将默认的 HTML 请求的 404 响应重定向到指定的页面。

        // config/config.default.js
        +module.exports = {
        +  notfound: {
        +    // 也可以是一个统一的 404 外链
        +    pageUrl: '/404.html',
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        自定义 404 响应

        在一些场景下,我们需要自定义服务器 404 时的响应,只需要加入一个中间件即可统一处理:

        // app/middleware/notfound_handler.js
        +module.exports = () => {
        +  return async function notFoundHandler(ctx, next) {
        +    await next();
        +    if (ctx.status === 404 && !ctx.body) {
        +      if (ctx.acceptJSON) {
        +        ctx.body = { error: 'Not Found' };
        +      } else {
        +        ctx.body = '<h1>Page Not Found</h1>';
        +      }
        +    }
        +  };
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        挂载中间件:

        // config/config.default.js
        +module.exports = {
        +  middleware: [ 'notfoundHandler' ],
        +};
        +
        1
        2
        3
        4

        常见问题

        该不该 Catch

        具体情况具体分析,没有绝对的银弹。

        如果错误是非主流程的,是可选的,那可以自行兜底处理。

        // app/service/ad.js
        +class AdService extends Service {
        +  async list() {
        +    // 查询推荐的广告位数据,失败则返回空。
        +    try {
        +      return await this.ctx.db.ad.list();
        +    } catch (err) {
        +      // 打印错误日志
        +      this.logger.error('list ad fail', err);
        +      // 返回空数据,不影响主流程
        +      return [];
        +    }
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        如果对应的错误,是需要告知用户或通知前端代码的,那可以通过上述的 业务错误处理 来统一反馈给用户。

        回调错误无法捕获

        按照正常代码写法,所有的异常都可以用这个方式进行捕获并处理,但是一定要注意一些特殊的写法可能带来的问题。

        打一个不太正式的比方,我们的代码全部都在一个异步调用链上,所有的异步操作都通过 await 串接起来了,但是只要有一个地方跳出了异步调用链,异常就捕获不到了。

        // app/controller/home.js
        +class HomeController extends Controller {
        +  async error () {
        +    // 在回调里面抛错
        +    setTimeout(() => {
        +      throw new Error('this is an error throw from callback');
        +    });
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        正确的做法

        // app/controller/home.js
        +class HomeController extends Controller {
        +  async buy () {
        +    const { ctx } = this;
        +
        +    const config = await ctx.service.trade.buy({ id: '12345' });
        +    // 下单后需要进行一次核对,且不阻塞当前请求
        +    setImmediate(() => {
        +      ctx.service.trade.check(request).catch(err => ctx.logger.error(err));
        +    });
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        在这个场景中,如果 service.trade.check 方法中代码有问题,导致执行时抛出了异常,尽管框架会在最外层通过 try catch 统一捕获错误,但是由于 setImmediate 中的代码『跳出』了异步链,它里面的错误就无法被捕捉到了。因此在编写类似代码的时候一定要注意。

        当然,框架也考虑到了这类场景,提供了 ctx.runInBackground(scope) 辅助方法,通过它又包装了一个异步链,所有在这个 scope 里面的错误都会统一捕获。

        class HomeController extends Controller {
        +  async buy () {
        +    const request = {};
        +    const config = await ctx.service.trade.buy(request);
        +    // 下单后需要进行一次核对,且不阻塞当前请求
        +    ctx.runInBackground(async () => {
        +      // 这里面的异常都会统统被 Backgroud 捕获掉,并打印错误日志
        +      await ctx.service.trade.check(request);
        +    });
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        + + + diff --git a/zh/guide/faq.html b/zh/guide/faq.html new file mode 100644 index 0000000..16c8459 --- /dev/null +++ b/zh/guide/faq.html @@ -0,0 +1,33 @@ + + + + + + FAQ | Egg + + + + + + + +

        如果下面的内容无法解决你的问题,请查看 Egg issues

        如何高效的反馈问题?

        感谢您向我们反馈问题。

        1. 我们推荐如果是小问题(错别字修改,小的 bug fix)直接提交 PR。
        2. 如果是一个新需求,请提供:详细需求描述,最好是有伪代码示意。
        3. 如果是一个 BUG,请提供:复现步骤,错误日志以及相关配置,并尽量填写下面的模板中的条目。
        4. 如果可以,尽可能使用 egg-init --type=simple bug 提供一个最小可复现的代码仓库,方便我们排查问题。
        5. 不要挤牙膏似的交流,扩展阅读:如何向开源项目提交无法解答的问题

        最重要的是,请明白一件事:开源项目的用户和维护者之间并不是甲方和乙方的关系,issue 也不是客服工单。在开 issue 的时候,请抱着一种『一起合作来解决这个问题』的心态,不要期待我们单方面地为你服务。

        为什么我的配置不生效?

        框架的配置功能比较强大,有不同环境变量,又有框架、插件、应用等很多地方配置。

        如果你分析问题时,想知道当前运行时使用的最终配置,可以查看下 ${root}/run/application_config.json(worker 进程配置) 和 ${root}/run/agent_config.json(agent 进程配置) 这两个文件。(root 为应用根目录,只有在 local 和 unittest 环境下为项目所在目录,其他环境下都为 HOME 目录)

        也可参见配置文件

        PS:请确保没有写出以下代码:

        // config/config.default.js
        +exports.someKeys = 'abc';
        +module.exports = appInfo => {
        +  const config = {};
        +  config.keys = '123456';
        +  return config;
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        线上的日志打印去哪里了?

        默认配置下,本地开发环境的日志都会打印在应用根目录的 logs 文件夹下(${baseDir}/logs) ,但是在非开发期的环境(非 local 和 unittest 环境),所有的日志都会打印到 $HOME/logs 文件夹下(例如 /home/admin/logs)。这样可以让本地开发时应用日志互不影响,服务器运行时又有统一的日志输出目录。

        进程管理为什么没有选型 PM2 ?

        1. PM2 模块本身复杂度很高,出了问题很难排查。我们认为框架使用的工具复杂度不应该过高,而 PM2 自身的复杂度超越了大部分应用本身。
        2. 没法做非常深的优化。
        3. 切实的需求问题,一个进程里跑 leader,其他进程代理到 leader 这种模式(多进程模型),在企业级开发中对于减少远端连接,降低数据通信压力等都是切实的需求。特别当应用规模大到一定程度,这就会是刚需。egg 本身起源于蚂蚁金服和阿里,我们对标的起点就是大规模企业应用的构建,所以要非常全面。这些特性通过 PM2 很难做到。

        进程模型非常重要,会影响到开发模式,运行期间的深度优化等,我们认为可能由框架来控制比较合适。

        如何使用 PM2 启动应用?

        尽管我们不推荐使用 PM2 启动,但仍然是可以做到的。

        首先,在项目根目录定义启动文件:

        // server.js
        +const egg = require('egg');
        +
        +const workers = Number(process.argv[2] || require('os').cpus().length);
        +egg.startCluster({
        +  workers,
        +  baseDir: __dirname,
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8

        这样,我们就可以通过 PM2 进行启动了:

        pm2 start server.js
        +
        1

        为什么会有 csrf 报错?

        通常有两种 csrf 报错:

        • missing csrf token
        • invalid csrf token

        Egg 内置的 egg-security 插件默认对所有『非安全』的方法,例如 POSTPUTDELETE 都进行 CSRF 校验。

        请求遇到 csrf 报错通常是因为没有加正确的 csrf token 导致,具体实现方式,请阅读安全威胁 CSRF 的防范

        本地开发时,修改代码后为什么 worker 进程没有自动重启?

        没有自动重启的情况一般是在使用 Jetbrains 旗下软件(IntelliJ IDEA, WebStorm..),并且开启了 Safe Write 选项。

        Jetbrains Safe Write 文档中有提到:

        If this check box is selected, a changed file is first saved in a temporary file. If the save operation succeeds, the file being saved is replaced with the saved file. (Technically, the original file is deleted and the temporary file is renamed.)

        由于使用了重命名导致文件监听的失效。解决办法是关掉 Safe Write 选项。(Settings | Appearance & Behavior | System Settings | Use "safe write" 路径可能根据版本有所不同)

        + + + diff --git a/zh/guide/helper.html b/zh/guide/helper.html new file mode 100644 index 0000000..9ec9424 --- /dev/null +++ b/zh/guide/helper.html @@ -0,0 +1,68 @@ + + + + + + Helper | Egg + + + + + + + +

        使用场景

        Helper 提供了一些实用的 utility 函数,避免逻辑分散各处,更容易编写测试用例。

        框架内置了一些常用的 Helper 方法,我们也可以编写自定义的 Helper 方法。

        访问方式

        它是一个 请求级别 的对象,可以通过 ctx.helper 访问到 helper 对象。

        Controller 中使用:

        // app/controller/user.js
        +class UserController extends Controller {
        +  async fetch() {
        +    const { app, ctx } = this;
        +    const id = ctx.query.id;
        +    const user = app.cache.get(id);
        +    ctx.body = ctx.helper.formatUser(user);
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        模板引擎中使用:

        <!-- app/view/home.tpl -->
        +{{ helper.shtml(value) }}
        +
        1
        2

        常用的属性和方法

        Helper 上有以下属性:

        • thisHelper 对象本身,可以用来调用其他 Helper 方法。
        • this.ctx:对应的 Context 对象。
        • this.app:对应的 Application 对象。

        框架默认提供以下 Helper 方法:

        • pathFor(name, params): 生成对应[路由]的 path 路径。
        • urlFor(name, params): 生成对应[路由]的 URL
        • shtml() / sjs() / ...: 由安全组件提供的安全方法。
        // app/router.js
        +app.get('user', '/user', controller.user);
        +
        +// 使用 helper 计算指定 path
        +ctx.helper.pathFor('user', { limit: 10, sort: 'name' });
        +// => /user?limit=10&sort=name
        +
        1
        2
        3
        4
        5
        6

        如何扩展

        我们支持开发者通过 app/extend/helper.js 来扩展 Helper

        // app/extend/helper.js
        +module.exports = {
        +  foo(param) {
        +    // this 是 helper 对象,在其中可以调用其他 helper 方法
        +    // this.ctx => context 对象
        +    // this.app => application 对象
        +  },
        +
        +  formatUser(user) {
        +    return only(user, [ 'name', 'phone' ]);
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        对应的测试:

        // test/app/extend/helper.js
        +const { app, assert } = require('egg-mock');
        +
        +describe('test/app/extend/helper.js', () => {
        +  it('formatUser()', () => {
        +    // 创建 ctx
        +    const ctx = app.mockContext();
        +
        +    const result = ctx.helper.formatUser({ name: 'TZ', phone: 123, token: 'abcd' });
        +
        +    assert(result.name === 'TZ');
        +    assert(!result.token);
        +  });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        + + + diff --git a/zh/guide/httpclient.html b/zh/guide/httpclient.html new file mode 100644 index 0000000..2c07ace --- /dev/null +++ b/zh/guide/httpclient.html @@ -0,0 +1,465 @@ + + + + + + HttpClient | Egg + + + + + + + +

        使用背景

        互联网时代,无数服务是基于 HTTP 协议进行通信的。

        在前面我们了解到的,都是 Node.js 作为 Web 服务端的相关知识。

        其实应用本身作为发起者,来调用后端服务也是一种非常常见的应用场景。

        譬如:

        • 调用后端微服务,查询或更新数据。
        • 把日志上报给第三方服务。
        • 上传文件给后端服务。

        因此,框架内置实现了一个 HttpClient,应用可以使用它来非常便捷地完成任何 HTTP 请求。

        获取方式

        app.httpclient

        框架在应用初始化的时候,会自动将 HttpClient 初始化到 app.httpclient

        它是基于 urllib 模块的扩展。

        app.curl(http://github.com/url,%20options)

        框架提供的语法糖,它等价于 app.httpclient.request(url, options)

        const url = 'https://registry.npm.taobao.org/egg/latest';
        +const result = await app.curl(url, { dataType: 'json' });
        +console.log(result.data);
        +
        1
        2
        3

        ctx.curl(http://github.com/url,%20options)

        框架在 Context 中同样提供了对应的语法糖,这将是我们最常用的方法。

        它的区别在于,会默认注入 options.ctx,从而在错误处理或打印 Trace 日志时,可以方便的获取到上游请求的相关信息。

        // app/controller/http.js
        +class HttpController extends Controller {
        +  async index() {
        +    const { ctx } = this;
        +
        +    // 示例:请求一个 npm 模块信息
        +    const url = 'https://registry.npm.taobao.org/egg/latest';
        +    const result = await ctx.curl(url, {
        +      // 自动解析 JSON response
        +      dataType: 'json',
        +      // 3 秒超时
        +      timeout: 3000,
        +    });
        +
        +    ctx.body = {
        +      status: result.status,
        +      headers: result.headers,
        +      package: result.data,
        +    };
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        常用参数及响应

        请求参数

        最常用到的 Options 参数如下:

        • options.methodHTTP 请求方法,默认为 GET,全大写格式。
        • options.data:发送的请求体,会根据 contentType 进行不同的处理。
        • options.contentType:发送的数据格式,取值 jsonform
        • options.dataType:对响应的数据进行格式转换,取值 jsontext
        • options.headers:请求头。

        完整的请求参数 options 说明,参见下文的 options 参数详解 章节。

        响应数据

        • result.status: 响应状态码,如 200, 302, 404, 500 等等。
        • result.headers: 响应头,类似 { 'content-type': 'text/html', ... }
        • result.data: 响应 body 数据,会根据 options.dataType 进行相应的格式转换。
        • result.res.timing:请求各阶段的耗时统计,需传递 options.timing 才会采集。

        HttpClient 实战

        以下示例,我们都使用 https://httpbin.org 提供的服务来测试。

        发起 GET 请求

        读取数据几乎都是使用 GET 请求,它是 HTTP 世界最常见的场景,也是最广泛的场景。

        // app/controller/http.js
        +class HttpController extends Controller {
        +  async get() {
        +    const { ctx } = this;
        +    const result = await ctx.curl('https://httpbin.org/get?foo=bar');
        +    ctx.status = result.status;
        +    ctx.set(result.headers);
        +    ctx.body = result.data;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        通过 POST 发送 JSON

        微服务间通讯,JSON 是最常见的协议。

        譬如,创建数据的场景一般来说都会使用 POST 发送 JSON 数据。

        关键配置为:

        • method: 必须配置为 POST
        • data:需要传递的数据对应,Object 类型。
        • contentType: 'json':声明以 JSON 格式发送,框架会自动对其 stringify 处理。
        • dataType: 'json':告知框架应该自动把响应数据解析为 JSON 对象。
        // app/controller/http.js
        +class HttpController extends Controller {
        +  async post() {
        +    const { ctx } = this;
        +    const result = await ctx.curl('https://httpbin.org/post', {
        +      // 必须指定 method
        +      method: 'POST',
        +      // 通过 contentType 声明以 JSON 格式发送
        +      contentType: 'json',
        +      data: {
        +        hello: 'world',
        +        now: Date.now(),
        +      },
        +      // 明确告诉 HttpClient 以 JSON 格式处理返回的响应 body
        +      dataType: 'json',
        +    });
        +    ctx.body = result.data;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        提交 Form 表单

        也有很多接口是面向浏览器设计的,需要通过 Form 表单方式提交接口。

        只需把对应的 contentType 配置为 form 即可,框架会自动组装为对应的格式,并通过 application/x-www-form-urlencoded 提交。








         















        // app/controller/http.js
        +class HttpController extends Controller {
        +  async submit() {
        +    const { ctx } = this;
        +    const result = await ctx.curl('https://httpbin.org/post', {
        +      method: 'POST',
        +      // 通过 `form` 格式提交,application/x-www-form-urlencoded
        +      contentType: 'form',
        +      data: {
        +        now: Date.now(),
        +        foo: 'bar',
        +      },
        +      dataType: 'json',
        +    });
        +    ctx.body = result.data.form;
        +    // 响应最终会是类似以下的结果:
        +    // {
        +    //   "foo": "bar",
        +    //   "now": "1483864184348"
        +    // }
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

        文件上传(Multipart)

        当一个表单提交包含文件的时候,请求数据格式就必须以 multipart/form-data 进行提交了。

        urllib 内置了 formstream 模块来帮助我们生成可以被消费的 form 对象。

        关键配置为:

        • files:需要上传的文件,支持多种形式: +
          • 单文件上传:支持直接传递:String 文件路径 / Stream 对象 / Buffer 对象。
          • 多文件上传:数组或 Object 格式,若为后者,则 key 为对应的 fieldName。
        • data:将被转换为对应的 form field
        // app/controller/http.js
        +class HttpController extends Controller {
        +  async upload() {
        +    const { ctx } = this;
        +
        +    const result = await ctx.curl('https://httpbin.org/post', {
        +      method: 'POST',
        +      dataType: 'json',
        +      data: {
        +        foo: 'bar',
        +      },
        +
        +      // 单文件上传
        +      files: __filename,
        +
        +      // 多文件上传
        +      // files: {
        +      //   file1: __filename,
        +      //   file2: fs.createReadStream(__filename),
        +      //   file3: Buffer.from('mock file content'),
        +      // },
        +    });
        +
        +    ctx.body = result.data.files;
        +    // 响应最终会是类似以下的结果:
        +    // {
        +    //   "file": "'use strict';\n\nconst For...."
        +    // }
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30

        文件上传(Stream)

        Node.js 的世界里面,Stream 才是主流。

        如果服务端支持流式上传,最友好的方式还是直接发送 Stream

        Stream 实际会以 Transfer-Encoding: chunked 传输编码格式发送,这个转换是 HTTP 模块自动实现的。

        关键配置为:

        • stream:通过 Stream 模式发送数据。
        • dataAsQueryString:可选,需要传递额外的请求参数的场景。
        • data:可选,会被强制 querystring.stringify 处理之后拼接到 URLquery 参数上。
        // app/controller/http.js
        +const fs = require('fs');
        +const FormStream = require('formstream');
        +
        +class HttpController extends Controller {
        +  async uploadByStream() {
        +    const { ctx } = this;
        +
        +    // 上传当前文件本身用于测试
        +    const fileStream = fs.createReadStream(__filename);
        +
        +    // httpbin.org 不支持 stream 模式,使用本地 stream 接口代替
        +    const url = `${ctx.protocol}://${ctx.host}/stream`;
        +    const result = await ctx.curl(url, {
        +      method: 'POST',
        +      // 以 stream 模式提交
        +      stream: fileStream,
        +
        +      // 额外传递参数
        +      dataAsQueryString: true,
        +      data: {
        +        // 一般来说都是 access token 之类的权限验证参数
        +        accessToken: 'some access token value',
        +      },
        +    });
        +
        +    ctx.body = result.data;
        +    // 响应最终会是类似以下的结果:
        +    // {"streamSize":574}
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31

        发送 XML

        此时,可以用 content 参数代替 data 参数,框架会原样发送数据。

        // app/controller/http.js
        +class HttpController extends Controller {
        +  async xml() {
        +    const { ctx } = this;
        +    const result = await ctx.curl('https://httpbin.org/xml', {
        +      method: 'POST',
        +      // 直接发送原始 xml 数据,不需要 HttpClient 做特殊处理
        +      content: '<xml><hello>world</hello></xml>',
        +      headers: {
        +        'content-type': 'text/html',
        +      },
        +      dataType: 'json',
        +    });
        +    ctx.body = result.data;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

        超时时间

        请求超时时间,默认是 [ 5000, 5000 ],即创建连接超时是 5 秒,接收响应超时是 5 秒。

        支持 Number[ Number, Number ] 格式,前者代表两个时间取同个值。

        // app/controller/http.js
        +class HttpController extends Controller {
        +  async timeout() {
        +    const { ctx } = this;
        +    const result = await ctx.curl('https://httpbin.org/timeout', {
        +      // 创建连接超时 1 秒,接收响应超时 30 秒,用于响应比较大的场景
        +      timeout: [ 1000, 30000 ],
        +      dataType: 'json',
        +    });
        +    ctx.body = result.data;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        处理重定向

        有些时候,需要对后端的重定向进行跟进处理,框架提供了:

        • followRedirect:是否自动跟进 3xx 的跳转响应,默认是 false
        • maxRedirects:最大自动跳转次数,避免死循环,默认是 10 次。 此参数不宜设置过大。
        • formatRedirectUrl(from, to):跳转 URL 校正,默认是 url.resolve(from, to)
        // app/controller/http.js
        +class HttpController extends Controller {
        +  async followRedirect() {
        +    const { ctx } = this;
        +    const result = await ctx.curl('/your_redirect_url', {
        +      formatRedirectUrl: (from, to) => {
        +        // 允许跟踪跳转
        +        followRedirect: true,
        +
        +        // 最大只允许自动跳转 5 次。
        +        maxRedirects: 5,
        +
        +        // 例如可在这里修正跳转不正确的 url
        +        if (to === '//foo/') {
        +          to = '/foo';
        +        }
        +        return url.resolve(from, to);
        +      },
        +    });
        +    ctx.body = result.data;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

        抓包调试

        有些时候,我们需要抓包来调试对应的 HTTP 请求。

        修改本地开发配置:

        // config/config.local.js
        +module.exports = () => {
        +  const config = {};
        +
        +  // add http_proxy to httpclient
        +  if (process.env.http_proxy) {
        +    config.httpclient = {
        +      request: {
        +        enableProxy: true,
        +        rejectUnauthorized: false,
        +        proxy: process.env.http_proxy,
        +      },
        +    };
        +  }
        +
        +  return config;
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17

        使用环境变量启动你的应用:

        $ http_proxy=http://127.0.0.1:8888 npm run dev
        +
        1

        然后启动你的抓包工具,如 CharlesFiddler,就可以看到对应的 HTTP 抓包信息。

        事件监听

        在企业应用场景,常常会有统一 Tracer 日志的需求。

        为了方便在统一监听 HttpClient 的请求和响应,我们约定了两个事件。

        // 对请求做拦截,设置一些 trace headers,方便全链路跟踪。
        +app.httpclient.on('request', req => {
        +  const { requestId, url, args, ctx } = req;
        +
        +  console.log(req.url);
        +  console.log(req.ctx); // 仅在 `ctx.curl()` 时才有值,方便记录上游请求信息。
        +
        +  // 例如我们可以设置全局请求 ID,方便日志跟踪
        +  req.headers['x-request-id'] = uuid.v1();
        +
        +  // 开启 timing 统计
        +  req.args.timing = true;
        +});
        +
        +// 订阅事件来打印日志
        +app.httpclient.on('response', result => {
        +  const { requestId, ctx, req, res, error } = result;
        +  console.log(req.url, res.status);
        +  console.log(result.res.timing); // 统计请求各阶段的耗时
        +  console.log(ctx); // 仅在 `ctx.curl()` 时才有值,方便记录上游请求信息。
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        如何扩展

        我们跟后端的接口协议,往往会在 HTTP 上做一层简单的协议封装,如加解密和校验。

        如果每次调用 HttpClient 的时候,都要传递参数和解析协议,未免太麻烦。

        此时可以扩展下:

        // app/extend/context.js
        +const rpc = require('../../lib/rpc');
        +
        +module.exports = {
        +  async rpc(url, options) {
        +    // 提供请求的默认值
        +    options = Object.assign({
        +      method: 'POST',
        +      dataType: 'json',
        +      contentType: 'json',
        +    }, options);
        +
        +    // 发起 HTTP 请求
        +    let result = await this.curl(url, options);
        +
        +    // 对后端返回结果进行预处理,如校验、解密等。
        +    result = rpc.process(result);
        +
        +    return result;
        +  },
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        这样,在 ControllerService 等地方就可以直接使用了:







         










        // app/controller/http.js
        +class HttpController extends Controller {
        +  async post() {
        +    const { ctx } = this;
        +
        +    // 调用对应的扩展方法
        +    const result = await ctx.rpc('https://httpbin.org/post', {
        +      data: {
        +        hello: 'world',
        +        now: Date.now(),
        +      },
        +    });
        +
        +    ctx.body = result.data;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

        编写测试

        对于 HttpClient 这种关键的请求交互,单元测试就更必不可少。

        框架通过 egg-mock 提供了 app.mockHttpclient(url, method, data) 的模拟能力。

        describe('GET /httpclient', () => {
        +  it('should mock httpclient response', () => {
        +    app.mockHttpclient('https://eggjs.org/', {
        +      // 模拟的参数,可以是 `Buffer/String/JSON`
        +      // 会按照请求时的 `options.dataType` 来做对应的转换
        +      data: 'mock eggjs.org response',
        +    });
        +
        +    return app.httpRequest()
        +      .get('/httpclient')
        +      .expect('mock eggjs.org response');
        +  });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        详见对应的 Mock API

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        常见错误码

        ConnectionTimeoutError

        • 异常名称:创建连接超时ConnectionTimeoutError
        • 出现场景:通常是 DNS 查询比较慢,或者客户端与服务端之间的网络速度比较慢导致的。
        • 排查建议:请适当增大 timeout 参数。

        ResponseTimeoutError

        • 异常名称:服务响应超时ResponseTimeoutError
        • 出现场景:通常是客户端与服务端之间网络速度比较慢,并且响应数据比较大的情况下会发生。
        • 排查建议:请适当增大 timeout 参数。

        ECONNRESET

        • 异常名称:服务主动断开连接ResponseError, code: ECONNRESET
        • 出现场景:通常是服务端主动断开 Socket 连接,导致 HTTP 请求链路异常。
        • 排查建议:请检查当时服务端是否发生网络异常。

        ECONNREFUSED

        • 异常名称:服务不可达RequestError, code: ECONNREFUSED, status: -1
        • 出现场景:通常是因为请求的 URL 所属 IP 或者端口无法连接成功。
        • 排查建议:请确保 IP 或者端口设置正确,目标网络是通的。

        ENOTFOUND

        • 异常名称:域名不存在RequestError, code: ENOTFOUND, status: -1
        • 出现场景:通常是因为请求的 URL 所在的域名无法通过 DNS 解析成功。
        • 排查建议:请确保域名存在,也需要排查一下 DNS 服务是否配置正确。

        JSONResponseFormatError

        • 异常名称:JSON 响应数据解析失败JSONResponseFormatError
        • 出现场景:设置了 dataType=json,但响应数据不符合 JSON 格式,就会抛出此异常。
        • 排查建议:确保服务端无论在什么情况下都要正确返回 JSON 格式的数据。

        有些 CGI 系统返回的 JSON 数据会包含某些特殊控制字符(U+0000 ~ U+001F),可以通过 fixJSONCtlChars 参数自动过滤掉它们。

        Options 参数详解

        由于 HTTP 请求的复杂性,导致 HttpClientoptions 参数会非常多。

        接下来讲解常用的可选参数的实际用途,更多的参数可以参见 urllib 文档。

        默认全局配置

        // config/config.default.js
        +exports.httpclient = {
        +  // 是否开启本地 DNS 缓存,默认关闭,开启后有两个特性
        +  // 1. 所有的 DNS 查询都会默认优先使用缓存的,即使 DNS 查询错误也不影响应用
        +  // 2. 对同一个域名,在 dnsCacheLookupInterval 的间隔内(默认 10s)只会查询一次
        +  enableDNSCache: false,
        +  // 对同一个域名进行 DNS 查询的最小间隔时间
        +  dnsCacheLookupInterval: 10000,
        +  // DNS 同时缓存的最大域名数量,默认 1000
        +  dnsCacheMaxLength: 1000,
        +
        +  request: {
        +    // 默认 request 超时时间
        +    timeout: 3000,
        +  },
        +
        +  httpAgent: {
        +    // 默认开启 http KeepAlive 功能
        +    keepAlive: true,
        +    // 空闲的 KeepAlive socket 最长可以存活 4 秒
        +    freeSocketTimeout: 4000,
        +    // 当 socket 超过 30 秒都没有任何活动,就会被当作超时处理掉
        +    timeout: 30000,
        +    // 允许创建的最大 socket 数
        +    maxSockets: Number.MAX_SAFE_INTEGER,
        +    // 最大空闲 socket 数
        +    maxFreeSockets: 256,
        +  },
        +
        +  httpsAgent: {
        +    // 默认开启 https KeepAlive 功能
        +    keepAlive: true,
        +    // 空闲的 KeepAlive socket 最长可以存活 4 秒
        +    freeSocketTimeout: 4000,
        +    // 当 socket 超过 30 秒都没有任何活动,就会被当作超时处理掉
        +    timeout: 30000,
        +    // 允许创建的最大 socket 数
        +    maxSockets: Number.MAX_SAFE_INTEGER,
        +    // 最大空闲 socket 数
        +    maxFreeSockets: 256,
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42

        应用可以通过 config/config.default.js 覆盖此配置。

        method: String

        HTTP 请求方法,默认是 GET,全大写格式,支持所有 HTTP 方法

        data: Object

        需要发送的请求数据,会根据 method 自动选择正确的数据处理方式。

        • GETHEAD:通过 querystring.stringify(data) 处理后拼接到 URL 的查询参数上。
        • POSTPUTDELETE 等:需要根据 contentType 做进一步判断处理。 +
          • contentType = json:通过 JSON.stringify(data) 处理,并通过请求 body 发送。
          • 其他:通过 querystring.stringify(data) 处理,并通过请求 body 发送。
        // GET + Query, `/api/user?foo=bar`
        +ctx.curl(url, {
        +  data: { foo: 'bar' },
        +});
        +
        +// POST + Form + body
        +ctx.curl(url, {
        +  method: 'POST',
        +  data: { foo: 'bar' },
        +});
        +
        +// POST + JSON + body
        +ctx.curl(url, {
        +  method: 'POST',
        +  contentType: 'json',
        +  data: { foo: 'bar' },
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17

        contentType: String

        设置请求数据格式,支持 jsonform,决定了请求数据的序列化格式。

        如需要以 JSON 格式发送 data

        ctx.curl(url, {
        +  method: 'POST',
        +  data: {
        +    foo: 'bar',
        +    now: Date.now(),
        +  },
        +  contentType: 'json',
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8

        dataType: String

        设置响应数据格式,默认不对响应数据做任何处理,直接返回原始的 buffer 格式数据。

        支持 textjson 两种取值。

        const jsonResult = await ctx.curl(url, {
        +  dataType: 'json',
        +});
        +console.log(jsonResult.data);
        +
        +const htmlResult = await ctx.curl(url, {
        +  dataType: 'text',
        +});
        +console.log(htmlResult.data);
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        注意

        设置成 json 时,如果响应数据解析失败会抛 JSONResponseFormatError 异常。

        dataAsQueryString: Boolean

        如果设置为 true,那么即使在 POST 情况下,也会强制将 options.dataquerystring.stringify 处理之后拼接到 URL 的查询参数上。

        可以很好地解决以 stream 发送数据,且额外的请求参数以 URL Query 形式传递的应用场景:

        ctx.curl(url, {
        +  method: 'POST',
        +  dataAsQueryString: true,
        +  data: {
        +    // 一般来说都是 access token 之类的权限验证参数
        +    accessToken: 'some access token value',
        +  },
        +  stream: myFileStream,
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        content: String|Buffer

        发送请求正文,如果设置了此参数,那么会直接忽略 data 参数。

        ctx.curl(url, {
        +  method: 'POST',
        +  // 直接发送原始 xml 数据,不需要 HttpClient 做特殊处理
        +  content: '<xml><hello>world</hello></xml>',
        +  headers: {
        +    'content-type': 'text/html',
        +  },
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8

        headers: Object

        自定义请求头。

        ctx.curl(url, {
        +  headers: {
        +    'x-foo': 'bar',
        +  },
        +});
        +
        1
        2
        3
        4
        5

        timeout: Number|Array

        请求超时时间,默认是 [ 5000, 5000 ],即创建连接超时是 5 秒,接收响应超时是 5 秒。

        ctx.curl(url, {
        +  // 创建连接超时 3 秒,接收响应超时 3 秒
        +  timeout: 3000,
        +});
        +
        +ctx.curl(url, {
        +  // 创建连接超时 1 秒,接收响应超时 30 秒,用于响应比较大的场景
        +  timeout: [ 1000, 30000 ],
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        files: Mixed

        文件上传,支持格式: String | ReadStream | Buffer | Array | Object

        ctx.curl(url, {
        +  method: 'POST',
        +  files: '/path/to/read',
        +  data: {
        +    foo: 'other fields',
        +  },
        +});
        +
        1
        2
        3
        4
        5
        6
        7

        多文件上传:

        ctx.curl(url, {
        +  method: 'POST',
        +  files: {
        +    file1: '/path/to/read',
        +    file2: fs.createReadStream(__filename),
        +    file3: Buffer.from('mock file content'),
        +  },
        +  data: {
        +    foo: 'other fields',
        +  },
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        stream: ReadStream

        设置发送请求正文的可读数据流,一旦设置了此参数,将会忽略 datacontent

        ctx.curl(url, {
        +  method: 'POST',
        +  stream: fs.createReadStream('/path/to/read'),
        +});
        +
        1
        2
        3
        4

        writeStream: WriteStream

        设置接受响应数据的可写数据流,默认是 null。 +一旦设置此参数,那么返回值 result.data 将会被设置为 null, +因为数据已经全部写入到 writeStream 中了。

        ctx.curl(url, {
        +  writeStream: fs.createWriteStream('/path/to/store'),
        +});
        +
        1
        2
        3

        注意事项

        请在你充分理解 Stream异步编程 的基础上,再使用。

        streaming: Boolean

        是否直接返回响应流。

        开启后会在拿到响应对象 res 时马上返回,此时 headersstatus 已经可以读取到,但还没有读取 data 数据。

        const result = await ctx.curl(url, {
        +  streaming: true,
        +});
        +
        +console.log(result.status, result.data);
        +// result.res 是一个 ReadStream 对象
        +ctx.body = result.res;
        +
        1
        2
        3
        4
        5
        6
        7

        注意

        若 res 不是直接传递给 body,那么我们必须消费这个 stream,并且要做好 error 事件处理。

        beforeRequest: Function(options)

        在请求正式发送之前,会尝试调用 beforeRequest 钩子,允许我们在这里对请求参数做最后一次修改。

        ctx.curl(url, {
        +  beforeRequest: options => {
        +    // 例如我们可以设置全局请求 id,方便日志跟踪
        +    options.headers['x-request-id'] = uuid.v1();
        +  },
        +});
        +
        1
        2
        3
        4
        5
        6

        gzip: Boolean

        是否支持 gzip 响应格式,开启后将自动设置 Accept-Encoding: gzip 请求头, +并且会自动解压带 Content-Encoding: gzip 响应头的数据。

        ctx.curl(url, {
        +  gzip: true,
        +});
        +
        1
        2
        3

        timing: Boolean

        是否开启请求各阶段的时间测量。

        开启后可以通过 result.res.timing 拿到这次 HTTP 请求各阶段的时间测量值(单位是毫秒)。

        通过这些测量值,我们可以非常方便地定位到这次请求最慢的环境发生在那个阶段,效果如同 Chrome Network Timing 的作用。

        各阶段测量值:

        • queuing:分配 Socket 耗时。
        • dnslookupDNS 查询耗时。
        • connectedSocket 三次握手连接成功耗时。
        • requestSent:请求数据完整发送完毕耗时。
        • waiting:收到第一个字节的响应数据耗时。
        • contentDownload:全部响应数据接收完毕耗时。
        const result = await ctx.curl(url, {
        +  timing: true,
        +});
        +console.log(result.res.timing);
        +// {
        +//   "queuing":29,
        +//   "dnslookup":37,
        +//   "connected":370,
        +//   "requestSent":1001,
        +//   "waiting":1833,
        +//   "contentDownload":3416
        +// }
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        HTTPS 相关参数

        包括 keycertpassphrase 等参数,都将透传给 HTTPS 模块。

        其中 rejectUnauthorized 用于在本地调试时忽略无效的 HTTPS 证书。

        具体请查看 https.request() 文档。

        示例代码

        完整示例代码可以在 eggjs/examples/httpclient 找到。

        + + + diff --git a/zh/guide/i18n.html b/zh/guide/i18n.html new file mode 100644 index 0000000..a3cbbe4 --- /dev/null +++ b/zh/guide/i18n.html @@ -0,0 +1,88 @@ + + + + + + 国际化 | Egg + + + + + + + +

        使用场景

        为了方便开发多语言应用,框架内置了国际化(I18n)支持,由 egg-i18n 插件提供。

        i18ninternationalization 的缩写,代表 in 之间有 18 个字母。

        • 开发者需定义多个 locale 多语言文件。
        • 开发者在 ControllerView 中使用对应的语法糖渲染字符串。
        • 插件会根据约定,渲染指定的多语言字符串。

        定义 locale

        目录规范

        多种语言的配置是独立的,统一存放在 config/locale/*.js 下。

        showcase
        +└── config
        +    ├── plugin.js
        +    ├── config.default.js
        +    └── locale
        +        ├── en-US.js
        +        └── zh-CN.js
        +
        1
        2
        3
        4
        5
        6
        7

        不仅对于应用目录生效,在框架,插件的 config/locale 目录下同样生效。

        友情提示

        注意单词拼写,是 locale 不是 locals。

        文件格式

        支持 jsJSON 两种格式:

        // config/locale/zh-CN.js
        +module.exports = {
        +  Email: '邮箱',
        +};
        +
        1
        2
        3
        4

        // config/locale/zh-CN.json
        +{
        +  "Email": "邮箱"
        +}
        +
        1
        2
        3
        4

        占位符

        支持类似 util.format()%s%j 等占位符语法。

        // config/locale/zh-CN.js
        +module.exports = {
        +  'Welcome back, %s!': '欢迎回来,%s!',
        +};
        +
        +ctx.__('Welcome back, %s!', 'Shawn');
        +// zh-CN => 欢迎回来,Shawn!
        +// en-US => Welcome back, Shawn!
        +
        1
        2
        3
        4
        5
        6
        7
        8

        同时支持数组下标占位符方式,例如:

        // config/locale/zh-CN.js
        +module.exports = {
        +  'Hello {0}! My name is {1}.': '你好 {0}! 我的名字叫 {1}。',
        +};
        +
        +ctx.__('Hello {0}! My name is {1}.', [ 'foo', 'bar' ]);
        +// zh-CN => 你好 foo!我的名字叫 bar。
        +// en-US => Hello foo! My name is bar.
        +
        1
        2
        3
        4
        5
        6
        7
        8

        使用 i18n

        ctx.__(key, ...values)

        插件提供了 ctx.__(key, ...values) 来获取语言配置。

        它等价于 ctx.gettext(key, ...values)

        class HomeController extends Controller {
        +  async index() {
        +    const ctx = this.ctx;
        +    ctx.body = {
        +      // zh-CN => 邮箱
        +      // en-US => Email
        +      email: ctx.__('Email'),
        +
        +      // zh-CN => 欢迎回来,Shawn!
        +      // en-US => Welcome back, Shawn!
        +      message: ctx.__('Welcome back, %s!', 'Shawn'),
        +
        +      // zh-CN => 你好 foo!我的名字叫 bar。
        +      // en-US => Hello foo! My name is bar.
        +      descriptions: ctx.__('Hello {0}! My name is {1}.', [ 'foo', 'bar' ]),
        +    };
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18

        在 View 中使用

        插件也同时把对应的语法糖注入到 ctx.locals 上,因此也可以直接在模板引擎里面使用:

        <li>{{ __('Email') }}: {{ user.email }}</li>
        +<li>
        +  {{ __('Welcome back, %s!', user.name) }}
        +</li>
        +<li>
        +  {{ __('Hello {0}! My name is {1}.', ['foo', 'bar']) }}
        +</li>
        +
        1
        2
        3
        4
        5
        6
        7

        切换语言

        默认语言

        默认语言是 en-US。假设我们想修改默认语言为简体中文:

        // config/config.default.js
        +exports.i18n = {
        +  defaultLocale: 'zh-CN',
        +};
        +
        1
        2
        3
        4

        切换语言

        插件也会根据请求参数不同,自动选择指定的语言。

        修改后会记录到 locale 这个 Cookie,下次请求直接用设定好的语言。

        优先级从高到低:

        1. Query: /?locale=en-US
        2. Cookie: locale=zh-TW
        3. Header: Accept-Language: zh-CN,zh;q=0.5

        如果想修改 Query 或者 Cookie 参数名称:

        // config/config.default.js
        +exports.i18n = {
        +  queryField: 'locale',
        +  cookieField: 'locale',
        +  // Cookie 默认一年后过期, 如果设置为 Number,则单位为 ms
        +  cookieMaxAge: '1y',
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        局限性

        一般来说,国际化是需要有配套的运营后台的,该插件只是一个简化的实现,开发者根据具体情况选择使用。

        + + + diff --git a/zh/guide/index.html b/zh/guide/index.html new file mode 100644 index 0000000..5e948ab --- /dev/null +++ b/zh/guide/index.html @@ -0,0 +1,21 @@ + + + + + + 概述 | Egg + + + + + + + +

        在本篇中,我们会对每一个术语概念,逐一进行详细的讲解。

        包括它的适用场景、如何使用、常用的方法和属性、如何扩展、如何测试等等。

        Web 模型

        框架奉行『约定优于配置』,因此我们首先需要了解下 目录规范 的约定。

        其次,对于一个 Web 应用来说,一般会采用 MVC 模型。

        对应的概念有:

        • MiddlewareKoa 的洋葱模型,类似 JavaFilter
        • Controller:控制器,处理和校验用户请求,然后调用业务逻辑层,最终发送响应给用户。
        • Router:路由,对用户请求进行分派。
        • Service:业务逻辑层。
        • Application:全局应用对象,通过它可以获取 配置文件 等信息。
        • Context:用户请求的上下文,用于获取请求信息和设置响应信息。
        • 此外,还有 CookieSessionHelper 等等。

        功能模块

        除此之外,还提供了很多研发过程中需要的 Utils

        • 使用插件:生态共建的基础,一分钟即可通过插件接入各自基础中间件服务。
        • 生命周期:方便开发者做一些初始化工作。
        • 日志:对应用的运行状态监控、问题排查等都有非常重要的意义。
        • 异常处理:程序健壮性的保障。
        • 安全:安全无小事。
        • 还有 文件上传国际化 等等。
        + + + diff --git a/zh/guide/lifecycle.html b/zh/guide/lifecycle.html new file mode 100644 index 0000000..4db614f --- /dev/null +++ b/zh/guide/lifecycle.html @@ -0,0 +1,147 @@ + + + + + + 生命周期 | Egg + + + + + + + +

        使用场景

        我们常常需要在应用启动期间进行一些初始化工作,在本文我们将一起理解下框架的生命周期。

        框架约定可以通过 app.js 来编写 Boot 类来注入 Hook

        提供了以下生命周期Hook

        • configWillLoad:配置文件即将加载,这是最后动态修改配置的时机。
        • configDidLoad:配置文件加载完成。
        • didLoad:文件加载完成。
        • willReady:插件启动完毕,用于定义前置操作。
        • didReady:应用启动完毕。
        • serverDidReadyServer 启动完毕,可以开始导入流量。
        • beforeClose:应用即将关闭。

        定义生命周期

        我们可以通过 app.js 来挂载各个点的 Hook

        // app.js
        +class AppBootHook {
        +  constructor(app) {
        +    this.app = app;
        +  }
        +
        +  // 配置文件已读取合并但还未生效,修改配置的最后时机,仅支持同步操作。
        +  configWillLoad() {}
        +
        +  // 所有配置已经加载完毕,用于自定义 Loader 挂载。
        +  configDidLoad() {}
        +
        +  // 插件的初始化
        +  async didLoad() {}
        +
        +  // 所有插件启动完毕,用于做应用启动成功前的一些必须的前置操作。
        +  async willReady() {}
        +
        +  // 应用已经启动完毕,可以用于做一些初始化工作。
        +  async didReady() {}
        +
        +  // Server 已经启动成功,可以开始导入流量,处理外部请求。
        +  async serverDidReady() {}
        +
        +  // 应用即将关闭前
        +  async beforeClose() {}
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27

        注意

        在自定义生命周期函数中不建议做太耗时的操作,框架会有启动的超时检测。

        详解生命周期

        configWillLoad()

        此时配置文件已经被读取并合并,但是还并未生效,这是应用层修改配置的最后时机

        使用场景举例:

        • 对配置中的秘钥进行解密。
        • 修改框架内置中间件顺序。
        // app.js
        +class AppBootHook {
        +  constructor(app) {
        +    this.app = app;
        +  }
        +
        +  // 注意:此函数只支持同步调用
        +  configWillLoad() {
        +    // 此时 config 文件已经被读取并合并,但是还并未生效,这是修改配置的最后时机
        +    // 例如:参数中的密码是加密的,在此处进行解密
        +    this.app.config.mysql.password = decrypt(this.app.config.mysql.password);
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        注意事项

        Hook 现在只支持同步调用。

        configDidLoad()

        所有的配置已经加载完毕,此 Hook 可以用来加载应用自定义的文件,启动自定义的服务。

        使用场景举例:

        • 初始化自定义的模块。
        • 自定义 Loader 加载规范。
        • 插入一个中间件到框架的 coreMiddleware 之间。
        // app.js
        +class AppBootHook {
        +  constructor(app) {
        +    this.app = app;
        +  }
        +
        +  configDidLoad() {
        +    // 所有的配置已经加载完毕,可以用来加载应用自定义的文件,初始化自定义的服务
        +    this.app.loader.loadToContext(path.join(__dirname, 'app/tasks'), 'tasks', {
        +      fieldClass: 'tasksClasses',
        +    });
        +
        +    // 例如:插入一个中间件到框架的 coreMiddleware 之间
        +    const statusIndex = this.app.config.coreMiddleware.indexOf('status');
        +    this.app.config.coreMiddleware.splice(statusIndex + 1, 0, 'limit');
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17

        async didLoad()

        Hook 可以用来插件的初始化。

        把初始化逻辑拆分为 configDidLoaddidLoad 两个阶段的考虑在于:插件之间可能有服务依赖

        // app.js
        +class AppBootHook {
        +  constructor(app) {
        +    this.app = app;
        +  }
        +
        +  configDidLoad() {
        +    // 初始化自定义服务
        +    this.app.queue = new Queue(this.app.config.queue);
        +  }
        +
        +  async didLoad() {
        +    // 启动自定义的服务
        +    await this.app.queue.init();
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

        async willReady()

        所有的插件都已启动完毕,但是应用整体还未 Ready

        在该 Hook 可以做一些必须的前置操作,这些操作成功才会启动应用。

        • 如做一些数据初始化等操作。
        // app.js
        +class AppBootHook {
        +  constructor(app) {
        +    this.app = app;
        +  }
        +
        +  async willReady() {
        +    // 所有的插件都已启动完毕,但是应用整体还未 Ready
        +    // 可以做一些数据初始化等操作,这些操作成功才会启动应用
        +
        +    // 例如:从数据库加载数据到内存缓存
        +    this.app.cacheData = await this.app.model.query('select * from QUERY_CACHE_SQL');
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        注意

        在自定义生命周期函数中不建议做太耗时的操作,框架会有启动的超时检测。

        async didReady()

        应用已经启动完毕,可以用于做一些初始化工作。

        willReady() 的区别在于: 该 Hook 的操作是可选的,失败不会阻塞应用启动。

        // app.js
        +class AppBootHook {
        +  constructor(app) {
        +    this.app = app;
        +  }
        +
        +  async didReady() {
        +    // 应用已经启动完毕
        +    // 该操作是可选的,失败也不影响应用对外服务。
        +    const ctx = this.app.createAnonymousContext();
        +    await ctx.service.Biz.request();
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        async serverDidReady()

        HTTP/HTTPS Server 已经启动成功,可以开始导入流量,处理外部请求。

        此时可以拿到 app.server 实例。

        // app.js
        +class AppBootHook {
        +  constructor(app) {
        +    this.app = app;
        +  }
        +
        +  async serverDidReady() {
        +    this.app.server.on('timeout', socket => {
        +      // handle socket timeout
        +    });
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        async beforeClose()

        应用即将关闭前的处理 Hook,一般用于资源的释放操作。

        注意:该 Hook 将按注册的逆序执行。

        // app.js
        +class AppBootHook {
        +  constructor(app) {
        +    this.app = app;
        +  }
        +
        +  async beforeClose() {
        +    // do sth before app close
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        注意事项

        框架默认最多只会等到 5s 就会退出,不保证会等待所有的该 Hook 执行完毕。

        + + + diff --git a/zh/guide/logger.html b/zh/guide/logger.html new file mode 100644 index 0000000..df64123 --- /dev/null +++ b/zh/guide/logger.html @@ -0,0 +1,163 @@ + + + + + + 日志 | Egg + + + + + + + +

        使用场景

        日志对于 Web 开发的重要性毋庸置疑,对应用的运行状态监控、问题排查等都有非常重要的意义。

        框架内置了强大的企业级日志支持,由 egg-logger 模块提供。

        主要特性:

        • 日志分级
        • 统一错误日志
        • 启动日志和运行日志分离
        • 多进程日志
        • 自动切割日志
        • 高性能
        • 可扩展,支持自定义日志

        打印日志

        在绝大部分的地方,你都可以获取到 Logger 实例。

        以下介绍几个常用的获取方式,它们的对应的日志都会写入到 ${appInfo.name}-web.log 文件。

        app.logger

        应用级别的日志,记录一些业务上与请求无关的信息,如启动阶段。

        // app/middleware/static.js
        +module.exports = (options, app) => {
        +  app.logger.info(`[egg-static] mount ${options.dir} as static root`);
        +
        +  return async function static() {};
        +};
        +
        1
        2
        3
        4
        5
        6

        ctx.logger

        用于记录请求相关的日志。

        它打印的日志都会在前面带上一些当前请求相关的信息。

        [${userId}/${ip}/${traceId}/${cost}ms ${method} ${url}]

        // app/controller/user.js
        +class UserController extends Controller {
        +  async list() {
        +    const { app, ctx } = this;
        +    // 打印日志
        +    ctx.logger.info('ctx.logger');
        +    ctx.body = [ { name: 'TZ' } ];
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        对应的日志输出为:

        2019-02-03 11:18:56,157 INFO 46536 [-/127.0.0.1/-/5ms GET /api/user] ctx.logger
        +
        1

        this.logger

        ControllerService 等实例中可以获取该对象。

        类似 ctx.logger,不同之处是它会额外加上该日志的文件路径,以便快速定位日志打印位置。

        // app/controller/user.js
        +class UserController extends Controller {
        +  async list() {
        +    const { app, ctx } = this;
        +    ctx.logger.info('ctx.logger');
        +    // 打印日志,会添加路径
        +    this.logger.info('this.logger');
        +    ctx.body = [ { name: 'TZ' } ];
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        对应的日志输出为:

        2019-02-03 11:18:56,157 INFO 46536 [-/127.0.0.1/-/5ms GET /api/user] ctx.logger
        +2019-02-03 11:18:56,158 INFO 46536 [-/127.0.0.1/-/5ms GET /api/user] [controller.user] this.logger
        +
        1
        2

        日志级别

        日志分为 NONEDEBUGINFOWARNERROR 5 个级别。

        分别对应于:logger.debug() / logger.info() / logger.warn() / logger.error()

        默认只会输出 INFO 及以上级别,可以通过对应的 logger.level 来配置。

        // config/config.default.js
        +config.logger = {
        +  level: 'INFO',
        +};
        +
        1
        2
        3
        4

        错误日志

        为了更方便的进行错误追踪,框架默认会把所有 LoggerERROR 日志统一输出到 common-error.log 文件

        另外,为了保证异常可追踪,请输出 Error 类型,从而获取到堆栈信息。

        ctx.logger.error(new Error('whoops'));
        +
        1

        将输出:

        2019-02-03 14:23:25,481 ERROR 93655 [-/127.0.0.1/-/6ms GET /] nodejs.Error: whoops
        +    at HomeController.index (/Users/tz/Workspaces/coding/github.com/atian25/egg-showcase/app/controller/home.js:13:23)
        +
        1
        2

        输出方式

        文件日志

        日志文件默认都放在 ${appInfo.root}/logs/${appInfo.name} 目录下。

        值得注意的是:appInfo.root 会根据运行环境自动适配根目录。

        • localunittest 环境下为 baseDir,即项目源码的根目录。
        • prod 和其他运行环境,都为 HOME,即用户目录,如 /home/admin

        这是一个优雅的适配,因为:

        • 为了统一管控,线上环境都统一写入用户目录,如 /home/admin/logs/${appInfo.name}
        • 本地开发时,为了避免冲突,不想污染用户目录,会倾向于直接打印在项目源码的 logs 目录。

        终端日志

        日志打印到文件中的同时,为了方便开发,也会同时打印到终端中。

        开发环境下默认只会输出 INFO 及以上级别,可以通过对应的 logger.consoleLevel 来配置。

        // config/config.default.js
        +config.logger = {
        +  consoleLevel: 'INFO',
        +};
        +
        1
        2
        3
        4

        注意事项

        基于性能的考虑,在正式环境下,默认会关闭终端日志输出。

        正式环境

        基于性能和统一管控的考虑,正式环境的日志配置,有以下默认约定。

        落盘方式

        通常 Web 访问是高频访问,每次打印日志都写磁盘会造成频繁磁盘 IO。

        为了提高性能,我们采用的文件日志写入策略是:

        日志同步写入内存,异步每隔一段时间(默认 1 秒)刷盘。

        更多详细请参考 egg-loggeregg-logrotator

        日志文件输出位置

        为了统一管控,一般要求线上环境都统一写入用户目录,如 /home/admin/logs/${appInfo.name}

        具体参见上面的 文件日志 章节相关描述。

        禁止输出 DEBUG 日志

        在生产环境,为了避免一些插件的调试日志打印导致性能问题,默认禁止打印 DEBUG 日志。

        如果确实有需求,需要打开 allowDebugAtProd 配置项。(不推荐

        // config/config.default.js
        +exports.logger = {
        +  level: 'DEBUG',
        +  allowDebugAtProd: true,
        +};
        +
        1
        2
        3
        4
        5

        禁止输出终端日志

        基于性能的考虑,在正式环境下,默认会关闭终端日志输出。

        如有需要,你可以通过下面的配置开启。(不推荐

        // config/config.default.js
        +exports.logger = {
        +  disableConsoleAfterReady: false,
        +};
        +
        1
        2
        3
        4

        自定义日志

        一般应用无需自己配置自定义日志,因为日志打太多或太分散都会导致关注度分散,反而难以管理和难以排查发现问题。

        框架内置日志

        • ${appInfo.name}-web.log:应用输出的日志,通过上述的 ctx.logger 等打印。
        • egg-web.log: 用于框架内核、插件日志,通过 app.coreLogger 打印。
        • common-error.log:所有 Logger 的错误日志会统一汇集到该文件。
        • 还有很多内置插件输出的 Tracer 日志,详见对应的文档。

        增加自定义日志

        你也可以通过以下配置,增加自定义日志:

        // config/config.default.js
        +const path = require('path');
        +
        +module.exports = appInfo => {
        +  const config = {};
        +
        +  // 自定义日志
        +  config.customLogger = {
        +    oneLogger: {
        +      file: 'one.log',
        +    },
        +  };
        +
        +  return config;
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        如果配置为文件名,则会自动转换为 path.join(this.app.config.logger.dir, file)

        然后可通过 app.getLogger('oneLogger') / ctx.getLogger('oneLogger') 获取,获取到的 logger 会使用对应的 Logger 配置,并以 config.logger 为默认值。

        注意

        app.getLoggerctx.getLogger 获取到的 logger 实例是有区别的,前者拿到是应用级别的日志实例( 参考 app.logger ),后者拿到的是请求级别的日志实例( 参考 ctx.logger ),如果需要自定义日志中也有请求信息( 比如 userId、traceId 等 ),请选择 ctx.getLogger,否则选择 app.getLogger,请根据项目的日志实际使用场景选择合理的方法。

        日志输出格式

        你也可以通过自定义 formattercontextFormatter 来自定义日志输出格式。

        // config/config.default.js
        +config.customLogger = {
        +  oneLogger: {
        +    file: 'one.log',
        +    formatter(meta) {
        +      const { level, date, pid, message } = meta;
        +      return `[${date}] [${level}] [${pid}] ${message}`;
        +    },
        +    contextFormatter(meta) {
        +      const { level, date, pid, message } = meta;
        +      return `[${date}] [${level}] [${pid}] [${meta.ctx.href}] ${message}]`;
        +    },
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        高级自定义日志

        日志默认是打印到日志文件中,当本地开发时同时会打印到终端。

        但是,有时候我们需要把日志上报到第三方服务,这时候我们就需要自定义日志的 Transport

        Transport 是一种传输通道,一个 Logger 可包含多个传输通道。

        默认的 Logger 均有 FileConsole 两个通道,分别负责打印到文件和终端。

        举个例子,我们不仅需要把错误日志打印到 common-error.log,还需要上报给第三方服务。

        首先我们定义一个日志的 Transport,代表第三方日志服务。

        // lib/remote_transport.js
        +const util = require('util');
        +const Transport = require('egg-logger').Transport;
        +
        +class RemoteErrorTransport extends Transport {
        +  // 定义 log 方法,在此方法中把日志上报给远端服务
        +  log(level, args) {
        +    let log;
        +    if (args[0] instanceof Error) {
        +      const err = args[0];
        +      log = util.format('%s: %s\n%s\npid: %s\n', err.name, err.message, err.stack, process.pid);
        +    } else {
        +      log = util.format(...args);
        +    }
        +
        +    this.options.app.curl('http://url/to/remote/error/log/service/logs', {
        +      data: log,
        +      method: 'POST',
        +    }).catch(console.error);
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        然后再对 Logger 添加 Transport,这样每条日志就会同时打印到这个 Transport 了。

        // app.js
        +app.getLogger('errorLogger').set('remote', new RemoteErrorTransport({ level: 'ERROR', app }));
        +
        1
        2

        上面的例子比较简单,实际情况中我们需要考虑性能,很可能采取先打印到内存,再定时上传的策略,以提高性能。

        日志切割

        企业级日志一个最常见的需求之一是对日志进行自动切割,以方便管理。

        框架内置了 egg-logrotator 插件来提供支持。

        按天切割

        这是框架的默认日志切割方式,在每日 00:01 按照 .log.YYYY-MM-DD 文件名进行切割。

        譬如当前写入的日志为 example-app-web.log,当凌晨 00:00 时,会对日志进行切割,把过去一天的日志按 example-app-web.log.YYYY-MM-DD 的形式切割为单独的文件。

        按照文件大小切割

        我们也可以按照文件大小进行切割。例如,当文件超过 2G 时进行切割。

        譬如,我们需要把 egg-web.log 按照大小进行切割:

        // config/config.default.js
        +const path = require('path');
        +
        +module.exports = appInfo => {
        +  const config = {};
        +
        +  config.logrotator = {
        +    filesRotateBySize: [
        +      'egg-web.log',
        +    ],
        +    maxFileSize: 2 * 1024 * 1024 * 1024,
        +  };
        +
        +  return config;
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        添加到 filesRotateBySize 的日志文件不再按天进行切割。

        如果配置为文件名,则会自动转换为 path.join(this.app.config.logger.dir, file)

        按照小时切割

        我们也可以选择按照小时进行切割,这和默认的按天切割非常类似,只是时间缩短到每小时。

        例如,我们需要把 common-error.log 按照小时进行切割:

        // config/config.${env}.js
        +const path = require('path');
        +
        +module.exports = appInfo => {
        +  return {
        +    logrotator: {
        +      filesRotateByHour: [
        +        'common-error.log',
        +      ],
        +    },
        +  };
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        添加到 filesRotateByHour 的日志文件不再被按天进行切割。

        如果配置为文件名,则会自动转换为 path.join(this.app.config.logger.dir, file)

        编写测试

        框架提供了 expectLog()mockLog() 来简化测试工作。

        后者会把对应的日志保留一份在缓存中,避免 IO 较高时,写入延迟导致的校验失败。

        it('should work', async () => {
        +  app.mockLog();
        +  await app.httpRequest()
        +    .get('/')
        +    .expect('hello world')
        +    .expect(200);
        +
        +  app.expectLog('foo in logger');
        +  app.expectLog(/foo in coreLogger/, 'coreLogger');
        +  app.expectLog('foo in myCustomLogger', 'myCustomLogger');
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        + + + diff --git a/zh/guide/middleware.html b/zh/guide/middleware.html new file mode 100644 index 0000000..cd288a8 --- /dev/null +++ b/zh/guide/middleware.html @@ -0,0 +1,133 @@ + + + + + + Middleware | Egg + + + + + + + +

        使用场景

        一个 HTTP 请求进来后,会执行一系列的处理,然后返回响应给用户。

        这个过程就像一条管道,管道的每一个切面逻辑,我们称之为 Middleware,也叫 中间件

        框架继承于 Koa,在 Koa 里面有个更形象的术语:洋葱模型

        Koa 中间件执行顺序:

        编写中间件

        我们约定把中间件放置在 app/middleware 目录下:

        // app/middleware/response_time.js
        +module.exports = () => {
        +  return async function responseTime(ctx, next) {
        +    const start = Date.now();
        +    await next();
        +    const cost = Date.now() - start;
        +    ctx.set('X-Response-Time', `${cost}ms`);
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        如上,需 exports 一个普通的 function,返回一个标准的 Koa Middleware 函数。

        加载规则

        框架会把 app/middleware 目录下的文件挂载到 app.middleware 上。

        支持多级目录,注意:对应的文件名会转换为驼峰格式

        app/middleware/api/auth.js => app.middleware.api.auth
        +app/middleware/response_time.js => app.middleware.responseTime
        +app/middleware/BlockBot.js => app.middleware.blockBot
        +
        1
        2
        3

        使用中间件

        由于中间件是洋葱模型的一部分,因此需要应用开发者显式挂载,决定它们的顺序

        // config/config.default.js
        +module.exports = {
        +  // 注意是驼峰格式
        +  middleware: [ 'responseTime' ],
        +};
        +
        1
        2
        3
        4
        5

        自定义配置

        一般来说中间件也会有自己的配置。

        我们可以把之前的中间件改造如下:

        // app/middleware/response_time.js
        +module.exports = (options, app) => {
        +  return async function responseTime(ctx, next) {
        +    const start = Date.now();
        +    await next();
        +    const cost = Date.now() - start;
        +    // `options.headerKey` 等价于 `app.config.responseTime.headerKey`
        +    ctx.set(options.headerKey, `${cost}ms`);
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        如上,接受两个参数:

        • options: 中间件的配置项,框架会将 app.config[${middlewareName}] 传递进来。
        • app: 当前应用 Application 的实例。

        对应的配置:

        // config/config.default.js
        +module.exports = {
        +  middleware: [ 'responseTime' ],
        +
        +  // key 为驼峰格式
        +  responseTime: {
        +    headerKey: 'X-Response-Time',
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        通用配置

        中间件支持以下几个通用的配置项:

        • enable:控制中间件是否开启。
        • match:设置只有符合某些规则的请求才会经过这个中间件。
        • ignore:设置符合某些规则的请求不经过这个中间件。

        enable

        如果我们的应用并不需要默认的 bodyParser 中间件来进行请求体的解析,此时我们可以通过配置来关闭它。

        module.exports = {
        +  bodyParser: {
        +    enable: false,
        +  },
        +};
        +
        1
        2
        3
        4
        5

        match 和 ignore

        如果我们想让 responseTime 只针对 API 请求开启,我们可以配置:

        module.exports = {
        +  responseTime: {
        +    match: '/api',
        +  },
        +};
        +
        1
        2
        3
        4
        5

        matchignore 支持多种类型的配置方式,两者互斥不允许同时配置。

        • 字符串:当参数为字符串类型时,配置的是一个 URL 的路径前缀,所有以配置的字符串作为前缀的 URL 都会匹配上。当然,你也可以直接使用字符串数组。
        • 正则:当参数为正则时,直接匹配满足正则验证的 URL 的路径。
        • 函数:当参数为一个函数时,会将请求上下文传递给这个函数,最终取函数返回的结果(true/false)来判断是否匹配。
        module.exports = {
        +  responseTime: {
        +    match(ctx) {
        +      return ctx.url.startsWith('/api');
        +    },
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        详见 egg-path-matching

        修改内置中间件的配置

        除了应用层加载中间件之外,框架自身和其他的插件也会加载许多中间件。

        如果开发者期望自定义对应的配置,可以修改同名配置项进行覆盖。

        如框架内置的 bodyParser 中间件,可以自定义配置如下:

        // config/config.default.js
        +module.exports = {
        +  bodyParser: {
        +    jsonLimit: '10mb',
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6

        路由中间件

        如果 match / ignore 不能满足你的需求,如你期望在不同的路由中使用不同的配置。

        则可以在路由中单独初始化和挂载:

        // app/router.js
        +module.exports = app => {
        +  // 初始化
        +  const responseTime = app.middleware.responseTime({ headerKey: 'X-Time' }, app);
        +
        +  // 仅挂载到指定的路由上
        +  app.router.get('/test', responseTime, app.controller.test);
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8

        引入 Koa 生态

        我们也可以非常容易的引入 Koa 中间件生态。

        koa-compress 为例,在 Koa 中使用时:

        const koa = require('koa');
        +const compress = require('koa-compress');
        +
        +const app = koa();
        +
        +const options = { threshold: 2048 };
        +app.use(compress(options));
        +
        1
        2
        3
        4
        5
        6
        7

        在我们的应用中,会更简单一些,只需:

        // app/middleware/compress.js
        +// koa-compress 暴露的接口 `(options) => middleware` 和框架要求一致
        +module.exports = require('koa-compress');
        +
        1
        2
        3

        对应的配置:

        // config/config.default.js
        +module.exports = {
        +  middleware: [ 'compress' ],
        +  compress: {
        +    threshold: 2048,
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        如果使用到的 Koa 中间件不符合入参规范,则可以自行处理下:

        // config/config.default.js
        +module.exports = {
        +  webpack: {
        +    compiler: {},
        +    others: {},
        +  },
        +};
        +
        +// app/middleware/webpack.js
        +const webpackMiddleware = require('some-koa-middleware');
        +
        +module.exports = (options, app) => {
        +  return webpackMiddleware(options.compiler, options.others);
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        编写测试

        类似于 Controller 的测试,通过 app.httpRequest 来测试。

        // test/controller/home.test.js
        +const { app, mock, assert } = require('egg-mock');
        +
        +describe('test/middleware/response_time.test.js', () => {
        +  it('should response header', () => {
        +    return app.httpRequest()
        +      .get('/api/test')
        +      .expect('X-Response-Time', /\d+ms/);
        +  });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        + + + diff --git a/zh/guide/plugin.html b/zh/guide/plugin.html new file mode 100644 index 0000000..486bd0d --- /dev/null +++ b/zh/guide/plugin.html @@ -0,0 +1,96 @@ + + + + + + 使用插件 | Egg + + + + + + + +

        使用场景

        插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。

        我们在使用 Koa 中间件过程中发现了下面一些问题:

        1. 中间件是有先后顺序的,需要统一管控,但是它自身却无法管理这种顺序,只能交给使用者。这样其实非常不友好,一旦顺序不对,结果可能有天壤之别。
        2. 中间件的定位是拦截用户请求,并在它前后做一些事情,例如:鉴权、安全检查、访问日志等等。但实际情况是,有些功能是和请求无关的,例如:定时任务、消息订阅、后台逻辑等等。
        3. 一些复杂的初始化逻辑,需要在应用启动的时候完成,这显然也不适合放到中间件中去实现。

        综上所述,我们需要一套更加强大的机制,来管理、编排那些相对独立的业务逻辑。

        使用插件

        举个例子,我们想引入 egg-validate 这个插件。

        安装依赖

        插件一般通过 npm 模块的方式进行复用:

        $ npm install egg-validate --save
        +
        1

        注意:我们建议通过 ^ 的方式引入依赖,并且强烈不建议锁定版本。

        友情提示

        有些插件是内置到框架中,但默认不开启的,此时无需手动安装依赖。详见下文。

        挂载插件

        config/plugin.js 中声明:

        // config/plugin.js
        +exports.validate = {
        +  enable: true,
        +  package: 'egg-validate',
        +};
        +
        1
        2
        3
        4
        5

        使用插件

        然后就可以使用插件提供的功能:

        // app/controller/user.js
        +class UserController extends Controller {
        +  async create() {
        +    const rule = { name: 'string' };
        +    ctx.validate(rule, ctx.request.body);
        +
        +    // ...
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        了解插件

        一个插件其实就是一个『迷你的应用』,和应用几乎一模一样

        目录结构

        my-plugin
        +├── app
        +│   ├── service
        +│   |   └── user.js
        +│   ├── middleware
        +│   |   └── response_time.js
        +│   └── extend
        +│       ├── application.js
        +│       ├── context.js
        +│       └── helper.js
        +├── config
        +|   ├── config.default.js
        +│   ├── config.prod.js
        +|   ├── config.local.js
        +|   └── config.unittest.js
        +├── test
        +|   └── service
        +|       └── user.test.js
        +└── package.json
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        Service

        插件可以包含 Service,框架会自动挂载。

        Config

        插件可以包含 配置

        插件一般会包含自己的默认配置,应用开发者可以自由覆盖对应的配置:

        譬如 egg-static 插件默认的 prefix/public/

        你可以在应用的配置里面覆盖掉它:

        // config/config.default.js
        +config.static = {
        +  prefix: '/static/',
        +};
        +
        1
        2
        3
        4

        具体合并规则可以参见配置

        Middleware

        插件可以包含 中间件

        框架把插件的 app/middleware 目录下的文件,同样加载到 app.middleware 上。

        大部分情况下,插件开发者会自动挂载中间件到对应的地方,无需应用开发者处理。

        但某些情况下,插件仅提供了中间件定义,并不帮应用开发者决定挂载顺序。

        此时,应用开发者只需遵循 中间件 文档来使用即可。

        Extend

        插件可以提供 ContextApplicationHelper 等的扩展。

        譬如在插件里面提供以下扩展,对应的逻辑就可以共享给其他应用。

        // {plugin_root}/app/extend/context.js
        +const UA = Symbol('Context#ua');
        +const useragent = require('useragent');
        +
        +module.exports = {
        +  get ua() {
        +    if (!this[UA]) {
        +      // this 就是 ctx 对象,在其中可以调用 ctx 上的其他方法,或访问属性
        +      const uaString = this.get('user-agent');
        +      this[UA] = useragent.parse(uaString);
        +    }
        +    return this[UA];
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        不支持的特性

        • 没有 RouterController
        • 没有 plugin.js,只能声明跟其他插件的依赖,而不能决定其他插件的开启与否。

        插件配置

        参数介绍

        应用开发者通过 config/plugin.js 来声明插件的挂载。

        除了上面我们使用到的 enablepackage 外,其他参数如下:

        • enable - 是否开启此插件,默认为 true
        • package - npm 模块名称,通过 npm 模块形式引入插件。
        • path - 插件绝对路径,跟 package 配置互斥。
        • env - 数组,仅在指定运行环境才开启,会覆盖插件自身 package.json 中的配置。

        插件本身的 package.json 里面也会有一个 eggPlugin 属性来声明默认的属性。

        开启框架内置插件

        框架一般也会内置一些插件,它们有可能默认是开启或关闭的。

        此时,应用无需配置 package,直接配置 enable 即可:

        // config/plugin.js
        +exports.cors = {
        +  enable: true;
        +};
        +
        +// 也可以简写为:
        +exports.validate = true;
        +
        1
        2
        3
        4
        5
        6
        7

        packagepath

        • package:通过 npm 方式引入,也是最常见的引入方式。
        • path:通过绝对路径引入。
        • 后者主要场景是:应用内部抽象了一个插件,但还没达到可以发布独立插件的阶段临时使用。
        • 关于这两种方式的使用场景,可以参见渐进式开发
        // config/plugin.js
        +const path = require('path');
        +exports.mysql = {
        +  enable: true,
        +  path: path.join(__dirname, '../lib/plugin/egg-mysql'),
        +};
        +
        1
        2
        3
        4
        5
        6

        根据环境配置

        同时,我们还支持 plugin.{env}.js 这种模式,会根据运行环境加载插件配置。

        比如定义了一个开发环境使用的插件 egg-dev,只希望在本地环境加载,可以安装到 devDependencies

        譬如 egg-development-proxyagent 这个插件,只会在开发环境使用。

        则我们可以只安装到 devDependencies

        $ npm i egg-dev --save-dev
        +
        1

        然后在 plugin.local.js 中声明:

        // config/plugin.local.js
        +exports.proxyagent = {
        +  enable: true,
        +  package: 'egg-development-proxyagent',
        +};
        +
        1
        2
        3
        4
        5

        这样在生产环境可以 npm i --production 不需要下载 egg-development-proxyagent 的包了。

        注意:

        • 不存在 plugin.default.js
        • 只能在应用层使用,在框架层请勿使用。

        常见问题

        如何开发一个插件

        恭喜你迈出这一步,可以回馈社区。

        具体可以参见文档:

        插件太多,每个应用都要开启怎么办?

        此时应该考虑包装为一个上层框架

        + + + diff --git a/zh/guide/router.html b/zh/guide/router.html new file mode 100644 index 0000000..bdd69aa --- /dev/null +++ b/zh/guide/router.html @@ -0,0 +1,154 @@ + + + + + + Router | Egg + + + + + + + +

        使用场景

        Router 也称之为 路由,用于描述请求 URL 和具体承担执行动作的 Controller 的对应关系。

        框架通过 egg-router 来提供相关支持。

        编写路由

        我们约定 app/router.js 文件用于统一所有路由规则。

        通过统一的配置,可以避免路由规则逻辑散落在多个地方,从而出现未知的冲突,可以更方便的来查看全局的路由规则。

        假设有以下 Controller 定义:

        // app/controller/user.js
        +class UserController extends Controller {
        +  async info() {
        +    const { ctx } = this;
        +    ctx.body = {
        +      name: `hello ${ctx.params.id}`,
        +    };
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        则我们可以定义对应的路由如下:

        // app/router.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +  // GET /user/123
        +  router.get('/user/:id', controller.user.info);
        +};
        +
        1
        2
        3
        4
        5
        6

        这样就完成了一个最简单的 Router 定义,当用户访问 GET /user/123 时,这个 UserController 里面的 info 方法就会执行。

        路由定义

        router.verb('/some-path', controller.action);
        +
        1

        路由方法

        即为上面的 verb,代表用户触发动作,支持 GET、POST 等所有 HTTP 方法。

        • router.head - 对应 HTTP HEAD 方法。
        • router.get - 对应 HTTP GET 方法。
        • router.put - 对应 HTTP PUT 方法。
        • router.post - 对应 HTTP POST 方法。
        • router.patch - 对应 HTTP PATCH 方法。
        • router.delete - 对应 HTTP DELETE 方法。
        • router.del - 由于 delete 是保留字,故一般会用 router.del 别名。
        • router.options - 对应 HTTP OPTIONS 方法。

        除此之外,还提供了:

        • router.redirect - 可以对 URL 进行重定向处理,比如把用户访问的根目录路由到某个主页。
        • router.all - 对所有的 HTTP 方法都挂载。

        路由路径

        即为上面的 /some-path,并支持命名参数。

        // app/router.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +  router.get('/home', controller.home.index);
        +  // 支持命名参数,通过 `ctx.params.id` 可以取出。
        +  router.get('/user/:id', controller.user.detail);
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        也支持正则式:

        // app/router.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +
        +  // 可以通过 `ctx.params[0]` 获取到对应的正则分组信息。
        +  router.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, controller.package.detail);
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        如果你有一个通配的路由映射,需注意顺序,放在后面,如:

        router.get('/user/manager', controller.user.manager);
        +router.get('/user/:id', controller.user.detail);
        +
        1
        2

        路径解析使用了 path-to-regexp 模块,更多规则可以参见其文档。

        路由中间件

        支持对特定路由挂载中间件。

        router.verb('/some-path', middleware1, ..., middlewareN, controller.action);
        +
        1

        如下示例:

        // app/router.js
        +module.exports = app => {
        +  const { router, controller, middleware } = app;
        +
        +  // 初始化
        +  const responseTime = middleware.responseTime({ headerKey: 'X-Time' }, app);
        +
        +  // 仅挂载到指定的路由上
        +  router.get('/test', responseTime, controller.test);
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        路由别名

        支持对路由定义别名,用于生成路由链接。

        router.verb('router-name', '/some-path', controller.action);
        +router.verb('router-name', '/some-path', middleware1, ..., middlewareN, controller.action);
        +
        1
        2

        然后可以通过 Helper 提供的辅助函数 pathForurlFor 来生成链接。

        // app/router.js
        +router.get('user', '/user', controller.user);
        +
        +// 使用 helper 计算指定 path
        +ctx.helper.pathFor('user', { limit: 10, sort: 'name' });
        +// => /user?limit=10&sort=name
        +
        1
        2
        3
        4
        5
        6

        你可以通过 ctx.routerName 获取到当前命中的路由别名。

        RESTful 风格的 URL 定义

        RESTful 是非常经典的 Web API 设计规范,如 CRUD 的路由结构。

        我们提供了 app.resources('routerName', 'pathMatch', controller) 来简化开发。

        // app/router.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +  router.resources('posts', '/api/posts', controller.posts);
        +  router.resources('users', '/api/v1/users', controller.v1.users); // app/controller/v1/users.js
        +};
        +
        1
        2
        3
        4
        5
        6

        如上,我们对 /posts 路径设置了映射到 app/controller/posts.js

        然后,你只需要在 Controller 里面按需提供对应的方法即可,框架会自动映射。

        Method Path Route Name Controller.Action
        GET /posts posts controller.posts.index
        GET /posts/new new_post controller.posts.new
        GET /posts/:id post controller.posts.show
        GET /posts/:id/edit edit_post controller.posts.edit
        POST /posts posts controller.posts.create
        PUT /posts/:id post controller.posts.update
        DELETE /posts/:id post controller.posts.destroy
        // app/controller/posts.js
        +class PostController extends Controller {
        +  async index() {}
        +  async new() {}
        +  async create() {}
        +  async show() {}
        +  async edit() {}
        +  async update() {}
        +  async destroy() {}
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        具体示例,可以参考 实现 RESTful API 文档。

        Router 实战

        下面通过更多实际的例子,来说明 Router 的用法。

        获取查询参数

        // app/router.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +  router.get('/user/list', controller.user.list);
        +};
        +
        +// app/controller/user.js
        +class UserController extends Controller {
        +  async list() {
        +    // curl http://127.0.0.1:7001/user/list?name=tz
        +    const { ctx } = this;
        +    ctx.body = `name: ${ctx.query.name}`;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        获取命名参数

        // app/router.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +  router.get('/user/:id/:name', controller.user.detail);
        +};
        +
        +// app/controller/user.js
        +class UserController extends Controller {
        +  async detail() {
        +    // curl http://127.0.0.1:7001/user/123/tz
        +    const { ctx } = this;
        +    ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        重定向

        使用方式:router.redirect(source, destination, [code])

        • sourcedestination 可以是路径,也可以是路径别名。
        • code 默认 301,可选参数。
        // app/router.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +  router.get('index', '/home/index', controller.home.index);
        +  router.redirect('/', '/home/index', 302);
        +};
        +
        +// app/controller/home.js
        +class HomeController extends Controller {
        +  async index() {
        +    // curl -L http://localhost:7001
        +    const { ctx } = this;
        +    ctx.body = 'hello controller';
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        常见问题

        路由映射太多?

        一般来说,我们并不推荐把路由规则逻辑散落在多个地方,这会给排查问题带来困扰。

        若确实有需求,可以如下拆分:

        // app/router.js
        +module.exports = app => {
        +  require('./router/news')(app);
        +  require('./router/admin')(app);
        +};
        +
        +// app/router/news.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +  router.get('/news/list', controller.news.list);
        +  router.get('/news/detail', controller.news.detail);
        +};
        +
        +// app/router/admin.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +  router.get('/admin/user', controller.admin.user);
        +  router.get('/admin/log', controller.admin.log);
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        也可直接使用 egg-router-plus

        另外,框架会在启动期把最终的路由映射 dump 到 run/router.json 中。

        自动映射路由?

        一般来说,如果符合 RESTful 风格的路由,直接用上述的 router.resource() 配置即可。

        如果你的业务场景中,有其他约定的规则,则可以参考对应的 resource 源码,扩展自己的方法,封装为插件。

        通过装饰器映射?

        装饰器目前还不是 ECMA 的正式规范,框架未提供该功能。

        开发者可以自行通过 TypeScriptBabel 转义对应的自定义装饰器。

        + + + diff --git a/zh/guide/service.html b/zh/guide/service.html new file mode 100644 index 0000000..826a88d --- /dev/null +++ b/zh/guide/service.html @@ -0,0 +1,65 @@ + + + + + + Service | Egg + + + + + + + +

        使用场景

        Service 是在复杂业务场景下用于做业务逻辑封装的一个抽象层:

        • 保持 Controller 中的逻辑更加简洁。
        • 保持业务逻辑的独立性,抽象出来的 Service 可以被多个 Controller 重复调用。
        • 将逻辑和展现分离,更容易编写测试用例。

        场景举例:

        • 复杂数据的处理,如从数据库获取信息后,需经过一定的规则计算,才能返回用户显示。
        • 第三方服务的调用,如调用后端微服务的接口。

        编写 Service

        我们约定把 Service 放置在 app/service 目录下:

        // app/service/user.js
        +const { Service } = require('egg');
        +
        +class UserService extends Service {
        +  async find(uid) {
        +    const user = await this.ctx.db.query('select * from user where uid = ?', uid);
        +    return user;
        +  }
        +}
        +
        +module.exports = UserService;
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        使用 Service

        框架会默认挂载到 ctx.service 上,对应的 Key 为文件名的驼峰格式。

        如上面的 Service 会挂载为 ctx.service.user

        然后就可以在 Controller 里调用:

        // app/controller/user.js
        +const { Controller } = require('egg');
        +
        +class UserController extends Controller {
        +  async info() {
        +    const { ctx } = this;
        +    const userId = ctx.params.id;
        +    const userInfo = await ctx.service.user.find(userId);
        +    ctx.body = userInfo;
        +  }
        +}
        +
        +module.exports = UserController;
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        生命周期

        Service 不是单例,是 请求级别 的对象,它挂载在 Context 上的。

        Service 是延迟实例化的,仅在每一次请求中,首次调用到该 Service 的时候,才会实例化。

        因此,无需担心实例化的性能损耗,经过我们大规模的实践证明,可以忽略不计。

        挂载规则

        约定放置在 app/service 目录下,支持多级目录,对应的文件名会转换为驼峰格式

        app/service/biz/user.js => ctx.service.biz.user
        +app/service/sync_user.js => ctx.service.syncUser
        +app/service/HackerNews.js => ctx.service.hackerNews
        +
        1
        2
        3

        常用属性和方法

        Service 实例继承 egg.Service,提供以下属性:

        • this.ctx: 当前请求的上下文 Context 的实例,可以拿到各种便捷属性和方法。
        • this.app: 当前应用 Application 的实例,可以拿到全局对象和方法。
        • this.service:应用定义的 Service,可以调用其他 Service
        • this.config:应用运行时的配置项
        • this.logger:logger 对象,使用方法类似 Context Logger,不同之处是通过这个 Logger 对象记录的日志,会额外加上该日志的文件路径,以便快速定位日志打印位置。

        编写测试

        可以通过 app.mockContext() 获取到 Context 实例来测试。

        // test/service/user.test.js
        +const { app, mock, assert } = require('egg-mock');
        +
        +describe('test/service/user.test.js', () => {
        +  it('should get exists user', async () => {
        +    // 创建 ctx
        +    const ctx = app.mockContext();
        +    // 通过 ctx 访问到 service.user
        +    const user = await ctx.service.user.find('TZ');
        +    assert(user);
        +    assert(user.name === 'TZ');
        +  });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        + + + diff --git a/zh/guide/session.html b/zh/guide/session.html new file mode 100644 index 0000000..c385562 --- /dev/null +++ b/zh/guide/session.html @@ -0,0 +1,84 @@ + + + + + + Session | Egg + + + + + + + +

        使用场景

        在 Web 应用中经常用 Cookie 来承担标识请求方身份的功能,但浏览器给每个站点分配的空间是很有限的。

        从而提出了 Session 的概念,用于用户身份识别,以及会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)。

        最佳实践

        对于 Egg 的用户来说,请不要直接操作 ctx.session,而应该:

        • 使用 用户系统 提供的统一登录方式,由它来操作 Session
        • 如果你有额外的用户信息需要存储,直接操作 ZCache(Tair) 提供的 API。

        使用 Session

        框架内置了 Session 插件,给我们提供了 ctx.session 来访问或者修改当前用户 Session

        // app/controller/home.js
        +class HomeController extends Controller {
        +  async fetchPosts() {
        +    const { ctx } = this;
        +    // 获取 Session 上的内容
        +    const userId = ctx.session.userId;
        +    const posts = await ctx.service.post.fetch(userId);
        +    // 修改 Session 的值
        +    ctx.session.visited = ctx.session.visited ? (Number(ctx.session.visited) + 1) : 1;
        +    ctx.body = {
        +      success: true,
        +      posts,
        +    };
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        Session 的使用方法非常直观,直接读取或修改它就可以了,如果要删除,直接赋值为 null

        ctx.session = null;
        +
        1

        禁止使用的 Key 值

        需要 特别注意 的是:设置 session 属性时需要避免:

        • 不要以 _ 开头
        • 不能为 isNew

        否则会造成字段丢失,详见 koa-session 源码。

        // ❌ 错误的用法
        +ctx.session._visited = 1;   //    --> 该字段会在下一次请求时丢失
        +ctx.session.isNew = 'HeHe'; //    --> 为内部关键字, 不应该去更改
        +
        +// ✔️ 正确的用法
        +ctx.session.visited = 1;    //   -->  此处没有问题
        +
        1
        2
        3
        4
        5
        6

        存储方式

        默认配置下,会把用户的 Session 加密后直接存储在 Cookie 中的一个字段中,浏览器每次请求时会带上这个 Cookie,我们在服务端解密后使用。

        Session 写入 Cookie 的默认配置如下:

        config.session = {
        +  key: 'EGG_SESS', // 存储 `Session` 的 `Cookie` 键值对的 key
        +  maxAge: 24 * 3600 * 1000, // 1 天
        +  httpOnly: true,
        +  encrypt: true,
        +};
        +
        1
        2
        3
        4
        5
        6

        可以看到,默认配置下,存放 SessionCookie 将会加密存储、不可被前端 js 访问,这样可以保证用户数据是安全的。

        Redis

        默认存储在 Cookie 时,如果 Session 对象过于庞大,就会导致:

        • 浏览器通常都有限制最大的 Cookie 长度,当设置的 Session 过大时,浏览器可能拒绝保存。
        • Session 过大时,每次请求都要额外带上庞大的 Cookie 信息,影响性能。

        对于社区的用户,可以使用 egg-session-redis 插件来配置存储。

        你需要:

        • 参考 egg-redis 插件的文档,来配置对应的 Redis 地址信息。
        • 安装并开启对应的插件。
        // plugin.js
        +exports.redis = {
        +  enable: true,
        +  package: 'egg-redis',
        +};
        +
        +exports.sessionRedis = {
        +  enable: true,
        +  package: 'egg-session-redis',
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        注意事项

        一旦选择了将 Session 存入到外部存储中,就意味着系统将强依赖于这个外部存储,当它挂了的时候,我们就完全无法使用 Session 相关的功能了。

        一般来说,建议只将必要的信息存储在 Session 中,保持 Session 的精简并使用默认的 Cookie 存储,用户级别的缓存不要存储在 Session 中。

        注意事项

        再次提醒,对于 Egg 的用户来说,请不要直接读取和写入 ctx.session

        应该使用 用户系统 提供的统一登录方式,读取 ctx.user

        Session 实战

        删除 Session

        ctx.session = null;
        +
        1

        修改失效时间

        虽然在 Session 的配置中有一项是 maxAge,但是它只能全局设置 Session 的有效期。

        我们经常可以在一些网站的登陆页上看到有 记住我 的选项框,勾选之后可以让登陆用户的 Session 有效期更长。

        这种针对特定用户的 Session 有效时间设置我们可以通过 ctx.session.maxAge= 来实现。

        // app/controller/user.js
        +const ms = require('ms');
        +class UserController extends Controller {
        +  async login() {
        +    const ctx = this.ctx;
        +    const { username, password, rememberMe } = ctx.request.body;
        +    const user = await ctx.loginAndGetUser(username, password);
        +
        +    // 设置 Session
        +    ctx.session.user = user;
        +    // 如果用户勾选了 `记住我`,设置 30 天的过期时间
        +    if (rememberMe) ctx.session.maxAge = ms('30d');
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        延长有效期

        默认情况下,当用户请求没有导致 Session 被修改时,框架都不会延长 Session 的有效期。

        但是在有些场景下,我们希望用户如果长时间都在访问我们的站点,则延长他们的 Session 有效期,不让用户退出登录态。

        框架提供了一个 renew 配置项用于实现此功能,它会在发现当用户 Session 的有效期仅剩下最大有效期一半的时候,重置 Session 的有效期。

        // config/config.default.js
        +module.exports = {
        +  session: {
        +    renew: true,
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        + + + diff --git a/zh/guide/upload.html b/zh/guide/upload.html new file mode 100644 index 0000000..1a186d2 --- /dev/null +++ b/zh/guide/upload.html @@ -0,0 +1,194 @@ + + + + + + 文件上传 | Egg + + + + + + + +

        使用场景

        文件上传,是 Web 应用的一个常见的功能。

        框架内置了 Multipart 插件:

        • 解析浏览器上传的 multipart/form-data 的数据。
        • 提供 filestream 两种处理接口供开发者选择。
        • 默认提供了安全的限制。

        获取到用户上传的数据后,开发者可以:

        • 存储为本地文件。
        • 提交给第三方服务,参见 通过 HttpClient 上传文件
        • 大部分情况下,我们会转存给云存储服务,在本文中我们也会一并介绍到。

        File 模式

        虽然在 Node.js 的世界里面,Stream 才是主流。

        但对于一般开发者来说,Stream 并不是很容易掌握,尤其是错误处理环节。

        因此,框架提供了 File 模式来简化开发。

        相关的示例代码参见:eggjs/example/multipart-file-mode

        配置

        // config/config.default.js
        +config.multipart = {
        +  mode: 'file',
        +};
        +
        1
        2
        3
        4

        前端代码

        前端可以通过 FormAJAX 等方式来上传文件。

        譬如:

        <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
        +  title: <input name="title" />
        +  file1: <input name="file1" type="file" />
        +  file2: <input name="file2" type="file" />
        +  <button type="submit">Upload</button>
        +</form>
        +
        1
        2
        3
        4
        5
        6

        注意事项

        文件上传需要通过 POST 协议,因此会受到 CSRF 安全的管控,具体参见对应文档。

        获取上传的文件

        框架在 File 模式下,会把获取到的文件挂载到 ctx.request.files 数组上。

        关键代码:

        • ctx.request.files: 获取到的文件列表。
        • ctx.oss.put():示例代码,此处为上传到 OSS 云存储,下文会介绍到。
        • ctx.cleanupRequestFiles():处理完毕后,清理临时文件。
        // app/controller/upload.js
        +class UploadController extends Controller {
        +  async upload() {
        +    const { ctx } = this;
        +    console.log(ctx.request.body);
        +    console.log('got %d files', ctx.request.files.length);
        +
        +    try {
        +      // 遍历处理多个文件
        +      for (const file of ctx.request.files) {
        +        console.log('field: ' + file.fieldname);
        +        console.log('filename: ' + file.filename);
        +        console.log('encoding: ' + file.encoding);
        +        console.log('mime: ' + file.mime);
        +        console.log('tmp filepath: ' + file.filepath);
        +
        +        // 处理文件,比如上传到云端
        +        const result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath);
        +        console.log(result);
        +      }
        +    } finally {
        +      // 需要删除临时文件
        +      await ctx.cleanupRequestFiles();
        +    }
        +  }
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26

        Stream 模式

        如果你对于 Node.js 中的 Stream 模式非常熟悉,那么你可以选择此模式。

        相关的示例代码参见:eggjs/example/multipart

        上传单个文件

        框架同样提供了简化开发的语法糖:

        • ctx.getFileStream():获取上传的文件流,仅支持上传一个文件的情况。
        • stream.fields 获取其他表单字段。

        注意事项

        由于表单解析是有时序的,因此前端代码中,文件 fileds 必须在最后面。

        否则在拿到文件流时,stream.fields 还没解析完,从而获取不到。

        因此对应的前端代码:

        <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
        +  title: <input name="title" />
        +
        +  <!-- 只能有一个 File,且必须放在最后-->
        +  file: <input name="file" type="file" />
        +  <button type="submit">Upload</button>
        +</form>
        +
        1
        2
        3
        4
        5
        6
        7

        对应的后端代码:

        const path = require('path');
        +const sendToWormhole = require('stream-wormhole');
        +const Controller = require('egg').Controller;
        +
        +class UploadController extends Controller {
        +  async upload() {
        +    const ctx = this.ctx;
        +    const stream = await ctx.getFileStream();
        +    const name = 'egg-multipart-test/' + path.basename(stream.filename);
        +    // 文件处理,上传到云存储等等
        +    let result;
        +    try {
        +      result = await ctx.oss.put(name, stream);
        +    } catch (err) {
        +      // 必须将上传的文件流消费掉,要不然浏览器响应会卡死
        +      await sendToWormhole(stream);
        +      throw err;
        +    }
        +
        +    ctx.body = {
        +      url: result.url,
        +      // 所有表单字段都能通过 `stream.fields` 获取到
        +      fields: stream.fields,
        +    };
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26

        上传多个文件

        同时上传多个文件的场景,不能通过 ctx.getFileStream() 来获取,只能通过以下方式:

        const sendToWormhole = require('stream-wormhole');
        +const Controller = require('egg').Controller;
        +
        +class UploadController extends Controller {
        +  async upload() {
        +    const ctx = this.ctx;
        +    const parts = ctx.multipart();
        +    let part;
        +    // parts() 返回 promise 对象
        +    while ((part = await parts()) != null) {
        +      if (part.length) {
        +        // 这是 busboy 的字段
        +        console.log('field: ' + part[0]);
        +        console.log('value: ' + part[1]);
        +        console.log('valueTruncated: ' + part[2]);
        +        console.log('fieldnameTruncated: ' + part[3]);
        +      } else {
        +        if (!part.filename) {
        +          // 这时是用户没有选择文件就点击了上传(part 是 file stream,但是 part.filename 为空)
        +          // 需要做出处理,例如给出错误提示消息
        +          return;
        +        }
        +        // part 是上传的文件流
        +        console.log('field: ' + part.fieldname);
        +        console.log('filename: ' + part.filename);
        +        console.log('encoding: ' + part.encoding);
        +        console.log('mime: ' + part.mime);
        +        // 文件处理,上传到云存储等等
        +        let result;
        +        try {
        +          result = await ctx.oss.put('egg-multipart-test/' + part.filename, part);
        +        } catch (err) {
        +          // 必须将上传的文件流消费掉,要不然浏览器响应会卡死
        +          await sendToWormhole(part);
        +          throw err;
        +        }
        +        console.log(result);
        +      }
        +    }
        +    console.log('and we are done parsing the form!');
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42

        错误处理

        Stream 模式下,在异常处理里面,必须将上传的文件流消费掉,要不然浏览器响应会卡死

        如上示例,你可以使用 stream-wormholemz-modules/pump 模块来处理。

        友情提示

        如果你对 Stream 没有足够了解的时候,建议直接使用 File 模式。

        安全限制

        文件大小

        为了避免恶意的攻击,框架默认对文件上传接口,限制了 FileField 的个数和大小。

        默认配置如下,开发者可以根据需求修改对应的配置。

        config.multipart = {
        +  // 表单 Field 文件名长度限制
        +  fieldNameSize: 100,
        +  // 表单 Field 内容大小
        +  fieldSize: '100kb',
        +  // 表单 Field 最大个数
        +  fields: 10,
        +
        +  // 单个文件大小
        +  fileSize: '10mb',
        +  // 允许上传的最大文件数
        +  files: 10,
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        其中,fileSize 支持 10mb 这种人性化的方式,具体参见 humanize-bytes 模块。

        文件类型

        为了保证文件上传的安全,框架限制了支持的文件格式。默认的后缀白名单参见源码

        开发者可以通过配置 fileExtensions 来新增允许的类型:

        module.exports = {
        +  multipart: {
        +    fileExtensions: [ '.apk' ] // 增加对 apk 扩展名的文件支持
        +  },
        +};
        +
        1
        2
        3
        4
        5

        如果你希望覆盖框架内置的白名单,可以配置 whitelist 属性:

        module.exports = {
        +  multipart: {
        +    // 覆盖整个白名单,只允许上传 '.png' 格式
        +    whitelist: [ '.png' ],
        +    // 也支持函数格式
        +    // whitelist: (filename) => [ '.png' ].includes(path.extname(filename) || ''),
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8

        友情提示

        当重写了 whitelist 时,fileExtensions 不生效。

        云存储

        当获得上传的文件之后,我们一般会转存到云存储服务,尤其是在集群的情况下。

        常用的服务有:

        OSS

        框架内置了 egg-oss 插件,默认未开启。

        配置

        首先需要开启插件:

        // config/plugin.js
        +exports.oss = true;
        +
        1
        2

        然后配置一下你的 OSSbucket, accessKeyId, accessKeySecret 等必要信息。

        // config/config.default.js
        +config.oss = {
        +  client: {
        +    accessKeyId: 'your access key',
        +    accessKeySecret: 'your access secret',
        +    bucket: 'your bucket name',
        +    endpoint: 'oss-cn-hongkong.aliyun.com',
        +    timeout: '60s',
        +    // accessKeyId 和 accessKeySecret 是否经过 egg-bin 加密的
        +    // encryptPassword: false,
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        然后通过 ctx.oss.put() 方法即可上传,支持 FileStream 两种模式。

        File 模式

        class UploadController extends Controller {
        +  async upload() {
        +    // ...
        +
        +    // file 是拿到的上传的文件对象
        +    const { url } = await this.ctx.oss.put(name, file.filepath);
        +    console.info(url); // url 即为上传后的文件链接
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        Stream 模式

        class UploadController extends Controller {
        +  async upload() {
        +    // ...
        +
        +    // stream 是拿到的上传的文件流对象
        +    const { url } = await this.ctx.oss.put(name, stream);
        +    console.info(url); // url 即为上传后的文件链接
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9

        前端直接上传 OSS

        还有一种常见的需求:前端直接上传文件到 OSS,不经过我们的 Web 应用。

        OSS 提供了 STS 临时授权方式

        上述的 egg-oss 插件的底层是 ali-oss 模块,也提供了对应的支持,具体参见文档。

        + + + diff --git a/zh/index.html b/zh/index.html new file mode 100644 index 0000000..daf4db6 --- /dev/null +++ b/zh/index.html @@ -0,0 +1,21 @@ + + + + + + Egg + + + + + + + + + + + diff --git a/zh/quickstart/egg.html b/zh/quickstart/egg.html new file mode 100644 index 0000000..74c6571 --- /dev/null +++ b/zh/quickstart/egg.html @@ -0,0 +1,158 @@ + + + + + + 简单的 Egg 应用 | Egg + + + + + + + +

        在本章中我们先来学习如何写一个简单的 Egg 应用,通过它来了解一些基本的概念和术语。

        友情提示

        需注意的是,本文介绍的是 Egg 的基础使用。 +对于 Egg 的开发者而言,很多插件无需自行安装,已经内置到框架,直接开启即可。 +更多内容,在开发指南中可以了解到。

        典型场景

        我们以 TodoMVC 这个典型的前端应用场景为例,一步步从零开始搭建。

        完整的源码参见 eggjs/examples/todomvc

        逐步搭建

        环境准备

        • 操作系统:支持 macOSLinuxWindows,推荐本地开发用 macOS
        • 运行环境:仅需要 Node.js,对应的安装参见文档

        初始化项目

        通过骨架来初始化

        # 使用 `Egg` 的 `simple` 骨架来初始化
        +$ mkdir demo && cd demo
        +$ npm init egg --type=simple
        +$ npm install
        +
        1
        2
        3
        4

        目录结构

        框架奉行『约定优于配置』,所以我们首先来看看生成的目录结构,更多可以参见目录规范

        demo
        +├── app
        +│   ├── controller # 控制器
        +│   │   └── home.js
        +│   └── router.js  # 路由映射
        +├── config # 配置文件
        +│   ├── config.default.js
        +│   └── plugin.js
        +├── test # 单元测试
        +├── README.md
        +└── package.json
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        Controller

        Controller 负责解析用户的输入,处理后返回相应的结果

        // app/controller/home.js
        +const { Controller } = require('egg');
        +
        +class HomeController extends Controller {
        +  async index() {
        +    const { ctx } = this;
        +    ctx.body = 'hi, egg';
        +  }
        +}
        +
        +module.exports = HomeController;
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        接着配置 路由 映射到对应的 URL 上。

        // app/router.js
        +/**
        + * @param {Egg.Application} app - egg application
        + */
        +module.exports = app => {
        +  const { router, controller } = app;
        +  router.get('/', controller.home.index);
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8

        本地开发

        框架提供了本地开发的辅助工具。

        • 辅助本地启动应用,监控代码变更自动重启。
        • 自动生成 d.ts 文件,提供 智能提示代码跳转 等能力。

        通过命令启动应用:

        $ npm run dev
        +
        1

        然后就可以访问 http://127.0.0.1:7001

        模板渲染

        绝大多数情况,我们都需要读取数据后渲染模板,然后呈现给用户。

        Egg 并不强制你使用某种模板引擎,故我们需要引入对应的『插件』。

        术语讲堂

        插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。 +详见开发指南 - 插件文档。

        在本章中,我们使用 Nunjucks 来渲染,先安装对应的插件 egg-view-nunjucks

        $ npm i egg-view-nunjucks --save
        +
        1

        开启插件:

        // config/plugin.js
        +exports.nunjucks = {
        +  enable: true,
        +  package: 'egg-view-nunjucks'
        +};
        +
        1
        2
        3
        4
        5

        按照约定,在 app/view 目录下添加对应的模板文件:

        <!-- app/view/home.tpl -->
        +<html>
        +  ...
        +  <script src="/public/main.js"></script>
        +</html>
        +
        1
        2
        3
        4
        5

        对应的 Controller 改为:

        class HomeController extends Controller {
        +  async index() {
        +    const { ctx } = this;
        +    // 渲染模板 `app/view/home.tpl`
        +    await ctx.render('home.tpl');
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7

        静态资源

        前端代码的发布,一般有:

        • 构建后发布到 CDN。(推荐)
        • 直接在应用中托管。

        Egg 内置了 egg-static 插件,对后者提供了支持。

        默认会把 app/public 目录映射到 /public 路由上。

        在本例中,我们使用 Vue 来写对应的前端逻辑,可以直接参见示例代码。

        注意事项

        • static 插件,线上会默认设置一年的 magAge
        • 框架默认开启了 CSRF 防护,故 AJAX 请求需要带上对应的 token
        // app/public/main.js
        +axios.defaults.headers.common['x-csrf-token'] = Cookies.get('csrfToken');
        +
        1
        2

        配置文件

        写业务的时候,不可避免的需要有配置文件

        框架提供了强大的配置合并管理功能。

        如上述的 nunjucks 插件,添加对应的配置:

        // config/config.default.js
        +config.view = {
        +  defaultViewEngine: 'nunjucks',
        +  mapping: {
        +    '.tpl': 'nunjucks',
        +    '.html': 'nunjucks',
        +  },
        +};
        +
        1
        2
        3
        4
        5
        6
        7
        8

        注意事项

        config 目录,不是 app/config!

        Service

        我们的业务逻辑一般会写在 Service 里,然后供 Controller 调用。

        // app/service/todo.js
        +const { Service } = require('egg');
        +
        +class TodoService extends Service {
        +  /**
        +   * create todo
        +   * @param {Todo} todo - todo info without `id`, but `title` required
        +   */
        +  async create(todo) {
        +    // validate
        +    if (!todo.title) this.ctx.throw(422, 'task title required');
        +
        +    // normalize
        +    todo.id = Date.now().toString();
        +    todo.completed = false;
        +
        +    this.store.push(todo);
        +    return todo;
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20

        对应的 Controller 如下:

        // app/controller/todo.js
        +class TodoController extends Controller {
        +  async create() {
        +    const { ctx, service } = this;
        +
        +    // params validate, need `egg-validate` plugin
        +    // ctx.validate({ title: { type: 'string' } });
        +
        +    ctx.status = 201;
        +    ctx.body = await service.todo.create(ctx.request.body);
        +  }
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        RESTful

        EggRESTful 这种常见的场景提供了内建的支持

        // app/router.js
        +module.exports = app => {
        +  const { router, controller } = app;
        +
        +  // RESTful 映射
        +  router.resources('/api/todo', controller.todo);
        +};
        +
        1
        2
        3
        4
        5
        6
        7

        对应的 Controller

        // app/controller/todo.js
        +class TodoController extends Controller {
        +  // `GET /api/todo`
        +  async index() {}
        +
        +  // `POST /api/todo`
        +  async create() {}
        +
        +  // `PUT /api/todo`
        +  async update() {}
        +
        +  // `DELETE /api/todo`
        +  async destroy() {}
        +}
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        单元测试

        Web 应用中的单元测试非常重要,框架也提供了对应的单元测试能力支持

        // test/app/controller/todo.test.js
        +const { app, mock, assert } = require('egg-mock/bootstrap');
        +
        +describe('test/app/controller/todo.test.js', () => {
        +  it('should add todo', () => {
        +    return app.httpRequest()
        +      .post('/api/todo')
        +      .send({ title: 'Add one' })
        +      .expect('Content-Type', /json/)
        +      .expect('X-Response-Time', /\d+ms/)
        +      .expect(201)
        +      .expect(res => {
        +        assert(res.body.id);
        +        assert(res.body.title === 'Add one');
        +        assert(res.body.completed === false);
        +      });
        +  });
        +});
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        + + + diff --git a/zh/quickstart/index.html b/zh/quickstart/index.html new file mode 100644 index 0000000..0c49e72 --- /dev/null +++ b/zh/quickstart/index.html @@ -0,0 +1,21 @@ + + + + + + 快速开始 | Egg + + + + + + + + + + + From a04e57cb18a1e899e689f27a722c1feb0c61faa5 Mon Sep 17 00:00:00 2001 From: Suyi Date: Thu, 11 Jul 2019 15:41:17 +0800 Subject: [PATCH 02/11] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..aeb4a1f --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +eggjs.dev \ No newline at end of file From 963f7a43b8e79e721d643846fcce3463cf32ac47 Mon Sep 17 00:00:00 2001 From: Suyi Date: Thu, 11 Jul 2019 15:41:28 +0800 Subject: [PATCH 03/11] Delete CNAME --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index aeb4a1f..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -eggjs.dev \ No newline at end of file From 436f4f5c76dde1da827747a804e792ee1112490a Mon Sep 17 00:00:00 2001 From: Suyi Date: Thu, 11 Jul 2019 15:53:40 +0800 Subject: [PATCH 04/11] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..cca6e6f --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +eggjs.org \ No newline at end of file From 3cb54ba78270ef9eb6f7a9239e964e10dda78097 Mon Sep 17 00:00:00 2001 From: Suyi Date: Thu, 11 Jul 2019 15:54:08 +0800 Subject: [PATCH 05/11] Delete CNAME --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index cca6e6f..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -eggjs.org \ No newline at end of file From c9f0167d8846b90ae689b56f12cbf027b2de0a78 Mon Sep 17 00:00:00 2001 From: Auto Doc Publisher Date: Fri, 12 Jul 2019 07:10:02 +0000 Subject: [PATCH 06/11] docs: auto generate by ci chore: update deploy branch --- 404.html | 10 +- assets/css/0.styles.4659e948.css | 1 + assets/css/0.styles.5203916d.css | 1 - assets/img/search.83621669.svg | 1 - assets/js/1.afc15f94.js | 1 + assets/js/10.1d10814c.js | 1 + assets/js/10.c3972b62.js | 1 - assets/js/11.5abde44a.js | 1 + assets/js/11.e9585f68.js | 1 - assets/js/12.2889c771.js | 1 + assets/js/12.3815fe1e.js | 1 - assets/js/13.5756d463.js | 1 + assets/js/13.5e06d49d.js | 1 - assets/js/14.3b6fddd8.js | 1 - assets/js/14.76791e11.js | 1 + assets/js/15.494082f6.js | 1 - assets/js/15.496a396e.js | 1 + assets/js/16.b174f96e.js | 1 + assets/js/16.b546ed30.js | 1 - assets/js/17.7da596f5.js | 1 + assets/js/17.97cab730.js | 1 - assets/js/18.a0b1deac.js | 1 + assets/js/18.bbddf561.js | 1 - assets/js/19.1c667745.js | 1 - assets/js/19.e65fcbf6.js | 1 + assets/js/2.7bd862e7.js | 1 - assets/js/20.79b3431f.js | 1 - assets/js/20.a4fb7e93.js | 1 + assets/js/21.4be0f224.js | 1 - assets/js/21.ff93a132.js | 1 + assets/js/22.36f8502b.js | 1 - assets/js/22.828a3b43.js | 1 + assets/js/23.4a400231.js | 1 - assets/js/23.6397e5b9.js | 1 + assets/js/24.9be1b01e.js | 1 + assets/js/24.e0d88e74.js | 1 - assets/js/25.32f81bbe.js | 1 + assets/js/25.e176e978.js | 1 - assets/js/26.2b5d15f2.js | 1 + assets/js/26.e04f1b85.js | 1 - assets/js/27.3f5677d2.js | 1 + assets/js/27.46820c07.js | 1 - assets/js/28.29168215.js | 1 + assets/js/28.56abde8a.js | 1 - assets/js/29.4e4c2114.js | 1 - assets/js/29.dd5dfed9.js | 1 + assets/js/3.dcc3f7c0.js | 1 + assets/js/3.e44b477d.js | 1 - assets/js/30.b6d90030.js | 1 + assets/js/30.dcc0f8a4.js | 1 - assets/js/31.1141f7ac.js | 1 + assets/js/31.7a987d06.js | 1 - assets/js/32.28875310.js | 1 - assets/js/32.dfb8cc9e.js | 1 + assets/js/33.0955b641.js | 1 + assets/js/34.bdc8e075.js | 1 + assets/js/35.eeb51878.js | 1 + assets/js/4.3e5c2d0b.js | 1 + assets/js/4.d58994b3.js | 1 - assets/js/5.10b31697.js | 1 - assets/js/5.b83169a7.js | 1 + assets/js/6.3a86b35b.js | 1 - assets/js/6.b736b694.js | 1 + assets/js/7.a26c7040.js | 1 + assets/js/7.bb785290.js | 1 - assets/js/8.80b3eee6.js | 1 - assets/js/8.cf9e16b0.js | 1 + assets/js/9.3f5434be.js | 1 - assets/js/9.7106307e.js | 1 + .../js/{app.317272b7.js => app.77aa8c4f.js} | 8 +- favicon.png | Bin 0 -> 1685 bytes guide/index.html | 16 ++- img_egg/background.png | Bin 0 -> 9254 bytes img_egg/banner.png | Bin 0 -> 53844 bytes img_egg/icon-1.png | Bin 0 -> 14418 bytes img_egg/icon-2.png | Bin 0 -> 12785 bytes img_egg/icon-3.png | Bin 0 -> 12417 bytes img_egg/icon-4.png | Bin 0 -> 13996 bytes img_egg/qrcode.png | Bin 0 -> 7781 bytes img_egg/qrcode_dingtalk.png | Bin 0 -> 29497 bytes img_egg/whosusing.png | Bin 0 -> 5374 bytes index.html | 20 +-- logo.svg | 60 ++++++++ quickstart/egg.html | 23 +-- quickstart/index.html | 23 +-- zh/guide/application.html | 78 +++++++--- zh/guide/config.html | 78 +++++++--- zh/guide/context.html | 114 ++++++++++----- zh/guide/controller.html | 118 +++++++++------ zh/guide/cookie.html | 82 ++++++++--- zh/guide/directory.html | 66 +++++++-- zh/guide/error_handler.html | 88 ++++++++---- zh/guide/faq.html | 58 ++++++-- zh/guide/helper.html | 72 +++++++--- zh/guide/httpclient.html | 134 +++++++++++------- zh/guide/i18n.html | 75 +++++++--- zh/guide/index.html | 57 ++++++-- zh/guide/lifecycle.html | 78 +++++++--- zh/guide/logger.html | 98 +++++++++---- zh/guide/middleware.html | 92 ++++++++---- zh/guide/plugin.html | 82 ++++++++--- zh/guide/router.html | 94 ++++++++---- zh/guide/service.html | 70 ++++++--- zh/guide/session.html | 78 +++++++--- zh/guide/upload.html | 88 ++++++++---- zh/index.html | 20 +-- zh/quickstart/egg.html | 59 ++++---- zh/quickstart/index.html | 23 +-- 108 files changed, 1427 insertions(+), 603 deletions(-) create mode 100644 assets/css/0.styles.4659e948.css delete mode 100644 assets/css/0.styles.5203916d.css delete mode 100644 assets/img/search.83621669.svg create mode 100644 assets/js/1.afc15f94.js create mode 100644 assets/js/10.1d10814c.js delete mode 100644 assets/js/10.c3972b62.js create mode 100644 assets/js/11.5abde44a.js delete mode 100644 assets/js/11.e9585f68.js create mode 100644 assets/js/12.2889c771.js delete mode 100644 assets/js/12.3815fe1e.js create mode 100644 assets/js/13.5756d463.js delete mode 100644 assets/js/13.5e06d49d.js delete mode 100644 assets/js/14.3b6fddd8.js create mode 100644 assets/js/14.76791e11.js delete mode 100644 assets/js/15.494082f6.js create mode 100644 assets/js/15.496a396e.js create mode 100644 assets/js/16.b174f96e.js delete mode 100644 assets/js/16.b546ed30.js create mode 100644 assets/js/17.7da596f5.js delete mode 100644 assets/js/17.97cab730.js create mode 100644 assets/js/18.a0b1deac.js delete mode 100644 assets/js/18.bbddf561.js delete mode 100644 assets/js/19.1c667745.js create mode 100644 assets/js/19.e65fcbf6.js delete mode 100644 assets/js/2.7bd862e7.js delete mode 100644 assets/js/20.79b3431f.js create mode 100644 assets/js/20.a4fb7e93.js delete mode 100644 assets/js/21.4be0f224.js create mode 100644 assets/js/21.ff93a132.js delete mode 100644 assets/js/22.36f8502b.js create mode 100644 assets/js/22.828a3b43.js delete mode 100644 assets/js/23.4a400231.js create mode 100644 assets/js/23.6397e5b9.js create mode 100644 assets/js/24.9be1b01e.js delete mode 100644 assets/js/24.e0d88e74.js create mode 100644 assets/js/25.32f81bbe.js delete mode 100644 assets/js/25.e176e978.js create mode 100644 assets/js/26.2b5d15f2.js delete mode 100644 assets/js/26.e04f1b85.js create mode 100644 assets/js/27.3f5677d2.js delete mode 100644 assets/js/27.46820c07.js create mode 100644 assets/js/28.29168215.js delete mode 100644 assets/js/28.56abde8a.js delete mode 100644 assets/js/29.4e4c2114.js create mode 100644 assets/js/29.dd5dfed9.js create mode 100644 assets/js/3.dcc3f7c0.js delete mode 100644 assets/js/3.e44b477d.js create mode 100644 assets/js/30.b6d90030.js delete mode 100644 assets/js/30.dcc0f8a4.js create mode 100644 assets/js/31.1141f7ac.js delete mode 100644 assets/js/31.7a987d06.js delete mode 100644 assets/js/32.28875310.js create mode 100644 assets/js/32.dfb8cc9e.js create mode 100644 assets/js/33.0955b641.js create mode 100644 assets/js/34.bdc8e075.js create mode 100644 assets/js/35.eeb51878.js create mode 100644 assets/js/4.3e5c2d0b.js delete mode 100644 assets/js/4.d58994b3.js delete mode 100644 assets/js/5.10b31697.js create mode 100644 assets/js/5.b83169a7.js delete mode 100644 assets/js/6.3a86b35b.js create mode 100644 assets/js/6.b736b694.js create mode 100644 assets/js/7.a26c7040.js delete mode 100644 assets/js/7.bb785290.js delete mode 100644 assets/js/8.80b3eee6.js create mode 100644 assets/js/8.cf9e16b0.js delete mode 100644 assets/js/9.3f5434be.js create mode 100644 assets/js/9.7106307e.js rename assets/js/{app.317272b7.js => app.77aa8c4f.js} (59%) create mode 100644 favicon.png create mode 100644 img_egg/background.png create mode 100644 img_egg/banner.png create mode 100644 img_egg/icon-1.png create mode 100644 img_egg/icon-2.png create mode 100644 img_egg/icon-3.png create mode 100644 img_egg/icon-4.png create mode 100644 img_egg/qrcode.png create mode 100644 img_egg/qrcode_dingtalk.png create mode 100644 img_egg/whosusing.png create mode 100644 logo.svg diff --git a/404.html b/404.html index 1e2e361..5f81211 100644 --- a/404.html +++ b/404.html @@ -5,13 +5,13 @@ Egg + - - - + + -

        404

        There's nothing here.
        Take me home.
        - +

        404

        That's a Four-Oh-Four.
        Take me home.
        + diff --git a/assets/css/0.styles.4659e948.css b/assets/css/0.styles.4659e948.css new file mode 100644 index 0000000..b34b4a2 --- /dev/null +++ b/assets/css/0.styles.4659e948.css @@ -0,0 +1 @@ +#nprogress{pointer-events:none}#nprogress .bar{background:#3eaf7c;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #3eaf7c,0 0 5px #3eaf7c;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border-color:#3eaf7c transparent transparent #3eaf7c;border-style:solid;border-width:2px;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.icon.outbound{color:#aaa;display:inline-block;vertical-align:middle;position:relative;top:-1px}.sticker{position:fixed}.sticker.stick-float{top:auto;position:absolute}.table-of-contents{display:none!important}.egg-toc{position:fixed;display:none;width:11.5rem;max-height:100vh;overflow-y:auto;padding:7rem 0;top:0;right:0;box-sizing:border-box;background:#fff;z-index:0}.egg-toc .egg-toc-item{position:relative;padding:.25rem .6rem .25rem 1.5rem;line-height:1.5rem;border-left:2px solid rgba(0,0,0,.08);overflow:hidden}.egg-toc .egg-toc-item a{display:block;color:#2c3e50;width:100%;box-sizing:border-box;font-size:.88rem;font-weight:400;text-decoration:none;transition:color .3s;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.egg-toc .egg-toc-item.active{border-left-color:#3eaf7c}.egg-toc .egg-toc-item.active a,.egg-toc .egg-toc-item:hover a{color:#3eaf7c}.egg-toc .egg-toc-h3 a{padding-left:1rem}.egg-toc .egg-toc-h4 a{padding-left:2rem}.egg-toc .egg-toc-h5 a{padding-left:3rem}.egg-toc .egg-toc-h6 a{padding-left:4rem}@media (min-width:1000px){.egg-toc{display:block}}.home{padding:4rem 0 0;margin:0 auto;display:block;font-size:.88rem}.home .hero{text-align:center;background:#fff no-repeat 50%;background-size:cover;color:#fff;overflow:hidden}.home .hero h1{letter-spacing:.13rem;line-height:2rem;margin:3rem 0 1rem}.home .hero h1,.home .hero h2{font-size:2rem;text-align:center}.home .hero h2{margin:1rem 0 2rem;line-height:2.25rem;border:none;font-weight:200;padding:0 1rem}.home .hero .description{max-width:35rem;font-size:1.6rem;line-height:1.3;color:#6a8bad}.home .hero .action-button{display:flex;width:12.13rem;height:2.38rem;font-size:1rem;align-items:center;justify-content:center;color:#fff;background-color:#04ae62;border-radius:.25rem;margin:2.94rem auto 3.06rem;transition:background-color .3s;border:1px solid transparent;border-bottom-color:#0aea85}.home .hero .action-button:hover{background-color:#05ce74}.home .features{padding:4.5rem 0 9.88rem;margin:auto;text-align:center;color:#62726b}.home .feature{width:16.01rem;padding:0 4.13rem/2;display:inline-block;vertical-align:top;margin-bottom:1rem;box-sizing:border-box}.home .feature .feature-icon{height:8.5rem;display:flex;align-items:center;justify-content:center}.home .feature .feature-icon img{transform:scale(.5)}.home .feature h2{font-size:1.25rem;line-height:2rem;color:#0d261b;margin:1.5rem 0 1rem;border-bottom:none;padding-bottom:0}.home .feature p{color:#315947;text-align:justify;line-height:1.5rem;width:11.88rem;margin:auto}.home .whosusing{position:relative;background:#f8f8f8;border:1px solid transparent}.home .whosusing .icon{display:block;margin:3.38rem auto 2.75rem;width:19.81rem}.home .whosusing .using-list{position:relative;max-width:58.25rem;margin:0 auto 7.75rem;padding:0;z-index:2}.home .whosusing .using-list li{list-style-type:none;background:#fff;margin:0 1rem 1rem;overflow:hidden;padding:1.5rem 1rem;border-radius:.5rem;box-shadow:0 0 2rem rgba(0,0,0,.1)}.home .whosusing .using-list .avatar{position:absolute;width:4rem;height:4rem;border-radius:100%;overflow:hidden;border:1px solid #16d98e;box-sizing:border-box;left:0}.home .whosusing .using-list .avatar img{width:100%;height:100%}.home .whosusing .using-list .info-wrapper{position:relative;max-width:43.13rem;min-height:4rem;padding-left:6rem;overflow:hidden;margin:auto}.home .whosusing .using-list .comment{margin:.69rem 0 .5rem;line-height:1.13rem;color:#0d261b}.home .whosusing .using-list .user-name{margin:0;line-height:1rem;font-size:.75rem;color:#698c7c}.home .background-icon{position:absolute;right:0;bottom:-3.13rem;z-index:1}@media (max-width:1150px){.home .features{max-width:700px}}.sidebar-button{cursor:pointer;display:none;width:1.25rem;height:1.25rem;position:absolute;padding:.6rem;top:.6rem;left:1rem}.sidebar-button .icon{display:block;width:1.25rem;height:1.25rem}@media (max-width:719px){.sidebar-button{display:block}}.egg-search-box{display:inline-block;position:relative;margin-right:1rem}.egg-search-box input{cursor:text;width:12.5rem;height:1.5rem;color:#4e6e8e;display:inline-block;border:1px solid #cfd4db;border-radius:2rem;font-size:.9rem;line-height:2rem;padding:0 .5rem 0 2rem;outline:none;transition:all .2s ease;background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAAAXNSR0IArs4c6QAAAhhJREFUOBG1VTtLXEEUPmd29wekSYpUKwipk8JuRWJpCMTClDYWRlfB1VId2dIsJK7RwkJBEC0MgaSMiIqFjXVAWKsVTJMfsI/xfKNzuTs77t5FMs2d8/i+OXMec5kCa6q08taQGTOGcsz0Ei6yr8r+lIkPvhWWjwKwFhXHpZmvxf56vbFljBm0euaKOFxjb4iywt6HPTOfpNOpibXZxSvIoRURT33RuWadfgjFM0H+ShlaWJ/Xf+Kg6c/6VYNpVQ4YEfp/aaXel+eWzuI+bm+JEWmt1riQq2bEkN8o6B3nEPp+Kulx0ZclPbVMJjUQilwBiOvbSBOQwv/h4Dww91hoWxejUE3T/I3rbxb0u1ZzZ2mypH8iLYrVsF9QheoDjpx2pmm3OozjiHsotJREW/ELFXd6bG8xgrUcnpNCn7qW8myJRGDB4Tvb4vnKnmVDUds6LFJRtc3vNL1+DfUJ/saHIRWnmCg0v2/sJgMjhcsKR9uQKMw+COxEdWPy7A4jHHueiRT6D7OPfnyYKN8nKFtfjDbzud/DANji4UHB7ItcTkLuRlpIDStg21dUzZ4fISGVW0oW+PDFa/qoh3Q9Th8RQ5n02cT1EalpNopCPhoibyF2J7Y99NKnaClUH4VyOdXHOn17Sfsh8iCxOyDJN04uh35//obGkJYnTx5IkGOkQ5L+4e8lbSOgJxODJCIn3pV/WD90/23dAScr+l05jhNpAAAAAElFTkSuQmCC) .6rem no-repeat;background-size:.75rem;font-size:.75rem;box-sizing:border-box}.egg-search-box input:focus{cursor:auto;border-color:#3eaf7c}.egg-search-box .suggestions{background:#fff;width:20rem;position:absolute;top:1.5rem;padding:.4rem 0;list-style-type:none;box-shadow:0 14px 11px -6px rgba(0,0,0,.15)}.egg-search-box .suggestions.align-right{right:0}.egg-search-box .suggestion{line-height:1.4rem;padding:.5rem 1rem;border-radius:4px;cursor:pointer}.egg-search-box .suggestion a{font-size:.88rem;white-space:normal;color:#0d261d;font-weight:400}.egg-search-box .suggestion.focused a{color:#698c7c}@media (max-width:959px){.egg-search-box input{cursor:pointer;width:0;border-color:transparent;position:relative;background-size:1rem}.egg-search-box input:focus{cursor:text;left:0;width:10rem;background-size:.75rem}}@media (-ms-high-contrast:none){.egg-search-box input{height:2rem}}@media (max-width:959px) and (min-width:719px){.egg-search-box .suggestions{left:0}}@media (max-width:719px){.egg-search-box{margin-right:0}.egg-search-box input{left:1rem}.egg-search-box .suggestions{right:0}}@media (max-width:419px){.egg-search-box .suggestions{width:calc(100vw - 4rem)}.egg-search-box input:focus{width:8rem}}.dropdown-enter,.dropdown-leave-to{height:0!important}.dropdown-wrapper{position:relative;cursor:pointer;height:100%}.dropdown-wrapper .dropdown-title{display:block}.dropdown-wrapper .dropdown-title:hover{border-color:transparent}.dropdown-wrapper .dropdown-title .arrow{vertical-align:middle;margin-top:-1px;margin-left:.4rem}.dropdown-wrapper .dropdown-item{color:inherit;line-height:1.7rem}.dropdown-wrapper .dropdown-item h4{position:relative;margin:.1rem 0 0;padding:.2rem 1.5rem .2rem 1.25rem}.dropdown-wrapper .dropdown-item h4>a{position:static}.dropdown-wrapper .dropdown-item .dropdown-subitem-wrapper{padding:0;list-style:none}.dropdown-wrapper .dropdown-item .dropdown-subitem-wrapper .dropdown-subitem{font-size:.9em}.dropdown-wrapper .dropdown-item .dropdown-subitem-wrapper a{line-height:1.7rem;border-bottom:none;font-weight:400;margin-bottom:0;padding:0 1.5rem 0 1.25rem;color:#444}.dropdown-wrapper .dropdown-item a{position:relative;display:block}.dropdown-wrapper .dropdown-item a.router-link-active,.dropdown-wrapper .dropdown-item a:hover{color:#3eaf7c}.dropdown-wrapper .dropdown-item:first-child h4{margin-top:0}@media (max-width:719px){.dropdown-wrapper.open .dropdown-title{margin-bottom:.5rem}.dropdown-wrapper .nav-dropdown{transition:height .1s ease-out;overflow:hidden}.dropdown-wrapper .nav-dropdown .dropdown-item h4{border-top:0;margin-top:0;padding-top:0}.dropdown-wrapper .nav-dropdown .dropdown-item>a,.dropdown-wrapper .nav-dropdown .dropdown-item h4{line-height:2rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem{font-size:14px;padding-left:1rem}.dropdown-wrapper .dropdown-title{position:relative}.dropdown-wrapper .dropdown-title .arrow{position:absolute;right:100%}.dropdown-wrapper .dropdown-subitem-wrapper a{font-size:.88rem!important}}@media (min-width:719px){.dropdown-wrapper.dropdown-horizontal .nav-dropdown{right:0;transform:translateX(20%);left:auto;width:42rem}.dropdown-wrapper.dropdown-horizontal .nav-dropdown .dropdown-item{float:left;list-style-type:none;height:14rem;width:25%}.dropdown-wrapper.dropdown-horizontal .nav-dropdown .dropdown-item h4{margin-bottom:.5rem}.dropdown-wrapper:hover .nav-dropdown,.nav-item:hover .nav-dropdown{display:block!important}.dropdown-wrapper .dropdown-title .arrow{border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid #ccc;border-bottom:0}.dropdown-wrapper .nav-dropdown{display:none;height:auto!important;box-sizing:border-box;max-height:calc(100vh - 2.7rem);overflow-y:auto;position:absolute;top:100%;left:0;margin:0;background-color:#fff;padding:.6rem 0;box-shadow:0 .68rem .69rem -.2rem rgba(0,0,0,.15);text-align:left;white-space:nowrap}}.nav-links{display:inline-block;height:100%}.nav-links a{line-height:1.4rem;color:inherit}.nav-links a.router-link-active,.nav-links a:hover{color:#3eaf7c}.nav-links .nav-item{position:relative;display:inline-block;height:100%;padding-left:1.5rem;line-height:2rem;vertical-align:top}.nav-links .nav-item .dropdown-title,.nav-links .nav-item>a{position:relative;display:flex;padding:0 .38rem;height:100%;align-items:center;line-height:2rem}.nav-links .nav-item:first-child{padding-left:0}.nav-links .repo-link{padding-left:1.5rem}@media (max-width:719px){.nav-links .nav-item,.nav-links .repo-link{padding-left:0}}@media (min-width:719px){.nav-links a.router-link-active,.nav-links a:hover{color:#2c3e50}.dropdown-wrapper.active:after,.nav-item>a:not(.external).router-link-active:after,.nav-item>a:not(.external):hover:after{position:absolute;display:block;content:"";width:100%;height:2px;bottom:0;left:0;background-color:#46bd87}}.navbar{padding:0 1.5rem;line-height:2.6rem;position:fixed;z-index:20;top:0;left:0;right:0;height:4rem;background-color:#fff;box-sizing:border-box;box-shadow:0 .13rem 1.5rem 0 rgba(0,0,0,.08)}.navbar .left-logo-part{position:absolute;width:17.5rem;left:0;top:0;bottom:0;margin:auto;text-align:right;display:flex;align-items:center;justify-content:right;color:#0d261d;z-index:1}.navbar .left-logo-part img{vertical-align:middle;margin-top:-.3rem}.navbar .left-logo-part>a{width:100%;height:1.88rem;line-height:1.88rem;box-sizing:border-box;padding:0 2.5rem;border-right:1px solid #ebebeb}.navbar .left-logo-part .egg-search-box{position:absolute;vertical-align:top;top:0;left:100%;bottom:0;margin:auto auto auto 2.5rem}.navbar .left-logo-part .egg-search-box .suggestions{top:4rem;padding-top:0;margin:0;text-align:left}.navbar .left-logo-part .egg-search-box input{position:absolute;top:0;bottom:0;margin:auto}.navbar a,.navbar img,.navbar span{display:inline-block}.navbar .logo{height:2.6rem;min-width:2.6rem;margin-right:.8rem;vertical-align:top}.navbar .site-name{font-size:1.3rem;font-weight:600;color:#2c3e50;position:relative}.navbar .links{padding-left:1.5rem;box-sizing:border-box;background-color:#fff;white-space:nowrap;font-size:.9rem;position:absolute;right:11.5rem;top:0;bottom:0;margin:auto;display:flex}@media (max-width:1200px){.navbar .left-logo-part{position:relative;width:auto;text-align:left;display:inline-block;height:4rem;line-height:4rem}.navbar .left-logo-part img{display:none}.navbar .left-logo-part>a{padding:0 2.5rem 0 0}.navbar .links{right:1.5rem}}@media (max-width:1000px){.navbar .left-logo-part>a{padding-right:1rem}.navbar .left-logo-part .egg-search-box{margin-left:.5rem}}@media (max-width:719px){.navbar{padding-left:4rem}.navbar .left-logo-part{width:100%}.navbar .left-logo-part>a{width:auto;display:inline-block;border-right:none}.navbar .left-logo-part .egg-search-box,.navbar .left-logo-part .egg-search-box input{right:0;left:auto}.navbar .left-logo-part .egg-search-box input:focus{right:0}.navbar .can-hide{display:none}.navbar .links{padding-left:1.5rem}}.page-edit,.page-nav{margin:0 auto;padding:3rem 4rem}@media (max-width:1000px){.page-edit,.page-nav{padding:2rem}}@media (max-width:419px){.page-edit,.page-nav{padding:1.5rem}}.page{padding-bottom:2rem;display:block;min-height:100vh}.page-edit{padding-top:1rem;padding-bottom:1rem;overflow:auto;font-size:.9em}.page-edit .edit-link{float:right}.page-edit .edit-link .outbound{display:inline-block!important}.page-edit .edit-link a{color:#4e6e8e;margin-right:.25rem}.page-edit .last-updated{display:inline-block}.page-edit .last-updated .prefix{font-weight:500;color:#4e6e8e}.page-edit .last-updated .time{font-weight:400;color:#aaa}.page-nav{padding-top:1rem;padding-bottom:0;font-size:1rem;line-height:1.5rem}.page-nav .inner{min-height:2rem;margin-top:0;border-top:1px solid #eaecef;padding-top:3rem;overflow:auto}.page-nav .inner a{color:#698c7f}.page-nav .inner a:hover span{color:#00b362}.page-nav .inner span{font-size:1rem;color:#0d261d}.page-nav .prev{float:left}.page-nav .next{float:right}@media (max-width:719px){.page-edit .edit-link{margin-bottom:.5rem}.page-edit .last-updated{font-size:.8em;float:none;text-align:left}}.global-foot{position:relative}.global-foot .friend-links{background:#06080a;min-height:28.38rem;color:hsla(0,0%,100%,.65);border-bottom:1px solid #2b2b2b;box-sizing:border-box}.global-foot .friend-links *{color:hsla(0,0%,100%,.65)}.global-foot .friend-links .friend-list-wrapper{max-width:80rem;display:flex;margin:auto;padding-top:5.38rem}.global-foot .friend-links .friend-list{flex:1;text-align:center}.global-foot .friend-links .friend-list-align{display:inline-block;text-align:left}.global-foot .friend-links .friend-list-item{display:block;font-size:.88rem;line-height:1.38rem;margin:.75rem 0}.global-foot .friend-links .friend-list-item:hover{color:#fff}.global-foot .friend-links .friend-list-title{font-size:1rem;color:#fff;line-height:1.5rem;padding-bottom:.75rem;font-weight:700}.global-foot .friend-links .friend-qrcode{width:11.63rem;height:11.31rem;background:#fff;margin:.75rem auto;border-radius:.25rem;overflow:hidden}.global-foot .friend-links .friend-qrcode img{width:100%;height:100%}.global-foot .copyright{display:flex;min-height:4rem;align-items:center;justify-content:center;background:#06080a;font-size:1rem;color:hsla(0,0%,100%,.65);padding:1rem 0;box-sizing:border-box}.global-foot .copyright span{margin:0 .5rem}.global-foot .copyright *{color:hsla(0,0%,100%,.65)}@media (max-width:719px){.global-foot .friend-links .friend-list-wrapper{display:block;padding-top:2rem}.global-foot .friend-links .friend-list-wrapper .friend-list{display:block;margin-bottom:2rem}.global-foot .friend-links .friend-list-wrapper .friend-list-align{width:21.88rem;text-align:center}}.sidebar-item{margin-right:-1px}.sidebar-item .sidebar-item-heading{position:relative;font-size:1rem;color:#0d261d;line-height:2.5rem;box-sizing:border-box;padding-left:3rem;padding-right:1rem;display:block;cursor:pointer}.sidebar-item .sidebar-group-items a.active,.sidebar-item .sidebar-item-heading.active{border-right:2px solid #00b362}.sidebar-item .sidebar-group-items a.active,.sidebar-item .sidebar-group-items a:hover,.sidebar-item .sidebar-item-heading.active,.sidebar-item .sidebar-item-heading:hover{background:rgba(60,241,173,.15)}.sidebar-item .arrow{position:absolute;left:2rem;top:0;bottom:0;margin:auto}.sidebar-item .sidebar-group-items{transition:height .1s ease-out;overflow:hidden;line-height:2rem;font-size:.88rem}.sidebar-item .sidebar-group-items a{color:#315947;padding-left:4rem;padding-right:1rem}@media (max-width:719px){.sidebar-item{margin-left:-1.5rem}}.sidebar{font-size:15px;background-color:#fff;width:17.5rem;position:fixed;z-index:10;margin:0;height:100vh;left:0;top:0;box-sizing:border-box;overflow-y:auto;padding-top:7rem}.sidebar .sidebar-wrap{min-height:100%;border-right:1px solid #eaecef;padding-bottom:3rem;box-sizing:border-box}.sidebar ul{padding:0;margin:0;list-style-type:none}.sidebar a{display:block}.sidebar .nav-links{display:none;border-bottom:1px solid #eaecef;padding:.5rem 0 .75rem}.sidebar .nav-links a{font-weight:600;font-size:1rem}.sidebar .nav-links .nav-item,.sidebar .nav-links .repo-link{display:block;line-height:1.25rem;font-size:1.1em;padding:.2rem 0 .2rem 1.18rem}@media (max-width:719px){.sidebar .nav-links{display:block}}code[class*=language-],pre[class*=language-]{color:#ccc;background:none;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;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}.content code{color:#476582;padding:.25rem .5rem;margin:0;font-size:.85em;background-color:rgba(27,31,35,.05);border-radius:3px}.content code .token.deleted{color:#ec5975}.content code .token.inserted{color:#3eaf7c}.content pre,.content pre[class*=language-]{line-height:1.4;padding:1.25rem 1.5rem;margin:.85rem 0;background-color:#282c34;border-radius:6px;overflow:auto}.content pre[class*=language-] code,.content pre code{color:#fff;padding:0;background-color:transparent;border-radius:0}div[class*=language-]{position:relative;background-color:#282c34;border-radius:6px}div[class*=language-] .highlight-lines{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding-top:1.3rem;position:absolute;top:0;left:0;width:100%;line-height:1.4}div[class*=language-] .highlight-lines .highlighted{background-color:rgba(0,0,0,.66)}div[class*=language-] pre,div[class*=language-] pre[class*=language-]{background:transparent;position:relative;z-index:1}div[class*=language-]:before{position:absolute;z-index:3;top:.8em;right:1em;font-size:.75rem;color:hsla(0,0%,100%,.4)}div[class*=language-]:not(.line-numbers-mode) .line-numbers-wrapper{display:none}div[class*=language-].line-numbers-mode .highlight-lines .highlighted{position:relative}div[class*=language-].line-numbers-mode .highlight-lines .highlighted:before{content:" ";position:absolute;z-index:3;left:0;top:0;display:block;width:3.5rem;height:100%;background-color:rgba(0,0,0,.66)}div[class*=language-].line-numbers-mode pre{padding-left:4.5rem;vertical-align:middle}div[class*=language-].line-numbers-mode .line-numbers-wrapper{position:absolute;top:0;width:3.5rem;text-align:center;color:hsla(0,0%,100%,.3);padding:1.25rem 0;line-height:1.4}div[class*=language-].line-numbers-mode .line-numbers-wrapper .line-number,div[class*=language-].line-numbers-mode .line-numbers-wrapper br{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div[class*=language-].line-numbers-mode .line-numbers-wrapper .line-number{position:relative;z-index:4;font-size:.85em}div[class*=language-].line-numbers-mode:after{content:"";position:absolute;z-index:2;top:0;left:0;width:3.5rem;height:100%;border-radius:6px 0 0 6px;border-right:1px solid rgba(0,0,0,.66);background-color:#282c34}.language-log pre code{word-break:break-all;white-space:normal}div[class~=language-js]:before{content:"js"}div[class~=language-ts]:before{content:"ts"}div[class~=language-html]:before{content:"html"}div[class~=language-md]:before{content:"md"}div[class~=language-vue]:before{content:"vue"}div[class~=language-css]:before{content:"css"}div[class~=language-sass]:before{content:"sass"}div[class~=language-scss]:before{content:"scss"}div[class~=language-less]:before{content:"less"}div[class~=language-stylus]:before{content:"stylus"}div[class~=language-go]:before{content:"go"}div[class~=language-java]:before{content:"java"}div[class~=language-c]:before{content:"c"}div[class~=language-sh]:before{content:"sh"}div[class~=language-yaml]:before{content:"yaml"}div[class~=language-py]:before{content:"py"}div[class~=language-docker]:before{content:"docker"}div[class~=language-dockerfile]:before{content:"dockerfile"}div[class~=language-makefile]:before{content:"makefile"}div[class~=language-javascript]:before{content:"js"}div[class~=language-typescript]:before{content:"ts"}div[class~=language-markup]:before{content:"html"}div[class~=language-markdown]:before{content:"md"}div[class~=language-json]:before{content:"json"}div[class~=language-ruby]:before{content:"rb"}div[class~=language-python]:before{content:"py"}div[class~=language-bash]:before{content:"sh"}div[class~=language-php]:before{content:"php"}.custom-block .custom-block-title{font-weight:600;margin-bottom:-.4rem}.custom-block.danger,.custom-block.tip,.custom-block.warning{padding:.1rem 1.5rem;border-left-width:.5rem;border-left-style:solid;margin:1rem 0}.custom-block.tip{background-color:#f3f5f7;border-color:#42b983}.custom-block.warning{background-color:rgba(255,229,100,.3);border-color:#e7c000;color:#6b5900}.custom-block.warning .custom-block-title{color:#b29400}.custom-block.warning a{color:#2c3e50}.custom-block.danger{background-color:#ffe6e6;border-color:#c00;color:#4d0000}.custom-block.danger .custom-block-title{color:#900}.custom-block.danger a{color:#2c3e50}pre.vue-container{border-left:.5rem solid;border-color:#42b983;border-radius:0}pre.vue-container>code{font-size:14px!important}pre.vue-container>code>p{margin:-5px 0 -20px}pre.vue-container>code code{background-color:#42b983!important;padding:3px 5px;border-radius:3px;color:#000}pre.vue-container>code em{color:grey;font-weight:light}.arrow{display:inline-block;width:0;height:0}.arrow.up{border-bottom:6px solid #698c7f}.arrow.down,.arrow.up{border-left:4px solid transparent;border-right:4px solid transparent}.arrow.down{border-top:6px solid #698c7f}.arrow.right{border-left:6px solid #698c7f}.arrow.left,.arrow.right{border-top:4px solid transparent;border-bottom:4px solid transparent}.arrow.left{border-right:6px solid #698c7f}.content:not(.custom){margin:0 auto;padding:3rem 4rem}@media (max-width:1000px){.content:not(.custom){padding:2rem}}@media (max-width:419px){.content:not(.custom){padding:1.5rem}}body,html{padding:0;margin:0;background-color:#fff;width:100%;overflow-x:hidden}*{-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:1rem;color:#2c3e50}.outbound{display:none!important}.theme-container{position:relative}.page{padding-left:17.5rem}.sidebar-mask{position:fixed;z-index:9;top:0;left:0;width:100vw;height:100vh;display:none}.content img{max-width:100%;display:block;box-shadow:0 0 1px rgba(0,0,0,.5);border-radius:2px}.content .md-img-wrapper:after{content:"";display:block;clear:both}.content .align-left{margin-left:0}.content .align-right{float:right}.content .align-center{margin-left:auto;margin-right:auto}.content:not(.custom)>:first-child{margin-top:4rem}.content:not(.custom) a:hover{text-decoration:underline}.content:not(.custom) p.demo{padding:1rem 1.5rem;border:1px solid #ddd;border-radius:4px}.content.custom{padding:0;margin:0}a{font-weight:500;text-decoration:none}a,p a code{color:#04ae62}p a code{font-weight:400}kbd{background:#eee;border:.15rem solid #ddd;border-bottom:.25rem solid #ddd;border-radius:.15rem;padding:0 .15em}blockquote{font-size:.9rem;color:#999;border-left:.5rem solid #dfe2e5;margin:.5rem 0;padding:.25rem 0 .25rem 1rem}blockquote>p{margin:0}ol,ul{padding-left:1.2em}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25}.content:not(.custom)>h1,.content:not(.custom)>h2,.content:not(.custom)>h3,.content:not(.custom)>h4,.content:not(.custom)>h5,.content:not(.custom)>h6{margin-top:-3.5rem;padding-top:5rem;margin-bottom:0}.content:not(.custom)>h1:first-child,.content:not(.custom)>h2:first-child,.content:not(.custom)>h3:first-child,.content:not(.custom)>h4:first-child,.content:not(.custom)>h5:first-child,.content:not(.custom)>h6:first-child{margin-top:-1.5rem;margin-bottom:1rem}.content:not(.custom)>h1:first-child+.custom-block,.content:not(.custom)>h1:first-child+p,.content:not(.custom)>h1:first-child+pre,.content:not(.custom)>h2:first-child+.custom-block,.content:not(.custom)>h2:first-child+p,.content:not(.custom)>h2:first-child+pre,.content:not(.custom)>h3:first-child+.custom-block,.content:not(.custom)>h3:first-child+p,.content:not(.custom)>h3:first-child+pre,.content:not(.custom)>h4:first-child+.custom-block,.content:not(.custom)>h4:first-child+p,.content:not(.custom)>h4:first-child+pre,.content:not(.custom)>h5:first-child+.custom-block,.content:not(.custom)>h5:first-child+p,.content:not(.custom)>h5:first-child+pre,.content:not(.custom)>h6:first-child+.custom-block,.content:not(.custom)>h6:first-child+p,.content:not(.custom)>h6:first-child+pre{margin-top:2rem}h1:hover .header-anchor,h2:hover .header-anchor,h3:hover .header-anchor,h4:hover .header-anchor,h5:hover .header-anchor,h6:hover .header-anchor{opacity:1}h1{font-size:2.2rem}h2{font-size:1.65rem;padding-bottom:.3rem;border-bottom:1px solid #eaecef}h3{font-size:1.35rem}a.header-anchor{font-size:.85em;float:left;margin-left:-.87em;padding-right:.23em;margin-top:.125em;opacity:0}a.header-anchor:hover{text-decoration:none}.line-number,code,kbd{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}ol,p,ul{line-height:1.7}hr{border:0;border-top:1px solid #eaecef}table{border-collapse:collapse;margin:1rem 0;display:block;overflow-x:auto}tr{border-top:1px solid #dfe2e5}tr:nth-child(2n){background-color:#f6f8fa}td,th{border:1px solid #dfe2e5;padding:.6em 1em}.theme-container.sidebar-open .sidebar-mask{display:block}.theme-container.no-navbar .content:not(.custom)>h1,.theme-container.no-navbar h2,.theme-container.no-navbar h3,.theme-container.no-navbar h4,.theme-container.no-navbar h5,.theme-container.no-navbar h6{margin-top:1.5rem;padding-top:0}.theme-container.no-navbar .sidebar{top:0}@media (min-width:720px){.theme-container.no-sidebar .sidebar{display:none}.theme-container.no-sidebar .content{padding-left:0}}@media (min-width:1000px){.page{padding-right:11.5rem}}@media (max-width:1000px){.sidebar{font-size:15px;width:14.35rem}.page{padding-left:14.35rem}.no-sidebar .page{padding-left:2rem}}@media (max-width:719px){.sidebar{top:0;padding-top:4rem;transform:translateX(-100%);transition:transform .2s ease}.page{padding-left:0!important}.theme-container.sidebar-open .sidebar{transform:translateX(0)}.theme-container.no-navbar .sidebar{padding-top:0}}@media (max-width:419px){h1{font-size:1.9rem}.content div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}.module-layout .content{text-align:center;max-width:1000px}.module-layout h1{position:relative;font-size:1.5rem;height:1.75rem;line-height:1.75rem}.module-layout h1>a{display:none}.module-layout h1:after{content:"";position:absolute;width:3.5rem;height:2px;background-color:#16d98e;bottom:-1rem;left:0;right:0;margin:auto}.module-layout .ecosystem-list{padding-top:.38rem}.module-layout .ecosystem-item{position:relative;width:16.44rem;height:11rem;padding:1rem 1.5rem;box-sizing:border-box;box-shadow:0 1.06rem 2.38rem -1.25rem #ced9d5;display:inline-block;overflow:hidden;text-align:left;margin:1.5rem;transition:box-shadow .3s}.module-layout .ecosystem-item:hover{box-shadow:0 1.06rem 2.38rem -.5rem #ced9d5}.module-layout .ecosystem-item>h2{font-size:1rem;border-bottom:none;height:1.5rem;line-height:1.5rem;margin:0}.module-layout .ecosystem-item>ul{height:6rem;overflow:hidden}.module-layout .ecosystem-item ul{padding-left:1.25rem;color:#698c7f;margin:0}.module-layout .ecosystem-item li{font-size:.75rem;text-align:justify;height:1.25rem;line-height:1.25rem;margin:.25rem 0}.module-layout .ecosystem-item li a{color:#698c7f}.module-layout .ecosystem-item li a:hover{color:#16d98e}.badge[data-v-e8bb0bea]{display:inline-block;font-size:14px;height:18px;line-height:18px;border-radius:3px;padding:0 6px;color:#fff;margin-right:5px;background-color:#42b983}.badge.middle[data-v-e8bb0bea]{vertical-align:middle}.badge.top[data-v-e8bb0bea]{vertical-align:top}.badge.green[data-v-e8bb0bea],.badge.tip[data-v-e8bb0bea]{background-color:#42b983}.badge.error[data-v-e8bb0bea]{background-color:#da5961}.badge.warn[data-v-e8bb0bea],.badge.warning[data-v-e8bb0bea],.badge.yellow[data-v-e8bb0bea]{background-color:#e7c000} \ No newline at end of file diff --git a/assets/css/0.styles.5203916d.css b/assets/css/0.styles.5203916d.css deleted file mode 100644 index 321ab2a..0000000 --- a/assets/css/0.styles.5203916d.css +++ /dev/null @@ -1 +0,0 @@ -#nprogress{pointer-events:none}#nprogress .bar{background:#3eaf7c;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #3eaf7c,0 0 5px #3eaf7c;opacity:1;transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border-color:#3eaf7c transparent transparent #3eaf7c;border-style:solid;border-width:2px;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes nprogress-spinner{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.icon.outbound{color:#aaa;display:inline-block;vertical-align:middle;position:relative;top:-1px}.home{padding:3.6rem 2rem 0;max-width:960px;margin:0 auto;display:block}.home .hero{text-align:center}.home .hero img{max-width:100%;max-height:280px;display:block;margin:3rem auto 1.5rem}.home .hero h1{font-size:3rem}.home .hero .action,.home .hero .description,.home .hero h1{margin:1.8rem auto}.home .hero .description{max-width:35rem;font-size:1.6rem;line-height:1.3;color:#6a8bad}.home .hero .action-button{display:inline-block;font-size:1.2rem;color:#fff;background-color:#3eaf7c;padding:.8rem 1.6rem;border-radius:4px;transition:background-color .1s ease;box-sizing:border-box;border-bottom:1px solid #389d70}.home .hero .action-button:hover{background-color:#4abf8a}.home .features{border-top:1px solid #eaecef;padding:1.2rem 0;margin-top:2.5rem;display:flex;flex-wrap:wrap;align-items:flex-start;align-content:stretch;justify-content:space-between}.home .feature{flex-grow:1;flex-basis:30%;max-width:30%}.home .feature h2{font-size:1.4rem;font-weight:500;border-bottom:none;padding-bottom:0;color:#3a5169}.home .feature p{color:#4e6e8e}.home .footer{padding:2.5rem;border-top:1px solid #eaecef;text-align:center;color:#4e6e8e}@media (max-width:719px){.home .features{flex-direction:column}.home .feature{max-width:100%;padding:0 2.5rem}}@media (max-width:419px){.home{padding-left:1.5rem;padding-right:1.5rem}.home .hero img{max-height:210px;margin:2rem auto 1.2rem}.home .hero h1{font-size:2rem}.home .hero .action,.home .hero .description,.home .hero h1{margin:1.2rem auto}.home .hero .description{font-size:1.2rem}.home .hero .action-button{font-size:1rem;padding:.6rem 1.2rem}.home .feature h2{font-size:1.25rem}}.search-box{display:inline-block;position:relative;margin-right:1rem}.search-box input{cursor:text;width:10rem;height:2rem;color:#4e6e8e;display:inline-block;border:1px solid #cfd4db;border-radius:2rem;font-size:.9rem;line-height:2rem;padding:0 .5rem 0 2rem;outline:none;transition:all .2s ease;background:#fff url(http://github.com/assets/img/search.83621669.svg) .6rem .5rem no-repeat;background-size:1rem}.search-box input:focus{cursor:auto;border-color:#3eaf7c}.search-box .suggestions{background:#fff;width:20rem;position:absolute;top:1.5rem;border:1px solid #cfd4db;border-radius:6px;padding:.4rem;list-style-type:none}.search-box .suggestions.align-right{right:0}.search-box .suggestion{line-height:1.4;padding:.4rem .6rem;border-radius:4px;cursor:pointer}.search-box .suggestion a{white-space:normal;color:#5d82a6}.search-box .suggestion a .page-title{font-weight:600}.search-box .suggestion a .header{font-size:.9em;margin-left:.25em}.search-box .suggestion.focused{background-color:#f3f4f5}.search-box .suggestion.focused a{color:#3eaf7c}@media (max-width:959px){.search-box input{cursor:pointer;width:0;border-color:transparent;position:relative}.search-box input:focus{cursor:text;left:0;width:10rem}}@media (-ms-high-contrast:none){.search-box input{height:2rem}}@media (max-width:959px) and (min-width:719px){.search-box .suggestions{left:0}}@media (max-width:719px){.search-box{margin-right:0}.search-box input{left:1rem}.search-box .suggestions{right:0}}@media (max-width:419px){.search-box .suggestions{width:calc(100vw - 4rem)}.search-box input:focus{width:8rem}}.sidebar-button{cursor:pointer;display:none;width:1.25rem;height:1.25rem;position:absolute;padding:.6rem;top:.6rem;left:1rem}.sidebar-button .icon{display:block;width:1.25rem;height:1.25rem}@media (max-width:719px){.sidebar-button{display:block}}.dropdown-enter,.dropdown-leave-to{height:0!important}.dropdown-wrapper{cursor:pointer}.dropdown-wrapper .dropdown-title{display:block}.dropdown-wrapper .dropdown-title:hover{border-color:transparent}.dropdown-wrapper .dropdown-title .arrow{vertical-align:middle;margin-top:-1px;margin-left:.4rem}.dropdown-wrapper .nav-dropdown .dropdown-item{color:inherit;line-height:1.7rem}.dropdown-wrapper .nav-dropdown .dropdown-item h4{margin:.45rem 0 0;border-top:1px solid #eee;padding:.45rem 1.5rem 0 1.25rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper{padding:0;list-style:none}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem-wrapper .dropdown-subitem{font-size:.9em}.dropdown-wrapper .nav-dropdown .dropdown-item a{display:block;line-height:1.7rem;position:relative;border-bottom:none;font-weight:400;margin-bottom:0;padding:0 1.5rem 0 1.25rem}.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active,.dropdown-wrapper .nav-dropdown .dropdown-item a:hover{color:#3eaf7c}.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active:after{content:"";width:0;height:0;border-left:5px solid #3eaf7c;border-top:3px solid transparent;border-bottom:3px solid transparent;position:absolute;top:calc(50% - 2px);left:9px}.dropdown-wrapper .nav-dropdown .dropdown-item:first-child h4{margin-top:0;padding-top:0;border-top:0}@media (max-width:719px){.dropdown-wrapper.open .dropdown-title{margin-bottom:.5rem}.dropdown-wrapper .nav-dropdown{transition:height .1s ease-out;overflow:hidden}.dropdown-wrapper .nav-dropdown .dropdown-item h4{border-top:0;margin-top:0;padding-top:0}.dropdown-wrapper .nav-dropdown .dropdown-item>a,.dropdown-wrapper .nav-dropdown .dropdown-item h4{font-size:15px;line-height:2rem}.dropdown-wrapper .nav-dropdown .dropdown-item .dropdown-subitem{font-size:14px;padding-left:1rem}}@media (min-width:719px){.dropdown-wrapper{height:1.8rem}.dropdown-wrapper:hover .nav-dropdown{display:block!important}.dropdown-wrapper .dropdown-title .arrow{border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid #ccc;border-bottom:0}.dropdown-wrapper .nav-dropdown{display:none;height:auto!important;box-sizing:border-box;max-height:calc(100vh - 2.7rem);overflow-y:auto;position:absolute;top:100%;right:0;background-color:#fff;padding:.6rem 0;border:1px solid;border-color:#ddd #ddd #ccc;text-align:left;border-radius:.25rem;white-space:nowrap;margin:0}}.nav-links{display:inline-block}.nav-links a{line-height:1.4rem;color:inherit}.nav-links a.router-link-active,.nav-links a:hover{color:#3eaf7c}.nav-links .nav-item{position:relative;display:inline-block;margin-left:1.5rem;line-height:2rem}.nav-links .nav-item:first-child{margin-left:0}.nav-links .repo-link{margin-left:1.5rem}@media (max-width:719px){.nav-links .nav-item,.nav-links .repo-link{margin-left:0}}@media (min-width:719px){.nav-links a.router-link-active,.nav-links a:hover{color:#2c3e50}.nav-item>a:not(.external).router-link-active,.nav-item>a:not(.external):hover{margin-bottom:-2px;border-bottom:2px solid #46bd87}}.navbar{padding:.7rem 1.5rem;line-height:2.2rem}.navbar a,.navbar img,.navbar span{display:inline-block}.navbar .logo{height:2.2rem;min-width:2.2rem;margin-right:.8rem;vertical-align:top}.navbar .site-name{font-size:1.3rem;font-weight:600;color:#2c3e50;position:relative}.navbar .links{padding-left:1.5rem;box-sizing:border-box;background-color:#fff;white-space:nowrap;font-size:.9rem;position:absolute;right:1.5rem;top:.7rem;display:flex}.navbar .links .search-box{flex:0 0 auto;vertical-align:top}@media (max-width:719px){.navbar{padding-left:4rem}.navbar .can-hide{display:none}.navbar .links{padding-left:1.5rem}}.page-edit,.page-nav{max-width:740px;margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.page-edit,.page-nav{padding:2rem}}@media (max-width:419px){.page-edit,.page-nav{padding:1.5rem}}.page{padding-bottom:2rem;display:block}.page-edit{padding-top:1rem;padding-bottom:1rem;overflow:auto}.page-edit .edit-link{display:inline-block}.page-edit .edit-link a{color:#4e6e8e;margin-right:.25rem}.page-edit .last-updated{float:right;font-size:.9em}.page-edit .last-updated .prefix{font-weight:500;color:#4e6e8e}.page-edit .last-updated .time{font-weight:400;color:#aaa}.page-nav{padding-top:1rem;padding-bottom:0}.page-nav .inner{min-height:2rem;margin-top:0;border-top:1px solid #eaecef;padding-top:1rem;overflow:auto}.page-nav .next{float:right}@media (max-width:719px){.page-edit .edit-link{margin-bottom:.5rem}.page-edit .last-updated{font-size:.8em;float:none;text-align:left}}.sidebar-group .sidebar-group{padding-left:.5em}.sidebar-group:not(.collapsable) .sidebar-heading:not(.clickable){cursor:auto;color:inherit}.sidebar-group.is-sub-group{padding-left:0}.sidebar-group.is-sub-group>.sidebar-heading{font-size:.95em;line-height:1.4;font-weight:400;padding-left:2rem}.sidebar-group.is-sub-group>.sidebar-heading:not(.clickable){opacity:.5}.sidebar-group.is-sub-group>.sidebar-group-items{padding-left:1rem}.sidebar-group.is-sub-group>.sidebar-group-items>li>.sidebar-link{font-size:.95em;border-left:none}.sidebar-group.depth-2>.sidebar-heading{border-left:none}.sidebar-heading{color:#2c3e50;transition:color .15s ease;cursor:pointer;font-size:1.1em;font-weight:700;padding:.35rem 1.5rem .35rem 1.25rem;width:100%;box-sizing:border-box;margin:0;border-left:.25rem solid transparent}.sidebar-heading.open,.sidebar-heading:hover{color:inherit}.sidebar-heading .arrow{position:relative;top:-.12em;left:.5em}.sidebar-heading.clickable.active{font-weight:600;color:#3eaf7c;border-left-color:#3eaf7c}.sidebar-heading.clickable:hover{color:#3eaf7c}.sidebar-group-items{transition:height .1s ease-out;font-size:.95em;overflow:hidden}.sidebar .sidebar-sub-headers{padding-left:1rem;font-size:.95em}a.sidebar-link{font-size:1em;font-weight:400;display:inline-block;color:#2c3e50;border-left:.25rem solid transparent;padding:.35rem 1rem .35rem 1.25rem;line-height:1.4;width:100%;box-sizing:border-box}a.sidebar-link:hover{color:#3eaf7c}a.sidebar-link.active{font-weight:600;color:#3eaf7c;border-left-color:#3eaf7c}.sidebar-group a.sidebar-link{padding-left:2rem}.sidebar-sub-headers a.sidebar-link{padding-top:.25rem;padding-bottom:.25rem;border-left:none}.sidebar-sub-headers a.sidebar-link.active{font-weight:500}.sidebar ul{padding:0;margin:0;list-style-type:none}.sidebar a{display:inline-block}.sidebar .nav-links{display:none;border-bottom:1px solid #eaecef;padding:.5rem 0 .75rem}.sidebar .nav-links a{font-weight:600}.sidebar .nav-links .nav-item,.sidebar .nav-links .repo-link{display:block;line-height:1.25rem;font-size:1.1em;padding:.5rem 0 .5rem 1.5rem}.sidebar>.sidebar-links{padding:1.5rem 0}.sidebar>.sidebar-links>li>a.sidebar-link{font-size:1.1em;line-height:1.7;font-weight:700}.sidebar>.sidebar-links>li:not(:first-child){margin-top:.75rem}@media (max-width:719px){.sidebar .nav-links{display:block}.sidebar .nav-links .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active:after{top:calc(1rem - 2px)}.sidebar>.sidebar-links{padding:1rem 0}}code[class*=language-],pre[class*=language-]{color:#ccc;background:none;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;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}.theme-default-content code{color:#476582;padding:.25rem .5rem;margin:0;font-size:.85em;background-color:rgba(27,31,35,.05);border-radius:3px}.theme-default-content code .token.deleted{color:#ec5975}.theme-default-content code .token.inserted{color:#3eaf7c}.theme-default-content pre,.theme-default-content pre[class*=language-]{line-height:1.4;padding:1.25rem 1.5rem;margin:.85rem 0;background-color:#282c34;border-radius:6px;overflow:auto}.theme-default-content pre[class*=language-] code,.theme-default-content pre code{color:#fff;padding:0;background-color:transparent;border-radius:0}div[class*=language-]{position:relative;background-color:#282c34;border-radius:6px}div[class*=language-] .highlight-lines{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding-top:1.3rem;position:absolute;top:0;left:0;width:100%;line-height:1.4}div[class*=language-] .highlight-lines .highlighted{background-color:rgba(0,0,0,.66)}div[class*=language-] pre,div[class*=language-] pre[class*=language-]{background:transparent;position:relative;z-index:1}div[class*=language-]:before{position:absolute;z-index:3;top:.8em;right:1em;font-size:.75rem;color:hsla(0,0%,100%,.4)}div[class*=language-]:not(.line-numbers-mode) .line-numbers-wrapper{display:none}div[class*=language-].line-numbers-mode .highlight-lines .highlighted{position:relative}div[class*=language-].line-numbers-mode .highlight-lines .highlighted:before{content:" ";position:absolute;z-index:3;left:0;top:0;display:block;width:3.5rem;height:100%;background-color:rgba(0,0,0,.66)}div[class*=language-].line-numbers-mode pre{padding-left:4.5rem;vertical-align:middle}div[class*=language-].line-numbers-mode .line-numbers-wrapper{position:absolute;top:0;width:3.5rem;text-align:center;color:hsla(0,0%,100%,.3);padding:1.25rem 0;line-height:1.4}div[class*=language-].line-numbers-mode .line-numbers-wrapper .line-number,div[class*=language-].line-numbers-mode .line-numbers-wrapper br{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div[class*=language-].line-numbers-mode .line-numbers-wrapper .line-number{position:relative;z-index:4;font-size:.85em}div[class*=language-].line-numbers-mode:after{content:"";position:absolute;z-index:2;top:0;left:0;width:3.5rem;height:100%;border-radius:6px 0 0 6px;border-right:1px solid rgba(0,0,0,.66);background-color:#282c34}div[class~=language-js]:before{content:"js"}div[class~=language-ts]:before{content:"ts"}div[class~=language-html]:before{content:"html"}div[class~=language-md]:before{content:"md"}div[class~=language-vue]:before{content:"vue"}div[class~=language-css]:before{content:"css"}div[class~=language-sass]:before{content:"sass"}div[class~=language-scss]:before{content:"scss"}div[class~=language-less]:before{content:"less"}div[class~=language-stylus]:before{content:"stylus"}div[class~=language-go]:before{content:"go"}div[class~=language-java]:before{content:"java"}div[class~=language-c]:before{content:"c"}div[class~=language-sh]:before{content:"sh"}div[class~=language-yaml]:before{content:"yaml"}div[class~=language-py]:before{content:"py"}div[class~=language-docker]:before{content:"docker"}div[class~=language-dockerfile]:before{content:"dockerfile"}div[class~=language-makefile]:before{content:"makefile"}div[class~=language-javascript]:before{content:"js"}div[class~=language-typescript]:before{content:"ts"}div[class~=language-markup]:before{content:"html"}div[class~=language-markdown]:before{content:"md"}div[class~=language-json]:before{content:"json"}div[class~=language-ruby]:before{content:"rb"}div[class~=language-python]:before{content:"py"}div[class~=language-bash]:before{content:"sh"}div[class~=language-php]:before{content:"php"}.custom-block .custom-block-title{font-weight:600;margin-bottom:-.4rem}.custom-block.danger,.custom-block.tip,.custom-block.warning{padding:.1rem 1.5rem;border-left-width:.5rem;border-left-style:solid;margin:1rem 0}.custom-block.tip{background-color:#f3f5f7;border-color:#42b983}.custom-block.warning{background-color:rgba(255,229,100,.3);border-color:#e7c000;color:#6b5900}.custom-block.warning .custom-block-title{color:#b29400}.custom-block.warning a{color:#2c3e50}.custom-block.danger{background-color:#ffe6e6;border-color:#c00;color:#4d0000}.custom-block.danger .custom-block-title{color:#900}.custom-block.danger a{color:#2c3e50}.arrow{display:inline-block;width:0;height:0}.arrow.up{border-bottom:6px solid #ccc}.arrow.down,.arrow.up{border-left:4px solid transparent;border-right:4px solid transparent}.arrow.down{border-top:6px solid #ccc}.arrow.right{border-left:6px solid #ccc}.arrow.left,.arrow.right{border-top:4px solid transparent;border-bottom:4px solid transparent}.arrow.left{border-right:6px solid #ccc}.theme-default-content:not(.custom){max-width:740px;margin:0 auto;padding:2rem 2.5rem}@media (max-width:959px){.theme-default-content:not(.custom){padding:2rem}}@media (max-width:419px){.theme-default-content:not(.custom){padding:1.5rem}}.table-of-contents .badge{vertical-align:middle}body,html{padding:0;margin:0;background-color:#fff}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:16px;color:#2c3e50}.page{padding-left:20rem}.navbar{z-index:20;right:0;height:3.6rem;background-color:#fff;box-sizing:border-box;border-bottom:1px solid #eaecef}.navbar,.sidebar-mask{position:fixed;top:0;left:0}.sidebar-mask{z-index:9;width:100vw;height:100vh;display:none}.sidebar{font-size:16px;background-color:#fff;width:20rem;position:fixed;z-index:10;margin:0;top:3.6rem;left:0;bottom:0;box-sizing:border-box;border-right:1px solid #eaecef;overflow-y:auto}.theme-default-content:not(.custom)>:first-child{margin-top:3.6rem}.theme-default-content:not(.custom) a:hover{text-decoration:underline}.theme-default-content:not(.custom) p.demo{padding:1rem 1.5rem;border:1px solid #ddd;border-radius:4px}.theme-default-content:not(.custom) img{max-width:100%}.theme-default-content.custom{padding:0;margin:0}.theme-default-content.custom img{max-width:100%}a{font-weight:500;text-decoration:none}a,p a code{color:#3eaf7c}p a code{font-weight:400}kbd{background:#eee;border:.15rem solid #ddd;border-bottom:.25rem solid #ddd;border-radius:.15rem;padding:0 .15em}blockquote{font-size:1rem;color:#999;border-left:.2rem solid #dfe2e5;margin:1rem 0;padding:.25rem 0 .25rem 1rem}blockquote>p{margin:0}ol,ul{padding-left:1.2em}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25}.theme-default-content:not(.custom)>h1,.theme-default-content:not(.custom)>h2,.theme-default-content:not(.custom)>h3,.theme-default-content:not(.custom)>h4,.theme-default-content:not(.custom)>h5,.theme-default-content:not(.custom)>h6{margin-top:-3.1rem;padding-top:4.6rem;margin-bottom:0}.theme-default-content:not(.custom)>h1:first-child,.theme-default-content:not(.custom)>h2:first-child,.theme-default-content:not(.custom)>h3:first-child,.theme-default-content:not(.custom)>h4:first-child,.theme-default-content:not(.custom)>h5:first-child,.theme-default-content:not(.custom)>h6:first-child{margin-top:-1.5rem;margin-bottom:1rem}.theme-default-content:not(.custom)>h1:first-child+.custom-block,.theme-default-content:not(.custom)>h1:first-child+p,.theme-default-content:not(.custom)>h1:first-child+pre,.theme-default-content:not(.custom)>h2:first-child+.custom-block,.theme-default-content:not(.custom)>h2:first-child+p,.theme-default-content:not(.custom)>h2:first-child+pre,.theme-default-content:not(.custom)>h3:first-child+.custom-block,.theme-default-content:not(.custom)>h3:first-child+p,.theme-default-content:not(.custom)>h3:first-child+pre,.theme-default-content:not(.custom)>h4:first-child+.custom-block,.theme-default-content:not(.custom)>h4:first-child+p,.theme-default-content:not(.custom)>h4:first-child+pre,.theme-default-content:not(.custom)>h5:first-child+.custom-block,.theme-default-content:not(.custom)>h5:first-child+p,.theme-default-content:not(.custom)>h5:first-child+pre,.theme-default-content:not(.custom)>h6:first-child+.custom-block,.theme-default-content:not(.custom)>h6:first-child+p,.theme-default-content:not(.custom)>h6:first-child+pre{margin-top:2rem}h1:hover .header-anchor,h2:hover .header-anchor,h3:hover .header-anchor,h4:hover .header-anchor,h5:hover .header-anchor,h6:hover .header-anchor{opacity:1}h1{font-size:2.2rem}h2{font-size:1.65rem;padding-bottom:.3rem;border-bottom:1px solid #eaecef}h3{font-size:1.35rem}a.header-anchor{font-size:.85em;float:left;margin-left:-.87em;padding-right:.23em;margin-top:.125em;opacity:0}a.header-anchor:hover{text-decoration:none}.line-number,code,kbd{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}ol,p,ul{line-height:1.7}hr{border:0;border-top:1px solid #eaecef}table{border-collapse:collapse;margin:1rem 0;display:block;overflow-x:auto}tr{border-top:1px solid #dfe2e5}tr:nth-child(2n){background-color:#f6f8fa}td,th{border:1px solid #dfe2e5;padding:.6em 1em}.theme-container.sidebar-open .sidebar-mask{display:block}.theme-container.no-navbar .theme-default-content:not(.custom)>h1,.theme-container.no-navbar h2,.theme-container.no-navbar h3,.theme-container.no-navbar h4,.theme-container.no-navbar h5,.theme-container.no-navbar h6{margin-top:1.5rem;padding-top:0}.theme-container.no-navbar .sidebar{top:0}@media (min-width:720px){.theme-container.no-sidebar .sidebar{display:none}.theme-container.no-sidebar .page{padding-left:0}}@media (max-width:959px){.sidebar{font-size:15px;width:16.4rem}.page{padding-left:16.4rem}}@media (max-width:719px){.sidebar{top:0;padding-top:3.6rem;transform:translateX(-100%);transition:transform .2s ease}.page{padding-left:0}.theme-container.sidebar-open .sidebar{transform:translateX(0)}.theme-container.no-navbar .sidebar{padding-top:0}}@media (max-width:419px){h1{font-size:1.9rem}.theme-default-content div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}.badge[data-v-c13ee5b0]{display:inline-block;font-size:14px;height:18px;line-height:18px;border-radius:3px;padding:0 6px;color:#fff}.badge.green[data-v-c13ee5b0],.badge.tip[data-v-c13ee5b0],.badge[data-v-c13ee5b0]{background-color:#42b983}.badge.error[data-v-c13ee5b0]{background-color:#da5961}.badge.warn[data-v-c13ee5b0],.badge.warning[data-v-c13ee5b0],.badge.yellow[data-v-c13ee5b0]{background-color:#e7c000}.badge+.badge[data-v-c13ee5b0]{margin-left:5px} \ No newline at end of file diff --git a/assets/img/search.83621669.svg b/assets/img/search.83621669.svg deleted file mode 100644 index 03d8391..0000000 --- a/assets/img/search.83621669.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/assets/js/1.afc15f94.js b/assets/js/1.afc15f94.js new file mode 100644 index 0000000..d3afdf2 --- /dev/null +++ b/assets/js/1.afc15f94.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([[1],[,,,,,,,,,,function(t,n,e){var r=e(13),i=e(34),o=e(17),u=e(27),s=e(47),c=function(t,n,e){var a,f,l,p,h=t&c.F,v=t&c.G,d=t&c.S,g=t&c.P,y=t&c.B,x=v?r:d?r[n]||(r[n]={}):(r[n]||{}).prototype,m=v?i:i[n]||(i[n]={}),b=m.prototype||(m.prototype={});for(a in v&&(e=n),e)l=((f=!h&&x&&void 0!==x[a])?x:e)[a],p=y&&f?s(l,r):g&&"function"==typeof l?s(Function.call,l):l,x&&u(x,a,l,t&c.U),m[a]!=l&&o(m,a,p),g&&b[a]!=l&&(b[a]=l)};r.core=i,c.F=1,c.G=2,c.S=4,c.P=8,c.B=16,c.W=32,c.U=64,c.R=128,t.exports=c},function(t,n,e){var r=e(46)("wks"),i=e(45),o=e(13).Symbol,u="function"==typeof o;(t.exports=function(t){return r[t]||(r[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=r},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n){var e=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=e)},function(t,n,e){var r=e(18);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,n,e){var r=e(104)("wks"),i=e(105),o=e(24).Symbol,u="function"==typeof o;(t.exports=function(t){return r[t]||(r[t]=u&&o[t]||(u?o:i)("Symbol."+t))}).store=r},function(t,n,e){"use strict";e.d(n,"a",function(){return o}),e.d(n,"j",function(){return u}),e.d(n,"c",function(){return s}),e.d(n,"i",function(){return c}),e.d(n,"f",function(){return f}),e.d(n,"g",function(){return l}),e.d(n,"h",function(){return p}),e.d(n,"d",function(){return h}),e.d(n,"b",function(){return v}),e.d(n,"e",function(){return d}),e.d(n,"l",function(){return g}),e.d(n,"m",function(){return x}),e.d(n,"k",function(){return m});const r=/#.*$/,i=/\.(md|html)$/,o=/\/$/,u=/^(https?:|mailto:|tel:)/;function s(t,n,e){if(!t)return e;let r,i=n;for(;(i=i.$parent)&&!r;)r=i.$refs[t];return r&&r.$el&&(r=r.$el),r||e}function c(t){return decodeURI(t).replace(r,"").replace(i,"")}function a(t){const n=t.match(r);if(n)return n[0]}function f(t){return u.test(t)}function l(t){return/^mailto:/.test(t)}function p(t){return/^tel:/.test(t)}function h(t){return t&&t.getBoundingClientRect?t.getBoundingClientRect().top+document.body.scrollTop+document.documentElement.scrollTop:0}function v(t){if(f(t))return t;const n=t.match(r),e=n?n[0]:"",i=c(t);return o.test(i)?t:i+".html"+e}function d(t,n){const e=t.hash,r=a(n);return(!r||e===r)&&c(t.path)===c(n)}function g(t,n,e){e&&(n=y(n,e));const r=c(n);for(let n=0;nObject.assign({},t))).forEach(t=>{2===t.level?n=t:n&&(n.children||(n.children=[])).push(t)}),t.filter(t=>2===t.level)}(t.headers||[]);return[{type:"group",collapsable:!1,title:t.title,children:n.map(n=>({type:"auto",title:n.title,basePath:t.path,path:t.path+"#"+n.slug,children:n.children||[]}))}]}(t);const s=u.sidebar||o.sidebar;if(!s)return[];const{base:c,config:a}=function(t,n){if(Array.isArray(n))return{base:"/",config:n};for(const r in n)if(0===(e=t,/(\.html|\/)$/.test(e)?e:e+"/").indexOf(r))return{base:r,config:n[r]};var e;return{}}(n,s);return a?a.map(t=>(function t(n,e,r,i){if("string"==typeof n)return g(e,n,r);if(Array.isArray(n))return Object.assign(g(e,n[0],r),{title:n[1]});i&&console.error("[vuepress] Nested sidebar groups are not supported. Consider using navbar + categories instead.");const o=n.children||[];return{type:"group",title:n.title,children:o.map(n=>t(n,e,r,!0)),collapsable:!1!==n.collapsable}})(t,i,c)):[]}function m(t,n,e="/",r=1){const{pages:i}=n;return t.map(t=>{if("string"==typeof t){const n=g(i,t,t.startsWith("/")?"/":e);return{text:n.title,link:n.regularPath,items:[],type:"link"}}let o=e;if(t.link){t.link=v(y(t.link,e));const n=t.link.split(/#|\?/)[0];o=(o=n.endsWith("/")?n:n.endsWith(".html")?n.substring(0,n.lastIndexOf("/")+1)||"/":`${n}/`).startsWith("/")?o:`${e}${o}`}return t.items&&r<3&&(t.items=m(t.items,n,o,r+1)),t.text=t.text||(a(t.link)||"").substring(1),t.type=t.items&&t.items.length?"links":"link",t})}},function(t,n,e){var r=e(26),i=e(44);t.exports=e(19)?function(t,n,e){return r.f(t,n,i(1,e))}:function(t,n,e){return t[n]=e,t}},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n,e){t.exports=!e(12)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,n){var e=t.exports={version:"2.6.9"};"number"==typeof __e&&(__e=e)},function(t,n,e){var r=e(35),i=Math.min;t.exports=function(t){return t>0?i(r(t),9007199254740991):0}},function(t,n,e){"use strict";var r=e(12);t.exports=function(t,n){return!!t&&r(function(){n?t.call(null,function(){},1):t.call(null)})}},function(t,n){var e=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=e)},function(t,n){t.exports={}},function(t,n,e){var r=e(14),i=e(85),o=e(87),u=Object.defineProperty;n.f=e(19)?Object.defineProperty:function(t,n,e){if(r(t),n=o(n,!0),r(e),i)try{return u(t,n,e)}catch(t){}if("get"in e||"set"in e)throw TypeError("Accessors not supported!");return"value"in e&&(t[n]=e.value),t}},function(t,n,e){var r=e(13),i=e(17),o=e(28),u=e(45)("src"),s=e(124),c=(""+s).split("toString");e(34).inspectSource=function(t){return s.call(t)},(t.exports=function(t,n,e,s){var a="function"==typeof e;a&&(o(e,"name")||i(e,"name",n)),t[n]!==e&&(a&&(o(e,u)||i(e,u,t[n]?""+t[n]:c.join(String(n)))),t===r?t[n]=e:s?t[n]?t[n]=e:i(t,n,e):(delete t[n],i(t,n,e)))})(Function.prototype,"toString",function(){return"function"==typeof this&&this[u]||s.call(this)})},function(t,n){var e={}.hasOwnProperty;t.exports=function(t,n){return e.call(t,n)}},function(t,n,e){var r=e(47),i=e(91),o=e(30),u=e(22),s=e(125);t.exports=function(t,n){var e=1==t,c=2==t,a=3==t,f=4==t,l=6==t,p=5==t||l,h=n||s;return function(n,s,v){for(var d,g,y=o(n),x=i(y),m=r(s,v,3),b=u(x.length),k=0,_=e?h(n,b):c?h(n,0):void 0;b>k;k++)if((p||k in x)&&(g=m(d=x[k],k,y),t))if(e)_[k]=g;else if(g)switch(t){case 3:return!0;case 5:return d;case 6:return k;case 2:_.push(d)}else if(f)return!1;return l?-1:a||f?f:_}}},function(t,n,e){var r=e(20);t.exports=function(t){return Object(r(t))}},function(t,n,e){var r=e(32),i=e(61);t.exports=e(33)?function(t,n,e){return r.f(t,n,i(1,e))}:function(t,n,e){return t[n]=e,t}},function(t,n,e){var r=e(39),i=e(154),o=e(155),u=Object.defineProperty;n.f=e(33)?Object.defineProperty:function(t,n,e){if(r(t),n=o(n,!0),r(e),i)try{return u(t,n,e)}catch(t){}if("get"in e||"set"in e)throw TypeError("Accessors not supported!");return"value"in e&&(t[n]=e.value),t}},function(t,n,e){t.exports=!e(98)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,n){var e=t.exports={version:"2.6.9"};"number"==typeof __e&&(__e=e)},function(t,n){var e=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:e)(t)}},function(t,n){var e={}.toString;t.exports=function(t){return e.call(t).slice(8,-1)}},function(t,n,e){var r=e(91),i=e(20);t.exports=function(t){return r(i(t))}},function(t,n,e){var r=e(24),i=e(21),o=e(97),u=e(31),s=e(40),c=function(t,n,e){var a,f,l,p=t&c.F,h=t&c.G,v=t&c.S,d=t&c.P,g=t&c.B,y=t&c.W,x=h?i:i[n]||(i[n]={}),m=x.prototype,b=h?r:v?r[n]:(r[n]||{}).prototype;for(a in h&&(e=n),e)(f=!p&&b&&void 0!==b[a])&&s(x,a)||(l=f?b[a]:e[a],x[a]=h&&"function"!=typeof b[a]?e[a]:g&&f?o(l,r):y&&b[a]==l?function(t){var n=function(n,e,r){if(this instanceof t){switch(arguments.length){case 0:return new t;case 1:return new t(n);case 2:return new t(n,e)}return new t(n,e,r)}return t.apply(this,arguments)};return n.prototype=t.prototype,n}(l):d&&"function"==typeof l?o(Function.call,l):l,d&&((x.virtual||(x.virtual={}))[a]=l,t&c.R&&m&&!m[a]&&u(m,a,l)))};c.F=1,c.G=2,c.S=4,c.P=8,c.B=16,c.W=32,c.U=64,c.R=128,t.exports=c},function(t,n,e){var r=e(60);t.exports=function(t){if(!r(t))throw TypeError(t+" is not an object!");return t}},function(t,n){var e={}.hasOwnProperty;t.exports=function(t,n){return e.call(t,n)}},function(t,n,e){for(var r=e(184),i=e(68),o=e(27),u=e(13),s=e(17),c=e(67),a=e(11),f=a("iterator"),l=a("toStringTag"),p=c.Array,h={CSSRuleList:!0,CSSStyleDeclaration:!1,CSSValueList:!1,ClientRectList:!1,DOMRectList:!1,DOMStringList:!1,DOMTokenList:!0,DataTransferItemList:!1,FileList:!1,HTMLAllCollection:!1,HTMLCollection:!1,HTMLFormElement:!1,HTMLSelectElement:!1,MediaList:!0,MimeTypeArray:!1,NamedNodeMap:!1,NodeList:!0,PaintRequestList:!1,Plugin:!1,PluginArray:!1,SVGLengthList:!1,SVGNumberList:!1,SVGPathSegList:!1,SVGPointList:!1,SVGStringList:!1,SVGTransformList:!1,SourceBufferList:!1,StyleSheetList:!0,TextTrackCueList:!1,TextTrackList:!1,TouchList:!1},v=i(h),d=0;df;)if((s=c[f++])!=s)return!0}else for(;a>f;f++)if((t||f in c)&&c[f]===e)return t||f||0;return!t&&-1}}},function(t,n,e){var r=e(11)("unscopables"),i=Array.prototype;null==i[r]&&e(17)(i,r,{}),t.exports=function(t){i[r][t]=!0}},function(t,n,e){"use strict";var r=e(134)(!0);t.exports=function(t,n,e){return n+(e?r(t,n).length:1)}},function(t,n,e){"use strict";var r=e(135),i=RegExp.prototype.exec;t.exports=function(t,n){var e=t.exec;if("function"==typeof e){var o=e.call(t,n);if("object"!=typeof o)throw new TypeError("RegExp exec method returned something other than an Object or null");return o}if("RegExp"!==r(t))throw new TypeError("RegExp#exec called on incompatible receiver");return i.call(t,n)}},function(t,n,e){"use strict";var r,i,o=e(94),u=RegExp.prototype.exec,s=String.prototype.replace,c=u,a=(r=/a/,i=/b*/g,u.call(r,"a"),u.call(i,"a"),0!==r.lastIndex||0!==i.lastIndex),f=void 0!==/()??/.exec("")[1];(a||f)&&(c=function(t){var n,e,r,i,c=this;return f&&(e=new RegExp("^"+c.source+"$(?!\\s)",o.call(c))),a&&(n=c.lastIndex),r=u.call(c,t),a&&r&&(c.lastIndex=c.global?r.index+r[0].length:n),f&&r&&r.length>1&&s.call(r[0],e,function(){for(i=1;i")}),l=function(){var t=/(?:)/,n=t.exec;t.exec=function(){return n.apply(this,arguments)};var e="ab".split(t);return 2===e.length&&"a"===e[0]&&"b"===e[1]}();t.exports=function(t,n,e){var p=s(t),h=!o(function(){var n={};return n[p]=function(){return 7},7!=""[t](n)}),v=h?!o(function(){var n=!1,e=/a/;return e.exec=function(){return n=!0,null},"split"===t&&(e.constructor={},e.constructor[a]=function(){return e}),e[p](""),!n}):void 0;if(!h||!v||"replace"===t&&!f||"split"===t&&!l){var d=/./[p],g=e(u,p,""[t],function(t,n,e,r,i){return n.exec===c?h&&!i?{done:!0,value:d.call(n,e,r)}:{done:!0,value:t.call(e,n,r)}:{done:!1}}),y=g[0],x=g[1];r(String.prototype,t,y),i(RegExp.prototype,p,2==n?function(t,n){return x.call(t,this,n)}:function(t){return x.call(t,this)})}}},function(t,n,e){},function(t,n,e){var r=e(46)("keys"),i=e(45);t.exports=function(t){return r[t]||(r[t]=i(t))}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,n){t.exports=function(t,n){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:n}}},function(t,n){var e={}.toString;t.exports=function(t){return e.call(t).slice(8,-1)}},function(t,n){var e=Math.ceil,r=Math.floor;t.exports=function(t){return isNaN(t=+t)?0:(t>0?r:e)(t)}},function(t,n){t.exports=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t}},function(t,n,e){var r=e(166),i=e(64);t.exports=function(t){return r(i(t))}},function(t,n,e){var r=e(104)("keys"),i=e(105);t.exports=function(t){return r[t]||(r[t]=i(t))}},function(t,n){t.exports={}},function(t,n,e){var r=e(96),i=e(59);t.exports=Object.keys||function(t){return r(t,i)}},function(t,n,e){},function(t,n,e){},function(t,n,e){},function(t,n,e){},function(t,n,e){},function(t,n,e){},function(t,n,e){},,,,,,,,,function(t,n,e){"use strict";var r=e(10),i=e(22),o=e(89),u="".startsWith;r(r.P+r.F*e(90)("startsWith"),"String",{startsWith:function(t){var n=o(this,t,"startsWith"),e=i(Math.min(arguments.length>1?arguments[1]:void 0,n.length)),r=String(t);return u?u.call(n,r,e):n.slice(e,e+r.length)===r}})},function(t,n,e){t.exports=!e(19)&&!e(12)(function(){return 7!=Object.defineProperty(e(86)("div"),"a",{get:function(){return 7}}).a})},function(t,n,e){var r=e(18),i=e(13).document,o=r(i)&&r(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n,e){var r=e(18);t.exports=function(t,n){if(!r(t))return t;var e,i;if(n&&"function"==typeof(e=t.toString)&&!r(i=e.call(t)))return i;if("function"==typeof(e=t.valueOf)&&!r(i=e.call(t)))return i;if(!n&&"function"==typeof(e=t.toString)&&!r(i=e.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,n){t.exports=!1},function(t,n,e){var r=e(49),i=e(20);t.exports=function(t,n,e){if(r(n))throw TypeError("String#"+e+" doesn't accept regex!");return String(i(t))}},function(t,n,e){var r=e(11)("match");t.exports=function(t){var n=/./;try{"/./"[t](n)}catch(e){try{return n[r]=!1,!"/./"[t](n)}catch(t){}}return!0}},function(t,n,e){var r=e(36);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==r(t)?t.split(""):Object(t)}},function(t,n,e){var r=e(36);t.exports=Array.isArray||function(t){return"Array"==r(t)}},function(t,n,e){"use strict";var r=e(49),i=e(14),o=e(133),u=e(53),s=e(22),c=e(54),a=e(55),f=e(12),l=Math.min,p=[].push,h=!f(function(){RegExp(4294967295,"y")});e(56)("split",2,function(t,n,e,f){var v;return v="c"=="abbc".split(/(b)*/)[1]||4!="test".split(/(?:)/,-1).length||2!="ab".split(/(?:ab)*/).length||4!=".".split(/(.?)(.?)/).length||".".split(/()()/).length>1||"".split(/.?/).length?function(t,n){var i=String(this);if(void 0===t&&0===n)return[];if(!r(t))return e.call(i,t,n);for(var o,u,s,c=[],f=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),l=0,h=void 0===n?4294967295:n>>>0,v=new RegExp(t.source,f+"g");(o=a.call(v,i))&&!((u=v.lastIndex)>l&&(c.push(i.slice(l,o.index)),o.length>1&&o.index=h));)v.lastIndex===o.index&&v.lastIndex++;return l===i.length?!s&&v.test("")||c.push(""):c.push(i.slice(l)),c.length>h?c.slice(0,h):c}:"0".split(void 0,0).length?function(t,n){return void 0===t&&0===n?[]:e.call(this,t,n)}:e,[function(e,r){var i=t(this),o=null==e?void 0:e[n];return void 0!==o?o.call(e,i,r):v.call(String(i),e,r)},function(t,n){var r=f(v,t,this,n,v!==e);if(r.done)return r.value;var a=i(t),p=String(this),d=o(a,RegExp),g=a.unicode,y=(a.ignoreCase?"i":"")+(a.multiline?"m":"")+(a.unicode?"u":"")+(h?"y":"g"),x=new d(h?a:"^(?:"+a.source+")",y),m=void 0===n?4294967295:n>>>0;if(0===m)return[];if(0===p.length)return null===c(x,p)?[p]:[];for(var b=0,k=0,_=[];kc;)r(s,e=n[c++])&&(~o(a,e)||a.push(e));return a}},function(t,n,e){var r=e(153);t.exports=function(t,n,e){if(r(t),void 0===n)return t;switch(e){case 1:return function(e){return t.call(n,e)};case 2:return function(e,r){return t.call(n,e,r)};case 3:return function(e,r,i){return t.call(n,e,r,i)}}return function(){return t.apply(n,arguments)}}},function(t,n){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,n,e){var r=e(60),i=e(24).document,o=r(i)&&r(i.createElement);t.exports=function(t){return o?i.createElement(t):{}}},function(t,n,e){"use strict";var r=e(159)(!0);e(101)(String,"String",function(t){this._t=String(t),this._i=0},function(){var t,n=this._t,e=this._i;return e>=n.length?{value:void 0,done:!0}:(t=r(n,e),this._i+=t.length,{value:t,done:!1})})},function(t,n,e){"use strict";var r=e(102),i=e(38),o=e(160),u=e(31),s=e(25),c=e(161),a=e(107),f=e(170),l=e(15)("iterator"),p=!([].keys&&"next"in[].keys()),h=function(){return this};t.exports=function(t,n,e,v,d,g,y){c(e,n,v);var x,m,b,k=function(t){if(!p&&t in O)return O[t];switch(t){case"keys":case"values":return function(){return new e(this,t)}}return function(){return new e(this,t)}},_=n+" Iterator",w="values"==d,S=!1,O=t.prototype,L=O[l]||O["@@iterator"]||d&&O[d],C=L||k(d),$=d?w?k("entries"):C:void 0,j="Array"==n&&O.entries||L;if(j&&(b=f(j.call(new t)))!==Object.prototype&&b.next&&(a(b,_,!0),r||"function"==typeof b[l]||u(b,l,h)),w&&L&&"values"!==L.name&&(S=!0,C=function(){return L.call(this)}),r&&!y||!p&&!S&&O[l]||u(O,l,C),s[n]=C,s[_]=h,d)if(x={values:w?C:k("values"),keys:g?C:k("keys"),entries:$},y)for(m in x)m in O||o(O,m,x[m]);else i(i.P+i.F*(p||S),n,x);return x}},function(t,n){t.exports=!0},function(t,n,e){var r=e(63),i=Math.min;t.exports=function(t){return t>0?i(r(t),9007199254740991):0}},function(t,n,e){var r=e(21),i=e(24),o=i["__core-js_shared__"]||(i["__core-js_shared__"]={});(t.exports=function(t,n){return o[t]||(o[t]=void 0!==n?n:{})})("versions",[]).push({version:r.version,mode:e(102)?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(t,n){var e=0,r=Math.random();t.exports=function(t){return"Symbol(".concat(void 0===t?"":t,")_",(++e+r).toString(36))}},function(t,n){t.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(t,n,e){var r=e(32).f,i=e(40),o=e(15)("toStringTag");t.exports=function(t,n,e){t&&!i(t=e?t:t.prototype,o)&&r(t,o,{configurable:!0,value:n})}},function(t,n,e){var r=e(64);t.exports=function(t){return Object(r(t))}},function(t,n,e){var r=e(62),i=e(15)("toStringTag"),o="Arguments"==r(function(){return arguments}());t.exports=function(t){var n,e,u;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(e=function(t,n){try{return t[n]}catch(t){}}(n=Object(t),i))?e:o?r(n):"Object"==(u=r(n))&&"function"==typeof n.callee?"Arguments":u}},function(t,n,e){"use strict";var r=e(14),i=e(30),o=e(22),u=e(35),s=e(53),c=e(54),a=Math.max,f=Math.min,l=Math.floor,p=/\$([$&`']|\d\d?|<[^>]*>)/g,h=/\$([$&`']|\d\d?)/g;e(56)("replace",2,function(t,n,e,v){return[function(r,i){var o=t(this),u=null==r?void 0:r[n];return void 0!==u?u.call(r,o,i):e.call(String(o),r,i)},function(t,n){var i=v(e,t,this,n);if(i.done)return i.value;var l=r(t),p=String(this),h="function"==typeof n;h||(n=String(n));var g=l.global;if(g){var y=l.unicode;l.lastIndex=0}for(var x=[];;){var m=c(l,p);if(null===m)break;if(x.push(m),!g)break;""===String(m[0])&&(l.lastIndex=s(p,o(l.lastIndex),y))}for(var b,k="",_=0,w=0;w=_&&(k+=p.slice(_,O)+A,_=O+S.length)}return k+p.slice(_)}];function d(t,n,r,o,u,s){var c=r+t.length,a=o.length,f=h;return void 0!==u&&(u=i(u),f=p),e.call(s,f,function(e,i){var s;switch(i.charAt(0)){case"$":return"$";case"&":return t;case"`":return n.slice(0,r);case"'":return n.slice(c);case"<":s=u[i.slice(1,-1)];break;default:var f=+i;if(0===f)return e;if(f>a){var p=l(f/10);return 0===p?e:p<=a?void 0===o[p-1]?i.charAt(1):o[p-1]+i.charAt(1):e}s=o[f-1]}return void 0===s?"":s})}})},function(t,n,e){var r=e(26).f,i=e(28),o=e(11)("toStringTag");t.exports=function(t,n,e){t&&!i(t=e?t:t.prototype,o)&&r(t,o,{configurable:!0,value:n})}},function(t,n,e){var r=e(30),i=e(68);e(192)("keys",function(){return function(t){return i(r(t))}})},function(t,n,e){"use strict";e(193)("link",function(t){return function(n){return t(this,"a","href",n)}})},function(t,n,e){"use strict";var r=e(10),i=e(29)(0),o=e(23)([].forEach,!0);r(r.P+r.F*!o,"Array",{forEach:function(t){return i(this,t,arguments[1])}})},function(t,n,e){"use strict";var r=e(74);e.n(r).a},function(t,n,e){"use strict";var r=e(75);e.n(r).a},function(t,n,e){"use strict";e(142),e(149);var r=e(121),i=(e(50),e(110),e(95),e(41),e(112),e(84),e(93),e(113),e(114),e(16)),o={props:{item:{required:!0}},computed:{link:function(){return Object(i.b)(this.item.link)},exact:function(){var t=this;return this.$site.locales?Object.keys(this.$site.locales).some(function(n){return n===t.link}):"/"===this.link}},methods:{isExternal:i.f,isMailto:i.g,isTel:i.h}},u=e(0),s=Object(u.a)(o,function(){var t=this,n=t.$createElement,e=t._self._c||n;return t.isExternal(t.link)?e("a",t._b({staticClass:"nav-link external",attrs:{href:t.link,target:t.isMailto(t.link)||t.isTel(t.link)?null:"_blank",rel:t.isMailto(t.link)||t.isTel(t.link)?null:"noopener noreferrer"}},"a",t.$attrs,!1),[t._v("\n "+t._s(t.item.text)+"\n "),e("OutboundLink")],1):e("router-link",t._b({staticClass:"nav-link",attrs:{to:t.link,exact:t.exact}},"router-link",t.$attrs,!1),[t._v(t._s(t.item.text))])},[],!1,null,null,null).exports,c={components:{NavLink:s,DropdownTransition:e(119).a},data:function(){return{open:!1,activeLink:"",exactLink:!1}},props:{item:{required:!0}},watch:{$route:function(){this.exactLink=!1,this.activeLink="",this.checkActive([this.item])}},mounted:function(){this.checkActive([this.item])},methods:{toggle:function(){this.open=!this.open},checkActive:function(t){var n=this;t.forEach(function(t){if(!n.exactLink&&"/"!==n.$route.path&&n.$route.path!==n.$localePath&&!t.addr&&(t.items&&n.checkActive(t.items),t.link)){var e=t.link.split(/#|\?/)[0];if(e===n.$route.path)n.activeLink=t.link,n.exactLink=!0;else if(e.startsWith(n.$route.path)||n.$route.path.startsWith(e)){if(n.activeLink&&e.length1){var e=this.$page.path,i=this.$router.options.routes,o=this.$site.themeConfig.locales||{},u={text:this.$themeLocaleConfig.selectText||"Languages",type:"links",addr:"langs",items:Object.keys(n).map(function(r){var u,s=n[r],c=o[r]&&o[r].label||s.lang;return s.lang===t.$lang?u=e:(u=e.replace(t.$localeConfig.path,r),i.some(function(t){return t.path===u})||(u=r)),{text:c,link:u}})};return[].concat(Object(r.a)(this.userNav),[u])}return this.userNav},repoLink:function(){var t=this.$site.themeConfig,n=t.repo,e=t.repoLink;if(e=!1===e?void 0:e||n,e)return/^https?:/.test(e)?e:"https://github.com/".concat(e)},repoLabel:function(){if(this.repoLink){if(this.$site.themeConfig.repoLabel)return this.$site.themeConfig.repoLabel;for(var t=this.repoLink.match(/^https?:\/\/[^\/]+/)[0],n=["GitHub","GitLab","Bitbucket"],e=0;e2&&void 0!==arguments[2]?arguments[2]:0;if(t){if((t=t.toLowerCase()).includes(n))r+=t===n?1e4:1e3;else{if(!(i.length>1))return;var s=i.filter(function(n){if(t.includes(n))return r+=n===t?500:300,!0});if(!s.length)return}var c=e();u[c.path]||(u[c.path]=!0,o.push({weight:r,page:e()}))}},c=function(n){var i=e[n];if(t.getPageLocalePath(i)!==r)return"continue";if(!t.isSearchable(i))return"continue";if(i.frontmatter&&i.frontmatter.keyword){Array.isArray(i.frontmatter.keyword)||(i.frontmatter.keyword=i.frontmatter.keyword.split(",").map(function(t){return t.trim()}));for(var o=0;o0?this.focusIndex--:this.focusIndex=this.suggestions.length-1)},onDown:function(){this.showSuggestions&&(this.focusIndex "+t._s(n.header.title))]:t._e()],2)])}),0):t._e()])},[],!1,null,null,null).exports);function s(t,n){return t.ownerDocument.defaultView.getComputedStyle(t,null)[n]}var c={components:{SidebarButton:i,NavLinks:e(117).a,SearchBox:u},data:function(){return{linksWrapMaxWidth:null}},mounted:function(){var t=this,n=parseInt(s(this.$el,"paddingLeft"))+parseInt(s(this.$el,"paddingRight")),e=function(){document.documentElement.clientWidth<719?t.linksWrapMaxWidth=null:t.linksWrapMaxWidth=t.$el.offsetWidth-n-(t.$refs.siteName&&t.$refs.siteName.offsetWidth||0)};e(),window.addEventListener("resize",e,!1)},computed:{algolia:function(){return this.$themeLocaleConfig.algolia||this.$site.themeConfig.algolia||{}},isAlgoliaSearch:function(){return this.algolia&&this.algolia.apiKey&&this.algolia.indexName},searchTitle:function(){return this.$siteTitle?"在 ".concat(this.$siteTitle," 中搜索"):"搜索"}}},a=(e(197),Object(r.a)(c,function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("header",{staticClass:"navbar"},[e("SidebarButton",{on:{"toggle-sidebar":function(n){return t.$emit("toggle-sidebar")}}}),t._v(" "),e("div",{staticClass:"left-logo-part"},[e("router-link",{staticClass:"home-link",attrs:{to:t.$localePath}},[t.$site.themeConfig.logo?e("img",{staticClass:"logo",attrs:{src:t.$withBase(t.$site.themeConfig.logo),alt:t.$siteTitle}}):t._e(),t._v(" "),t.$siteTitle?e("span",{ref:"siteName",staticClass:"site-name",class:{"can-hide":t.$site.themeConfig.logo}},[t._v(t._s(t.$siteTitle))]):t._e()]),t._v(" "),!1!==t.$site.themeConfig.search?e("SearchBox",{attrs:{placeholder:t.searchTitle}}):t._e()],1),t._v(" "),e("div",{staticClass:"links",style:t.linksWrapMaxWidth?{"max-width":t.linksWrapMaxWidth+"px"}:{}},[e("NavLinks",{staticClass:"can-hide"})],1)],1)},[],!1,null,null,null));n.a=a.exports},function(t,n,e){"use strict";var r={name:"DropdownTransition",methods:{setHeight:function(t){t.style.height=t.scrollHeight+"px"},unsetHeight:function(t){t.style.height=""}}},i=(e(194),e(0)),o=Object(i.a)(r,function(){var t=this.$createElement;return(this._self._c||t)("transition",{attrs:{name:"dropdown"},on:{enter:this.setHeight,"after-enter":this.unsetHeight,"before-leave":this.setHeight}},[this._t("default")],2)},[],!1,null,null,null);n.a=o.exports},function(t,n,e){"use strict";var r={computed:{config:function(){return this.$themeLocaleConfig.foot}}},i=(e(198),e(0)),o=Object(i.a)(r,function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("div",{staticClass:"global-foot"},[e("div",{staticClass:"friend-links"},[e("div",{staticClass:"friend-list-wrapper"},t._l(t.config.friendList,function(n,r){return e("div",{key:r,staticClass:"friend-list"},[e("div",{staticClass:"friend-list-align"},[e("div",{staticClass:"friend-list-title"},[t._v(t._s(n.title))]),t._v(" "),t._l(n.list,function(n,r){return e("a",{key:r,staticClass:"friend-list-item",attrs:{href:n.url,target:"_blank"}},[t._v(t._s(n.name))])}),t._v(" "),n.qrcode?e("div",{staticClass:"friend-qrcode"},[e("img",{attrs:{src:t.$withBase(n.qrcode),alt:n.name}})]):t._e()],2)])}),0)]),t._v(" "),e("div",{staticClass:"copyright"},[e("span",{staticClass:"span"},[t._l(t.config.copyright,function(n,r){return[n.url?e("a",{key:r,attrs:{href:n.url}},[t._v(t._s(n.text))]):e("span",{key:r},[t._v(t._s(n.text))])]})],2)])])},[],!1,null,null,null);n.a=o.exports},function(t,n,e){"use strict";var r=e(150),i=e.n(r);var o=e(157),u=e.n(o),s=e(177),c=e.n(s);function a(t){return function(t){if(i()(t)){for(var n=0,e=new Array(t.length);n1?arguments[1]:void 0)}}),e(52)("includes")},function(t,n,e){"use strict";var r=e(10),i=e(89);r(r.P+r.F*e(90)("includes"),"String",{includes:function(t){return!!~i(this,t,"includes").indexOf(t,arguments.length>1?arguments[1]:void 0)}})},function(t,n,e){var r=e(14),i=e(48),o=e(11)("species");t.exports=function(t,n){var e,u=r(t).constructor;return void 0===u||null==(e=r(u)[o])?n:i(e)}},function(t,n,e){var r=e(35),i=e(20);t.exports=function(t){return function(n,e){var o,u,s=String(i(n)),c=r(e),a=s.length;return c<0||c>=a?t?"":void 0:(o=s.charCodeAt(c))<55296||o>56319||c+1===a||(u=s.charCodeAt(c+1))<56320||u>57343?t?s.charAt(c):o:t?s.slice(c,c+2):u-56320+(o-55296<<10)+65536}}},function(t,n,e){var r=e(36),i=e(11)("toStringTag"),o="Arguments"==r(function(){return arguments}());t.exports=function(t){var n,e,u;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(e=function(t,n){try{return t[n]}catch(t){}}(n=Object(t),i))?e:o?r(n):"Object"==(u=r(n))&&"function"==typeof n.callee?"Arguments":u}},function(t,n,e){"use strict";var r=e(55);e(10)({target:"RegExp",proto:!0,forced:r!==/./.exec},{exec:r})},function(t,n,e){"use strict";var r=e(10),i=e(29)(2);r(r.P+r.F*!e(23)([].filter,!0),"Array",{filter:function(t){return i(this,t,arguments[1])}})},function(t,n,e){"use strict";e(139)("trim",function(t){return function(){return t(this,3)}})},function(t,n,e){var r=e(10),i=e(20),o=e(12),u=e(140),s="["+u+"]",c=RegExp("^"+s+s+"*"),a=RegExp(s+s+"*$"),f=function(t,n,e){var i={},s=o(function(){return!!u[t]()||"​…"!="​…"[t]()}),c=i[t]=s?n(l):u[t];e&&(i[e]=c),r(r.P+r.F*s,"String",i)},l=f.trim=function(t,n){return t=String(i(t)),1&n&&(t=t.replace(c,"")),2&n&&(t=t.replace(a,"")),t};t.exports=f},function(t,n){t.exports="\t\n\v\f\r   ᠎              \u2028\u2029\ufeff"},function(t,n,e){"use strict";var r=e(57);e.n(r).a},function(t,n,e){var r=e(13),i=e(143),o=e(26).f,u=e(147).f,s=e(49),c=e(94),a=r.RegExp,f=a,l=a.prototype,p=/a/g,h=/a/g,v=new a(p)!==p;if(e(19)&&(!v||e(12)(function(){return h[e(11)("match")]=!1,a(p)!=p||a(h)==h||"/a/i"!=a(p,"i")}))){a=function(t,n){var e=this instanceof a,r=s(t),o=void 0===n;return!e&&r&&t.constructor===a&&o?t:i(v?new f(r&&!o?t.source:t,n):f((r=t instanceof a)?t.source:t,r&&o?c.call(t):n),e?this:l,a)};for(var d=function(t){t in a||o(a,t,{configurable:!0,get:function(){return f[t]},set:function(n){f[t]=n}})},g=u(f),y=0;g.length>y;)d(g[y++]);l.constructor=a,a.prototype=l,e(27)(r,"RegExp",a)}e(148)("RegExp")},function(t,n,e){var r=e(18),i=e(144).set;t.exports=function(t,n,e){var o,u=n.constructor;return u!==e&&"function"==typeof u&&(o=u.prototype)!==e.prototype&&r(o)&&i&&i(t,o),t}},function(t,n,e){var r=e(18),i=e(14),o=function(t,n){if(i(t),!r(n)&&null!==n)throw TypeError(n+": can't set as prototype!")};t.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(t,n,r){try{(r=e(47)(Function.call,e(145).f(Object.prototype,"__proto__").set,2))(t,[]),n=!(t instanceof Array)}catch(t){n=!0}return function(t,e){return o(t,e),n?t.__proto__=e:r(t,e),t}}({},!1):void 0),check:o}},function(t,n,e){var r=e(146),i=e(44),o=e(37),u=e(87),s=e(28),c=e(85),a=Object.getOwnPropertyDescriptor;n.f=e(19)?a:function(t,n){if(t=o(t),n=u(n,!0),c)try{return a(t,n)}catch(t){}if(s(t,n))return i(!r.f.call(t,n),t[n])}},function(t,n){n.f={}.propertyIsEnumerable},function(t,n,e){var r=e(96),i=e(59).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(t){return r(t,i)}},function(t,n,e){"use strict";var r=e(13),i=e(26),o=e(19),u=e(11)("species");t.exports=function(t){var n=r[t];o&&n&&!n[u]&&i.f(n,u,{configurable:!0,get:function(){return this}})}},function(t,n,e){"use strict";var r=e(14),i=e(22),o=e(53),u=e(54);e(56)("match",1,function(t,n,e,s){return[function(e){var r=t(this),i=null==e?void 0:e[n];return void 0!==i?i.call(e,r):new RegExp(e)[n](String(r))},function(t){var n=s(e,t,this);if(n.done)return n.value;var c=r(t),a=String(this);if(!c.global)return u(c,a);var f=c.unicode;c.lastIndex=0;for(var l,p=[],h=0;null!==(l=u(c,a));){var v=String(l[0]);p[h]=v,""===v&&(c.lastIndex=o(a,i(c.lastIndex),f)),h++}return 0===h?null:p}]})},function(t,n,e){t.exports=e(151)},function(t,n,e){e(152),t.exports=e(21).Array.isArray},function(t,n,e){var r=e(38);r(r.S,"Array",{isArray:e(156)})},function(t,n){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,n,e){t.exports=!e(33)&&!e(98)(function(){return 7!=Object.defineProperty(e(99)("div"),"a",{get:function(){return 7}}).a})},function(t,n,e){var r=e(60);t.exports=function(t,n){if(!r(t))return t;var e,i;if(n&&"function"==typeof(e=t.toString)&&!r(i=e.call(t)))return i;if("function"==typeof(e=t.valueOf)&&!r(i=e.call(t)))return i;if(!n&&"function"==typeof(e=t.toString)&&!r(i=e.call(t)))return i;throw TypeError("Can't convert object to primitive value")}},function(t,n,e){var r=e(62);t.exports=Array.isArray||function(t){return"Array"==r(t)}},function(t,n,e){t.exports=e(158)},function(t,n,e){e(100),e(171),t.exports=e(21).Array.from},function(t,n,e){var r=e(63),i=e(64);t.exports=function(t){return function(n,e){var o,u,s=String(i(n)),c=r(e),a=s.length;return c<0||c>=a?t?"":void 0:(o=s.charCodeAt(c))<55296||o>56319||c+1===a||(u=s.charCodeAt(c+1))<56320||u>57343?t?s.charAt(c):o:t?s.slice(c,c+2):u-56320+(o-55296<<10)+65536}}},function(t,n,e){t.exports=e(31)},function(t,n,e){"use strict";var r=e(162),i=e(61),o=e(107),u={};e(31)(u,e(15)("iterator"),function(){return this}),t.exports=function(t,n,e){t.prototype=r(u,{next:i(1,e)}),o(t,n+" Iterator")}},function(t,n,e){var r=e(39),i=e(163),o=e(106),u=e(66)("IE_PROTO"),s=function(){},c=function(){var t,n=e(99)("iframe"),r=o.length;for(n.style.display="none",e(169).appendChild(n),n.src="/service/javascript:",(t=n.contentWindow.document).open(),t.write(" + + diff --git a/img_egg/background.png b/img_egg/background.png new file mode 100644 index 0000000000000000000000000000000000000000..51663132b59c3ab0e5b885e583092b56f233a42e GIT binary patch literal 9254 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 DXcl4l literal 0 HcmV?d00001 diff --git a/img_egg/banner.png b/img_egg/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..f97ad4a368ccf51d575f41bcdbfbceba67249463 GIT binary patch literal 53844 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 literal 0 HcmV?d00001 diff --git a/img_egg/icon-1.png b/img_egg/icon-1.png new file mode 100644 index 0000000000000000000000000000000000000000..0cd4e4aa2d480d74b3243591eb5795243d26b4f8 GIT binary patch literal 14418 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 L6vV4U4TJt0%)vMw literal 0 HcmV?d00001 diff --git a/img_egg/icon-2.png b/img_egg/icon-2.png new file mode 100644 index 0000000000000000000000000000000000000000..e313b3bd75f0ad860f20971add6abf37a934b4e6 GIT binary patch literal 12785 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 literal 0 HcmV?d00001 diff --git a/img_egg/icon-3.png b/img_egg/icon-3.png new file mode 100644 index 0000000000000000000000000000000000000000..9c53f459e437a3756ad634c9b39990e0c3252731 GIT binary patch literal 12417 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 T1$_Qf0460a|E)&UFzCMkB|zjx literal 0 HcmV?d00001 diff --git a/img_egg/icon-4.png b/img_egg/icon-4.png new file mode 100644 index 0000000000000000000000000000000000000000..1ca6ab5544b2eeda594327959a757fb8ed181dab GIT binary patch literal 13996 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

        om7 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 literal 0 HcmV?d00001 diff --git a/img_egg/qrcode.png b/img_egg/qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..d4ad3fdb723ad85999f84145096bead9b953aa28 GIT binary patch literal 7781 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 literal 0 HcmV?d00001 diff --git a/img_egg/qrcode_dingtalk.png b/img_egg/qrcode_dingtalk.png new file mode 100644 index 0000000000000000000000000000000000000000..f15d2a827ced5cb2e7aa9117e652a72502b4459a GIT binary patch literal 29497 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 literal 0 HcmV?d00001 diff --git a/img_egg/whosusing.png b/img_egg/whosusing.png new file mode 100644 index 0000000000000000000000000000000000000000..fbd9d04fa1404d4a9975754240fba5e3f18688c2 GIT binary patch literal 5374 zcmX|Fbx>6A*I$-aYL~7>x?2eWkxuDG8bn%3U z2{XD!_)rT7kZ>@Pa59l_F_CgXh%uP^5itjZhKn83{}m^Ml#@|bL5@Y52g8zbLddut zk#Io%6(=KzhlvCu;Q8P9|9SyT0wX5&pO>2n{Fse|i}}CJ|BU}Aj0>~D2rwAL%}mC{ zgc-s7tpCMgSm%Tg4901+!*mc2%Rey(#49@zv&XnRQo{cUFm}#=QJ7P?CWT_KQ+xXH z;WlE*_J7?DugL+Y*^cy+6ItUMjKC!!6jT3ExDB>4X4*!6E|MmiqB@EKYR?!Y`7s*I zNpKo$G0)Wrv#`Tl_gf!5Eqws+;gYJU-gmKGitG92cR2XB^0I};=RC>G-Jf!@$bOId z2$8T~R!^j@B>aso*OsZLcj|DzI)#-$;1Lf4tjT}9%(s8acX)tm$Z~&AOY@~&OLjQeeuKOX@B0Ai2aT2uX|R{oph6 z7<0w<34#u?g1}q&h1^?jpO=CgY57tI>MReiCH{^#2|mScjt1Yb#1T|P`@0IaJf%D+ zUl_YU&N@jZ1>X8VtlOj{bNF^|k;HI+{QF-E(lh}x?}%B?$}S1`s4D7_0`cD(d}Zh1 z7p?~(;h=S7oeXwdq-T-e4Q~8Q+|qnw=unAo`z4L?zUS0+DliTSv!LmO1)=!nML=P` zZd10+CIZ=-T^Z_P*f!=h1(sB6@B2F)H_&hQ5I^o7kzy;6dWH`Zld7E->0Ts1=5P4% zLpm=fdE11(LSsb1KRf;BD{w|&q(%`dRCU``?=pE%HsPIA9gPn!Hd_Q6F&*xHYj_mX zmFyWD}Wq{9Gxo<=iWH=45+S6?=@NO3CvPuCo@K==0!&CouEIzMeBt zR1EH8@S)~e9rdzYl%7K8O?;R-9X`-h$5gQexH%#*-Q9Kp9+whtg}ld2PzsxtLb1D! zZh}9tk5Rp;!s8KO>kKAZ8Geddw&=#&%1<*DyrFO?e`_OPd0EQ0lCx;qm}p8j1$->|J_xWp>>{O}E))4w*^(5xS3-eZcsxx9Jtz%6d$_ z!9(*NDfp^5I(ooz5=88yG<_J+=JjQB1^&&vjl2ZVE^D|Kkw+ln*$@`wsSKTN`u>p1 zl+1dk*VgKzd<#}l5Rj1Vd2t<*SQd51-~ly<#a&G4(=w$pN>y1fwoyBji3eDuM|3|T zcRQ_gAdkVOSgcx&!0I%x%A~r4&Yo0UmbBwj$F9n!T8R~q3Tit*N#;OHu|0ysHOkul zOz6l|r915=V*7jRU-M{^0`E5|5;~&dO%>6qZ|SDvN(U#p^pbG^!i^pyiK)&?)S--H zd}M2(Vlu*18hI2WSxHv&E3lv&_dC>x;QL%8%4}Q;{-?v;ht(62<-V!KkRzTI`9)t* zPe|gDnPlL+=X(Re`^=xwTRm{66@d+Ys5*IeUl;pYIP2N9LYIiTh(NlJ&1>?{?7Y(( z&Z1)gmaU~+VWD%UI~|+5w1snUqJK?#*6@L|WQ(=@us}?hdNSl7%*?f1Cp{`@tnF(JBDJKxU`ve7LY>&&Mz=nPfx9 zA(zSW>_NWIxg1rf6V;eBD{NR3_k%i#(fnDJUbUlESI2s~{!!cOyQtk|n`IIdS3^~Y zueb~B6N0^|c53-I%p2=`_k`J?(+*$eaDaIL|8Vf!=&Wkir$7W&@eJk~=Ue;T6q#Z`Wu&B%&BEUgv$6o)Z z?v0JvYnPSjmHqr(eHImpz7+rv_*ArkA>0eWUE;+S=B^2_)3mKu8}BsiJhIcjnn4v7 z@=b5#?jG8_q7iC_9}O7s*M9wWhnk;9ZT-qN!09|KJl-3tj3}nV&9u_8d?0x>BnQI+ zedFO*hna*Cs6smR1r=y!6q>;6bXv?FwFRnw9(7>PnzQoRH5IW(N863%L%*}hp?|fi z7{jN=g<&;9qG`p#I&6$$y?*{^uZYs3rmee^A5pTwIn_uk5V>VMR6T{DldenGLs3GH z(d8_OJnycl-(Ys!Pe{K&`iOiuk3TB2of?5*?#Hjc3jViTM z85uagHQ{A{JACq5No<%tB@?2*T;&(e`v*CrU-_QGC7eaMvVv$-4Da%KxZ`aa3x%O~ zDH1P71J>lcz@j1gkb{n2hVRJjm5Vs`(VDPbYPsA)ABh3H~x zG}o-?a`u2P{9zrfwi(lIQcVQ7!|ABXa$@Ia?2w}L5weG;^+La?B2MdAG9%p~T--UI zNEUVjuR}iH;!$cHce9$@yWX z)fG0rGTTNK%S%XA|40qKISfjJwujm0fU^I%w?S7z8C#sICohkX!+u8H%CHJ}l7m<$Ij%E7ve%DDX!cm{af2lziYXzck{vh+f4~M`CT2{DVx44kI zR1Gy=8Jze+9=_Sy%Zo-RR_<89LRY0#k#y)CDd)`hG!K5RZ@Ahh$XV$o z-g3wWXTzr|stB-oWGSRP^^R~{lMT#1(zpueJ9_FS)y4dX%$x1T62%p}Z+^iE6Bc3> z&z~J2z9`^C|B-x~IP5!(h}ELQi8}1mJ^JA?jshb>SNWG-Nfz!YhL^;Kn8-hmPYJ} zRqp5}XfZ>!bku#c+GR=3XD^Ail&HOgx8WH~i+FV3%y&ODYE(>Cw5wlNgJrTIC>E{1 z8e#K#`8rJ#Ay**eWyY{p10ji~^&+wrefM>fJ_1zS-55Sr2Le3PIIzyygwF^zOc*QvFnNzpDJ6 ztpxNoSm>FMw7VnbiV>7%KVz}hEnyz{vo~{JC1+-a=ioEpHepQ^ULU2D$$Dn;{HXvW{70+wie^pDjThAl!Nh9cQYO*LX7O5x7|0_a{(qa*qzB_0b<;xy;8 zSq0l}PdJ##<)MxTlg0!hDl6fW(9foY?9`Kng2%CBgSAlM<1kGZL3^n*=idqT8Dc_l z!FM-U=lu#bA8^D32}hn^R6|PKYI_37@AoX0K3|sHpJOq!i^TX6fUeRK{Z$dkS7 zsmbbPiC!Gv>eAgEg+Wg`NjB$R;ap&szNcx43szHO?br*G9(|0x8$qh2A5Z_+u|pNw zLa$4x_u@v6^j(5Ru-`ZRn%5LKtI7rgn0JLU-1R0hs_A#N->Cb}F>qNvB6oay$5D2K zEj(c6X4(`c+fRA=-4x`Ao$83Tr<^5EA>ID?v;0P%6y-fS#H&qs+PQEqeNqN-$H5s0 z;nRkJ++CNH2K_4|6GpFJl?yYT2N+G=r;2DxYH%@A?oI^i@LN@t-&k z;>>#c@>KzW1>cLRhNWw$%gzp&rK6$EaomineFqD89Gb$MPoU>cFO-gNFDte7u2xhx zQVL{;csSUzhRi0x_;|tYuZ>*Df=?XoAdy!K3mcZYTpO!;BAS#=vf~m+TQ4t1lz=V_wxa|YEL%up zR*ja54qqF=1Xh=ru$|E0nZKk)4G>M@V#8YyHPoOd9Wo{QC)JRxW&4QjyfzD~%QT|! z$Xo7$%abZ3PMTfHBuYbHThiZf$BzE$0gfb4G*rUxfP!OGs%l$SuHXyyaa?!hc@>%2 z?{7u!?7?Io_MRv%Xa|`M$9kZ<#_whUi0lqyQgxhr?6vR6XdLgi9pukHaTIS-H&`ar z<2_qY>GAP%o~QhR&Pano5^*Ix29gh)K!Iq&0^fe@EP1vKahCvvVFEdHHZbwhp=fm8 zb7r{`s+X_xF_)4y9q}s7UsoG`s=ZZme$}?fFRzx-UkjyVO-(;m5xOv-s)JnQk}vhf z{MhjGCTa+kZ>?aMJqxA*uQ3%pmEhZ6xmvXq)!@oKObkkuzgKf@1iOzAHbN)}9qwFf z!J{t4;W)*LceVJs)46S_%2kV=wUA8@LND3{TBa3Ck?jeY8hl^*n3pP2u@_|&Y^tn{ z`}_v5)`bIC)kE$RT0uPOahbR&w;5D1v>+)Y?kJmFpR`Yt#}f`BY}qrc8H^(0doDw8 zrkzqV^RgB{l&`sx)VoTYxz&l!0ESn*A3}EijM&<{)k_a3%JlxEIviRc*h`isK*k(N zBJpOz82S`mgU^~lu&Wk4qcCT3S!44YeH$1c=j_&7R!z^c>ZP{l>-pEYP>)@UAGL5e zC*|9LY`g)L;>LtblSnW-Ua4=-h7Z&TZH=vJ$dtY0eD%@6Py7Y*8V3lZIS{xv!~%?) z$||#TT_!+3V$Wxl=rGbz0(Wk`{7&9yQs z2T7ve3=GsYEN&dn=lSRebgDHD?Ys7x39n~E_OU;rDyzfNkW{4ws)$QO?hdu1jm0gj z4tC<-GP0>kFB$eRQcLkm8-2nDNcD4n7*9H|1aG!50v~=aBB}AK`l90)HFI6t-KWca z;7-9Rm5^dgogab!401vAntX>$TPs&o<+)==O^~?5l6JEA%g!5JC?-0}E(f?fn*S zwsmPUC-^6KUy|qUw|jt%C)Otq&4>c0O_v%S{Ms(3j>>hEJM)omch~QIt!kStfsc#Ju5E2KCRQT(j17D3^@DjXCrw#(wpuREFbY>CnQmX`&VOeLw4?zYlwIQkNZytAb2p+ zRbi1ja!@3z$%!f)xj_WIFS0iXv%cPYP$++kM>>dq1h-TA%3dGj=KiET$Ur)BBred$ zB921u!l)OfDCe5}(u^4c>+T0q@E`& zh5|^G{QA%yC@`-XvhDWq8`Az$?kq|==PcBe$LTt&&Y% zJlRz#z(bYaBgYzGQq{uXvq&hEVDw^hlUqP!kwD1ozTh0pxEZS8qx%G8NJ`G5-zet) z?STQ>iMwyGM(c2WLm<<#hxZ0Vu#}i>SX@(#KInadjH+SIedSMVZ2uyEa~t(LkJC48 fnPwX3Uo2Q$ZD-j3`OX&e+XtYgq@~y(Zyol3`~M13 literal 0 HcmV?d00001 diff --git a/index.html b/index.html index b53e405..bb81226 100644 --- a/index.html +++ b/index.html @@ -5,17 +5,19 @@ Egg + - - - + + - - +

        Egg.js

        Born to build better enterprise frameworks and apps with Node.js & Koa

        + QuickStart → +

        完善的生态

        基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。

        高效自然的研发体验

        渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。

        高质量、可信赖

        高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。

        灵活、高扩展性

        约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。

        + diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..612e30c --- /dev/null +++ b/logo.svg @@ -0,0 +1,60 @@ + + + + icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/quickstart/egg.html b/quickstart/egg.html index 7ff32a7..dab8397 100644 --- a/quickstart/egg.html +++ b/quickstart/egg.html @@ -5,17 +5,22 @@ Simple Egg Application | Egg + - - - + + - - + + diff --git a/quickstart/index.html b/quickstart/index.html index 645f72d..4e953e2 100644 --- a/quickstart/index.html +++ b/quickstart/index.html @@ -5,17 +5,22 @@ QuickStart | Egg + - - - + + - - + + diff --git a/zh/guide/application.html b/zh/guide/application.html index 3ae3d35..ed59df3 100644 --- a/zh/guide/application.html +++ b/zh/guide/application.html @@ -5,13 +5,55 @@ Application | Egg + - - - + + -

        使用场景

        Application 是全局应用对象,继承于 Koa.Application,可以用于扩展全局的方法和对象。

        在一个应用中,一个进程只会实例化一个 Application 实例。

        注意事项

        Node.js 进程间是无法共享对象的,因此每个进程都会有一个 Application 实例。

        获取方式

        Application 对象几乎可以在编写应用时的任何一个地方获取到:

        ControllerService 等可以通过 this.app,或者所有 Context 对象上的 ctx.app

        // app/controller/home.js
        +    

        Application

        使用场景

        Application 是全局应用对象,继承于 Koa.Application,可以用于扩展全局的方法和对象。

        在一个应用中,一个进程只会实例化一个 Application 实例。

        注意事项

        Node.js 进程间是无法共享对象的,因此每个进程都会有一个 Application 实例。

        获取方式

        Application 对象几乎可以在编写应用时的任何一个地方获取到:

        ControllerService 等可以通过 this.app,或者所有 Context 对象上的 ctx.app

        // app/controller/home.js
         class HomeController extends Controller {
           async index() {
             // 从 `Controller/Service` 基类继承的属性: `this.app`
        @@ -20,27 +62,27 @@
             console.log(this.ctx.app.config.name);
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        几乎所有被框架 Loader 加载的文件,都可以 export 一个函数,并接收 app 作为参数:

        Router

        // app/router.js
        +

        几乎所有被框架 Loader 加载的文件,都可以 export 一个函数,并接收 app 作为参数:

        Router

        // app/router.js
         module.exports = app => {
           const { router, controller } = app;
           router.get('/', controller.home.index);
         };
        -
        1
        2
        3
        4
        5

        Middleware

        // app/middleware/response_time.js
        +

        Middleware

        // app/middleware/response_time.js
         module.exports = (options, app) => {
           // 加载期传递 app 实例
           console.log(app);
         
           return async function responseTime(ctx, next) {};
         };
        -
        1
        2
        3
        4
        5
        6
        7

        常用属性和方法

        app.config

        应用的配置

        app.router

        对应的路由对象。

        app.controller

        对应的 Controller 对象。

        app.logger

        用于应用级别的日志记录,如记录启动阶段的一些数据信息,记录一些业务上与请求无关的信息。

        更多参见 日志 文档。

        app.middleware

        挂载后的所有 Middleware 对象。

        app.server

        对应的 HTTP ServerHTTPS Server 实例。

        可以在 生命周期serverDidReady 事件之后获取到。

        app.curl()

        通过 HttpClient 发起请求。

        app.createAnonymousContext()

        在某些非用户请求的场景下,我们也需要访问到 Context,此时该方法获取:

        const ctx = app.createAnonymousContext();
        +

        常用属性和方法

        app.config

        应用的配置

        app.router

        对应的路由对象。

        app.controller

        对应的 Controller 对象。

        app.logger

        用于应用级别的日志记录,如记录启动阶段的一些数据信息,记录一些业务上与请求无关的信息。

        更多参见 日志 文档。

        app.middleware

        挂载后的所有 Middleware 对象。

        app.server

        对应的 HTTP ServerHTTPS Server 实例。

        可以在 生命周期serverDidReady 事件之后获取到。

        app.curl()

        通过 HttpClient 发起请求。

        app.createAnonymousContext()

        在某些非用户请求的场景下,我们也需要访问到 Context,此时该方法获取:

        const ctx = app.createAnonymousContext();
         await ctx.service.user.list();
        -
        1
        2

        如何扩展

        我们支持开发者通过 app/extend/application.js 来扩展 Application

        方法扩展

        // app/extend/application.js
        +

        如何扩展

        我们支持开发者通过 app/extend/application.js 来扩展 Application

        方法扩展

        // app/extend/application.js
         module.exports = {
           foo(param) {
             // this 就是 app 对象,在其中可以调用 app 上的其他方法,或访问属性
           },
         };
        -
        1
        2
        3
        4
        5
        6

        属性扩展

        一般来说属性的计算只需要进行一次,否则在多次访问属性时会计算多次,降低应用性能。

        推荐的方式是使用 Symbol + Getter 的模式来实现缓存。

        例如,增加一个 app.nunjucks 属性:

        // app/extend/application.js
        +

        属性扩展

        一般来说属性的计算只需要进行一次,否则在多次访问属性时会计算多次,降低应用性能。

        推荐的方式是使用 Symbol + Getter 的模式来实现缓存。

        例如,增加一个 app.nunjucks 属性:

        // app/extend/application.js
         const NUNJUCKS = Symbol('Application#nunjucks');
         const nunjuck = require('nunjuck');
         
        @@ -53,7 +95,7 @@
             return this[NUNJUCKS];
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        编写测试

        对于扩展的逻辑,我们一般需要通过单元测试来保证代码质量。

        // test/app/extend/application.js
        +

        编写测试

        对于扩展的逻辑,我们一般需要通过单元测试来保证代码质量。

        // test/app/extend/application.js
         const { app, assert } = require('egg-mock');
         
         describe('test/app/extend/application.js', () => {
        @@ -62,20 +104,14 @@
             assert(app.nunjucks.renderString('{{ name }}', { name: 'TZ' }) === 'TZ');
           });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        按照环境进行扩展

        另外,还可以根据运行环境进行有选择的扩展。

        app/extend/application.unittest.js 定义的扩展,只在 unittest 环境生效。

        // app/extend/application.unittest.js
        +

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        按照环境进行扩展

        另外,还可以根据运行环境进行有选择的扩展。

        app/extend/application.unittest.js 定义的扩展,只在 unittest 环境生效。

        // app/extend/application.unittest.js
         module.exports = {
           mockXX(k, v) {
           },
         };
        -
        1
        2
        3
        4
        5

        这个文件只会在 unittest 环境加载。

        同理,对于下文中的 ApplicationContextRequestResponseHelper 都可以使用这种方式针对某个环境进行扩展。

        - +

        这个文件只会在 unittest 环境加载。

        同理,对于下文中的 ApplicationContextRequestResponseHelper 都可以使用这种方式针对某个环境进行扩展。

        + diff --git a/zh/guide/config.html b/zh/guide/config.html index be6484a..7193879 100644 --- a/zh/guide/config.html +++ b/zh/guide/config.html @@ -5,13 +5,55 @@ 配置 | Egg + - - - + + -

        方案选型

        配置的管理有多种方案,以下列一些常见的方案:

        • 使用平台管理配置,应用构建时将当前环境的配置放入包内,启动时指定该配置。但应用就无法一次构建多次部署,而且本地开发环境想使用配置会变的很麻烦。
        • 使用平台管理配置,在启动时将当前环境的配置通过环境变量传入,这是比较优雅的方式,但框架对运维的要求会比较高,需要部署平台支持,同时开发环境也有相同痛点。
        • 使用代码管理配置,在代码中添加多个环境的配置,在启动时传入当前环境的参数即可。但无法全局配置,必须修改代码。

        我们选择了最后一种配置方案,配置即代码,配置的变更也应该经过 Review 后才能发布。应用包本身是可以部署在多个环境的,只需要指定运行环境即可。

        运行环境

        Egg 应用是一次构建多地部署,所以 Egg 会根据外部传入的一些配置来决定如何运行。

        env

        应用开发者可以通过 app.config.env 获取当前运行环境。

        以下为框架支持的运行环境:

        serverEnv NODE_ENV 说明
        local - 本地开发环境
        unittest test 单元测试环境
        prod production 生产环境

        运行环境会决定插件是否开启,选择默认的配置项,对开发者非常友好。

        配置文件

        框架会根据不同的运行环境来加载不同的配置文件。

        showcase
        +    

        配置

        方案选型

        配置的管理有多种方案,以下列一些常见的方案:

        • 使用平台管理配置,应用构建时将当前环境的配置放入包内,启动时指定该配置。但应用就无法一次构建多次部署,而且本地开发环境想使用配置会变的很麻烦。
        • 使用平台管理配置,在启动时将当前环境的配置通过环境变量传入,这是比较优雅的方式,但框架对运维的要求会比较高,需要部署平台支持,同时开发环境也有相同痛点。
        • 使用代码管理配置,在代码中添加多个环境的配置,在启动时传入当前环境的参数即可。但无法全局配置,必须修改代码。

        我们选择了最后一种配置方案,配置即代码,配置的变更也应该经过 Review 后才能发布。应用包本身是可以部署在多个环境的,只需要指定运行环境即可。

        运行环境

        Egg 应用是一次构建多地部署,所以 Egg 会根据外部传入的一些配置来决定如何运行。

        env

        应用开发者可以通过 app.config.env 获取当前运行环境。

        以下为框架支持的运行环境:

        serverEnv NODE_ENV 说明
        local - 本地开发环境
        unittest test 单元测试环境
        prod production 生产环境

        运行环境会决定插件是否开启,选择默认的配置项,对开发者非常友好。

        配置文件

        框架会根据不同的运行环境来加载不同的配置文件。

        showcase
         ├── app
         └── config
             ├── config.default.js
        @@ -19,19 +61,19 @@
             ├── config.unittest.js
             ├── config.default.js
             └── config.local.js
        -
        1
        2
        3
        4
        5
        6
        7
        8
        • config.default.js 为默认的配置文件,所有环境都会加载它,绝大部分配置应该写在这里
        • 然后会根据运行环境加载对应的配置,并覆盖默认配置的同名配置。 -
          • prod 环境会加载 config.prod.jsconfig.default.js 文件。
          • 然后 config.prod.js 会覆盖 config.default.js 的同名配置。

        具体的运行环境与配置文件的加载规则,参见应用部署文档相关章节。

        配置定义

        配置文件返回的是一个 Object 对象,支持三种写法,请根据具体场合选择合适的写法。

        // config/config.default.js
        +
        • config.default.js 为默认的配置文件,所有环境都会加载它,绝大部分配置应该写在这里
        • 然后会根据运行环境加载对应的配置,并覆盖默认配置的同名配置。 +
          • prod 环境会加载 config.prod.jsconfig.default.js 文件。
          • 然后 config.prod.js 会覆盖 config.default.js 的同名配置。

        具体的运行环境与配置文件的加载规则,参见应用部署文档相关章节。

        配置定义

        配置文件返回的是一个 Object 对象,支持三种写法,请根据具体场合选择合适的写法。

        // config/config.default.js
         module.exports = {
           logger: {
             dir: '/home/admin/logs/demoapp',
           },
         };
        -
        1
        2
        3
        4
        5
        6

        配置文件也可以简化的写成 exports.key = value 形式。

        // config/config.default.js
        +

        配置文件也可以简化的写成 exports.key = value 形式。

        // config/config.default.js
         exports.keys = 'my-cookie-secret-key';
         exports.logger = {
           level: 'DEBUG',
         };
        -
        1
        2
        3
        4
        5

        也可以是一个 function,入参为 appInfo

        // config/config.default.js
        +

        也可以是一个 function,入参为 appInfo

        // config/config.default.js
         const path = require('path');
         
         module.exports = appInfo => {
        @@ -43,13 +85,13 @@
         
           return config;
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        友情提示

        一些插件文档里面,描述配置时,可能会使用 exports.pluginName = {} 的方式。

        复制时,请根据你的具体配置写法进行修正。

        AppInfo

        内置的 appInfo 有:

        appInfo 说明
        pkg package.json
        name 应用名,同 pkg.name
        baseDir 应用的代码根目录。
        HOME 用户目录,如 admin 账户为 /home/admin
        root 应用根目录,localunittest 环境下为 baseDir,其他都为 HOME

        注意事项

        值得注意的是:appInfo.root 是一个优雅的适配。

        比如在服务器环境我们会使用 /home/admin/logs 作为日志目录,而本地开发时又不想污染用户目录,这样的适配就很好解决这个问题。

        加载规则

        应用、插件、框架都可以定义这些配置,而且目录结构都是一致的。

        但存在优先级(应用 > 框架 > 插件),相对于此运行环境的优先级会更高。

        框架会按加载顺序使用 extend2 模块进行深度拷贝。

        比如在 prod 环境加载一个配置的加载顺序如下,后加载的会覆盖前面的同名配置。

        -> 插件 config.default.js
        +

        友情提示

        一些插件文档里面,描述配置时,可能会使用 exports.pluginName = {} 的方式。

        复制时,请根据你的具体配置写法进行修正。

        AppInfo

        内置的 appInfo 有:

        appInfo 说明
        pkg package.json
        name 应用名,同 pkg.name
        baseDir 应用的代码根目录。
        HOME 用户目录,如 admin 账户为 /home/admin
        root 应用根目录,localunittest 环境下为 baseDir,其他都为 HOME

        注意事项

        值得注意的是:appInfo.root 是一个优雅的适配。

        比如在服务器环境我们会使用 /home/admin/logs 作为日志目录,而本地开发时又不想污染用户目录,这样的适配就很好解决这个问题。

        加载规则

        应用、插件、框架都可以定义这些配置,而且目录结构都是一致的。

        但存在优先级(应用 > 框架 > 插件),相对于此运行环境的优先级会更高。

        框架会按加载顺序使用 extend2 模块进行深度拷贝。

        比如在 prod 环境加载一个配置的加载顺序如下,后加载的会覆盖前面的同名配置。

        -> 插件 config.default.js
         -> 框架 config.default.js
         -> 应用 config.default.js
         -> 插件 config.prod.js
         -> 框架 config.prod.js
         -> 应用 config.prod.js
        -
        1
        2
        3
        4
        5
        6

        注意事项

        合并配置时,对于数组的处理是直接覆盖而不是合并。

        const a = {
        +

        注意事项

        合并配置时,对于数组的处理是直接覆盖而不是合并。

        const a = {
           arr: [ 1, 2 ],
         };
         const b = {
        @@ -57,7 +99,7 @@
         };
         extend(true, a, b);
         // => { arr: [ 3 ] }
        -
        1
        2
        3
        4
        5
        6
        7
        8

        根据上面的例子,框架直接覆盖数组而不是进行合并。

        常见问题

        为什么我的配置不生效?

        首先,要确保不会犯以下的低级错误:

        // config/config.default.js
        +

        根据上面的例子,框架直接覆盖数组而不是进行合并。

        常见问题

        为什么我的配置不生效?

        首先,要确保不会犯以下的低级错误:

        // config/config.default.js
         exports.someKeys = 'abc';
         
         module.exports = appInfo => {
        @@ -65,15 +107,9 @@
           config.keys = '123456';
           return config;
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8

        其次,参考下一条 FAQ 来排查问题。

        如何查看最终的配置?

        框架的配置功能比较强大,有不同环境变量,又有框架、插件、应用等很多地方配置。

        如果你分析问题时,想知道当前运行时使用的最终配置,框架提供了:

        • run/application_config.json 文件:最终的配置合并结果,可以用来分析问题。
        • run/application_config_meta.json 文件:用来排查属性的来源。

        另外,基于安全的考虑,dump 出的文件中会对一些字段进行脱敏处理,主要包括两类:

        • 如密码、密钥等安全字段,可以通过 config.dump.ignore 配置。
        • 如函数、Buffer 等类型,JSON.stringify 后的内容特别大。

        友情提示

        注意:run 目录是每次启动期都会 dump 的信息,用于问题排查。

        开发者修改该目录的文件将不会有任何效果,应该把该目录加到 gitignore 中。

        - +

        其次,参考下一条 FAQ 来排查问题。

        如何查看最终的配置?

        框架的配置功能比较强大,有不同环境变量,又有框架、插件、应用等很多地方配置。

        如果你分析问题时,想知道当前运行时使用的最终配置,框架提供了:

        • run/application_config.json 文件:最终的配置合并结果,可以用来分析问题。
        • run/application_config_meta.json 文件:用来排查属性的来源。

        另外,基于安全的考虑,dump 出的文件中会对一些字段进行脱敏处理,主要包括两类:

        • 如密码、密钥等安全字段,可以通过 config.dump.ignore 配置。
        • 如函数、Buffer 等类型,JSON.stringify 后的内容特别大。

        友情提示

        注意:run 目录是每次启动期都会 dump 的信息,用于问题排查。

        开发者修改该目录的文件将不会有任何效果,应该把该目录加到 gitignore 中。

        + diff --git a/zh/guide/context.html b/zh/guide/context.html index 63878a2..e3dd980 100644 --- a/zh/guide/context.html +++ b/zh/guide/context.html @@ -5,20 +5,62 @@ Context | Egg + - - - + + -

        使用场景

        Context 是一个 请求级别 的对象,继承自 Koa.Context

        在每一次收到用户请求时都会实例化一个 Context 对象,它封装了该次请求的相关信息,并提供了许多便捷的方法来获取请求参数或者设置响应信息。

        框架会将所有的 Service 挂载到 Context 实例上,某些插件也会将挂载一些其他的方法和对象。

        获取方式

        最常见的 Context 实例获取方式是在 Middleware, Controller 以及 Service 中。

        ControllerService 等可以通过 this.ctx 获取:

        // app/controller/home.js
        +    

        Context

        使用场景

        Context 是一个 请求级别 的对象,继承自 Koa.Context

        在每一次收到用户请求时都会实例化一个 Context 对象,它封装了该次请求的相关信息,并提供了许多便捷的方法来获取请求参数或者设置响应信息。

        框架会将所有的 Service 挂载到 Context 实例上,某些插件也会将挂载一些其他的方法和对象。

        获取方式

        最常见的 Context 实例获取方式是在 Middleware, Controller 以及 Service 中。

        ControllerService 等可以通过 this.ctx 获取:

        // app/controller/home.js
         class HomeController extends Controller {
           async index() {
             const { ctx } = this;
             ctx.body = ctx.query('name');
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7

        MiddlewareKoa 框架保持一致:

        // app/middleware/response_time.js
        +

        MiddlewareKoa 框架保持一致:

        // app/middleware/response_time.js
         module.exports = () => {
           return async function responseTime(ctx, next) {
             const start = Date.now();
        @@ -27,13 +69,13 @@
             ctx.set('X-Response-Time', `${cost}ms`);
           }
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        在某些非用户请求的场景下,我们也需要访问到 Context,此时可以通过 ApplicationcreateAnonymousContext() 方法获取:

        const ctx = app.createAnonymousContext();
        +

        在某些非用户请求的场景下,我们也需要访问到 Context,此时可以通过 ApplicationcreateAnonymousContext() 方法获取:

        const ctx = app.createAnonymousContext();
         await ctx.service.user.list();
        -
        1
        2

        定时任务 也接收 Context 实例作为参数,以便执行一些定时的业务逻辑:

        // app/schedule/refresh.js
        +

        定时任务 也接收 Context 实例作为参数,以便执行一些定时的业务逻辑:

        // app/schedule/refresh.js
         exports.task = async ctx => {
           await ctx.service.posts.refresh();
         };
        -
        1
        2
        3
        4

        常用属性和方法

        ctx.app

        对应的 Application 实例。

        ctx.service

        对应的 Service 实例。

        ctx.logger

        与请求相关的 ContextLogger 实例。

        它打印的日志都会在前面带上一些当前请求相关的信息。

        [$userId/$ip/$traceId/${cost}ms $method $url]

        通过这些信息,我们可以从日志快速定位请求,并串联一次请求中的所有的日志。

        更多参见 日志 文档。

        ctx.curl()

        通过 HttpClient 发起请求。

        ctx.runInBackground()

        有些时候,我们在处理完用户请求后,希望立即返回响应,但同时需要异步执行一些操作。

        // app/controller/trade.js
        +

        常用属性和方法

        ctx.app

        对应的 Application 实例。

        ctx.service

        对应的 Service 实例。

        ctx.logger

        与请求相关的 ContextLogger 实例。

        它打印的日志都会在前面带上一些当前请求相关的信息。

        [$userId/$ip/$traceId/${cost}ms $method $url]

        通过这些信息,我们可以从日志快速定位请求,并串联一次请求中的所有的日志。

        更多参见 日志 文档。

        ctx.curl()

        通过 HttpClient 发起请求。

        ctx.runInBackground()

        有些时候,我们在处理完用户请求后,希望立即返回响应,但同时需要异步执行一些操作。

        // app/controller/trade.js
         class TradeController extends Controller {
           async buy () {
             const goods = {};
        @@ -48,7 +90,7 @@
             ctx.body = { msg: '已下单' };
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        ctx.query

        在 URL 中 ? 后面的部分是一个 Query String,这一部分经常用于 GET 请求中传递参数。

        // GET /api/user/list?limit=10&sort=name
        +

        ctx.query

        在 URL 中 ? 后面的部分是一个 Query String,这一部分经常用于 GET 请求中传递参数。

        // GET /api/user/list?limit=10&sort=name
         class UserController extends Controller {
           async list() {
             console.log(this.ctx.query);
        @@ -56,7 +98,7 @@
             ctx.body = 'hi, egg';
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8

        对应的测试:

        // test/controller/home.test.js
        +

        对应的测试:

        // test/controller/home.test.js
         const { app, mock, assert } = require('egg-mock');
         
         describe('test/controller/home.test.js', () => {
        @@ -68,25 +110,25 @@
               .expect(200);
           });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        友情提示

        鉴于 HTTP 协议的约定,在请求中获取到的查询参数,均为字符串,如有需要需自行转型。

        值得注意的是,ctx.query 对重复的 key 只取第一个值,后面将被忽略。

        /api/user?sort=name&id=2&id=3query.id === '2'

        这样处理的原因是为了保持统一性,由于通常情况下我们都不会设计让用户传递相同的 key,所以我们经常会写类似下面的代码:

        const key = ctx.query.key || '';
        +

        友情提示

        鉴于 HTTP 协议的约定,在请求中获取到的查询参数,均为字符串,如有需要需自行转型。

        值得注意的是,ctx.query 对重复的 key 只取第一个值,后面将被忽略。

        /api/user?sort=name&id=2&id=3query.id === '2'

        这样处理的原因是为了保持统一性,由于通常情况下我们都不会设计让用户传递相同的 key,所以我们经常会写类似下面的代码:

        const key = ctx.query.key || '';
         if (key.startsWith('egg')) {
           // do something
         }
        -
        1
        2
        3
        4

        而如果有人故意发起请求带上重复的 key 就会引发系统异常。因此框架保证了从 ctx.query 上获取的参数一旦存在,一定是字符串类型。

        ctx.queries

        如果你的系统设计允许用户传递相同的 key(不推荐),可以使用 ctx.queries

        // GET /api/user?sort=name&id=2&id=3
        +

        而如果有人故意发起请求带上重复的 key 就会引发系统异常。因此框架保证了从 ctx.query 上获取的参数一旦存在,一定是字符串类型。

        ctx.queries

        如果你的系统设计允许用户传递相同的 key(不推荐),可以使用 ctx.queries

        // GET /api/user?sort=name&id=2&id=3
         class UserController extends Controller {
           async list() {
             console.log(this.ctx.queries);
             // { sort: [ 'name' ], id: [ '2', '3' ] }
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        • queries.id === [ '2', '3']
        • ctx.queries 的属性一定是数组类型,如 queries.name === [ 'sort' ]
        • 如果你确定只会传递一个,则应该使用 query.sort 而不是 queries.sort

        ctx.params

        获取 Router 命名参数。

        ctx.routerPath

        获取当前命中的 Router 路径。

        ctx.routerName

        获取当前命中的 Router 别名。

        ctx.request.body

        框架内置了 bodyParser,用于获取 POST 等的 请求 body

        class UserController extends Controller {
        +
        • queries.id === [ '2', '3']
        • ctx.queries 的属性一定是数组类型,如 queries.name === [ 'sort' ]
        • 如果你确定只会传递一个,则应该使用 query.sort 而不是 queries.sort

        ctx.params

        获取 Router 命名参数。

        ctx.routerPath

        获取当前命中的 Router 路径。

        ctx.routerName

        获取当前命中的 Router 别名。

        ctx.request.body

        框架内置了 bodyParser,用于获取 POST 等的 请求 body

        class UserController extends Controller {
           async create() {
             // 获取请求信息 `{ name: 'TZ' }`
             console.log(this.ctx.request.body);
             // ...
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7

        对应的测试:

        // test/controller/home.test.js
        +

        对应的测试:

        // test/controller/home.test.js
         it('should POST form', () => {
         
           // 跳过 `CSRF` 校验
        @@ -107,24 +149,24 @@
             .send({ name: 'TZ' })
             .expect(200);
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        ctx.request.files

        获取 file 模式上传的文件对象,参见 文件上传 文档。

        ctx.get(name)

        获取请求 Header 信息。

        由于 HTTP 协议中 Header 是忽略大小写的,因此 ctx.headers 中的 Key 一律转为小写。

        一般我们推荐使用 ctx.get(name) 来获取对应的 Header,它会忽略大小写。

        ctx.get('User-Agent');
        +

        ctx.request.files

        获取 file 模式上传的文件对象,参见 文件上传 文档。

        ctx.get(name)

        获取请求 Header 信息。

        由于 HTTP 协议中 Header 是忽略大小写的,因此 ctx.headers 中的 Key 一律转为小写。

        一般我们推荐使用 ctx.get(name) 来获取对应的 Header,它会忽略大小写。

        ctx.get('User-Agent');
         
         ctx.headers['user-agent'];
         
         // 取不到值
         ctx.headers['User-Agent'];
        -
        1
        2
        3
        4
        5
        6

        ctx.cookies

        读取 Cookie 对象,参见 Cookie 文档。

        ctx.status =

        HTTP 设计了非常多的状态码

        正确地设置状态码,可以让响应更符合语义,参考 List of HTTP status codes

        框架提供了一个便捷的 Setter 来进行状态码的设置:

        class UserController extends Controller {
        +

        ctx.cookies

        读取 Cookie 对象,参见 Cookie 文档。

        ctx.status =

        HTTP 设计了非常多的状态码

        正确地设置状态码,可以让响应更符合语义,参考 List of HTTP status codes

        框架提供了一个便捷的 Setter 来进行状态码的设置:

        class UserController extends Controller {
           async create() {
             // 设置状态码为 201
             this.ctx.status = 201;
           }
         };
        -
        1
        2
        3
        4
        5
        6

        对应的测试:

        it('should POST /user', () => {
        +

        对应的测试:

        it('should POST /user', () => {
           return app.httpRequest()
             .post('/user')
             .expect(201);
         });
        -
        1
        2
        3
        4
        5

        ctx.body =

        HTTP 请求的绝大部分数据都是通过 body 发送给请求方的。

        • 作为 API 接口,通常直接赋值一个 Object 对象。
        • 作为 HTML 页面,通常返回 HTML 字符串。
        • 作为文件下载等场景,还可以直接赋值为 Stream
        // app/controller/home.js
        +

        ctx.body =

        HTTP 请求的绝大部分数据都是通过 body 发送给请求方的。

        • 作为 API 接口,通常直接赋值一个 Object 对象。
        • 作为 HTML 页面,通常返回 HTML 字符串。
        • 作为文件下载等场景,还可以直接赋值为 Stream
        // app/controller/home.js
         class HomeController extends Controller {
           // GET /
           async index() {
        @@ -152,7 +194,7 @@
             ctx.body = result.res;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28

        对应的测试:

        it('should response html', () => {
        +

        对应的测试:

        it('should response html', () => {
           return app.httpRequest()
             .get('/')
             .expect('<html><h1>Hello</h1></html>')
        @@ -171,7 +213,7 @@
               assert(res.body.name === 'egg');
             });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        ctx.set(name, value)

        除了 状态码响应体 外,还可以通过响应 Header 设置一些扩展信息。

        • ctx.set(key, value):可以设置一个 Header
        • ctx.set(headers):可以同时设置多个 Header
        // app/controller/proxy.js
        +

        ctx.set(name, value)

        除了 状态码响应体 外,还可以通过响应 Header 设置一些扩展信息。

        • ctx.set(key, value):可以设置一个 Header
        • ctx.set(headers):可以同时设置多个 Header
        // app/controller/proxy.js
         class ProxyController extends Controller {
           async show() {
             const { ctx } = this;
        @@ -182,31 +224,31 @@
             ctx.set('x-response-time', `${cost}ms`);
           }
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        对应的测试:

        it('should send response header', () => {
        +

        对应的测试:

        it('should send response header', () => {
           return app.httpRequest()
             .post('/api/post')
             .expect('X-Response-Time', /\d+ms/);
         });
        -
        1
        2
        3
        4
        5

        ctx.type =

        和请求中的 body 一样,在响应也需要对应的 Content-Type 告知客户端如何对数据进行解析。

        框架提供了该语法糖,等价于 ctx.set('Content-Type', mime)

        • json:对应于 API 接口的 application/json
        • html:对应于 HTML 页面的 text/html
        • 更多参见 mime-types

        一般可以省略,框架会自动根据取值,来赋值对应的 Content-Type

        // app/controller/user.js
        +

        ctx.type =

        和请求中的 body 一样,在响应也需要对应的 Content-Type 告知客户端如何对数据进行解析。

        框架提供了该语法糖,等价于 ctx.set('Content-Type', mime)

        • json:对应于 API 接口的 application/json
        • html:对应于 HTML 页面的 text/html
        • 更多参见 mime-types

        一般可以省略,框架会自动根据取值,来赋值对应的 Content-Type

        // app/controller/user.js
         class UserController extends Controller {
           async list() {
             // 一般可以省略,框架会自动根据取值
             this.ctx.body = { name: 'egg' };
           }
         };
        -
        1
        2
        3
        4
        5
        6
        7

        对应的测试:

        it('should response json', () => {
        +

        对应的测试:

        it('should response json', () => {
           return app.httpRequest()
             .get('/api/user')
             .expect('Content-Type', /json/);
         });
        -
        1
        2
        3
        4
        5

        ctx.render()

        通常来说,我们不会手写 HTML 页面,而是会通过模板引擎进行生成。

        我们可以通过使用模板插件,来提供渲染能力。

        class HomeController extends Controller {
        +

        ctx.render()

        通常来说,我们不会手写 HTML 页面,而是会通过模板引擎进行生成。

        我们可以通过使用模板插件,来提供渲染能力。

        class HomeController extends Controller {
           async index() {
             const ctx = this.ctx;
             await ctx.render('home.tpl', { name: 'egg' });
             // ctx.body = await ctx.renderString('hi, {{ name }}', { name: 'egg' });
           }
         };
        -
        1
        2
        3
        4
        5
        6
        7

        具体示例可以查看模板引擎

        ctx.redirect()

        重定向请求,默认为 302,如果需要,可以设置 ctx.status = 301

        class UserController extends Controller {
        +

        具体示例可以查看模板引擎

        ctx.redirect()

        重定向请求,默认为 302,如果需要,可以设置 ctx.status = 301

        class UserController extends Controller {
           async logout() {
             const { ctx } = this;
         
        @@ -214,13 +256,13 @@
             ctx.redirect(ctx.get('referer') || '/');
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8

        对应的测试:

        it('should logout', () => {
        +

        对应的测试:

        it('should logout', () => {
           return app.httpRequest()
             .get('/user/logout')
             .expect('Location', '/')
             .expect(302);
         });
        -
        1
        2
        3
        4
        5
        6

        安全提示

        基于安全考虑,默认只允许重定向处于白名单的域名。

        更多参见 安全链接 文档。

        ctx.request

        由于 Node.js 原生的 HTTP Request 对象比较底层。

        因此 Koa 做了一层薄薄的 Koa.Request 封装,提供了一系列方法获取 HTTP 请求相关信息。

        一般你不需要直接调用它,Context 已经代理了它们的大部分方法和属性,如上文所述。

        唯一的例外是:获取 POST 的 body 应该使用 ctx.request.body,而不是 ctx.body

        // app/controller/user.js
        +

        安全提示

        基于安全考虑,默认只允许重定向处于白名单的域名。

        更多参见 安全链接 文档。

        ctx.request

        由于 Node.js 原生的 HTTP Request 对象比较底层。

        因此 Koa 做了一层薄薄的 Koa.Request 封装,提供了一系列方法获取 HTTP 请求相关信息。

        一般你不需要直接调用它,Context 已经代理了它们的大部分方法和属性,如上文所述。

        唯一的例外是:获取 POST 的 body 应该使用 ctx.request.body,而不是 ctx.body

        // app/controller/user.js
         class UserController extends Controller {
           async update() {
             const { app, ctx } = this;
        @@ -234,7 +276,7 @@
             ctx.response.body = await app.service.update(id, postBody);
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        ctx.response

        由于 Node.js 原生的 HTTP Response 对象比较底层。

        因此 Koa 做了一层薄薄的 Koa.Response 封装,提供了一系列方法设置 HTTP 响应。

        一般你不需要直接调用它,Context 已经代理了它们的大部分方法和属性,如上文所述。

        更多

        更多语法糖,请参见 Koa Aliases 文档。

        如何扩展

        我们支持开发者通过:

        • 通过 app/extend/context.js 来扩展 Context
        • 通过 app/extend/request.js 来扩展 Request
        • 通过 app/extend/response.js 来扩展 Response
        • 同样也支持在 app/extend/context.unittest.js 来根据运行环境扩展。

        属性扩展

        一般来说属性的计算只需要进行一次,否则在多次访问属性时会计算多次,降低应用性能。

        推荐的方式是使用 Symbol + Getter 的模式来实现缓存。

        // app/extend/context.js
        +

        ctx.response

        由于 Node.js 原生的 HTTP Response 对象比较底层。

        因此 Koa 做了一层薄薄的 Koa.Response 封装,提供了一系列方法设置 HTTP 响应。

        一般你不需要直接调用它,Context 已经代理了它们的大部分方法和属性,如上文所述。

        更多

        更多语法糖,请参见 Koa Aliases 文档。

        如何扩展

        我们支持开发者通过:

        • 通过 app/extend/context.js 来扩展 Context
        • 通过 app/extend/request.js 来扩展 Request
        • 通过 app/extend/response.js 来扩展 Response
        • 同样也支持在 app/extend/context.unittest.js 来根据运行环境扩展。

        属性扩展

        一般来说属性的计算只需要进行一次,否则在多次访问属性时会计算多次,降低应用性能。

        推荐的方式是使用 Symbol + Getter 的模式来实现缓存。

        // app/extend/context.js
         const UA = Symbol('Context#ua');
         const useragent = require('useragent');
         
        @@ -248,7 +290,7 @@
             return this[UA];
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        编写测试

        // test/app/extend/context.js
        +

        编写测试

        // test/app/extend/context.js
         const { app, assert } = require('egg-mock');
         
         describe('test/app/extend/contex.js', () => {
        @@ -263,15 +305,9 @@
             assert(ctx.ua.chrome);
           });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        - +

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        + diff --git a/zh/guide/controller.html b/zh/guide/controller.html index a211e1d..448b4ab 100644 --- a/zh/guide/controller.html +++ b/zh/guide/controller.html @@ -5,13 +5,55 @@ Controller | Egg + - - - + + -

        使用场景

        Controller 负责解析用户的输入,处理后返回相应的结果

        Controller 其实就是一个特殊的 Middleware,它在洋葱模型的最里面。

        场景举例:

        • 提供 AJAX 接口,接收用户的参数,查找数据库返回给用户或将用户的请求更新到数据库中。
        • 根据用户访问的 URL,渲染对应的模板返回 HTML 给浏览器渲染。
        • 作为代理服务器时,将用户的请求转发到其他服务上,并将处理结果返回给用户。

        最佳实践

        Controller 仅负责 HTTP 层的相关处理逻辑,不要包含太多业务逻辑。

        1. 获取用户通过 HTTP 传递过来的请求参数。
        2. 校验、组装参数。
        3. 调用 Service 进行业务处理。
        4. 必要时处理转换 Service 的返回结果,如渲染模板。
        5. 通过 HTTP 将结果响应给用户。

        编写 Controller

        我们约定把 Controller 放置在 app/controller 目录下:

        // app/controller/user.js
        +    

        Controller

        使用场景

        Controller 负责解析用户的输入,处理后返回相应的结果

        Controller 其实就是一个特殊的 Middleware,它在洋葱模型的最里面。

        场景举例:

        • 提供 AJAX 接口,接收用户的参数,查找数据库返回给用户或将用户的请求更新到数据库中。
        • 根据用户访问的 URL,渲染对应的模板返回 HTML 给浏览器渲染。
        • 作为代理服务器时,将用户的请求转发到其他服务上,并将处理结果返回给用户。

        最佳实践

        Controller 仅负责 HTTP 层的相关处理逻辑,不要包含太多业务逻辑。

        1. 获取用户通过 HTTP 传递过来的请求参数。
        2. 校验、组装参数。
        3. 调用 Service 进行业务处理。
        4. 必要时处理转换 Service 的返回结果,如渲染模板。
        5. 通过 HTTP 将结果响应给用户。

        编写 Controller

        我们约定把 Controller 放置在 app/controller 目录下:

        // app/controller/user.js
         const { Controller } = require('egg');
         
         class UserController extends Controller {
        @@ -33,67 +75,67 @@
           }
         }
         module.exports = UserController;
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

        然后通过路由配置 URL 请求映射:

        // app/router.js
        +

        然后通过路由配置 URL 请求映射:

        // app/router.js
         module.exports = app => {
           const { router, controller } = app;
           router.post('/api/user', controller.user.create);
         };
        -
        1
        2
        3
        4
        5

        然后通过 POST /api/user 即可访问。

        生命周期

        Controller 类会被挂载到 app.Controller 上,用于在 路由 配置 URL 映射。

        但处理用户请求时,每一个请求都会实例化一个 Controller 实例。

        Controller 是延迟实例化的,仅在请求调用到该 Controller 的时候,才会实例化。

        因此,无需担心实例化的性能损耗,经过我们大规模的实践证明,可以忽略不计。

        挂载规则

        约定放置在 app/controller 目录下,支持多级目录,对应的文件名会转换为驼峰格式

        app/controller/biz/user.js => app.controller.biz.user
        +

        然后通过 POST /api/user 即可访问。

        生命周期

        Controller 类会被挂载到 app.Controller 上,用于在 路由 配置 URL 映射。

        但处理用户请求时,每一个请求都会实例化一个 Controller 实例。

        Controller 是延迟实例化的,仅在请求调用到该 Controller 的时候,才会实例化。

        因此,无需担心实例化的性能损耗,经过我们大规模的实践证明,可以忽略不计。

        挂载规则

        约定放置在 app/controller 目录下,支持多级目录,对应的文件名会转换为驼峰格式

        app/controller/biz/user.js => app.controller.biz.user
         app/controller/sync_user.js => app.controller.syncUser
         app/controller/HackerNews.js => app.controller.hackerNews
        -
        1
        2
        3

        常用属性和方法

        Controller 实例继承 egg.Controller,提供以下属性:

        • this.ctx: 当前请求的上下文 Context 的实例,可以拿到各种便捷属性和方法。
        • this.app: 当前应用 Application 的实例,可以拿到全局对象和方法。
        • this.service:应用定义的 Service,可以调用业务逻辑层。
        • this.config:应用运行时的配置项
        • this.logger:logger 对象,使用方法类似 Context Logger,不同之处是通过这个 Logger 对象记录的日志,会额外加上该日志的文件路径,以便快速定位日志打印位置。

        Controller 实战

        HTTP 基础

        由于 Controller 基本上是业务开发中唯一和 HTTP 协议打交道的地方,在继续往下了解之前,我们首先简单的看一下 HTTP 协议是怎样的。

        如果我们发起一个 HTTP 请求来访问前面写的的 Controller

        $ curl -X POST http://localhost:7001/api/user -d '{"name":"TZ"}' -H 'Content-Type:application/json; charset=UTF-8'
        -
        1

        通过 curl 发出的 HTTP 请求的内容就会是下面这样的:

        POST /api/user HTTP/1.1
        +

        常用属性和方法

        Controller 实例继承 egg.Controller,提供以下属性:

        • this.ctx: 当前请求的上下文 Context 的实例,可以拿到各种便捷属性和方法。
        • this.app: 当前应用 Application 的实例,可以拿到全局对象和方法。
        • this.service:应用定义的 Service,可以调用业务逻辑层。
        • this.config:应用运行时的配置项
        • this.logger:logger 对象,使用方法类似 Context Logger,不同之处是通过这个 Logger 对象记录的日志,会额外加上该日志的文件路径,以便快速定位日志打印位置。

        Controller 实战

        HTTP 基础

        由于 Controller 基本上是业务开发中唯一和 HTTP 协议打交道的地方,在继续往下了解之前,我们首先简单的看一下 HTTP 协议是怎样的。

        如果我们发起一个 HTTP 请求来访问前面写的的 Controller

        $ curl -X POST http://localhost:7001/api/user -d '{"name":"TZ"}' -H 'Content-Type:application/json; charset=UTF-8'
        +

        通过 curl 发出的 HTTP 请求的内容就会是下面这样的:

        POST /api/user HTTP/1.1
         Host: localhost:7001
         Content-Type:application/json; charset=UTF-8
         
         {"name":"TZ"}
        -
        1
        2
        3
        4
        5

        请求的第一行包含了三个信息,我们比较常用的是前面两个:

        • method:HTTP 方法,此处为 POST
        • path:HTTP 路径,此处为 /api/user,如果用户的请求中包含 query,也会在这里出现。

        从第二行开始直到空行位置,都是请求的 Headers 部分:

        • Host:我们在浏览器发起请求的时候,域名会用来通过 DNS 解析找到服务的 IP 地址,但是浏览器也会将域名和端口号放在 Host 头中一并发送给服务端。
        • Content-Type:当我们的请求有 body 的时候,都会有 Content-Type 来标明我们的请求体是什么格式的。

        之后的内容全部都是请求的 body,当请求是 POST, PUT 等方法的时候,可以带上请求体,服务端会根据 Content-Type 来解析请求体。

        在服务端处理完这个请求后,会发送一个 HTTP 响应给客户端:

        HTTP/1.1 201 Created
        +

        请求的第一行包含了三个信息,我们比较常用的是前面两个:

        • method:HTTP 方法,此处为 POST
        • path:HTTP 路径,此处为 /api/user,如果用户的请求中包含 query,也会在这里出现。

        从第二行开始直到空行位置,都是请求的 Headers 部分:

        • Host:我们在浏览器发起请求的时候,域名会用来通过 DNS 解析找到服务的 IP 地址,但是浏览器也会将域名和端口号放在 Host 头中一并发送给服务端。
        • Content-Type:当我们的请求有 body 的时候,都会有 Content-Type 来标明我们的请求体是什么格式的。

        之后的内容全部都是请求的 body,当请求是 POST, PUT 等方法的时候,可以带上请求体,服务端会根据 Content-Type 来解析请求体。

        在服务端处理完这个请求后,会发送一个 HTTP 响应给客户端:

        HTTP/1.1 201 Created
         Content-Type: application/json; charset=utf-8
         Content-Length: 13
         Date: Mon, 09 Jan 2019 08:40:28 GMT
         Connection: keep-alive
         
         {"id":1,"name":"TZ"}
        -
        1
        2
        3
        4
        5
        6
        7

        第一行中也包含了三段,其中我们常用的主要是响应状态码,这个例子中它的值是 201,它的含义是在服务端成功创建了一条资源。

        和请求一样,从第二行开始到下一个空行之间都是响应头,这里的 Content-Type, Content-Length 表示这个响应的格式是 JSON,长度为 13 个字节。

        最后剩下的部分就是这次响应真正的内容。

        获取请求参数

        在 URL 中 ? 后面的部分是一个 Query String,这一部分经常用于 GET 请求中传递参数。

        • ctx.query:解析查询参数,转换为 Object,属性为字符串。
        • ctx.queries:同上,但支持同名的多个参数解析,属性为数组。
        • ctx.params:获取 Router 命名参数。
        // GET /api/user/list?limit=10&sort=name
        +

        第一行中也包含了三段,其中我们常用的主要是响应状态码,这个例子中它的值是 201,它的含义是在服务端成功创建了一条资源。

        和请求一样,从第二行开始到下一个空行之间都是响应头,这里的 Content-Type, Content-Length 表示这个响应的格式是 JSON,长度为 13 个字节。

        最后剩下的部分就是这次响应真正的内容。

        获取请求参数

        在 URL 中 ? 后面的部分是一个 Query String,这一部分经常用于 GET 请求中传递参数。

        • ctx.query:解析查询参数,转换为 Object,属性为字符串。
        • ctx.queries:同上,但支持同名的多个参数解析,属性为数组。
        • ctx.params:获取 Router 命名参数。
        // GET /api/user/list?limit=10&sort=name
         class UserController extends Controller {
           async list() {
             console.log(this.ctx.query);
             // { limit: '10', sort: 'name' }
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7

        友情提示

        鉴于 HTTP 协议的约定,在请求中获取到的查询参数,均为字符串,如有需要需自行转型。

        具体使用参见 Context 文档。

        获取请求 body

        虽然我们可以通过 URL 传递参数,但是还是有诸多限制:

        • 浏览器中会对 URL 的长度有所限制,如果需要传递的参数过多就会无法传递。
        • 访问的 URL 往往会被记录到日志或浏览器中,有一些敏感数据通过 URL 传递会不安全。
        • GET 请求可能会被缓存,导致非预期的意外。

        框架内置了 bodyParser,开发者可以通过 ctx.request.body 获取到对应的数据。

        class UserController extends Controller {
        +

        友情提示

        鉴于 HTTP 协议的约定,在请求中获取到的查询参数,均为字符串,如有需要需自行转型。

        具体使用参见 Context 文档。

        获取请求 body

        虽然我们可以通过 URL 传递参数,但是还是有诸多限制:

        • 浏览器中会对 URL 的长度有所限制,如果需要传递的参数过多就会无法传递。
        • 访问的 URL 往往会被记录到日志或浏览器中,有一些敏感数据通过 URL 传递会不安全。
        • GET 请求可能会被缓存,导致非预期的意外。

        框架内置了 bodyParser,开发者可以通过 ctx.request.body 获取到对应的数据。

        class UserController extends Controller {
           async create() {
             // 获取请求信息 `{ name: 'TZ' }`
             console.log(this.ctx.request.body);
           }
         }
        -
        1
        2
        3
        4
        5
        6

        友情提示

        一个常见的错误是把 ctx.request.bodyctx.body 混淆,后者其实是 ctx.response.body 的简写。

        解析 JSON / Form 请求

        一般通过 Content-Type 来声明请求 body 的格式,常见的格式有 JSONForm

        • application/json:按 JSON 格式进行解析。
        • application/x-www-form-urlencoded:按 Form 格式进行解析。

        框架默认限制 body 的大小为 100kb,如果你需要上传更大的内容,需配置:

        // config/config.default.js
        +

        友情提示

        一个常见的错误是把 ctx.request.bodyctx.body 混淆,后者其实是 ctx.response.body 的简写。

        解析 JSON / Form 请求

        一般通过 Content-Type 来声明请求 body 的格式,常见的格式有 JSONForm

        • application/json:按 JSON 格式进行解析。
        • application/x-www-form-urlencoded:按 Form 格式进行解析。

        框架默认限制 body 的大小为 100kb,如果你需要上传更大的内容,需配置:

        // config/config.default.js
         module.exports = {
           bodyParser: {
             jsonLimit: '1mb',
             formLimit: '1mb',
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        • 如果 body 超过了最大长度配置,会抛出一个状态码为 413 的异常。
        • 如果 body 解析失败(错误的 JSON),会抛出一个状态码为 400 的异常。
        • 支持 10mb 这种人性化的方式,具体参见 humanize-bytes 模块。

        友情提示

        如果我们应用前面还有一层反向代理(Nginx),则也需要调整它的配置,以确保反向代理也支持同样长度的请求 body。

        解析 XML 请求

        有些时候,我们需要解析 XML 协议,可配置:

        // config/config.default.js
        +
        • 如果 body 超过了最大长度配置,会抛出一个状态码为 413 的异常。
        • 如果 body 解析失败(错误的 JSON),会抛出一个状态码为 400 的异常。
        • 支持 10mb 这种人性化的方式,具体参见 humanize-bytes 模块。

        友情提示

        如果我们应用前面还有一层反向代理(Nginx),则也需要调整它的配置,以确保反向代理也支持同样长度的请求 body。

        解析 XML 请求

        有些时候,我们需要解析 XML 协议,可配置:

        // config/config.default.js
         exports.bodyParser = {
           enableTypes: [ 'json', 'form', 'text' ],
           extendTypes: {
             text: [ 'application/xml' ],
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7

        然后可以自行使用 XML 解析库分析 ctx.request.body 的原始字符串。

        const { xml2js } = require('xml-js');
        +

        然后可以自行使用 XML 解析库分析 ctx.request.body 的原始字符串。

        const { xml2js } = require('xml-js');
         const xmlContent = xml2js(ctx.request.body);
        -
        1
        2

        解析自定义类型

        如需自定义协议,如 application/custom-rpc,内容一样为 JSON,则可以配置:

        // config/config.default.js
        +

        解析自定义类型

        如需自定义协议,如 application/custom-rpc,内容一样为 JSON,则可以配置:

        // config/config.default.js
         exports.bodyParser = {
           extendTypes: {
             json: 'application/custom-rpc',
           },
         };
        -
        1
        2
        3
        4
        5
        6

        文件上传

        请求 body 还可以通过 multipart/form-data 格式来实现文件上传。

        框架内置了 egg-multipart 来支持该特性。

        支持 filestream 模式,本文仅介绍前者,更多用法请阅读文件上传文档。

        先启用 file 模式:

        // config/config.default.js
        +

        文件上传

        请求 body 还可以通过 multipart/form-data 格式来实现文件上传。

        框架内置了 egg-multipart 来支持该特性。

        支持 filestream 模式,本文仅介绍前者,更多用法请阅读文件上传文档。

        先启用 file 模式:

        // config/config.default.js
         exports.multipart = {
           mode: 'file',
         };
        -
        1
        2
        3
        4

        然后接收文件:

        // app/controller/upload.js
        +

        然后接收文件:

        // app/controller/upload.js
         class UploadController extends Controller {
           async upload() {
             const { ctx } = this;
        @@ -103,12 +145,12 @@
             // ...
           }
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        获取 Header

        框架提供了 ctx.get(name) 方法来获取请求头,具体参见 Context 文档。

        class HomeController extends Controller {
        +

        获取 Header

        框架提供了 ctx.get(name) 方法来获取请求头,具体参见 Context 文档。

        class HomeController extends Controller {
           async index() {
             console.log(this.ctx.get('user-agent'));
           }
         }
        -
        1
        2
        3
        4
        5

        代理服务器

        大部分情况下,我们的 Web 服务都是在代理服务器(如Nginx) 后面,此时需要配置 config.proxy = true,框架对应的 Getter 会对应的增加处理逻辑。

        • ctx.ips:获取请求经过所有的中间设备 IP 地址列表。
        • ctx.ip:获取请求发起方的 IP 地址,对应的代理 HeaderX-Forwarded-For
        • ctx.host:获取 HOST,对应的代理 HeaderX-Forwarded-Host

        另外,代理服务器处理 HTTPS 请求时,我们的 Web 服务收到的是内部的 HTTP 请求。

        开发者可以通过 ctx.protocol 来获取客户端访问的协议,框架会解析 X-Forwarded-Prot

        详细参见源码实现

        通过 ctx.cookies,我们可以在 Controller 中便捷、安全的设置和读取 Cookie

        具体可参见 Cookie 文档。

        参数校验

        在获取到用户请求的参数后,不可避免的要对参数进行一些校验。

        在上面的示例中,我们简单的使用 ctx.assert 进行了校验。

        实际业务中,会需要更复杂的校验,可以查看 egg-validate 等插件的文档。

        调用 Service

        不建议 Controller 中实现太多业务逻辑,一般通过 Service 层进行业务逻辑的封装。

        这不仅能提高代码的复用性,同时可以让我们的业务逻辑更好测试。

        发送 HTTP 响应

        当业务逻辑完成之后,Controller 的最后一个职责就是将处理结果通过 HTTP 响应给用户。

        • ctx.body=:设置响应 body。
        • ctx.type=:设置响应的 Content-Type
        • ctx.status=:设置响应的状态码。
        • ctx.set(name, header):设置响应 Header
        // app/controller/home.js
        +

        代理服务器

        大部分情况下,我们的 Web 服务都是在代理服务器(如Nginx) 后面,此时需要配置 config.proxy = true,框架对应的 Getter 会对应的增加处理逻辑。

        • ctx.ips:获取请求经过所有的中间设备 IP 地址列表。
        • ctx.ip:获取请求发起方的 IP 地址,对应的代理 HeaderX-Forwarded-For
        • ctx.host:获取 HOST,对应的代理 HeaderX-Forwarded-Host

        另外,代理服务器处理 HTTPS 请求时,我们的 Web 服务收到的是内部的 HTTP 请求。

        开发者可以通过 ctx.protocol 来获取客户端访问的协议,框架会解析 X-Forwarded-Prot

        详细参见源码实现

        通过 ctx.cookies,我们可以在 Controller 中便捷、安全的设置和读取 Cookie

        具体可参见 Cookie 文档。

        参数校验

        在获取到用户请求的参数后,不可避免的要对参数进行一些校验。

        在上面的示例中,我们简单的使用 ctx.assert 进行了校验。

        实际业务中,会需要更复杂的校验,可以查看 egg-validate 等插件的文档。

        调用 Service

        不建议 Controller 中实现太多业务逻辑,一般通过 Service 层进行业务逻辑的封装。

        这不仅能提高代码的复用性,同时可以让我们的业务逻辑更好测试。

        发送 HTTP 响应

        当业务逻辑完成之后,Controller 的最后一个职责就是将处理结果通过 HTTP 响应给用户。

        • ctx.body=:设置响应 body。
        • ctx.type=:设置响应的 Content-Type
        • ctx.status=:设置响应的状态码。
        • ctx.set(name, header):设置响应 Header
        // app/controller/home.js
         class HomeController extends Controller {
           async index() {
             const { ctx } = this;
        @@ -121,20 +163,20 @@
             };
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        具体可以参见 Context 文档。

        模板渲染

        通常来说,我们不会手写 HTML 页面,而是会通过模板引擎进行生成。

        我们可以通过使用模板插件,来提供渲染能力。

        class HomeController extends Controller {
        +

        具体可以参见 Context 文档。

        模板渲染

        通常来说,我们不会手写 HTML 页面,而是会通过模板引擎进行生成。

        我们可以通过使用模板插件,来提供渲染能力。

        class HomeController extends Controller {
           async index() {
             const ctx = this.ctx;
             await ctx.render('home.tpl', { name: 'egg' });
             // ctx.body = await ctx.renderString('hi, {{ name }}', { name: 'egg' });
           }
         };
        -
        1
        2
        3
        4
        5
        6
        7

        具体示例可以查看模板引擎

        JSONP

        有时我们需要给非本域的页面提供接口服务,又由于一些历史原因无法通过 CORS 实现,可以通过 JSONP 来进行响应。

        框架内置了 egg-jsonp 插件,提供了 app.jsonp() 来支持响应 JSONP 格式的数据。

        使用

        先通过路由中间件的方式来局部开启:

        // app/router.js
        +

        具体示例可以查看模板引擎

        JSONP

        有时我们需要给非本域的页面提供接口服务,又由于一些历史原因无法通过 CORS 实现,可以通过 JSONP 来进行响应。

        框架内置了 egg-jsonp 插件,提供了 app.jsonp() 来支持响应 JSONP 格式的数据。

        使用

        先通过路由中间件的方式来局部开启:

        // app/router.js
         module.exports = app => {
           const jsonp = app.jsonp();
           app.router.get('/api/posts/:id', jsonp, app.controller.posts.show);
           app.router.get('/api/posts', jsonp, app.controller.posts.list);
         };
        -
        1
        2
        3
        4
        5
        6

        然后在 Controller 中,只需要正常编写即可:

        // app/controller/posts.js
        +

        然后在 Controller 中,只需要正常编写即可:

        // app/controller/posts.js
         class PostController extends Controller {
           async show() {
             this.ctx.body = {
        @@ -144,17 +186,17 @@
             };
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        用户请求对应的 URL 时带上 _callback=fn 查询参数,将会返回 JSONP 格式的数据。

        配置

        框架默认支持方法名为 callback_callback,并限制长度小于 50 字符。

        如有需要,可以自定义配置:

        // config/config.default.js
        +

        用户请求对应的 URL 时带上 _callback=fn 查询参数,将会返回 JSONP 格式的数据。

        配置

        框架默认支持方法名为 callback_callback,并限制长度小于 50 字符。

        如有需要,可以自定义配置:

        // config/config.default.js
         exports.jsonp = {
           callback: 'cb', // 识别 query 中的 `cb` 参数
           limit: 100, // 函数名最长为 100 个字符
         };
        -
        1
        2
        3
        4
        5

        通过上面的方式配置之后,如果用户通过 /api/posts/1?cb=fn 请求 JSONP

        也可以在 app.jsonp() 创建中间件时覆盖默认的配置,以达到不同路由使用不同配置的目的:

        // app/router.js
        +

        通过上面的方式配置之后,如果用户通过 /api/posts/1?cb=fn 请求 JSONP

        也可以在 app.jsonp() 创建中间件时覆盖默认的配置,以达到不同路由使用不同配置的目的:

        // app/router.js
         module.exports = app => {
           const { router, controller, jsonp } = app;
           router.get('/api/posts', jsonp({ callback: 'cb' }), controller.posts.list);
         };
        -
        1
        2
        3
        4
        5

        安全

        JSONP 如果使用不当会导致非常多的安全问题,可以将 JSONP 接口分为三种类型:

        1. 查询非敏感数据,例如获取一个论坛的公开文章列表。
        2. 查询敏感数据,例如获取一个用户的交易记录。
        3. 提交数据并修改数据库,例如给某一个用户创建一笔订单。

        如果我们的 JSONP 接口提供下面两类服务,在不做任何跨站防御的情况下,可能泄露用户敏感数据甚至导致用户被钓鱼。

        因此框架给 JSONP 默认提供了 CSRF 校验referrer 校验,具体参见 JSONP XSS 相关的安全防范 文档。

        // config/config.default.js
        +

        安全

        JSONP 如果使用不当会导致非常多的安全问题,可以将 JSONP 接口分为三种类型:

        1. 查询非敏感数据,例如获取一个论坛的公开文章列表。
        2. 查询敏感数据,例如获取一个用户的交易记录。
        3. 提交数据并修改数据库,例如给某一个用户创建一笔订单。

        如果我们的 JSONP 接口提供下面两类服务,在不做任何跨站防御的情况下,可能泄露用户敏感数据甚至导致用户被钓鱼。

        因此框架给 JSONP 默认提供了 CSRF 校验referrer 校验,具体参见 JSONP XSS 相关的安全防范 文档。

        // config/config.default.js
         module.exports = {
           jsonp: {
             csrf: true,
        @@ -164,7 +206,7 @@
             // whiteList: [ 'sub.test.com', 'sub2.test.com' ],
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        提示

        当 CSRF 和 referrer 校验同时开启时,请求发起方只需要满足任意一个条件即可通过 JSONP 的安全校验。

        重定向

        使用

        可以通过 ctx.redirect(url) 来重定向请求。

        默认为 302,如果需要,可以设置 ctx.status = 301

        class UserController extends Controller {
        +

        提示

        当 CSRF 和 referrer 校验同时开启时,请求发起方只需要满足任意一个条件即可通过 JSONP 的安全校验。

        重定向

        使用

        可以通过 ctx.redirect(url) 来重定向请求。

        默认为 302,如果需要,可以设置 ctx.status = 301

        class UserController extends Controller {
           async logout() {
             const ctx = this.ctx;
         
        @@ -172,7 +214,7 @@
             ctx.redirect(ctx.get('referer') || '/');
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8

        安全域名

        框架通过 egg-security 插件覆盖了 Koa 原生的 ctx.redirect 实现,以提供更加安全的重定向。

        • ctx.redirect(url) 如果不在配置的白名单域名内,则禁止跳转。
        • ctx.unsafeRedirect(url) 不判断域名,直接跳转,一般不建议使用,明确了解可能带来的风险后使用。

        security.domainWhiteList数组内为空,则默认会对所有跳转请求放行,即等同于ctx.unsafeRedirect(url)

        安全提示

        基于安全管控的原因,我们不推荐在应用层直接覆盖该属性,而是应该提交 Merge Request,除非该域名非阿里所属。

        更多参见 安全插件 文档。

        编写测试

        框架集成了 SuperTest 用于 HTTP 测试。

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        测试 GET 请求

        // test/controller/home.test.js
        +

        安全域名

        框架通过 egg-security 插件覆盖了 Koa 原生的 ctx.redirect 实现,以提供更加安全的重定向。

        • ctx.redirect(url) 如果不在配置的白名单域名内,则禁止跳转。
        • ctx.unsafeRedirect(url) 不判断域名,直接跳转,一般不建议使用,明确了解可能带来的风险后使用。

        security.domainWhiteList数组内为空,则默认会对所有跳转请求放行,即等同于ctx.unsafeRedirect(url)

        安全提示

        基于安全管控的原因,我们不推荐在应用层直接覆盖该属性,而是应该提交 Merge Request,除非该域名非阿里所属。

        更多参见 安全插件 文档。

        编写测试

        框架集成了 SuperTest 用于 HTTP 测试。

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        测试 GET 请求

        // test/controller/home.test.js
         const { app, mock, assert } = require('egg-mock');
         
         describe('test/controller/home.test.js', () => {
        @@ -186,7 +228,7 @@
               .expect(200);
           });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        测试 POST 请求

        可以通过 app.mockCsrf() 来跳过 CSRF 校验。

        // test/controller/home.test.js
        +

        测试 POST 请求

        可以通过 app.mockCsrf() 来跳过 CSRF 校验。

        // test/controller/home.test.js
         it('should POST form', () => {
           app.mockCsrf();
           return app.httpRequest()
        @@ -204,7 +246,7 @@
             .send({ name: 'TZ' })
             .expect(200);
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18

        测试文件上传

        // test/controller/home.test.js
        +

        测试文件上传

        // test/controller/home.test.js
         it('should upload file', () => {
           app.mockCsrf();
           return app.httpRequest()
        @@ -213,17 +255,11 @@
             .attach('file', path.join(__dirname, 'egg.png'))
             .expect(200);
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        常见问题

        missing csrf token

        框架默认开启了 CSRF 安全限制。

        因此新手开发者在 Postman 测试前端发起 AJAX单元测试 时经常遇到的一个报错:

        nodejs.ForbiddenError: missing csrf token
        -
        1

        如何处理可以阅读上述文档。

        redirection is prohibited

        nodejs.InternalServerError: a security problem has been detected for url "http://www.baidu.com/", redirection is prohibited.
        -
        1

        如上所述,不允许重定向到非白名单的域名,具体处理参见安全域名

        - +

        常见问题

        missing csrf token

        框架默认开启了 CSRF 安全限制。

        因此新手开发者在 Postman 测试前端发起 AJAX单元测试 时经常遇到的一个报错:

        nodejs.ForbiddenError: missing csrf token
        +

        如何处理可以阅读上述文档。

        redirection is prohibited

        nodejs.InternalServerError: a security problem has been detected for url "http://www.baidu.com/", redirection is prohibited.
        +

        如上所述,不允许重定向到非白名单的域名,具体处理参见安全域名

        + diff --git a/zh/guide/cookie.html b/zh/guide/cookie.html index 6f7eadd..119b794 100644 --- a/zh/guide/cookie.html +++ b/zh/guide/cookie.html @@ -5,20 +5,62 @@ Cookie | Egg + - - - + + -

        使用场景

        HTTP 请求都是无状态的,但是我们的 Web 应用通常都需要知道发起请求的人是谁。

        为了解决这个问题,HTTP 协议设计了一个特殊的请求头:Cookie

        服务端可以通过响应头将少量数据响应给客户端,浏览器会遵循协议将数据保存,并在下次请求同一个服务的时候带上对应的数据。

        Cookie 主要用于:

        • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
        • 个性化设置(如用户自定义设置、主题等)
        • 浏览器行为跟踪(如跟踪分析用户行为等)

        服务器使用 Set-Cookie 响应头部向用户浏览器发送 Cookie 信息:

        HTTP/1.0 200 OK
        +    

        Cookie

        使用场景

        HTTP 请求都是无状态的,但是我们的 Web 应用通常都需要知道发起请求的人是谁。

        为了解决这个问题,HTTP 协议设计了一个特殊的请求头:Cookie

        服务端可以通过响应头将少量数据响应给客户端,浏览器会遵循协议将数据保存,并在下次请求同一个服务的时候带上对应的数据。

        Cookie 主要用于:

        • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
        • 个性化设置(如用户自定义设置、主题等)
        • 浏览器行为跟踪(如跟踪分析用户行为等)

        服务器使用 Set-Cookie 响应头部向用户浏览器发送 Cookie 信息:

        HTTP/1.0 200 OK
         Content-type: text/html
         Set-Cookie: uid=123456
         Set-Cookie: user=tz
        -
        1
        2
        3
        4

        后续对该服务发起的每一次新请求,浏览器都会将之前保存的信息通过 Cookie 请求头回传:

        GET /user HTTP/1.1
        +

        后续对该服务发起的每一次新请求,浏览器都会将之前保存的信息通过 Cookie 请求头回传:

        GET /user HTTP/1.1
         Host: www.example.org
         Cookie: uid=123456; user=tz
        -
        1
        2
        3

        框架内置了 egg-cookies 插件,提供了 ctx.cookies,用于便捷、安全的读写 Cookie

        // app/controller/home.js
        +

        框架内置了 egg-cookies 插件,提供了 ctx.cookies,用于便捷、安全的读写 Cookie

        // app/controller/home.js
         class HomeController extends Controller {
           async add() {
             const { ctx } = this;
        @@ -33,24 +75,24 @@
             ctx.status = 204;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        友情提示

        在使用 Cookie 时我们需要思考清楚它的场景:

        • 需要被浏览器保存多久?
        • 是否可以被 js 获取到?
        • 是否可以被前端修改?

        框架默认配置下, Cookie 是加签不加密的,浏览器可以看到明文,js 不能访问,不能被客户端(手工)篡改。

        术语解释

        过期时间

        ExpiresMax-Age 用于定义 Cookie 对应的键值对的持久化时间。

        Expires 优先级低于 Max-Age,如果两者都没设置,则将会在关闭浏览器时失效。

        作用域

        DomainPath 标识定义了 Cookie 的作用域:即 Cookie 应该发送给哪些 URL。

        安全

        • SecureCookie 只有在 HTTPS 协议下才会发送给服务端。
        • HttpOnlyCookie 将无法被 JavaScript 访问,从而避免 XSS 攻击。

        加签 && 加密

        • 加签:对 Cookie 进行签名,避免前端篡改。不会修改原键值,而是新增一个 ${key}.sig 的键值。
        • 加密:对 Cookie 进行加密,避免 Cookie 明文写入,泄露给恶意用户。

        API 说明

        set(key, value, options)

        框架提供了 ctx.set(key, value, options) 来向用户发送 Cookie 信息。

        其中,keyvalue 称之为一个 键值对。配置参数 options下文

        get(key, options)

        Cookie 是通过同一个 Header 中传输过来的,因此需要通过该方法解析并获取对应的值。

        值得注意的是,获取时的 options.signedoptions.encrypt 要和 set() 的时候保持一致。

        options

        术语 一一对应,支持以下参数配置:

        • maxAge: {Number} 在浏览器的最长保存时间。
        • expires: {Date} 失效时间。优先级低于 maxAge。如果两者都没设置,则将会在关闭浏览器时失效。
        • path: {String} 生效的 URL 路径,默认为 /,即当前域名下均可访问这个 Cookie。
        • domain: {String} 对生效的域名,默认没有配置,可以配置成只在指定域名才能访问。
        • httpOnly: {Boolean} 是否可以被 js 访问,默认为 true,不允许被 js 访问
        • secure: {Boolean} 框架会自动判断当前请求是否为 HTTPS,从而自动赋值。
        • signed: {Boolean}:是否加签,默认为 true。
        • encrypt: {Boolean} 是否加密,默认为 false。

        此外,还扩展了:

        • overwrite {Boolean}:相同的 Key 的处理逻辑,为 true,则后设置的值会覆盖前面设置的,否则将会发送两个 Set-Cookie 响应头。

        配置秘钥

        由于我们在 Cookie 中需要用到加解密验签,所以需要配置一个秘钥供加密使用。

        // config/config.default.js
        +

        友情提示

        在使用 Cookie 时我们需要思考清楚它的场景:

        • 需要被浏览器保存多久?
        • 是否可以被 js 获取到?
        • 是否可以被前端修改?

        框架默认配置下, Cookie 是加签不加密的,浏览器可以看到明文,js 不能访问,不能被客户端(手工)篡改。

        术语解释

        过期时间

        ExpiresMax-Age 用于定义 Cookie 对应的键值对的持久化时间。

        Expires 优先级低于 Max-Age,如果两者都没设置,则将会在关闭浏览器时失效。

        作用域

        DomainPath 标识定义了 Cookie 的作用域:即 Cookie 应该发送给哪些 URL。

        安全

        • SecureCookie 只有在 HTTPS 协议下才会发送给服务端。
        • HttpOnlyCookie 将无法被 JavaScript 访问,从而避免 XSS 攻击。

        加签 && 加密

        • 加签:对 Cookie 进行签名,避免前端篡改。不会修改原键值,而是新增一个 ${key}.sig 的键值。
        • 加密:对 Cookie 进行加密,避免 Cookie 明文写入,泄露给恶意用户。

        API 说明

        set(key, value, options)

        框架提供了 ctx.set(key, value, options) 来向用户发送 Cookie 信息。

        其中,keyvalue 称之为一个 键值对。配置参数 options下文

        get(key, options)

        Cookie 是通过同一个 Header 中传输过来的,因此需要通过该方法解析并获取对应的值。

        值得注意的是,获取时的 options.signedoptions.encrypt 要和 set() 的时候保持一致。

        options

        术语 一一对应,支持以下参数配置:

        • maxAge: {Number} 在浏览器的最长保存时间。
        • expires: {Date} 失效时间。优先级低于 maxAge。如果两者都没设置,则将会在关闭浏览器时失效。
        • path: {String} 生效的 URL 路径,默认为 /,即当前域名下均可访问这个 Cookie。
        • domain: {String} 对生效的域名,默认没有配置,可以配置成只在指定域名才能访问。
        • httpOnly: {Boolean} 是否可以被 js 访问,默认为 true,不允许被 js 访问
        • secure: {Boolean} 框架会自动判断当前请求是否为 HTTPS,从而自动赋值。
        • signed: {Boolean}:是否加签,默认为 true。
        • encrypt: {Boolean} 是否加密,默认为 false。

        此外,还扩展了:

        • overwrite {Boolean}:相同的 Key 的处理逻辑,为 true,则后设置的值会覆盖前面设置的,否则将会发送两个 Set-Cookie 响应头。

        配置秘钥

        由于我们在 Cookie 中需要用到加解密验签,所以需要配置一个秘钥供加密使用。

        // config/config.default.js
         module.exports = {
           keys: 'key1,key2',
         };
        -
        1
        2
        3
        4

        如果你没配置该属性,则在访问时会报错:

        ERROR 17996 [-/::1/-/7ms GET /] nodejs.Error: Please set config.keysfirst
        -
        1

        keys 配置成一个字符串,可以按照逗号分隔配置多个 key。

        Cookie 在使用这个配置进行加解密时:

        • 加密加签时只会使用第一个秘钥。
        • 解密验签时会遍历 keys 进行解密。

        如果我们想要更新 Cookie 的秘钥,但是又不希望之前设置到用户浏览器上的 Cookie 失效,可以将新的秘钥配置到 keys 最前面,等过一段时间之后再删去不需要的秘钥即可。

        如果要获取前端或者其他系统设置的 Cookie,需要指定参数 signedfalse,避免对它做验签导致获取不到 Cookie 的值。

        ctx.cookies.get('frontend-cookie', {
        +

        如果你没配置该属性,则在访问时会报错:

        ERROR 17996 [-/::1/-/7ms GET /] nodejs.Error: Please set config.keysfirst
        +

        keys 配置成一个字符串,可以按照逗号分隔配置多个 key。

        Cookie 在使用这个配置进行加解密时:

        • 加密加签时只会使用第一个秘钥。
        • 解密验签时会遍历 keys 进行解密。

        如果我们想要更新 Cookie 的秘钥,但是又不希望之前设置到用户浏览器上的 Cookie 失效,可以将新的秘钥配置到 keys 最前面,等过一段时间之后再删去不需要的秘钥即可。

        如果要获取前端或者其他系统设置的 Cookie,需要指定参数 signedfalse,避免对它做验签导致获取不到 Cookie 的值。

        ctx.cookies.get('frontend-cookie', {
           signed: false,
         });
        -
        1
        2
        3

        如果想要 Cookie 在浏览器端可以被 js 访问并修改:

        ctx.cookies.set(key, value, {
        +

        如果想要 Cookie 在浏览器端可以被 js 访问并修改:

        ctx.cookies.set(key, value, {
           httpOnly: false,
           signed: false,
         });
        -
        1
        2
        3
        4

        不允许浏览器看到明文内容

        如果想要 Cookie 在浏览器端不能被修改,不能看到明文:

        ctx.cookies.set(key, value, {
        +

        不允许浏览器看到明文内容

        如果想要 Cookie 在浏览器端不能被修改,不能看到明文:

        ctx.cookies.set(key, value, {
           httpOnly: true, // 默认就是 true
           encrypt: true, // 加密传输
         });
        -
        1
        2
        3
        4
        ctx.cookies.set(key, null);
        -
        1

        编写测试

        类似 Controller 的测试。

        需注意的是:模拟 Cookies 可能需要加上对应的 sig 加签信息。

        // test/controller/cookies.test.js
        +
        ctx.cookies.set(key, null);
        +

        编写测试

        类似 Controller 的测试。

        需注意的是:模拟 Cookies 可能需要加上对应的 sig 加签信息。

        // test/controller/cookies.test.js
         const { app, mock, assert } = require('egg-mock');
         
         describe('test/controller/cookies.test.js', () => {
        @@ -62,15 +104,9 @@
               .expect(200);
           });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        注意事项

        1. 由于浏览器和其他客户端实现的不确定性,为了保证 Cookie 可以写入成功,建议 value 通过 base64 编码或者其他形式 encode 之后再写入。
        2. 由于浏览器对 Cookie 有长度限制限制,所以尽量不要设置太长的 Cookie。一般来说不要超过 4093 bytes。当设置的 Cookie value 大于这个值时,框架会打印一条警告日志。
        3. 尽可能少写入数据到 Cookie
        - +

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        注意事项

        1. 由于浏览器和其他客户端实现的不确定性,为了保证 Cookie 可以写入成功,建议 value 通过 base64 编码或者其他形式 encode 之后再写入。
        2. 由于浏览器对 Cookie 有长度限制限制,所以尽量不要设置太长的 Cookie。一般来说不要超过 4093 bytes。当设置的 Cookie value 大于这个值时,框架会打印一条警告日志。
        3. 尽可能少写入数据到 Cookie
        + diff --git a/zh/guide/directory.html b/zh/guide/directory.html index c84c2b2..df4f671 100644 --- a/zh/guide/directory.html +++ b/zh/guide/directory.html @@ -5,13 +5,55 @@ 目录规范 | Egg + - - - + + -

        对于一个团队框架来说,『约定优于配置』,按照一套统一的约定进行应用开发,可以极大地减少开发人员的沟通成本。

        框架通过 Loader 机制来自动挂载文件,应用开发者只需要添加文件到对应的目录即可。

        showcase
        +    

        目录规范

        对于一个团队框架来说,『约定优于配置』,按照一套统一的约定进行应用开发,可以极大地减少开发人员的沟通成本。

        框架通过 Loader 机制来自动挂载文件,应用开发者只需要添加文件到对应的目录即可。

        showcase
         ├── app
         |   ├── router.js
         │   ├── controller
        @@ -34,16 +76,10 @@
         |   └── service
         |       └── user.test.js
         └── package.json
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23

        如上,为一个常见的应用目录结构:

        • app: 为主要的逻辑代码目录。 -
          • 常规 MVC 如: app/controllerapp/serviceapp/router.js 等。
          • 某些插件也会自定义加载规范,如 app/rpc 等目录的自动挂载。
        • config: 为配置目录,包含不同环境的配置文件,以及插件挂载声明。
        • test: 为单元测试目录。
        • run:每次启动期都会 dump 的相关信息,用于问题排查,建议加入 gitignore

        文件挂载如下:

        • app/controller/home.js 会被自动挂载到 app.controller.home
        • app/service/user.js 会被自动挂载到 ctx.service.user

        注意事项

        需要注意的是,加载文件时会进行驼峰转换,因此文件名和挂载的属性名可能会存在差异:

        • 默认情况下,连字符和下划线均会被转换为驼峰格式。
        • app/middleware/response_time.js 挂载为 app.middleware.responseTime
        • 部分插件,如 mongoose 插件有特殊约定,会挂载为类格式,如 app.model.User

        在后面的章节中,我们会逐步介绍具体的目录约定。

        如果需要自定义加载规则,可以参见 Loader 相关文档。

        - +

        如上,为一个常见的应用目录结构:

        • app: 为主要的逻辑代码目录。 +
          • 常规 MVC 如: app/controllerapp/serviceapp/router.js 等。
          • 某些插件也会自定义加载规范,如 app/rpc 等目录的自动挂载。
        • config: 为配置目录,包含不同环境的配置文件,以及插件挂载声明。
        • test: 为单元测试目录。
        • run:每次启动期都会 dump 的相关信息,用于问题排查,建议加入 gitignore

        文件挂载如下:

        • app/controller/home.js 会被自动挂载到 app.controller.home
        • app/service/user.js 会被自动挂载到 ctx.service.user

        注意事项

        需要注意的是,加载文件时会进行驼峰转换,因此文件名和挂载的属性名可能会存在差异:

        • 默认情况下,连字符和下划线均会被转换为驼峰格式。
        • app/middleware/response_time.js 挂载为 app.middleware.responseTime
        • 部分插件,如 mongoose 插件有特殊约定,会挂载为类格式,如 app.model.User

        在后面的章节中,我们会逐步介绍具体的目录约定。

        如果需要自定义加载规则,可以参见 Loader 相关文档。

        + diff --git a/zh/guide/error_handler.html b/zh/guide/error_handler.html index 7ce12ef..d2412bc 100644 --- a/zh/guide/error_handler.html +++ b/zh/guide/error_handler.html @@ -5,13 +5,55 @@ 异常处理 | Egg + - - - + + -

        使用场景

        健壮性,是一个应用的基本要求。如何正确的处理错误是非常重要的一件事。

        实际开发中,错误可以分为几类:

        • 非期望的入参,如函数要求传递的是数值,却传递了字符串。
        • 意料之中的错误,如 Http 网络断开文件不存在等。
        • 完全意料之外的异常,譬如业务进程被外部杀死。

        错误的处理也有一些通用的实践:

        • 需要记录错误的信息,位置,堆栈和上下文。
        • 根据内容协商来返回不同的响应格式。
        • 正式环境下,不能把详细的错误信息和堆栈抛到用户侧。

        Node.js 异常处理

        Node.js 里,对异常的处理非常重要,如果有未捕获异常会直接导致进程退出。

        在早期的 Node.js 里, Error-first callbacks 是用的比较广泛的一种错误处理的约定。

        但嵌套层次一多起来,就需要一层层的往上抛出,非常容易遗漏和出现问题。

        因此,在 Async Function 异步编程模型出来后,通过 try..catch 来捕获错误,就直观了很多。

        async create(data) {
        +    

        异常处理

        使用场景

        健壮性,是一个应用的基本要求。如何正确的处理错误是非常重要的一件事。

        实际开发中,错误可以分为几类:

        • 非期望的入参,如函数要求传递的是数值,却传递了字符串。
        • 意料之中的错误,如 Http 网络断开文件不存在等。
        • 完全意料之外的异常,譬如业务进程被外部杀死。

        错误的处理也有一些通用的实践:

        • 需要记录错误的信息,位置,堆栈和上下文。
        • 根据内容协商来返回不同的响应格式。
        • 正式环境下,不能把详细的错误信息和堆栈抛到用户侧。

        Node.js 异常处理

        Node.js 里,对异常的处理非常重要,如果有未捕获异常会直接导致进程退出。

        在早期的 Node.js 里, Error-first callbacks 是用的比较广泛的一种错误处理的约定。

        但嵌套层次一多起来,就需要一层层的往上抛出,非常容易遗漏和出现问题。

        因此,在 Async Function 异步编程模型出来后,通过 try..catch 来捕获错误,就直观了很多。

        async create(data) {
           try {
             return await this.service.user.create(data);
           } catch (err) {
        @@ -19,7 +61,7 @@
             return {};
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8

        注意事项

        避免使用 callback,它抛出的错误,无法被 try 直接捕获,详见 Node.js Error 文档。

        框架内置支持

        框架内置了 onerror 插件,提供了统一的错误处理机制。

        对一个请求处理过程中的 MiddlewareControllerService 等抛出的任何异常都会被它捕获。

        业务错误处理

        如果你需要对业务错误进行统一处理,可以如下:

        // app/middleware/error_handler.js
        +

        注意事项

        避免使用 callback,它抛出的错误,无法被 try 直接捕获,详见 Node.js Error 文档。

        框架内置支持

        框架内置了 onerror 插件,提供了统一的错误处理机制。

        对一个请求处理过程中的 MiddlewareControllerService 等抛出的任何异常都会被它捕获。

        业务错误处理

        如果你需要对业务错误进行统一处理,可以如下:

        // app/middleware/error_handler.js
         module.exports = () => {
           return async function errorHandler(ctx, next) {
             try {
        @@ -39,7 +81,7 @@
               ctx.status = status;
             }
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20

        挂载中间件:

        // config/config.default.js
        +

        挂载中间件:

        // config/config.default.js
         module.exports = {
           middleware: [ 'errorHandler' ],
           errorHandler: {
        @@ -47,14 +89,14 @@
             match: '/api',
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8

        框架兜底处理

        框架通过 onerror 插件提供了统一的错误处理机制。

        对一个请求的所有处理方法(MiddlewareControllerService)中抛出的任何异常都会被它捕获。

        并自动根据请求想要获取的类型返回不同类型的错误(基于 Content Negotiation)。

        请求需求的格式 环境 errorPageUrl 是否配置 返回内容
        HTML & TEXT local & unittest - onerror 自带的错误页面,展示详细的错误信息
        HTML & TEXT 其他 重定向到 errorPageUrl
        HTML & TEXT 其他 onerror 自带的没有错误信息的简单错误页(不推荐)
        JSON & JSONP local & unittest - JSON 对象或对应的 JSONP 格式响应,带详细的错误信息
        JSON & JSONP 其他 - JSON 对象或对应的 JSONP 格式响应,不带详细的错误信息

        errorPageUrl

        onerror 插件支持 errorPageUrl 配置,当配置了 errorPageUrl 时,一旦用户请求线上应用的 HTML 页面异常,就会重定向到这个地址。

        config/config.default.js

        // config/config.default.js
        +

        框架兜底处理

        框架通过 onerror 插件提供了统一的错误处理机制。

        对一个请求的所有处理方法(MiddlewareControllerService)中抛出的任何异常都会被它捕获。

        并自动根据请求想要获取的类型返回不同类型的错误(基于 Content Negotiation)。

        请求需求的格式 环境 errorPageUrl 是否配置 返回内容
        HTML & TEXT local & unittest - onerror 自带的错误页面,展示详细的错误信息
        HTML & TEXT 其他 重定向到 errorPageUrl
        HTML & TEXT 其他 onerror 自带的没有错误信息的简单错误页(不推荐)
        JSON & JSONP local & unittest - JSON 对象或对应的 JSONP 格式响应,带详细的错误信息
        JSON & JSONP 其他 - JSON 对象或对应的 JSONP 格式响应,不带详细的错误信息

        errorPageUrl

        onerror 插件支持 errorPageUrl 配置,当配置了 errorPageUrl 时,一旦用户请求线上应用的 HTML 页面异常,就会重定向到这个地址。

        config/config.default.js

        // config/config.default.js
         module.exports = {
           onerror: {
             // 线上页面发生异常时,重定向到这个页面上
             errorPageUrl: '/50x.html',
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7

        自定义统一异常处理

        尽管框架提供了默认的统一异常处理机制,但是应用开发中经常需要对异常时的响应做自定义,特别是在做一些接口开发的时候。框架自带的 onerror 插件支持自定义配置错误处理方法,可以覆盖默认的错误处理方法。

        // config/config.default.js
        +

        自定义统一异常处理

        尽管框架提供了默认的统一异常处理机制,但是应用开发中经常需要对异常时的响应做自定义,特别是在做一些接口开发的时候。框架自带的 onerror 插件支持自定义配置错误处理方法,可以覆盖默认的错误处理方法。

        // config/config.default.js
         module.exports = {
           onerror: {
             all(err, ctx) {
        @@ -78,19 +120,19 @@
             },
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24

        404

        404 - NOT FOUND 是我们比较熟悉的一种错误。

        框架并不是把它视为是一种异常,并在上面的兜底流程做处理,而是另行提供了处理逻辑。

        默认返回值

        如果一次用户请求,经过了 MiddlewareController 处理后,对应的 ctx.bodyctx.status 都未被赋值时,框架会视为 404

        此时框架会默认根据 Accepet 头来响应对应的值:

        // Accpet: application/json
        +

        404

        404 - NOT FOUND 是我们比较熟悉的一种错误。

        框架并不是把它视为是一种异常,并在上面的兜底流程做处理,而是另行提供了处理逻辑。

        默认返回值

        如果一次用户请求,经过了 MiddlewareController 处理后,对应的 ctx.bodyctx.status 都未被赋值时,框架会视为 404

        此时框架会默认根据 Accepet 头来响应对应的值:

        // Accpet: application/json
         { "message": "Not Found" }
         
         // Accept: text/html
         <h1>404 Not Found</h1>
        -
        1
        2
        3
        4
        5

        重定向

        框架也支持通过配置,将默认的 HTML 请求的 404 响应重定向到指定的页面。

        // config/config.default.js
        +

        重定向

        框架也支持通过配置,将默认的 HTML 请求的 404 响应重定向到指定的页面。

        // config/config.default.js
         module.exports = {
           notfound: {
             // 也可以是一个统一的 404 外链
             pageUrl: '/404.html',
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7

        自定义 404 响应

        在一些场景下,我们需要自定义服务器 404 时的响应,只需要加入一个中间件即可统一处理:

        // app/middleware/notfound_handler.js
        +

        自定义 404 响应

        在一些场景下,我们需要自定义服务器 404 时的响应,只需要加入一个中间件即可统一处理:

        // app/middleware/notfound_handler.js
         module.exports = () => {
           return async function notFoundHandler(ctx, next) {
             await next();
        @@ -103,11 +145,11 @@
             }
           };
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        挂载中间件:

        // config/config.default.js
        +

        挂载中间件:

        // config/config.default.js
         module.exports = {
           middleware: [ 'notfoundHandler' ],
         };
        -
        1
        2
        3
        4

        常见问题

        该不该 Catch

        具体情况具体分析,没有绝对的银弹。

        如果错误是非主流程的,是可选的,那可以自行兜底处理。

        // app/service/ad.js
        +

        常见问题

        该不该 Catch

        具体情况具体分析,没有绝对的银弹。

        如果错误是非主流程的,是可选的,那可以自行兜底处理。

        // app/service/ad.js
         class AdService extends Service {
           async list() {
             // 查询推荐的广告位数据,失败则返回空。
        @@ -121,7 +163,7 @@
             }
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        如果对应的错误,是需要告知用户或通知前端代码的,那可以通过上述的 业务错误处理 来统一反馈给用户。

        回调错误无法捕获

        按照正常代码写法,所有的异常都可以用这个方式进行捕获并处理,但是一定要注意一些特殊的写法可能带来的问题。

        打一个不太正式的比方,我们的代码全部都在一个异步调用链上,所有的异步操作都通过 await 串接起来了,但是只要有一个地方跳出了异步调用链,异常就捕获不到了。

        // app/controller/home.js
        +

        如果对应的错误,是需要告知用户或通知前端代码的,那可以通过上述的 业务错误处理 来统一反馈给用户。

        回调错误无法捕获

        按照正常代码写法,所有的异常都可以用这个方式进行捕获并处理,但是一定要注意一些特殊的写法可能带来的问题。

        打一个不太正式的比方,我们的代码全部都在一个异步调用链上,所有的异步操作都通过 await 串接起来了,但是只要有一个地方跳出了异步调用链,异常就捕获不到了。

        // app/controller/home.js
         class HomeController extends Controller {
           async error () {
             // 在回调里面抛错
        @@ -130,7 +172,7 @@
             });
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        正确的做法

        // app/controller/home.js
        +

        正确的做法

        // app/controller/home.js
         class HomeController extends Controller {
           async buy () {
             const { ctx } = this;
        @@ -142,7 +184,7 @@
             });
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        在这个场景中,如果 service.trade.check 方法中代码有问题,导致执行时抛出了异常,尽管框架会在最外层通过 try catch 统一捕获错误,但是由于 setImmediate 中的代码『跳出』了异步链,它里面的错误就无法被捕捉到了。因此在编写类似代码的时候一定要注意。

        当然,框架也考虑到了这类场景,提供了 ctx.runInBackground(scope) 辅助方法,通过它又包装了一个异步链,所有在这个 scope 里面的错误都会统一捕获。

        class HomeController extends Controller {
        +

        在这个场景中,如果 service.trade.check 方法中代码有问题,导致执行时抛出了异常,尽管框架会在最外层通过 try catch 统一捕获错误,但是由于 setImmediate 中的代码『跳出』了异步链,它里面的错误就无法被捕捉到了。因此在编写类似代码的时候一定要注意。

        当然,框架也考虑到了这类场景,提供了 ctx.runInBackground(scope) 辅助方法,通过它又包装了一个异步链,所有在这个 scope 里面的错误都会统一捕获。

        class HomeController extends Controller {
           async buy () {
             const request = {};
             const config = await ctx.service.trade.buy(request);
        @@ -153,15 +195,9 @@
             });
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        - +
        + diff --git a/zh/guide/faq.html b/zh/guide/faq.html index 16c8459..e2e70db 100644 --- a/zh/guide/faq.html +++ b/zh/guide/faq.html @@ -5,20 +5,62 @@ FAQ | Egg + - - - + + -

        如果下面的内容无法解决你的问题,请查看 Egg issues

        如何高效的反馈问题?

        感谢您向我们反馈问题。

        1. 我们推荐如果是小问题(错别字修改,小的 bug fix)直接提交 PR。
        2. 如果是一个新需求,请提供:详细需求描述,最好是有伪代码示意。
        3. 如果是一个 BUG,请提供:复现步骤,错误日志以及相关配置,并尽量填写下面的模板中的条目。
        4. 如果可以,尽可能使用 egg-init --type=simple bug 提供一个最小可复现的代码仓库,方便我们排查问题。
        5. 不要挤牙膏似的交流,扩展阅读:如何向开源项目提交无法解答的问题

        最重要的是,请明白一件事:开源项目的用户和维护者之间并不是甲方和乙方的关系,issue 也不是客服工单。在开 issue 的时候,请抱着一种『一起合作来解决这个问题』的心态,不要期待我们单方面地为你服务。

        为什么我的配置不生效?

        框架的配置功能比较强大,有不同环境变量,又有框架、插件、应用等很多地方配置。

        如果你分析问题时,想知道当前运行时使用的最终配置,可以查看下 ${root}/run/application_config.json(worker 进程配置) 和 ${root}/run/agent_config.json(agent 进程配置) 这两个文件。(root 为应用根目录,只有在 local 和 unittest 环境下为项目所在目录,其他环境下都为 HOME 目录)

        也可参见配置文件

        PS:请确保没有写出以下代码:

        // config/config.default.js
        +    

        FAQ

        如果下面的内容无法解决你的问题,请查看 Egg issues

        如何高效的反馈问题?

        感谢您向我们反馈问题。

        1. 我们推荐如果是小问题(错别字修改,小的 bug fix)直接提交 PR。
        2. 如果是一个新需求,请提供:详细需求描述,最好是有伪代码示意。
        3. 如果是一个 BUG,请提供:复现步骤,错误日志以及相关配置,并尽量填写下面的模板中的条目。
        4. 如果可以,尽可能使用 egg-init --type=simple bug 提供一个最小可复现的代码仓库,方便我们排查问题。
        5. 不要挤牙膏似的交流,扩展阅读:如何向开源项目提交无法解答的问题

        最重要的是,请明白一件事:开源项目的用户和维护者之间并不是甲方和乙方的关系,issue 也不是客服工单。在开 issue 的时候,请抱着一种『一起合作来解决这个问题』的心态,不要期待我们单方面地为你服务。

        为什么我的配置不生效?

        框架的配置功能比较强大,有不同环境变量,又有框架、插件、应用等很多地方配置。

        如果你分析问题时,想知道当前运行时使用的最终配置,可以查看下 ${root}/run/application_config.json(worker 进程配置) 和 ${root}/run/agent_config.json(agent 进程配置) 这两个文件。(root 为应用根目录,只有在 local 和 unittest 环境下为项目所在目录,其他环境下都为 HOME 目录)

        也可参见配置文件

        PS:请确保没有写出以下代码:

        // config/config.default.js
         exports.someKeys = 'abc';
         module.exports = appInfo => {
           const config = {};
           config.keys = '123456';
           return config;
         };
        -
        1
        2
        3
        4
        5
        6
        7

        线上的日志打印去哪里了?

        默认配置下,本地开发环境的日志都会打印在应用根目录的 logs 文件夹下(${baseDir}/logs) ,但是在非开发期的环境(非 local 和 unittest 环境),所有的日志都会打印到 $HOME/logs 文件夹下(例如 /home/admin/logs)。这样可以让本地开发时应用日志互不影响,服务器运行时又有统一的日志输出目录。

        进程管理为什么没有选型 PM2 ?

        1. PM2 模块本身复杂度很高,出了问题很难排查。我们认为框架使用的工具复杂度不应该过高,而 PM2 自身的复杂度超越了大部分应用本身。
        2. 没法做非常深的优化。
        3. 切实的需求问题,一个进程里跑 leader,其他进程代理到 leader 这种模式(多进程模型),在企业级开发中对于减少远端连接,降低数据通信压力等都是切实的需求。特别当应用规模大到一定程度,这就会是刚需。egg 本身起源于蚂蚁金服和阿里,我们对标的起点就是大规模企业应用的构建,所以要非常全面。这些特性通过 PM2 很难做到。

        进程模型非常重要,会影响到开发模式,运行期间的深度优化等,我们认为可能由框架来控制比较合适。

        如何使用 PM2 启动应用?

        尽管我们不推荐使用 PM2 启动,但仍然是可以做到的。

        首先,在项目根目录定义启动文件:

        // server.js
        +

        线上的日志打印去哪里了?

        默认配置下,本地开发环境的日志都会打印在应用根目录的 logs 文件夹下(${baseDir}/logs) ,但是在非开发期的环境(非 local 和 unittest 环境),所有的日志都会打印到 $HOME/logs 文件夹下(例如 /home/admin/logs)。这样可以让本地开发时应用日志互不影响,服务器运行时又有统一的日志输出目录。

        进程管理为什么没有选型 PM2 ?

        1. PM2 模块本身复杂度很高,出了问题很难排查。我们认为框架使用的工具复杂度不应该过高,而 PM2 自身的复杂度超越了大部分应用本身。
        2. 没法做非常深的优化。
        3. 切实的需求问题,一个进程里跑 leader,其他进程代理到 leader 这种模式(多进程模型),在企业级开发中对于减少远端连接,降低数据通信压力等都是切实的需求。特别当应用规模大到一定程度,这就会是刚需。egg 本身起源于蚂蚁金服和阿里,我们对标的起点就是大规模企业应用的构建,所以要非常全面。这些特性通过 PM2 很难做到。

        进程模型非常重要,会影响到开发模式,运行期间的深度优化等,我们认为可能由框架来控制比较合适。

        如何使用 PM2 启动应用?

        尽管我们不推荐使用 PM2 启动,但仍然是可以做到的。

        首先,在项目根目录定义启动文件:

        // server.js
         const egg = require('egg');
         
         const workers = Number(process.argv[2] || require('os').cpus().length);
        @@ -26,8 +68,8 @@
           workers,
           baseDir: __dirname,
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8

        这样,我们就可以通过 PM2 进行启动了:

        pm2 start server.js
        -
        1

        为什么会有 csrf 报错?

        通常有两种 csrf 报错:

        • missing csrf token
        • invalid csrf token

        Egg 内置的 egg-security 插件默认对所有『非安全』的方法,例如 POSTPUTDELETE 都进行 CSRF 校验。

        请求遇到 csrf 报错通常是因为没有加正确的 csrf token 导致,具体实现方式,请阅读安全威胁 CSRF 的防范

        本地开发时,修改代码后为什么 worker 进程没有自动重启?

        没有自动重启的情况一般是在使用 Jetbrains 旗下软件(IntelliJ IDEA, WebStorm..),并且开启了 Safe Write 选项。

        Jetbrains Safe Write 文档中有提到:

        If this check box is selected, a changed file is first saved in a temporary file. If the save operation succeeds, the file being saved is replaced with the saved file. (Technically, the original file is deleted and the temporary file is renamed.)

        由于使用了重命名导致文件监听的失效。解决办法是关掉 Safe Write 选项。(Settings | Appearance & Behavior | System Settings | Use "safe write" 路径可能根据版本有所不同)

        - +

        这样,我们就可以通过 PM2 进行启动了:

        pm2 start server.js
        +

        为什么会有 csrf 报错?

        通常有两种 csrf 报错:

        • missing csrf token
        • invalid csrf token

        Egg 内置的 egg-security 插件默认对所有『非安全』的方法,例如 POSTPUTDELETE 都进行 CSRF 校验。

        请求遇到 csrf 报错通常是因为没有加正确的 csrf token 导致,具体实现方式,请阅读安全威胁 CSRF 的防范

        本地开发时,修改代码后为什么 worker 进程没有自动重启?

        没有自动重启的情况一般是在使用 Jetbrains 旗下软件(IntelliJ IDEA, WebStorm..),并且开启了 Safe Write 选项。

        Jetbrains Safe Write 文档中有提到:

        If this check box is selected, a changed file is first saved in a temporary file. If the save operation succeeds, the file being saved is replaced with the saved file. (Technically, the original file is deleted and the temporary file is renamed.)

        由于使用了重命名导致文件监听的失效。解决办法是关掉 Safe Write 选项。(Settings | Appearance & Behavior | System Settings | Use "safe write" 路径可能根据版本有所不同)

        + diff --git a/zh/guide/helper.html b/zh/guide/helper.html index 9ec9424..d10b2df 100644 --- a/zh/guide/helper.html +++ b/zh/guide/helper.html @@ -5,13 +5,55 @@ Helper | Egg + - - - + + -

        使用场景

        Helper 提供了一些实用的 utility 函数,避免逻辑分散各处,更容易编写测试用例。

        框架内置了一些常用的 Helper 方法,我们也可以编写自定义的 Helper 方法。

        访问方式

        它是一个 请求级别 的对象,可以通过 ctx.helper 访问到 helper 对象。

        Controller 中使用:

        // app/controller/user.js
        +    

        Helper

        使用场景

        Helper 提供了一些实用的 utility 函数,避免逻辑分散各处,更容易编写测试用例。

        框架内置了一些常用的 Helper 方法,我们也可以编写自定义的 Helper 方法。

        访问方式

        它是一个 请求级别 的对象,可以通过 ctx.helper 访问到 helper 对象。

        Controller 中使用:

        // app/controller/user.js
         class UserController extends Controller {
           async fetch() {
             const { app, ctx } = this;
        @@ -20,15 +62,15 @@
             ctx.body = ctx.helper.formatUser(user);
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        模板引擎中使用:

        <!-- app/view/home.tpl -->
        +

        模板引擎中使用:

        <!-- app/view/home.tpl -->
         {{ helper.shtml(value) }}
        -
        1
        2

        常用的属性和方法

        Helper 上有以下属性:

        • thisHelper 对象本身,可以用来调用其他 Helper 方法。
        • this.ctx:对应的 Context 对象。
        • this.app:对应的 Application 对象。

        框架默认提供以下 Helper 方法:

        • pathFor(name, params): 生成对应[路由]的 path 路径。
        • urlFor(name, params): 生成对应[路由]的 URL
        • shtml() / sjs() / ...: 由安全组件提供的安全方法。
        // app/router.js
        +

        常用的属性和方法

        Helper 上有以下属性:

        • thisHelper 对象本身,可以用来调用其他 Helper 方法。
        • this.ctx:对应的 Context 对象。
        • this.app:对应的 Application 对象。

        框架默认提供以下 Helper 方法:

        • pathFor(name, params): 生成对应[路由]的 path 路径。
        • urlFor(name, params): 生成对应[路由]的 URL
        • shtml() / sjs() / ...: 由安全组件提供的安全方法。
        // app/router.js
         app.get('user', '/user', controller.user);
         
         // 使用 helper 计算指定 path
         ctx.helper.pathFor('user', { limit: 10, sort: 'name' });
         // => /user?limit=10&sort=name
        -
        1
        2
        3
        4
        5
        6

        如何扩展

        我们支持开发者通过 app/extend/helper.js 来扩展 Helper

        // app/extend/helper.js
        +

        如何扩展

        我们支持开发者通过 app/extend/helper.js 来扩展 Helper

        // app/extend/helper.js
         module.exports = {
           foo(param) {
             // this 是 helper 对象,在其中可以调用其他 helper 方法
        @@ -40,7 +82,7 @@
             return only(user, [ 'name', 'phone' ]);
           }
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        对应的测试:

        // test/app/extend/helper.js
        +

        对应的测试:

        // test/app/extend/helper.js
         const { app, assert } = require('egg-mock');
         
         describe('test/app/extend/helper.js', () => {
        @@ -54,15 +96,9 @@
             assert(!result.token);
           });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        - +

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        + diff --git a/zh/guide/httpclient.html b/zh/guide/httpclient.html index 2c07ace..72336f1 100644 --- a/zh/guide/httpclient.html +++ b/zh/guide/httpclient.html @@ -5,16 +5,58 @@ HttpClient | Egg + - - - + + -

        使用背景

        互联网时代,无数服务是基于 HTTP 协议进行通信的。

        在前面我们了解到的,都是 Node.js 作为 Web 服务端的相关知识。

        其实应用本身作为发起者,来调用后端服务也是一种非常常见的应用场景。

        譬如:

        • 调用后端微服务,查询或更新数据。
        • 把日志上报给第三方服务。
        • 上传文件给后端服务。

        因此,框架内置实现了一个 HttpClient,应用可以使用它来非常便捷地完成任何 HTTP 请求。

        获取方式

        app.httpclient

        框架在应用初始化的时候,会自动将 HttpClient 初始化到 app.httpclient

        它是基于 urllib 模块的扩展。

        app.curl(http://github.com/url,%20options)

        框架提供的语法糖,它等价于 app.httpclient.request(url, options)

        const url = 'https://registry.npm.taobao.org/egg/latest';
        +    

        HttpClient

        使用背景

        互联网时代,无数服务是基于 HTTP 协议进行通信的。

        在前面我们了解到的,都是 Node.js 作为 Web 服务端的相关知识。

        其实应用本身作为发起者,来调用后端服务也是一种非常常见的应用场景。

        譬如:

        • 调用后端微服务,查询或更新数据。
        • 把日志上报给第三方服务。
        • 上传文件给后端服务。

        因此,框架内置实现了一个 HttpClient,应用可以使用它来非常便捷地完成任何 HTTP 请求。

        获取方式

        app.httpclient

        框架在应用初始化的时候,会自动将 HttpClient 初始化到 app.httpclient

        它是基于 urllib 模块的扩展。

        app.curl(http://github.com/url,%20options)

        框架提供的语法糖,它等价于 app.httpclient.request(url, options)

        const url = 'https://registry.npm.taobao.org/egg/latest';
         const result = await app.curl(url, { dataType: 'json' });
         console.log(result.data);
        -
        1
        2
        3

        ctx.curl(http://github.com/url,%20options)

        框架在 Context 中同样提供了对应的语法糖,这将是我们最常用的方法。

        它的区别在于,会默认注入 options.ctx,从而在错误处理或打印 Trace 日志时,可以方便的获取到上游请求的相关信息。

        // app/controller/http.js
        +

        ctx.curl(http://github.com/url,%20options)

        框架在 Context 中同样提供了对应的语法糖,这将是我们最常用的方法。

        它的区别在于,会默认注入 options.ctx,从而在错误处理或打印 Trace 日志时,可以方便的获取到上游请求的相关信息。

        // app/controller/http.js
         class HttpController extends Controller {
           async index() {
             const { ctx } = this;
        @@ -35,7 +77,7 @@
             };
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        常用参数及响应

        请求参数

        最常用到的 Options 参数如下:

        • options.methodHTTP 请求方法,默认为 GET,全大写格式。
        • options.data:发送的请求体,会根据 contentType 进行不同的处理。
        • options.contentType:发送的数据格式,取值 jsonform
        • options.dataType:对响应的数据进行格式转换,取值 jsontext
        • options.headers:请求头。

        完整的请求参数 options 说明,参见下文的 options 参数详解 章节。

        响应数据

        • result.status: 响应状态码,如 200, 302, 404, 500 等等。
        • result.headers: 响应头,类似 { 'content-type': 'text/html', ... }
        • result.data: 响应 body 数据,会根据 options.dataType 进行相应的格式转换。
        • result.res.timing:请求各阶段的耗时统计,需传递 options.timing 才会采集。

        HttpClient 实战

        以下示例,我们都使用 https://httpbin.org 提供的服务来测试。

        发起 GET 请求

        读取数据几乎都是使用 GET 请求,它是 HTTP 世界最常见的场景,也是最广泛的场景。

        // app/controller/http.js
        +

        常用参数及响应

        请求参数

        最常用到的 Options 参数如下:

        • options.methodHTTP 请求方法,默认为 GET,全大写格式。
        • options.data:发送的请求体,会根据 contentType 进行不同的处理。
        • options.contentType:发送的数据格式,取值 jsonform
        • options.dataType:对响应的数据进行格式转换,取值 jsontext
        • options.headers:请求头。

        完整的请求参数 options 说明,参见下文的 options 参数详解 章节。

        响应数据

        • result.status: 响应状态码,如 200, 302, 404, 500 等等。
        • result.headers: 响应头,类似 { 'content-type': 'text/html', ... }
        • result.data: 响应 body 数据,会根据 options.dataType 进行相应的格式转换。
        • result.res.timing:请求各阶段的耗时统计,需传递 options.timing 才会采集。

        HttpClient 实战

        以下示例,我们都使用 https://httpbin.org 提供的服务来测试。

        发起 GET 请求

        读取数据几乎都是使用 GET 请求,它是 HTTP 世界最常见的场景,也是最广泛的场景。

        // app/controller/http.js
         class HttpController extends Controller {
           async get() {
             const { ctx } = this;
        @@ -45,7 +87,7 @@
             ctx.body = result.data;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        通过 POST 发送 JSON

        微服务间通讯,JSON 是最常见的协议。

        譬如,创建数据的场景一般来说都会使用 POST 发送 JSON 数据。

        关键配置为:

        • method: 必须配置为 POST
        • data:需要传递的数据对应,Object 类型。
        • contentType: 'json':声明以 JSON 格式发送,框架会自动对其 stringify 处理。
        • dataType: 'json':告知框架应该自动把响应数据解析为 JSON 对象。
        // app/controller/http.js
        +

        通过 POST 发送 JSON

        微服务间通讯,JSON 是最常见的协议。

        譬如,创建数据的场景一般来说都会使用 POST 发送 JSON 数据。

        关键配置为:

        • method: 必须配置为 POST
        • data:需要传递的数据对应,Object 类型。
        • contentType: 'json':声明以 JSON 格式发送,框架会自动对其 stringify 处理。
        • dataType: 'json':告知框架应该自动把响应数据解析为 JSON 对象。
        // app/controller/http.js
         class HttpController extends Controller {
           async post() {
             const { ctx } = this;
        @@ -64,7 +106,7 @@
             ctx.body = result.data;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        提交 Form 表单

        也有很多接口是面向浏览器设计的,需要通过 Form 表单方式提交接口。

        只需把对应的 contentType 配置为 form 即可,框架会自动组装为对应的格式,并通过 application/x-www-form-urlencoded 提交。








         















        // app/controller/http.js
        +

        提交 Form 表单

        也有很多接口是面向浏览器设计的,需要通过 Form 表单方式提交接口。

        只需把对应的 contentType 配置为 form 即可,框架会自动组装为对应的格式,并通过 application/x-www-form-urlencoded 提交。








         















        // app/controller/http.js
         class HttpController extends Controller {
           async submit() {
             const { ctx } = this;
        @@ -86,8 +128,8 @@
             // }
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

        文件上传(Multipart)

        当一个表单提交包含文件的时候,请求数据格式就必须以 multipart/form-data 进行提交了。

        urllib 内置了 formstream 模块来帮助我们生成可以被消费的 form 对象。

        关键配置为:

        • files:需要上传的文件,支持多种形式: -
          • 单文件上传:支持直接传递:String 文件路径 / Stream 对象 / Buffer 对象。
          • 多文件上传:数组或 Object 格式,若为后者,则 key 为对应的 fieldName。
        • data:将被转换为对应的 form field
        // app/controller/http.js
        +

        文件上传(Multipart)

        当一个表单提交包含文件的时候,请求数据格式就必须以 multipart/form-data 进行提交了。

        urllib 内置了 formstream 模块来帮助我们生成可以被消费的 form 对象。

        关键配置为:

        • files:需要上传的文件,支持多种形式: +
          • 单文件上传:支持直接传递:String 文件路径 / Stream 对象 / Buffer 对象。
          • 多文件上传:数组或 Object 格式,若为后者,则 key 为对应的 fieldName。
        • data:将被转换为对应的 form field
        // app/controller/http.js
         class HttpController extends Controller {
           async upload() {
             const { ctx } = this;
        @@ -117,7 +159,7 @@
             // }
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30

        文件上传(Stream)

        Node.js 的世界里面,Stream 才是主流。

        如果服务端支持流式上传,最友好的方式还是直接发送 Stream

        Stream 实际会以 Transfer-Encoding: chunked 传输编码格式发送,这个转换是 HTTP 模块自动实现的。

        关键配置为:

        • stream:通过 Stream 模式发送数据。
        • dataAsQueryString:可选,需要传递额外的请求参数的场景。
        • data:可选,会被强制 querystring.stringify 处理之后拼接到 URLquery 参数上。
        // app/controller/http.js
        +

        文件上传(Stream)

        Node.js 的世界里面,Stream 才是主流。

        如果服务端支持流式上传,最友好的方式还是直接发送 Stream

        Stream 实际会以 Transfer-Encoding: chunked 传输编码格式发送,这个转换是 HTTP 模块自动实现的。

        关键配置为:

        • stream:通过 Stream 模式发送数据。
        • dataAsQueryString:可选,需要传递额外的请求参数的场景。
        • data:可选,会被强制 querystring.stringify 处理之后拼接到 URLquery 参数上。
        // app/controller/http.js
         const fs = require('fs');
         const FormStream = require('formstream');
         
        @@ -148,7 +190,7 @@
             // {"streamSize":574}
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31

        发送 XML

        此时,可以用 content 参数代替 data 参数,框架会原样发送数据。

        // app/controller/http.js
        +

        发送 XML

        此时,可以用 content 参数代替 data 参数,框架会原样发送数据。

        // app/controller/http.js
         class HttpController extends Controller {
           async xml() {
             const { ctx } = this;
        @@ -164,7 +206,7 @@
             ctx.body = result.data;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

        超时时间

        请求超时时间,默认是 [ 5000, 5000 ],即创建连接超时是 5 秒,接收响应超时是 5 秒。

        支持 Number[ Number, Number ] 格式,前者代表两个时间取同个值。

        // app/controller/http.js
        +

        超时时间

        请求超时时间,默认是 [ 5000, 5000 ],即创建连接超时是 5 秒,接收响应超时是 5 秒。

        支持 Number[ Number, Number ] 格式,前者代表两个时间取同个值。

        // app/controller/http.js
         class HttpController extends Controller {
           async timeout() {
             const { ctx } = this;
        @@ -176,7 +218,7 @@
             ctx.body = result.data;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        处理重定向

        有些时候,需要对后端的重定向进行跟进处理,框架提供了:

        • followRedirect:是否自动跟进 3xx 的跳转响应,默认是 false
        • maxRedirects:最大自动跳转次数,避免死循环,默认是 10 次。 此参数不宜设置过大。
        • formatRedirectUrl(from, to):跳转 URL 校正,默认是 url.resolve(from, to)
        // app/controller/http.js
        +

        处理重定向

        有些时候,需要对后端的重定向进行跟进处理,框架提供了:

        • followRedirect:是否自动跟进 3xx 的跳转响应,默认是 false
        • maxRedirects:最大自动跳转次数,避免死循环,默认是 10 次。 此参数不宜设置过大。
        • formatRedirectUrl(from, to):跳转 URL 校正,默认是 url.resolve(from, to)
        // app/controller/http.js
         class HttpController extends Controller {
           async followRedirect() {
             const { ctx } = this;
        @@ -198,7 +240,7 @@
             ctx.body = result.data;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

        抓包调试

        有些时候,我们需要抓包来调试对应的 HTTP 请求。

        修改本地开发配置:

        // config/config.local.js
        +

        抓包调试

        有些时候,我们需要抓包来调试对应的 HTTP 请求。

        修改本地开发配置:

        // config/config.local.js
         module.exports = () => {
           const config = {};
         
        @@ -215,8 +257,8 @@
         
           return config;
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17

        使用环境变量启动你的应用:

        $ http_proxy=http://127.0.0.1:8888 npm run dev
        -
        1

        然后启动你的抓包工具,如 CharlesFiddler,就可以看到对应的 HTTP 抓包信息。

        事件监听

        在企业应用场景,常常会有统一 Tracer 日志的需求。

        为了方便在统一监听 HttpClient 的请求和响应,我们约定了两个事件。

        // 对请求做拦截,设置一些 trace headers,方便全链路跟踪。
        +

        使用环境变量启动你的应用:

        $ http_proxy=http://127.0.0.1:8888 npm run dev
        +

        然后启动你的抓包工具,如 CharlesFiddler,就可以看到对应的 HTTP 抓包信息。

        事件监听

        在企业应用场景,常常会有统一 Tracer 日志的需求。

        为了方便在统一监听 HttpClient 的请求和响应,我们约定了两个事件。

        // 对请求做拦截,设置一些 trace headers,方便全链路跟踪。
         app.httpclient.on('request', req => {
           const { requestId, url, args, ctx } = req;
         
        @@ -237,7 +279,7 @@
           console.log(result.res.timing); // 统计请求各阶段的耗时
           console.log(ctx); // 仅在 `ctx.curl()` 时才有值,方便记录上游请求信息。
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        如何扩展

        我们跟后端的接口协议,往往会在 HTTP 上做一层简单的协议封装,如加解密和校验。

        如果每次调用 HttpClient 的时候,都要传递参数和解析协议,未免太麻烦。

        此时可以扩展下:

        // app/extend/context.js
        +

        如何扩展

        我们跟后端的接口协议,往往会在 HTTP 上做一层简单的协议封装,如加解密和校验。

        如果每次调用 HttpClient 的时候,都要传递参数和解析协议,未免太麻烦。

        此时可以扩展下:

        // app/extend/context.js
         const rpc = require('../../lib/rpc');
         
         module.exports = {
        @@ -258,7 +300,7 @@
             return result;
           },
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        这样,在 ControllerService 等地方就可以直接使用了:







         










        // app/controller/http.js
        +

        这样,在 ControllerService 等地方就可以直接使用了:







         










        // app/controller/http.js
         class HttpController extends Controller {
           async post() {
             const { ctx } = this;
        @@ -274,7 +316,7 @@
             ctx.body = result.data;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

        编写测试

        对于 HttpClient 这种关键的请求交互,单元测试就更必不可少。

        框架通过 egg-mock 提供了 app.mockHttpclient(url, method, data) 的模拟能力。

        describe('GET /httpclient', () => {
        +

        编写测试

        对于 HttpClient 这种关键的请求交互,单元测试就更必不可少。

        框架通过 egg-mock 提供了 app.mockHttpclient(url, method, data) 的模拟能力。

        describe('GET /httpclient', () => {
           it('should mock httpclient response', () => {
             app.mockHttpclient('https://eggjs.org/', {
               // 模拟的参数,可以是 `Buffer/String/JSON`
        @@ -287,7 +329,7 @@
               .expect('mock eggjs.org response');
           });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        详见对应的 Mock API

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        常见错误码

        ConnectionTimeoutError

        • 异常名称:创建连接超时ConnectionTimeoutError
        • 出现场景:通常是 DNS 查询比较慢,或者客户端与服务端之间的网络速度比较慢导致的。
        • 排查建议:请适当增大 timeout 参数。

        ResponseTimeoutError

        • 异常名称:服务响应超时ResponseTimeoutError
        • 出现场景:通常是客户端与服务端之间网络速度比较慢,并且响应数据比较大的情况下会发生。
        • 排查建议:请适当增大 timeout 参数。

        ECONNRESET

        • 异常名称:服务主动断开连接ResponseError, code: ECONNRESET
        • 出现场景:通常是服务端主动断开 Socket 连接,导致 HTTP 请求链路异常。
        • 排查建议:请检查当时服务端是否发生网络异常。

        ECONNREFUSED

        • 异常名称:服务不可达RequestError, code: ECONNREFUSED, status: -1
        • 出现场景:通常是因为请求的 URL 所属 IP 或者端口无法连接成功。
        • 排查建议:请确保 IP 或者端口设置正确,目标网络是通的。

        ENOTFOUND

        • 异常名称:域名不存在RequestError, code: ENOTFOUND, status: -1
        • 出现场景:通常是因为请求的 URL 所在的域名无法通过 DNS 解析成功。
        • 排查建议:请确保域名存在,也需要排查一下 DNS 服务是否配置正确。

        JSONResponseFormatError

        • 异常名称:JSON 响应数据解析失败JSONResponseFormatError
        • 出现场景:设置了 dataType=json,但响应数据不符合 JSON 格式,就会抛出此异常。
        • 排查建议:确保服务端无论在什么情况下都要正确返回 JSON 格式的数据。

        有些 CGI 系统返回的 JSON 数据会包含某些特殊控制字符(U+0000 ~ U+001F),可以通过 fixJSONCtlChars 参数自动过滤掉它们。

        Options 参数详解

        由于 HTTP 请求的复杂性,导致 HttpClientoptions 参数会非常多。

        接下来讲解常用的可选参数的实际用途,更多的参数可以参见 urllib 文档。

        默认全局配置

        // config/config.default.js
        +

        详见对应的 Mock API

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        常见错误码

        ConnectionTimeoutError

        • 异常名称:创建连接超时ConnectionTimeoutError
        • 出现场景:通常是 DNS 查询比较慢,或者客户端与服务端之间的网络速度比较慢导致的。
        • 排查建议:请适当增大 timeout 参数。

        ResponseTimeoutError

        • 异常名称:服务响应超时ResponseTimeoutError
        • 出现场景:通常是客户端与服务端之间网络速度比较慢,并且响应数据比较大的情况下会发生。
        • 排查建议:请适当增大 timeout 参数。

        ECONNRESET

        • 异常名称:服务主动断开连接ResponseError, code: ECONNRESET
        • 出现场景:通常是服务端主动断开 Socket 连接,导致 HTTP 请求链路异常。
        • 排查建议:请检查当时服务端是否发生网络异常。

        ECONNREFUSED

        • 异常名称:服务不可达RequestError, code: ECONNREFUSED, status: -1
        • 出现场景:通常是因为请求的 URL 所属 IP 或者端口无法连接成功。
        • 排查建议:请确保 IP 或者端口设置正确,目标网络是通的。

        ENOTFOUND

        • 异常名称:域名不存在RequestError, code: ENOTFOUND, status: -1
        • 出现场景:通常是因为请求的 URL 所在的域名无法通过 DNS 解析成功。
        • 排查建议:请确保域名存在,也需要排查一下 DNS 服务是否配置正确。

        JSONResponseFormatError

        • 异常名称:JSON 响应数据解析失败JSONResponseFormatError
        • 出现场景:设置了 dataType=json,但响应数据不符合 JSON 格式,就会抛出此异常。
        • 排查建议:确保服务端无论在什么情况下都要正确返回 JSON 格式的数据。

        有些 CGI 系统返回的 JSON 数据会包含某些特殊控制字符(U+0000 ~ U+001F),可以通过 fixJSONCtlChars 参数自动过滤掉它们。

        Options 参数详解

        由于 HTTP 请求的复杂性,导致 HttpClientoptions 参数会非常多。

        接下来讲解常用的可选参数的实际用途,更多的参数可以参见 urllib 文档。

        默认全局配置

        // config/config.default.js
         exports.httpclient = {
           // 是否开启本地 DNS 缓存,默认关闭,开启后有两个特性
           // 1. 所有的 DNS 查询都会默认优先使用缓存的,即使 DNS 查询错误也不影响应用
        @@ -329,8 +371,8 @@
             maxFreeSockets: 256,
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42

        应用可以通过 config/config.default.js 覆盖此配置。

        method: String

        HTTP 请求方法,默认是 GET,全大写格式,支持所有 HTTP 方法

        data: Object

        需要发送的请求数据,会根据 method 自动选择正确的数据处理方式。

        • GETHEAD:通过 querystring.stringify(data) 处理后拼接到 URL 的查询参数上。
        • POSTPUTDELETE 等:需要根据 contentType 做进一步判断处理。 -
          • contentType = json:通过 JSON.stringify(data) 处理,并通过请求 body 发送。
          • 其他:通过 querystring.stringify(data) 处理,并通过请求 body 发送。
        // GET + Query, `/api/user?foo=bar`
        +

        应用可以通过 config/config.default.js 覆盖此配置。

        method: String

        HTTP 请求方法,默认是 GET,全大写格式,支持所有 HTTP 方法

        data: Object

        需要发送的请求数据,会根据 method 自动选择正确的数据处理方式。

        • GETHEAD:通过 querystring.stringify(data) 处理后拼接到 URL 的查询参数上。
        • POSTPUTDELETE 等:需要根据 contentType 做进一步判断处理。 +
          • contentType = json:通过 JSON.stringify(data) 处理,并通过请求 body 发送。
          • 其他:通过 querystring.stringify(data) 处理,并通过请求 body 发送。
        // GET + Query, `/api/user?foo=bar`
         ctx.curl(url, {
           data: { foo: 'bar' },
         });
        @@ -347,7 +389,7 @@
           contentType: 'json',
           data: { foo: 'bar' },
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17

        contentType: String

        设置请求数据格式,支持 jsonform,决定了请求数据的序列化格式。

        如需要以 JSON 格式发送 data

        ctx.curl(url, {
        +

        contentType: String

        设置请求数据格式,支持 jsonform,决定了请求数据的序列化格式。

        如需要以 JSON 格式发送 data

        ctx.curl(url, {
           method: 'POST',
           data: {
             foo: 'bar',
        @@ -355,7 +397,7 @@
           },
           contentType: 'json',
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8

        dataType: String

        设置响应数据格式,默认不对响应数据做任何处理,直接返回原始的 buffer 格式数据。

        支持 textjson 两种取值。

        const jsonResult = await ctx.curl(url, {
        +

        dataType: String

        设置响应数据格式,默认不对响应数据做任何处理,直接返回原始的 buffer 格式数据。

        支持 textjson 两种取值。

        const jsonResult = await ctx.curl(url, {
           dataType: 'json',
         });
         console.log(jsonResult.data);
        @@ -364,7 +406,7 @@
           dataType: 'text',
         });
         console.log(htmlResult.data);
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        注意

        设置成 json 时,如果响应数据解析失败会抛 JSONResponseFormatError 异常。

        dataAsQueryString: Boolean

        如果设置为 true,那么即使在 POST 情况下,也会强制将 options.dataquerystring.stringify 处理之后拼接到 URL 的查询参数上。

        可以很好地解决以 stream 发送数据,且额外的请求参数以 URL Query 形式传递的应用场景:

        ctx.curl(url, {
        +

        注意

        设置成 json 时,如果响应数据解析失败会抛 JSONResponseFormatError 异常。

        dataAsQueryString: Boolean

        如果设置为 true,那么即使在 POST 情况下,也会强制将 options.dataquerystring.stringify 处理之后拼接到 URL 的查询参数上。

        可以很好地解决以 stream 发送数据,且额外的请求参数以 URL Query 形式传递的应用场景:

        ctx.curl(url, {
           method: 'POST',
           dataAsQueryString: true,
           data: {
        @@ -373,7 +415,7 @@
           },
           stream: myFileStream,
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        content: String|Buffer

        发送请求正文,如果设置了此参数,那么会直接忽略 data 参数。

        ctx.curl(url, {
        +

        content: String|Buffer

        发送请求正文,如果设置了此参数,那么会直接忽略 data 参数。

        ctx.curl(url, {
           method: 'POST',
           // 直接发送原始 xml 数据,不需要 HttpClient 做特殊处理
           content: '<xml><hello>world</hello></xml>',
        @@ -381,12 +423,12 @@
             'content-type': 'text/html',
           },
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8

        headers: Object

        自定义请求头。

        ctx.curl(url, {
        +

        headers: Object

        自定义请求头。

        ctx.curl(url, {
           headers: {
             'x-foo': 'bar',
           },
         });
        -
        1
        2
        3
        4
        5

        timeout: Number|Array

        请求超时时间,默认是 [ 5000, 5000 ],即创建连接超时是 5 秒,接收响应超时是 5 秒。

        ctx.curl(url, {
        +

        timeout: Number|Array

        请求超时时间,默认是 [ 5000, 5000 ],即创建连接超时是 5 秒,接收响应超时是 5 秒。

        ctx.curl(url, {
           // 创建连接超时 3 秒,接收响应超时 3 秒
           timeout: 3000,
         });
        @@ -395,14 +437,14 @@
           // 创建连接超时 1 秒,接收响应超时 30 秒,用于响应比较大的场景
           timeout: [ 1000, 30000 ],
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        files: Mixed

        文件上传,支持格式: String | ReadStream | Buffer | Array | Object

        ctx.curl(url, {
        +

        files: Mixed

        文件上传,支持格式: String | ReadStream | Buffer | Array | Object

        ctx.curl(url, {
           method: 'POST',
           files: '/path/to/read',
           data: {
             foo: 'other fields',
           },
         });
        -
        1
        2
        3
        4
        5
        6
        7

        多文件上传:

        ctx.curl(url, {
        +

        多文件上传:

        ctx.curl(url, {
           method: 'POST',
           files: {
             file1: '/path/to/read',
        @@ -413,33 +455,33 @@
             foo: 'other fields',
           },
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        stream: ReadStream

        设置发送请求正文的可读数据流,一旦设置了此参数,将会忽略 datacontent

        ctx.curl(url, {
        +

        stream: ReadStream

        设置发送请求正文的可读数据流,一旦设置了此参数,将会忽略 datacontent

        ctx.curl(url, {
           method: 'POST',
           stream: fs.createReadStream('/path/to/read'),
         });
        -
        1
        2
        3
        4

        writeStream: WriteStream

        设置接受响应数据的可写数据流,默认是 null。 +

        writeStream: WriteStream

        设置接受响应数据的可写数据流,默认是 null。 一旦设置此参数,那么返回值 result.data 将会被设置为 null, -因为数据已经全部写入到 writeStream 中了。

        ctx.curl(url, {
        +因为数据已经全部写入到 writeStream 中了。

        ctx.curl(url, {
           writeStream: fs.createWriteStream('/path/to/store'),
         });
        -
        1
        2
        3

        注意事项

        请在你充分理解 Stream异步编程 的基础上,再使用。

        streaming: Boolean

        是否直接返回响应流。

        开启后会在拿到响应对象 res 时马上返回,此时 headersstatus 已经可以读取到,但还没有读取 data 数据。

        const result = await ctx.curl(url, {
        +

        注意事项

        请在你充分理解 Stream异步编程 的基础上,再使用。

        streaming: Boolean

        是否直接返回响应流。

        开启后会在拿到响应对象 res 时马上返回,此时 headersstatus 已经可以读取到,但还没有读取 data 数据。

        const result = await ctx.curl(url, {
           streaming: true,
         });
         
         console.log(result.status, result.data);
         // result.res 是一个 ReadStream 对象
         ctx.body = result.res;
        -
        1
        2
        3
        4
        5
        6
        7

        注意

        若 res 不是直接传递给 body,那么我们必须消费这个 stream,并且要做好 error 事件处理。

        beforeRequest: Function(options)

        在请求正式发送之前,会尝试调用 beforeRequest 钩子,允许我们在这里对请求参数做最后一次修改。

        ctx.curl(url, {
        +

        注意

        若 res 不是直接传递给 body,那么我们必须消费这个 stream,并且要做好 error 事件处理。

        beforeRequest: Function(options)

        在请求正式发送之前,会尝试调用 beforeRequest 钩子,允许我们在这里对请求参数做最后一次修改。

        ctx.curl(url, {
           beforeRequest: options => {
             // 例如我们可以设置全局请求 id,方便日志跟踪
             options.headers['x-request-id'] = uuid.v1();
           },
         });
        -
        1
        2
        3
        4
        5
        6

        gzip: Boolean

        是否支持 gzip 响应格式,开启后将自动设置 Accept-Encoding: gzip 请求头, -并且会自动解压带 Content-Encoding: gzip 响应头的数据。

        ctx.curl(url, {
        +

        gzip: Boolean

        是否支持 gzip 响应格式,开启后将自动设置 Accept-Encoding: gzip 请求头, +并且会自动解压带 Content-Encoding: gzip 响应头的数据。

        ctx.curl(url, {
           gzip: true,
         });
        -
        1
        2
        3

        timing: Boolean

        是否开启请求各阶段的时间测量。

        开启后可以通过 result.res.timing 拿到这次 HTTP 请求各阶段的时间测量值(单位是毫秒)。

        通过这些测量值,我们可以非常方便地定位到这次请求最慢的环境发生在那个阶段,效果如同 Chrome Network Timing 的作用。

        各阶段测量值:

        • queuing:分配 Socket 耗时。
        • dnslookupDNS 查询耗时。
        • connectedSocket 三次握手连接成功耗时。
        • requestSent:请求数据完整发送完毕耗时。
        • waiting:收到第一个字节的响应数据耗时。
        • contentDownload:全部响应数据接收完毕耗时。
        const result = await ctx.curl(url, {
        +

        timing: Boolean

        是否开启请求各阶段的时间测量。

        开启后可以通过 result.res.timing 拿到这次 HTTP 请求各阶段的时间测量值(单位是毫秒)。

        通过这些测量值,我们可以非常方便地定位到这次请求最慢的环境发生在那个阶段,效果如同 Chrome Network Timing 的作用。

        各阶段测量值:

        • queuing:分配 Socket 耗时。
        • dnslookupDNS 查询耗时。
        • connectedSocket 三次握手连接成功耗时。
        • requestSent:请求数据完整发送完毕耗时。
        • waiting:收到第一个字节的响应数据耗时。
        • contentDownload:全部响应数据接收完毕耗时。
        const result = await ctx.curl(url, {
           timing: true,
         });
         console.log(result.res.timing);
        @@ -451,15 +493,9 @@
         //   "waiting":1833,
         //   "contentDownload":3416
         // }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        HTTPS 相关参数

        包括 keycertpassphrase 等参数,都将透传给 HTTPS 模块。

        其中 rejectUnauthorized 用于在本地调试时忽略无效的 HTTPS 证书。

        具体请查看 https.request() 文档。

        示例代码

        完整示例代码可以在 eggjs/examples/httpclient 找到。

        - +

        HTTPS 相关参数

        包括 keycertpassphrase 等参数,都将透传给 HTTPS 模块。

        其中 rejectUnauthorized 用于在本地调试时忽略无效的 HTTPS 证书。

        具体请查看 https.request() 文档。

        示例代码

        完整示例代码可以在 eggjs/examples/httpclient 找到。

        + diff --git a/zh/guide/i18n.html b/zh/guide/i18n.html index a3cbbe4..c6cfcee 100644 --- a/zh/guide/i18n.html +++ b/zh/guide/i18n.html @@ -5,28 +5,70 @@ 国际化 | Egg + - - - + + -

        使用场景

        为了方便开发多语言应用,框架内置了国际化(I18n)支持,由 egg-i18n 插件提供。

        i18ninternationalization 的缩写,代表 in 之间有 18 个字母。

        • 开发者需定义多个 locale 多语言文件。
        • 开发者在 ControllerView 中使用对应的语法糖渲染字符串。
        • 插件会根据约定,渲染指定的多语言字符串。

        定义 locale

        目录规范

        多种语言的配置是独立的,统一存放在 config/locale/*.js 下。

        showcase
        +    

        国际化

        使用场景

        为了方便开发多语言应用,框架内置了国际化(I18n)支持,由 egg-i18n 插件提供。

        i18ninternationalization 的缩写,代表 in 之间有 18 个字母。

        • 开发者需定义多个 locale 多语言文件。
        • 开发者在 ControllerView 中使用对应的语法糖渲染字符串。
        • 插件会根据约定,渲染指定的多语言字符串。

        定义 locale

        目录规范

        多种语言的配置是独立的,统一存放在 config/locale/*.js 下。

        showcase
         └── config
             ├── plugin.js
             ├── config.default.js
             └── locale
                 ├── en-US.js
                 └── zh-CN.js
        -
        1
        2
        3
        4
        5
        6
        7

        不仅对于应用目录生效,在框架,插件的 config/locale 目录下同样生效。

        友情提示

        注意单词拼写,是 locale 不是 locals。

        文件格式

        支持 jsJSON 两种格式:

        // config/locale/zh-CN.js
        +

        不仅对于应用目录生效,在框架,插件的 config/locale 目录下同样生效。

        友情提示

        注意单词拼写,是 locale 不是 locals。

        文件格式

        支持 jsJSON 两种格式:

        // config/locale/zh-CN.js
         module.exports = {
           Email: '邮箱',
         };
        -
        1
        2
        3
        4

        // config/locale/zh-CN.json
        +

        // config/locale/zh-CN.json
         {
           "Email": "邮箱"
         }
        -
        1
        2
        3
        4

        占位符

        支持类似 util.format()%s%j 等占位符语法。

        // config/locale/zh-CN.js
        +

        占位符

        支持类似 util.format()%s%j 等占位符语法。

        // config/locale/zh-CN.js
         module.exports = {
           'Welcome back, %s!': '欢迎回来,%s!',
         };
        @@ -34,7 +76,7 @@
         ctx.__('Welcome back, %s!', 'Shawn');
         // zh-CN => 欢迎回来,Shawn!
         // en-US => Welcome back, Shawn!
        -
        1
        2
        3
        4
        5
        6
        7
        8

        同时支持数组下标占位符方式,例如:

        // config/locale/zh-CN.js
        +

        同时支持数组下标占位符方式,例如:

        // config/locale/zh-CN.js
         module.exports = {
           'Hello {0}! My name is {1}.': '你好 {0}! 我的名字叫 {1}。',
         };
        @@ -42,7 +84,7 @@
         ctx.__('Hello {0}! My name is {1}.', [ 'foo', 'bar' ]);
         // zh-CN => 你好 foo!我的名字叫 bar。
         // en-US => Hello foo! My name is bar.
        -
        1
        2
        3
        4
        5
        6
        7
        8

        使用 i18n

        ctx.__(key, ...values)

        插件提供了 ctx.__(key, ...values) 来获取语言配置。

        它等价于 ctx.gettext(key, ...values)

        class HomeController extends Controller {
        +

        使用 i18n

        ctx.__(key, ...values)

        插件提供了 ctx.__(key, ...values) 来获取语言配置。

        它等价于 ctx.gettext(key, ...values)

        class HomeController extends Controller {
           async index() {
             const ctx = this.ctx;
             ctx.body = {
        @@ -60,29 +102,26 @@
             };
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18

        在 View 中使用

        插件也同时把对应的语法糖注入到 ctx.locals 上,因此也可以直接在模板引擎里面使用:

        <li>{{ __('Email') }}: {{ user.email }}</li>
        +

        在 View 中使用

        插件也同时把对应的语法糖注入到 ctx.locals 上,因此也可以直接在模板引擎里面使用:

        <li>{{ __('Email') }}: {{ user.email }}</li>
         <li>
           {{ __('Welcome back, %s!', user.name) }}
         </li>
         <li>
           {{ __('Hello {0}! My name is {1}.', ['foo', 'bar']) }}
         </li>
        -
        1
        2
        3
        4
        5
        6
        7

        切换语言

        默认语言

        默认语言是 en-US。假设我们想修改默认语言为简体中文:

        // config/config.default.js
        +

        切换语言

        默认语言

        默认语言是 en-US。假设我们想修改默认语言为简体中文:

        // config/config.default.js
         exports.i18n = {
           defaultLocale: 'zh-CN',
         };
        -
        1
        2
        3
        4

        切换语言

        插件也会根据请求参数不同,自动选择指定的语言。

        修改后会记录到 locale 这个 Cookie,下次请求直接用设定好的语言。

        优先级从高到低:

        1. Query: /?locale=en-US
        2. Cookie: locale=zh-TW
        3. Header: Accept-Language: zh-CN,zh;q=0.5

        如果想修改 Query 或者 Cookie 参数名称:

        // config/config.default.js
        +

        切换语言

        插件也会根据请求参数不同,自动选择指定的语言。

        修改后会记录到 locale 这个 Cookie,下次请求直接用设定好的语言。

        优先级从高到低:

        1. Query: /?locale=en-US
        2. Cookie: locale=zh-TW
        3. Header: Accept-Language: zh-CN,zh;q=0.5

        如果想修改 Query 或者 Cookie 参数名称:

        // config/config.default.js
         exports.i18n = {
           queryField: 'locale',
           cookieField: 'locale',
           // Cookie 默认一年后过期, 如果设置为 Number,则单位为 ms
           cookieMaxAge: '1y',
         };
        -
        1
        2
        3
        4
        5
        6
        7

        局限性

        一般来说,国际化是需要有配套的运营后台的,该插件只是一个简化的实现,开发者根据具体情况选择使用。

        - +

        局限性

        一般来说,国际化是需要有配套的运营后台的,该插件只是一个简化的实现,开发者根据具体情况选择使用。

        + diff --git a/zh/guide/index.html b/zh/guide/index.html index 5e948ab..f127f15 100644 --- a/zh/guide/index.html +++ b/zh/guide/index.html @@ -5,17 +5,56 @@ 概述 | Egg + - - - + + -

        在本篇中,我们会对每一个术语概念,逐一进行详细的讲解。

        包括它的适用场景、如何使用、常用的方法和属性、如何扩展、如何测试等等。

        Web 模型

        框架奉行『约定优于配置』,因此我们首先需要了解下 目录规范 的约定。

        其次,对于一个 Web 应用来说,一般会采用 MVC 模型。

        对应的概念有:

        • MiddlewareKoa 的洋葱模型,类似 JavaFilter
        • Controller:控制器,处理和校验用户请求,然后调用业务逻辑层,最终发送响应给用户。
        • Router:路由,对用户请求进行分派。
        • Service:业务逻辑层。
        • Application:全局应用对象,通过它可以获取 配置文件 等信息。
        • Context:用户请求的上下文,用于获取请求信息和设置响应信息。
        • 此外,还有 CookieSessionHelper 等等。

        功能模块

        除此之外,还提供了很多研发过程中需要的 Utils

        • 使用插件:生态共建的基础,一分钟即可通过插件接入各自基础中间件服务。
        • 生命周期:方便开发者做一些初始化工作。
        • 日志:对应用的运行状态监控、问题排查等都有非常重要的意义。
        • 异常处理:程序健壮性的保障。
        • 安全:安全无小事。
        • 还有 文件上传国际化 等等。
        - +

        概述

        在本篇中,我们会对每一个术语概念,逐一进行详细的讲解。

        包括它的适用场景、如何使用、常用的方法和属性、如何扩展、如何测试等等。

        Web 模型

        框架奉行『约定优于配置』,因此我们首先需要了解下 目录规范 的约定。

        其次,对于一个 Web 应用来说,一般会采用 MVC 模型。

        对应的概念有:

        • MiddlewareKoa 的洋葱模型,类似 JavaFilter
        • Controller:控制器,处理和校验用户请求,然后调用业务逻辑层,最终发送响应给用户。
        • Router:路由,对用户请求进行分派。
        • Service:业务逻辑层。
        • Application:全局应用对象,通过它可以获取 配置文件 等信息。
        • Context:用户请求的上下文,用于获取请求信息和设置响应信息。
        • 此外,还有 CookieSessionHelper 等等。

        功能模块

        除此之外,还提供了很多研发过程中需要的 Utils

        • 使用插件:生态共建的基础,一分钟即可通过插件接入各自基础中间件服务。
        • 生命周期:方便开发者做一些初始化工作。
        • 日志:对应用的运行状态监控、问题排查等都有非常重要的意义。
        • 异常处理:程序健壮性的保障。
        • 安全:安全无小事。
        • 还有 文件上传国际化 等等。
        + diff --git a/zh/guide/lifecycle.html b/zh/guide/lifecycle.html index 4db614f..71c5136 100644 --- a/zh/guide/lifecycle.html +++ b/zh/guide/lifecycle.html @@ -5,13 +5,55 @@ 生命周期 | Egg + - - - + + -

        使用场景

        我们常常需要在应用启动期间进行一些初始化工作,在本文我们将一起理解下框架的生命周期。

        框架约定可以通过 app.js 来编写 Boot 类来注入 Hook

        提供了以下生命周期Hook

        • configWillLoad:配置文件即将加载,这是最后动态修改配置的时机。
        • configDidLoad:配置文件加载完成。
        • didLoad:文件加载完成。
        • willReady:插件启动完毕,用于定义前置操作。
        • didReady:应用启动完毕。
        • serverDidReadyServer 启动完毕,可以开始导入流量。
        • beforeClose:应用即将关闭。

        定义生命周期

        我们可以通过 app.js 来挂载各个点的 Hook

        // app.js
        +    

        生命周期

        使用场景

        我们常常需要在应用启动期间进行一些初始化工作,在本文我们将一起理解下框架的生命周期。

        框架约定可以通过 app.js 来编写 Boot 类来注入 Hook

        提供了以下生命周期Hook

        • configWillLoad:配置文件即将加载,这是最后动态修改配置的时机。
        • configDidLoad:配置文件加载完成。
        • didLoad:文件加载完成。
        • willReady:插件启动完毕,用于定义前置操作。
        • didReady:应用启动完毕。
        • serverDidReadyServer 启动完毕,可以开始导入流量。
        • beforeClose:应用即将关闭。

        定义生命周期

        我们可以通过 app.js 来挂载各个点的 Hook

        // app.js
         class AppBootHook {
           constructor(app) {
             this.app = app;
        @@ -38,7 +80,7 @@
           // 应用即将关闭前
           async beforeClose() {}
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27

        注意

        在自定义生命周期函数中不建议做太耗时的操作,框架会有启动的超时检测。

        详解生命周期

        configWillLoad()

        此时配置文件已经被读取并合并,但是还并未生效,这是应用层修改配置的最后时机

        使用场景举例:

        • 对配置中的秘钥进行解密。
        • 修改框架内置中间件顺序。
        // app.js
        +

        注意

        在自定义生命周期函数中不建议做太耗时的操作,框架会有启动的超时检测。

        详解生命周期

        configWillLoad()

        此时配置文件已经被读取并合并,但是还并未生效,这是应用层修改配置的最后时机

        使用场景举例:

        • 对配置中的秘钥进行解密。
        • 修改框架内置中间件顺序。
        // app.js
         class AppBootHook {
           constructor(app) {
             this.app = app;
        @@ -51,7 +93,7 @@
             this.app.config.mysql.password = decrypt(this.app.config.mysql.password);
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        注意事项

        Hook 现在只支持同步调用。

        configDidLoad()

        所有的配置已经加载完毕,此 Hook 可以用来加载应用自定义的文件,启动自定义的服务。

        使用场景举例:

        • 初始化自定义的模块。
        • 自定义 Loader 加载规范。
        • 插入一个中间件到框架的 coreMiddleware 之间。
        // app.js
        +

        注意事项

        Hook 现在只支持同步调用。

        configDidLoad()

        所有的配置已经加载完毕,此 Hook 可以用来加载应用自定义的文件,启动自定义的服务。

        使用场景举例:

        • 初始化自定义的模块。
        • 自定义 Loader 加载规范。
        • 插入一个中间件到框架的 coreMiddleware 之间。
        // app.js
         class AppBootHook {
           constructor(app) {
             this.app = app;
        @@ -68,7 +110,7 @@
             this.app.config.coreMiddleware.splice(statusIndex + 1, 0, 'limit');
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17

        async didLoad()

        Hook 可以用来插件的初始化。

        把初始化逻辑拆分为 configDidLoaddidLoad 两个阶段的考虑在于:插件之间可能有服务依赖

        // app.js
        +

        async didLoad()

        Hook 可以用来插件的初始化。

        把初始化逻辑拆分为 configDidLoaddidLoad 两个阶段的考虑在于:插件之间可能有服务依赖

        // app.js
         class AppBootHook {
           constructor(app) {
             this.app = app;
        @@ -84,7 +126,7 @@
             await this.app.queue.init();
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

        async willReady()

        所有的插件都已启动完毕,但是应用整体还未 Ready

        在该 Hook 可以做一些必须的前置操作,这些操作成功才会启动应用。

        • 如做一些数据初始化等操作。
        // app.js
        +

        async willReady()

        所有的插件都已启动完毕,但是应用整体还未 Ready

        在该 Hook 可以做一些必须的前置操作,这些操作成功才会启动应用。

        • 如做一些数据初始化等操作。
        // app.js
         class AppBootHook {
           constructor(app) {
             this.app = app;
        @@ -98,7 +140,7 @@
             this.app.cacheData = await this.app.model.query('select * from QUERY_CACHE_SQL');
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        注意

        在自定义生命周期函数中不建议做太耗时的操作,框架会有启动的超时检测。

        async didReady()

        应用已经启动完毕,可以用于做一些初始化工作。

        willReady() 的区别在于: 该 Hook 的操作是可选的,失败不会阻塞应用启动。

        // app.js
        +

        注意

        在自定义生命周期函数中不建议做太耗时的操作,框架会有启动的超时检测。

        async didReady()

        应用已经启动完毕,可以用于做一些初始化工作。

        willReady() 的区别在于: 该 Hook 的操作是可选的,失败不会阻塞应用启动。

        // app.js
         class AppBootHook {
           constructor(app) {
             this.app = app;
        @@ -111,7 +153,7 @@
             await ctx.service.Biz.request();
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        async serverDidReady()

        HTTP/HTTPS Server 已经启动成功,可以开始导入流量,处理外部请求。

        此时可以拿到 app.server 实例。

        // app.js
        +

        async serverDidReady()

        HTTP/HTTPS Server 已经启动成功,可以开始导入流量,处理外部请求。

        此时可以拿到 app.server 实例。

        // app.js
         class AppBootHook {
           constructor(app) {
             this.app = app;
        @@ -123,7 +165,7 @@
             });
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        async beforeClose()

        应用即将关闭前的处理 Hook,一般用于资源的释放操作。

        注意:该 Hook 将按注册的逆序执行。

        // app.js
        +

        async beforeClose()

        应用即将关闭前的处理 Hook,一般用于资源的释放操作。

        注意:该 Hook 将按注册的逆序执行。

        // app.js
         class AppBootHook {
           constructor(app) {
             this.app = app;
        @@ -133,15 +175,9 @@
             // do sth before app close
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        注意事项

        框架默认最多只会等到 5s 就会退出,不保证会等待所有的该 Hook 执行完毕。

        - +

        注意事项

        框架默认最多只会等到 5s 就会退出,不保证会等待所有的该 Hook 执行完毕。

        + diff --git a/zh/guide/logger.html b/zh/guide/logger.html index df64123..bc1fc90 100644 --- a/zh/guide/logger.html +++ b/zh/guide/logger.html @@ -5,19 +5,61 @@ 日志 | Egg + - - - + + -

        使用场景

        日志对于 Web 开发的重要性毋庸置疑,对应用的运行状态监控、问题排查等都有非常重要的意义。

        框架内置了强大的企业级日志支持,由 egg-logger 模块提供。

        主要特性:

        • 日志分级
        • 统一错误日志
        • 启动日志和运行日志分离
        • 多进程日志
        • 自动切割日志
        • 高性能
        • 可扩展,支持自定义日志

        打印日志

        在绝大部分的地方,你都可以获取到 Logger 实例。

        以下介绍几个常用的获取方式,它们的对应的日志都会写入到 ${appInfo.name}-web.log 文件。

        app.logger

        应用级别的日志,记录一些业务上与请求无关的信息,如启动阶段。

        // app/middleware/static.js
        +    

        日志

        使用场景

        日志对于 Web 开发的重要性毋庸置疑,对应用的运行状态监控、问题排查等都有非常重要的意义。

        框架内置了强大的企业级日志支持,由 egg-logger 模块提供。

        主要特性:

        • 日志分级
        • 统一错误日志
        • 启动日志和运行日志分离
        • 多进程日志
        • 自动切割日志
        • 高性能
        • 可扩展,支持自定义日志

        打印日志

        在绝大部分的地方,你都可以获取到 Logger 实例。

        以下介绍几个常用的获取方式,它们的对应的日志都会写入到 ${appInfo.name}-web.log 文件。

        app.logger

        应用级别的日志,记录一些业务上与请求无关的信息,如启动阶段。

        // app/middleware/static.js
         module.exports = (options, app) => {
           app.logger.info(`[egg-static] mount ${options.dir} as static root`);
         
           return async function static() {};
         };
        -
        1
        2
        3
        4
        5
        6

        ctx.logger

        用于记录请求相关的日志。

        它打印的日志都会在前面带上一些当前请求相关的信息。

        [${userId}/${ip}/${traceId}/${cost}ms ${method} ${url}]

        // app/controller/user.js
        +

        ctx.logger

        用于记录请求相关的日志。

        它打印的日志都会在前面带上一些当前请求相关的信息。

        [${userId}/${ip}/${traceId}/${cost}ms ${method} ${url}]

        // app/controller/user.js
         class UserController extends Controller {
           async list() {
             const { app, ctx } = this;
        @@ -26,8 +68,8 @@
             ctx.body = [ { name: 'TZ' } ];
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        对应的日志输出为:

        2019-02-03 11:18:56,157 INFO 46536 [-/127.0.0.1/-/5ms GET /api/user] ctx.logger
        -
        1

        this.logger

        ControllerService 等实例中可以获取该对象。

        类似 ctx.logger,不同之处是它会额外加上该日志的文件路径,以便快速定位日志打印位置。

        // app/controller/user.js
        +

        对应的日志输出为:

        2019-02-03 11:18:56,157 INFO 46536 [-/127.0.0.1/-/5ms GET /api/user] ctx.logger
        +

        this.logger

        ControllerService 等实例中可以获取该对象。

        类似 ctx.logger,不同之处是它会额外加上该日志的文件路径,以便快速定位日志打印位置。

        // app/controller/user.js
         class UserController extends Controller {
           async list() {
             const { app, ctx } = this;
        @@ -37,29 +79,29 @@
             ctx.body = [ { name: 'TZ' } ];
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        对应的日志输出为:

        2019-02-03 11:18:56,157 INFO 46536 [-/127.0.0.1/-/5ms GET /api/user] ctx.logger
        +

        对应的日志输出为:

        2019-02-03 11:18:56,157 INFO 46536 [-/127.0.0.1/-/5ms GET /api/user] ctx.logger
         2019-02-03 11:18:56,158 INFO 46536 [-/127.0.0.1/-/5ms GET /api/user] [controller.user] this.logger
        -
        1
        2

        日志级别

        日志分为 NONEDEBUGINFOWARNERROR 5 个级别。

        分别对应于:logger.debug() / logger.info() / logger.warn() / logger.error()

        默认只会输出 INFO 及以上级别,可以通过对应的 logger.level 来配置。

        // config/config.default.js
        +

        日志级别

        日志分为 NONEDEBUGINFOWARNERROR 5 个级别。

        分别对应于:logger.debug() / logger.info() / logger.warn() / logger.error()

        默认只会输出 INFO 及以上级别,可以通过对应的 logger.level 来配置。

        // config/config.default.js
         config.logger = {
           level: 'INFO',
         };
        -
        1
        2
        3
        4

        错误日志

        为了更方便的进行错误追踪,框架默认会把所有 LoggerERROR 日志统一输出到 common-error.log 文件

        另外,为了保证异常可追踪,请输出 Error 类型,从而获取到堆栈信息。

        ctx.logger.error(new Error('whoops'));
        -
        1

        将输出:

        2019-02-03 14:23:25,481 ERROR 93655 [-/127.0.0.1/-/6ms GET /] nodejs.Error: whoops
        +

        错误日志

        为了更方便的进行错误追踪,框架默认会把所有 LoggerERROR 日志统一输出到 common-error.log 文件

        另外,为了保证异常可追踪,请输出 Error 类型,从而获取到堆栈信息。

        ctx.logger.error(new Error('whoops'));
        +

        将输出:

        2019-02-03 14:23:25,481 ERROR 93655 [-/127.0.0.1/-/6ms GET /] nodejs.Error: whoops
             at HomeController.index (/Users/tz/Workspaces/coding/github.com/atian25/egg-showcase/app/controller/home.js:13:23)
        -
        1
        2

        输出方式

        文件日志

        日志文件默认都放在 ${appInfo.root}/logs/${appInfo.name} 目录下。

        值得注意的是:appInfo.root 会根据运行环境自动适配根目录。

        • localunittest 环境下为 baseDir,即项目源码的根目录。
        • prod 和其他运行环境,都为 HOME,即用户目录,如 /home/admin

        这是一个优雅的适配,因为:

        • 为了统一管控,线上环境都统一写入用户目录,如 /home/admin/logs/${appInfo.name}
        • 本地开发时,为了避免冲突,不想污染用户目录,会倾向于直接打印在项目源码的 logs 目录。

        终端日志

        日志打印到文件中的同时,为了方便开发,也会同时打印到终端中。

        开发环境下默认只会输出 INFO 及以上级别,可以通过对应的 logger.consoleLevel 来配置。

        // config/config.default.js
        +

        输出方式

        文件日志

        日志文件默认都放在 ${appInfo.root}/logs/${appInfo.name} 目录下。

        值得注意的是:appInfo.root 会根据运行环境自动适配根目录。

        • localunittest 环境下为 baseDir,即项目源码的根目录。
        • prod 和其他运行环境,都为 HOME,即用户目录,如 /home/admin

        这是一个优雅的适配,因为:

        • 为了统一管控,线上环境都统一写入用户目录,如 /home/admin/logs/${appInfo.name}
        • 本地开发时,为了避免冲突,不想污染用户目录,会倾向于直接打印在项目源码的 logs 目录。

        终端日志

        日志打印到文件中的同时,为了方便开发,也会同时打印到终端中。

        开发环境下默认只会输出 INFO 及以上级别,可以通过对应的 logger.consoleLevel 来配置。

        // config/config.default.js
         config.logger = {
           consoleLevel: 'INFO',
         };
        -
        1
        2
        3
        4

        注意事项

        基于性能的考虑,在正式环境下,默认会关闭终端日志输出。

        正式环境

        基于性能和统一管控的考虑,正式环境的日志配置,有以下默认约定。

        落盘方式

        通常 Web 访问是高频访问,每次打印日志都写磁盘会造成频繁磁盘 IO。

        为了提高性能,我们采用的文件日志写入策略是:

        日志同步写入内存,异步每隔一段时间(默认 1 秒)刷盘。

        更多详细请参考 egg-loggeregg-logrotator

        日志文件输出位置

        为了统一管控,一般要求线上环境都统一写入用户目录,如 /home/admin/logs/${appInfo.name}

        具体参见上面的 文件日志 章节相关描述。

        禁止输出 DEBUG 日志

        在生产环境,为了避免一些插件的调试日志打印导致性能问题,默认禁止打印 DEBUG 日志。

        如果确实有需求,需要打开 allowDebugAtProd 配置项。(不推荐

        // config/config.default.js
        +

        注意事项

        基于性能的考虑,在正式环境下,默认会关闭终端日志输出。

        正式环境

        基于性能和统一管控的考虑,正式环境的日志配置,有以下默认约定。

        落盘方式

        通常 Web 访问是高频访问,每次打印日志都写磁盘会造成频繁磁盘 IO。

        为了提高性能,我们采用的文件日志写入策略是:

        日志同步写入内存,异步每隔一段时间(默认 1 秒)刷盘。

        更多详细请参考 egg-loggeregg-logrotator

        日志文件输出位置

        为了统一管控,一般要求线上环境都统一写入用户目录,如 /home/admin/logs/${appInfo.name}

        具体参见上面的 文件日志 章节相关描述。

        禁止输出 DEBUG 日志

        在生产环境,为了避免一些插件的调试日志打印导致性能问题,默认禁止打印 DEBUG 日志。

        如果确实有需求,需要打开 allowDebugAtProd 配置项。(不推荐

        // config/config.default.js
         exports.logger = {
           level: 'DEBUG',
           allowDebugAtProd: true,
         };
        -
        1
        2
        3
        4
        5

        禁止输出终端日志

        基于性能的考虑,在正式环境下,默认会关闭终端日志输出。

        如有需要,你可以通过下面的配置开启。(不推荐

        // config/config.default.js
        +

        禁止输出终端日志

        基于性能的考虑,在正式环境下,默认会关闭终端日志输出。

        如有需要,你可以通过下面的配置开启。(不推荐

        // config/config.default.js
         exports.logger = {
           disableConsoleAfterReady: false,
         };
        -
        1
        2
        3
        4

        自定义日志

        一般应用无需自己配置自定义日志,因为日志打太多或太分散都会导致关注度分散,反而难以管理和难以排查发现问题。

        框架内置日志

        • ${appInfo.name}-web.log:应用输出的日志,通过上述的 ctx.logger 等打印。
        • egg-web.log: 用于框架内核、插件日志,通过 app.coreLogger 打印。
        • common-error.log:所有 Logger 的错误日志会统一汇集到该文件。
        • 还有很多内置插件输出的 Tracer 日志,详见对应的文档。

        增加自定义日志

        你也可以通过以下配置,增加自定义日志:

        // config/config.default.js
        +

        自定义日志

        一般应用无需自己配置自定义日志,因为日志打太多或太分散都会导致关注度分散,反而难以管理和难以排查发现问题。

        框架内置日志

        • ${appInfo.name}-web.log:应用输出的日志,通过上述的 ctx.logger 等打印。
        • egg-web.log: 用于框架内核、插件日志,通过 app.coreLogger 打印。
        • common-error.log:所有 Logger 的错误日志会统一汇集到该文件。
        • 还有很多内置插件输出的 Tracer 日志,详见对应的文档。

        增加自定义日志

        你也可以通过以下配置,增加自定义日志:

        // config/config.default.js
         const path = require('path');
         
         module.exports = appInfo => {
        @@ -74,7 +116,7 @@
         
           return config;
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        如果配置为文件名,则会自动转换为 path.join(this.app.config.logger.dir, file)

        然后可通过 app.getLogger('oneLogger') / ctx.getLogger('oneLogger') 获取,获取到的 logger 会使用对应的 Logger 配置,并以 config.logger 为默认值。

        注意

        app.getLoggerctx.getLogger 获取到的 logger 实例是有区别的,前者拿到是应用级别的日志实例( 参考 app.logger ),后者拿到的是请求级别的日志实例( 参考 ctx.logger ),如果需要自定义日志中也有请求信息( 比如 userId、traceId 等 ),请选择 ctx.getLogger,否则选择 app.getLogger,请根据项目的日志实际使用场景选择合理的方法。

        日志输出格式

        你也可以通过自定义 formattercontextFormatter 来自定义日志输出格式。

        // config/config.default.js
        +

        如果配置为文件名,则会自动转换为 path.join(this.app.config.logger.dir, file)

        然后可通过 app.getLogger('oneLogger') / ctx.getLogger('oneLogger') 获取,获取到的 logger 会使用对应的 Logger 配置,并以 config.logger 为默认值。

        注意

        app.getLoggerctx.getLogger 获取到的 logger 实例是有区别的,前者拿到是应用级别的日志实例( 参考 app.logger ),后者拿到的是请求级别的日志实例( 参考 ctx.logger ),如果需要自定义日志中也有请求信息( 比如 userId、traceId 等 ),请选择 ctx.getLogger,否则选择 app.getLogger,请根据项目的日志实际使用场景选择合理的方法。

        日志输出格式

        你也可以通过自定义 formattercontextFormatter 来自定义日志输出格式。

        // config/config.default.js
         config.customLogger = {
           oneLogger: {
             file: 'one.log',
        @@ -88,7 +130,7 @@
             },
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        高级自定义日志

        日志默认是打印到日志文件中,当本地开发时同时会打印到终端。

        但是,有时候我们需要把日志上报到第三方服务,这时候我们就需要自定义日志的 Transport

        Transport 是一种传输通道,一个 Logger 可包含多个传输通道。

        默认的 Logger 均有 FileConsole 两个通道,分别负责打印到文件和终端。

        举个例子,我们不仅需要把错误日志打印到 common-error.log,还需要上报给第三方服务。

        首先我们定义一个日志的 Transport,代表第三方日志服务。

        // lib/remote_transport.js
        +

        高级自定义日志

        日志默认是打印到日志文件中,当本地开发时同时会打印到终端。

        但是,有时候我们需要把日志上报到第三方服务,这时候我们就需要自定义日志的 Transport

        Transport 是一种传输通道,一个 Logger 可包含多个传输通道。

        默认的 Logger 均有 FileConsole 两个通道,分别负责打印到文件和终端。

        举个例子,我们不仅需要把错误日志打印到 common-error.log,还需要上报给第三方服务。

        首先我们定义一个日志的 Transport,代表第三方日志服务。

        // lib/remote_transport.js
         const util = require('util');
         const Transport = require('egg-logger').Transport;
         
        @@ -109,9 +151,9 @@
             }).catch(console.error);
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

        然后再对 Logger 添加 Transport,这样每条日志就会同时打印到这个 Transport 了。

        // app.js
        +

        然后再对 Logger 添加 Transport,这样每条日志就会同时打印到这个 Transport 了。

        // app.js
         app.getLogger('errorLogger').set('remote', new RemoteErrorTransport({ level: 'ERROR', app }));
        -
        1
        2

        上面的例子比较简单,实际情况中我们需要考虑性能,很可能采取先打印到内存,再定时上传的策略,以提高性能。

        日志切割

        企业级日志一个最常见的需求之一是对日志进行自动切割,以方便管理。

        框架内置了 egg-logrotator 插件来提供支持。

        按天切割

        这是框架的默认日志切割方式,在每日 00:01 按照 .log.YYYY-MM-DD 文件名进行切割。

        譬如当前写入的日志为 example-app-web.log,当凌晨 00:00 时,会对日志进行切割,把过去一天的日志按 example-app-web.log.YYYY-MM-DD 的形式切割为单独的文件。

        按照文件大小切割

        我们也可以按照文件大小进行切割。例如,当文件超过 2G 时进行切割。

        譬如,我们需要把 egg-web.log 按照大小进行切割:

        // config/config.default.js
        +

        上面的例子比较简单,实际情况中我们需要考虑性能,很可能采取先打印到内存,再定时上传的策略,以提高性能。

        日志切割

        企业级日志一个最常见的需求之一是对日志进行自动切割,以方便管理。

        框架内置了 egg-logrotator 插件来提供支持。

        按天切割

        这是框架的默认日志切割方式,在每日 00:01 按照 .log.YYYY-MM-DD 文件名进行切割。

        譬如当前写入的日志为 example-app-web.log,当凌晨 00:00 时,会对日志进行切割,把过去一天的日志按 example-app-web.log.YYYY-MM-DD 的形式切割为单独的文件。

        按照文件大小切割

        我们也可以按照文件大小进行切割。例如,当文件超过 2G 时进行切割。

        譬如,我们需要把 egg-web.log 按照大小进行切割:

        // config/config.default.js
         const path = require('path');
         
         module.exports = appInfo => {
        @@ -126,7 +168,7 @@
         
           return config;
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        添加到 filesRotateBySize 的日志文件不再按天进行切割。

        如果配置为文件名,则会自动转换为 path.join(this.app.config.logger.dir, file)

        按照小时切割

        我们也可以选择按照小时进行切割,这和默认的按天切割非常类似,只是时间缩短到每小时。

        例如,我们需要把 common-error.log 按照小时进行切割:

        // config/config.${env}.js
        +

        添加到 filesRotateBySize 的日志文件不再按天进行切割。

        如果配置为文件名,则会自动转换为 path.join(this.app.config.logger.dir, file)

        按照小时切割

        我们也可以选择按照小时进行切割,这和默认的按天切割非常类似,只是时间缩短到每小时。

        例如,我们需要把 common-error.log 按照小时进行切割:

        // config/config.${env}.js
         const path = require('path');
         
         module.exports = appInfo => {
        @@ -138,7 +180,7 @@
             },
           };
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        添加到 filesRotateByHour 的日志文件不再被按天进行切割。

        如果配置为文件名,则会自动转换为 path.join(this.app.config.logger.dir, file)

        编写测试

        框架提供了 expectLog()mockLog() 来简化测试工作。

        后者会把对应的日志保留一份在缓存中,避免 IO 较高时,写入延迟导致的校验失败。

        it('should work', async () => {
        +

        添加到 filesRotateByHour 的日志文件不再被按天进行切割。

        如果配置为文件名,则会自动转换为 path.join(this.app.config.logger.dir, file)

        编写测试

        框架提供了 expectLog()mockLog() 来简化测试工作。

        后者会把对应的日志保留一份在缓存中,避免 IO 较高时,写入延迟导致的校验失败。

        it('should work', async () => {
           app.mockLog();
           await app.httpRequest()
             .get('/')
        @@ -149,15 +191,9 @@
           app.expectLog(/foo in coreLogger/, 'coreLogger');
           app.expectLog('foo in myCustomLogger', 'myCustomLogger');
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        - +
        + diff --git a/zh/guide/middleware.html b/zh/guide/middleware.html index cd288a8..22a26e5 100644 --- a/zh/guide/middleware.html +++ b/zh/guide/middleware.html @@ -5,13 +5,55 @@ Middleware | Egg + - - - + + -

        使用场景

        一个 HTTP 请求进来后,会执行一系列的处理,然后返回响应给用户。

        这个过程就像一条管道,管道的每一个切面逻辑,我们称之为 Middleware,也叫 中间件

        框架继承于 Koa,在 Koa 里面有个更形象的术语:洋葱模型

        Koa 中间件执行顺序:

        编写中间件

        我们约定把中间件放置在 app/middleware 目录下:

        // app/middleware/response_time.js
        +    

        Middleware

        使用场景

        一个 HTTP 请求进来后,会执行一系列的处理,然后返回响应给用户。

        这个过程就像一条管道,管道的每一个切面逻辑,我们称之为 Middleware,也叫 中间件

        框架继承于 Koa,在 Koa 里面有个更形象的术语:洋葱模型

        Koa 中间件执行顺序:

        编写中间件

        我们约定把中间件放置在 app/middleware 目录下:

        // app/middleware/response_time.js
         module.exports = () => {
           return async function responseTime(ctx, next) {
             const start = Date.now();
        @@ -20,15 +62,15 @@
             ctx.set('X-Response-Time', `${cost}ms`);
           }
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        如上,需 exports 一个普通的 function,返回一个标准的 Koa Middleware 函数。

        加载规则

        框架会把 app/middleware 目录下的文件挂载到 app.middleware 上。

        支持多级目录,注意:对应的文件名会转换为驼峰格式

        app/middleware/api/auth.js => app.middleware.api.auth
        +

        如上,需 exports 一个普通的 function,返回一个标准的 Koa Middleware 函数。

        加载规则

        框架会把 app/middleware 目录下的文件挂载到 app.middleware 上。

        支持多级目录,注意:对应的文件名会转换为驼峰格式

        app/middleware/api/auth.js => app.middleware.api.auth
         app/middleware/response_time.js => app.middleware.responseTime
         app/middleware/BlockBot.js => app.middleware.blockBot
        -
        1
        2
        3

        使用中间件

        由于中间件是洋葱模型的一部分,因此需要应用开发者显式挂载,决定它们的顺序

        // config/config.default.js
        +

        使用中间件

        由于中间件是洋葱模型的一部分,因此需要应用开发者显式挂载,决定它们的顺序

        // config/config.default.js
         module.exports = {
           // 注意是驼峰格式
           middleware: [ 'responseTime' ],
         };
        -
        1
        2
        3
        4
        5

        自定义配置

        一般来说中间件也会有自己的配置。

        我们可以把之前的中间件改造如下:

        // app/middleware/response_time.js
        +

        自定义配置

        一般来说中间件也会有自己的配置。

        我们可以把之前的中间件改造如下:

        // app/middleware/response_time.js
         module.exports = (options, app) => {
           return async function responseTime(ctx, next) {
             const start = Date.now();
        @@ -38,7 +80,7 @@
             ctx.set(options.headerKey, `${cost}ms`);
           }
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        如上,接受两个参数:

        • options: 中间件的配置项,框架会将 app.config[${middlewareName}] 传递进来。
        • app: 当前应用 Application 的实例。

        对应的配置:

        // config/config.default.js
        +

        如上,接受两个参数:

        • options: 中间件的配置项,框架会将 app.config[${middlewareName}] 传递进来。
        • app: 当前应用 Application 的实例。

        对应的配置:

        // config/config.default.js
         module.exports = {
           middleware: [ 'responseTime' ],
         
        @@ -47,30 +89,30 @@
             headerKey: 'X-Response-Time',
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        通用配置

        中间件支持以下几个通用的配置项:

        • enable:控制中间件是否开启。
        • match:设置只有符合某些规则的请求才会经过这个中间件。
        • ignore:设置符合某些规则的请求不经过这个中间件。

        enable

        如果我们的应用并不需要默认的 bodyParser 中间件来进行请求体的解析,此时我们可以通过配置来关闭它。

        module.exports = {
        +

        通用配置

        中间件支持以下几个通用的配置项:

        • enable:控制中间件是否开启。
        • match:设置只有符合某些规则的请求才会经过这个中间件。
        • ignore:设置符合某些规则的请求不经过这个中间件。

        enable

        如果我们的应用并不需要默认的 bodyParser 中间件来进行请求体的解析,此时我们可以通过配置来关闭它。

        module.exports = {
           bodyParser: {
             enable: false,
           },
         };
        -
        1
        2
        3
        4
        5

        match 和 ignore

        如果我们想让 responseTime 只针对 API 请求开启,我们可以配置:

        module.exports = {
        +

        match 和 ignore

        如果我们想让 responseTime 只针对 API 请求开启,我们可以配置:

        module.exports = {
           responseTime: {
             match: '/api',
           },
         };
        -
        1
        2
        3
        4
        5

        matchignore 支持多种类型的配置方式,两者互斥不允许同时配置。

        • 字符串:当参数为字符串类型时,配置的是一个 URL 的路径前缀,所有以配置的字符串作为前缀的 URL 都会匹配上。当然,你也可以直接使用字符串数组。
        • 正则:当参数为正则时,直接匹配满足正则验证的 URL 的路径。
        • 函数:当参数为一个函数时,会将请求上下文传递给这个函数,最终取函数返回的结果(true/false)来判断是否匹配。
        module.exports = {
        +

        matchignore 支持多种类型的配置方式,两者互斥不允许同时配置。

        • 字符串:当参数为字符串类型时,配置的是一个 URL 的路径前缀,所有以配置的字符串作为前缀的 URL 都会匹配上。当然,你也可以直接使用字符串数组。
        • 正则:当参数为正则时,直接匹配满足正则验证的 URL 的路径。
        • 函数:当参数为一个函数时,会将请求上下文传递给这个函数,最终取函数返回的结果(true/false)来判断是否匹配。
        module.exports = {
           responseTime: {
             match(ctx) {
               return ctx.url.startsWith('/api');
             },
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7

        详见 egg-path-matching

        修改内置中间件的配置

        除了应用层加载中间件之外,框架自身和其他的插件也会加载许多中间件。

        如果开发者期望自定义对应的配置,可以修改同名配置项进行覆盖。

        如框架内置的 bodyParser 中间件,可以自定义配置如下:

        // config/config.default.js
        +

        详见 egg-path-matching

        修改内置中间件的配置

        除了应用层加载中间件之外,框架自身和其他的插件也会加载许多中间件。

        如果开发者期望自定义对应的配置,可以修改同名配置项进行覆盖。

        如框架内置的 bodyParser 中间件,可以自定义配置如下:

        // config/config.default.js
         module.exports = {
           bodyParser: {
             jsonLimit: '10mb',
           },
         };
        -
        1
        2
        3
        4
        5
        6

        路由中间件

        如果 match / ignore 不能满足你的需求,如你期望在不同的路由中使用不同的配置。

        则可以在路由中单独初始化和挂载:

        // app/router.js
        +

        路由中间件

        如果 match / ignore 不能满足你的需求,如你期望在不同的路由中使用不同的配置。

        则可以在路由中单独初始化和挂载:

        // app/router.js
         module.exports = app => {
           // 初始化
           const responseTime = app.middleware.responseTime({ headerKey: 'X-Time' }, app);
        @@ -78,24 +120,24 @@
           // 仅挂载到指定的路由上
           app.router.get('/test', responseTime, app.controller.test);
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8

        引入 Koa 生态

        我们也可以非常容易的引入 Koa 中间件生态。

        koa-compress 为例,在 Koa 中使用时:

        const koa = require('koa');
        +

        引入 Koa 生态

        我们也可以非常容易的引入 Koa 中间件生态。

        koa-compress 为例,在 Koa 中使用时:

        const koa = require('koa');
         const compress = require('koa-compress');
         
         const app = koa();
         
         const options = { threshold: 2048 };
         app.use(compress(options));
        -
        1
        2
        3
        4
        5
        6
        7

        在我们的应用中,会更简单一些,只需:

        // app/middleware/compress.js
        +

        在我们的应用中,会更简单一些,只需:

        // app/middleware/compress.js
         // koa-compress 暴露的接口 `(options) => middleware` 和框架要求一致
         module.exports = require('koa-compress');
        -
        1
        2
        3

        对应的配置:

        // config/config.default.js
        +

        对应的配置:

        // config/config.default.js
         module.exports = {
           middleware: [ 'compress' ],
           compress: {
             threshold: 2048,
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7

        如果使用到的 Koa 中间件不符合入参规范,则可以自行处理下:

        // config/config.default.js
        +

        如果使用到的 Koa 中间件不符合入参规范,则可以自行处理下:

        // config/config.default.js
         module.exports = {
           webpack: {
             compiler: {},
        @@ -109,7 +151,7 @@
         module.exports = (options, app) => {
           return webpackMiddleware(options.compiler, options.others);
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        编写测试

        类似于 Controller 的测试,通过 app.httpRequest 来测试。

        // test/controller/home.test.js
        +

        编写测试

        类似于 Controller 的测试,通过 app.httpRequest 来测试。

        // test/controller/home.test.js
         const { app, mock, assert } = require('egg-mock');
         
         describe('test/middleware/response_time.test.js', () => {
        @@ -119,15 +161,9 @@
               .expect('X-Response-Time', /\d+ms/);
           });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        - +

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        + diff --git a/zh/guide/plugin.html b/zh/guide/plugin.html index 486bd0d..1f07b4e 100644 --- a/zh/guide/plugin.html +++ b/zh/guide/plugin.html @@ -5,19 +5,61 @@ 使用插件 | Egg + - - - + + -

        使用场景

        插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。

        我们在使用 Koa 中间件过程中发现了下面一些问题:

        1. 中间件是有先后顺序的,需要统一管控,但是它自身却无法管理这种顺序,只能交给使用者。这样其实非常不友好,一旦顺序不对,结果可能有天壤之别。
        2. 中间件的定位是拦截用户请求,并在它前后做一些事情,例如:鉴权、安全检查、访问日志等等。但实际情况是,有些功能是和请求无关的,例如:定时任务、消息订阅、后台逻辑等等。
        3. 一些复杂的初始化逻辑,需要在应用启动的时候完成,这显然也不适合放到中间件中去实现。

        综上所述,我们需要一套更加强大的机制,来管理、编排那些相对独立的业务逻辑。

        使用插件

        举个例子,我们想引入 egg-validate 这个插件。

        安装依赖

        插件一般通过 npm 模块的方式进行复用:

        $ npm install egg-validate --save
        -
        1

        注意:我们建议通过 ^ 的方式引入依赖,并且强烈不建议锁定版本。

        友情提示

        有些插件是内置到框架中,但默认不开启的,此时无需手动安装依赖。详见下文。

        挂载插件

        config/plugin.js 中声明:

        // config/plugin.js
        +    

        使用插件

        使用场景

        插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。

        我们在使用 Koa 中间件过程中发现了下面一些问题:

        1. 中间件是有先后顺序的,需要统一管控,但是它自身却无法管理这种顺序,只能交给使用者。这样其实非常不友好,一旦顺序不对,结果可能有天壤之别。
        2. 中间件的定位是拦截用户请求,并在它前后做一些事情,例如:鉴权、安全检查、访问日志等等。但实际情况是,有些功能是和请求无关的,例如:定时任务、消息订阅、后台逻辑等等。
        3. 一些复杂的初始化逻辑,需要在应用启动的时候完成,这显然也不适合放到中间件中去实现。

        综上所述,我们需要一套更加强大的机制,来管理、编排那些相对独立的业务逻辑。

        使用插件

        举个例子,我们想引入 egg-validate 这个插件。

        安装依赖

        插件一般通过 npm 模块的方式进行复用:

        $ npm install egg-validate --save
        +

        注意:我们建议通过 ^ 的方式引入依赖,并且强烈不建议锁定版本。

        友情提示

        有些插件是内置到框架中,但默认不开启的,此时无需手动安装依赖。详见下文。

        挂载插件

        config/plugin.js 中声明:

        // config/plugin.js
         exports.validate = {
           enable: true,
           package: 'egg-validate',
         };
        -
        1
        2
        3
        4
        5

        使用插件

        然后就可以使用插件提供的功能:

        // app/controller/user.js
        +

        使用插件

        然后就可以使用插件提供的功能:

        // app/controller/user.js
         class UserController extends Controller {
           async create() {
             const rule = { name: 'string' };
        @@ -26,7 +68,7 @@
             // ...
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        了解插件

        一个插件其实就是一个『迷你的应用』,和应用几乎一模一样

        目录结构

        my-plugin
        +

        了解插件

        一个插件其实就是一个『迷你的应用』,和应用几乎一模一样

        目录结构

        my-plugin
         ├── app
         │   ├── service|   └── user.js
        @@ -45,11 +87,11 @@
         |   └── service
         |       └── user.test.js
         └── package.json
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        Service

        插件可以包含 Service,框架会自动挂载。

        Config

        插件可以包含 配置

        插件一般会包含自己的默认配置,应用开发者可以自由覆盖对应的配置:

        譬如 egg-static 插件默认的 prefix/public/

        你可以在应用的配置里面覆盖掉它:

        // config/config.default.js
        +

        Service

        插件可以包含 Service,框架会自动挂载。

        Config

        插件可以包含 配置

        插件一般会包含自己的默认配置,应用开发者可以自由覆盖对应的配置:

        譬如 egg-static 插件默认的 prefix/public/

        你可以在应用的配置里面覆盖掉它:

        // config/config.default.js
         config.static = {
           prefix: '/static/',
         };
        -
        1
        2
        3
        4

        具体合并规则可以参见配置

        Middleware

        插件可以包含 中间件

        框架把插件的 app/middleware 目录下的文件,同样加载到 app.middleware 上。

        大部分情况下,插件开发者会自动挂载中间件到对应的地方,无需应用开发者处理。

        但某些情况下,插件仅提供了中间件定义,并不帮应用开发者决定挂载顺序。

        此时,应用开发者只需遵循 中间件 文档来使用即可。

        Extend

        插件可以提供 ContextApplicationHelper 等的扩展。

        譬如在插件里面提供以下扩展,对应的逻辑就可以共享给其他应用。

        // {plugin_root}/app/extend/context.js
        +

        具体合并规则可以参见配置

        Middleware

        插件可以包含 中间件

        框架把插件的 app/middleware 目录下的文件,同样加载到 app.middleware 上。

        大部分情况下,插件开发者会自动挂载中间件到对应的地方,无需应用开发者处理。

        但某些情况下,插件仅提供了中间件定义,并不帮应用开发者决定挂载顺序。

        此时,应用开发者只需遵循 中间件 文档来使用即可。

        Extend

        插件可以提供 ContextApplicationHelper 等的扩展。

        譬如在插件里面提供以下扩展,对应的逻辑就可以共享给其他应用。

        // {plugin_root}/app/extend/context.js
         const UA = Symbol('Context#ua');
         const useragent = require('useragent');
         
        @@ -63,34 +105,28 @@
             return this[UA];
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        不支持的特性

        • 没有 RouterController
        • 没有 plugin.js,只能声明跟其他插件的依赖,而不能决定其他插件的开启与否。

        插件配置

        参数介绍

        应用开发者通过 config/plugin.js 来声明插件的挂载。

        除了上面我们使用到的 enablepackage 外,其他参数如下:

        • enable - 是否开启此插件,默认为 true
        • package - npm 模块名称,通过 npm 模块形式引入插件。
        • path - 插件绝对路径,跟 package 配置互斥。
        • env - 数组,仅在指定运行环境才开启,会覆盖插件自身 package.json 中的配置。

        插件本身的 package.json 里面也会有一个 eggPlugin 属性来声明默认的属性。

        开启框架内置插件

        框架一般也会内置一些插件,它们有可能默认是开启或关闭的。

        此时,应用无需配置 package,直接配置 enable 即可:

        // config/plugin.js
        +

        不支持的特性

        • 没有 RouterController
        • 没有 plugin.js,只能声明跟其他插件的依赖,而不能决定其他插件的开启与否。

        插件配置

        参数介绍

        应用开发者通过 config/plugin.js 来声明插件的挂载。

        除了上面我们使用到的 enablepackage 外,其他参数如下:

        • enable - 是否开启此插件,默认为 true
        • package - npm 模块名称,通过 npm 模块形式引入插件。
        • path - 插件绝对路径,跟 package 配置互斥。
        • env - 数组,仅在指定运行环境才开启,会覆盖插件自身 package.json 中的配置。

        插件本身的 package.json 里面也会有一个 eggPlugin 属性来声明默认的属性。

        开启框架内置插件

        框架一般也会内置一些插件,它们有可能默认是开启或关闭的。

        此时,应用无需配置 package,直接配置 enable 即可:

        // config/plugin.js
         exports.cors = {
           enable: true;
         };
         
         // 也可以简写为:
         exports.validate = true;
        -
        1
        2
        3
        4
        5
        6
        7

        packagepath

        • package:通过 npm 方式引入,也是最常见的引入方式。
        • path:通过绝对路径引入。
        • 后者主要场景是:应用内部抽象了一个插件,但还没达到可以发布独立插件的阶段临时使用。
        • 关于这两种方式的使用场景,可以参见渐进式开发
        // config/plugin.js
        +

        packagepath

        • package:通过 npm 方式引入,也是最常见的引入方式。
        • path:通过绝对路径引入。
        • 后者主要场景是:应用内部抽象了一个插件,但还没达到可以发布独立插件的阶段临时使用。
        • 关于这两种方式的使用场景,可以参见渐进式开发
        // config/plugin.js
         const path = require('path');
         exports.mysql = {
           enable: true,
           path: path.join(__dirname, '../lib/plugin/egg-mysql'),
         };
        -
        1
        2
        3
        4
        5
        6

        根据环境配置

        同时,我们还支持 plugin.{env}.js 这种模式,会根据运行环境加载插件配置。

        比如定义了一个开发环境使用的插件 egg-dev,只希望在本地环境加载,可以安装到 devDependencies

        譬如 egg-development-proxyagent 这个插件,只会在开发环境使用。

        则我们可以只安装到 devDependencies

        $ npm i egg-dev --save-dev
        -
        1

        然后在 plugin.local.js 中声明:

        // config/plugin.local.js
        +

        根据环境配置

        同时,我们还支持 plugin.{env}.js 这种模式,会根据运行环境加载插件配置。

        比如定义了一个开发环境使用的插件 egg-dev,只希望在本地环境加载,可以安装到 devDependencies

        譬如 egg-development-proxyagent 这个插件,只会在开发环境使用。

        则我们可以只安装到 devDependencies

        $ npm i egg-dev --save-dev
        +

        然后在 plugin.local.js 中声明:

        // config/plugin.local.js
         exports.proxyagent = {
           enable: true,
           package: 'egg-development-proxyagent',
         };
        -
        1
        2
        3
        4
        5

        这样在生产环境可以 npm i --production 不需要下载 egg-development-proxyagent 的包了。

        注意:

        • 不存在 plugin.default.js
        • 只能在应用层使用,在框架层请勿使用。

        常见问题

        如何开发一个插件

        恭喜你迈出这一步,可以回馈社区。

        具体可以参见文档:

        插件太多,每个应用都要开启怎么办?

        此时应该考虑包装为一个上层框架

        - +

        这样在生产环境可以 npm i --production 不需要下载 egg-development-proxyagent 的包了。

        注意:

        • 不存在 plugin.default.js
        • 只能在应用层使用,在框架层请勿使用。

        常见问题

        如何开发一个插件

        恭喜你迈出这一步,可以回馈社区。

        具体可以参见文档:

        插件太多,每个应用都要开启怎么办?

        此时应该考虑包装为一个上层框架

        + diff --git a/zh/guide/router.html b/zh/guide/router.html index bdd69aa..4b3c9e9 100644 --- a/zh/guide/router.html +++ b/zh/guide/router.html @@ -5,13 +5,55 @@ Router | Egg + - - - + + -

        使用场景

        Router 也称之为 路由,用于描述请求 URL 和具体承担执行动作的 Controller 的对应关系。

        框架通过 egg-router 来提供相关支持。

        编写路由

        我们约定 app/router.js 文件用于统一所有路由规则。

        通过统一的配置,可以避免路由规则逻辑散落在多个地方,从而出现未知的冲突,可以更方便的来查看全局的路由规则。

        假设有以下 Controller 定义:

        // app/controller/user.js
        +    

        Router

        使用场景

        Router 也称之为 路由,用于描述请求 URL 和具体承担执行动作的 Controller 的对应关系。

        框架通过 egg-router 来提供相关支持。

        编写路由

        我们约定 app/router.js 文件用于统一所有路由规则。

        通过统一的配置,可以避免路由规则逻辑散落在多个地方,从而出现未知的冲突,可以更方便的来查看全局的路由规则。

        假设有以下 Controller 定义:

        // app/controller/user.js
         class UserController extends Controller {
           async info() {
             const { ctx } = this;
        @@ -20,31 +62,31 @@
             };
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        则我们可以定义对应的路由如下:

        // app/router.js
        +

        则我们可以定义对应的路由如下:

        // app/router.js
         module.exports = app => {
           const { router, controller } = app;
           // GET /user/123
           router.get('/user/:id', controller.user.info);
         };
        -
        1
        2
        3
        4
        5
        6

        这样就完成了一个最简单的 Router 定义,当用户访问 GET /user/123 时,这个 UserController 里面的 info 方法就会执行。

        路由定义

        router.verb('/some-path', controller.action);
        -
        1

        路由方法

        即为上面的 verb,代表用户触发动作,支持 GET、POST 等所有 HTTP 方法。

        • router.head - 对应 HTTP HEAD 方法。
        • router.get - 对应 HTTP GET 方法。
        • router.put - 对应 HTTP PUT 方法。
        • router.post - 对应 HTTP POST 方法。
        • router.patch - 对应 HTTP PATCH 方法。
        • router.delete - 对应 HTTP DELETE 方法。
        • router.del - 由于 delete 是保留字,故一般会用 router.del 别名。
        • router.options - 对应 HTTP OPTIONS 方法。

        除此之外,还提供了:

        • router.redirect - 可以对 URL 进行重定向处理,比如把用户访问的根目录路由到某个主页。
        • router.all - 对所有的 HTTP 方法都挂载。

        路由路径

        即为上面的 /some-path,并支持命名参数。

        // app/router.js
        +

        这样就完成了一个最简单的 Router 定义,当用户访问 GET /user/123 时,这个 UserController 里面的 info 方法就会执行。

        路由定义

        router.verb('/some-path', controller.action);
        +

        路由方法

        即为上面的 verb,代表用户触发动作,支持 GET、POST 等所有 HTTP 方法。

        • router.head - 对应 HTTP HEAD 方法。
        • router.get - 对应 HTTP GET 方法。
        • router.put - 对应 HTTP PUT 方法。
        • router.post - 对应 HTTP POST 方法。
        • router.patch - 对应 HTTP PATCH 方法。
        • router.delete - 对应 HTTP DELETE 方法。
        • router.del - 由于 delete 是保留字,故一般会用 router.del 别名。
        • router.options - 对应 HTTP OPTIONS 方法。

        除此之外,还提供了:

        • router.redirect - 可以对 URL 进行重定向处理,比如把用户访问的根目录路由到某个主页。
        • router.all - 对所有的 HTTP 方法都挂载。

        路由路径

        即为上面的 /some-path,并支持命名参数。

        // app/router.js
         module.exports = app => {
           const { router, controller } = app;
           router.get('/home', controller.home.index);
           // 支持命名参数,通过 `ctx.params.id` 可以取出。
           router.get('/user/:id', controller.user.detail);
         };
        -
        1
        2
        3
        4
        5
        6
        7

        也支持正则式:

        // app/router.js
        +

        也支持正则式:

        // app/router.js
         module.exports = app => {
           const { router, controller } = app;
         
           // 可以通过 `ctx.params[0]` 获取到对应的正则分组信息。
           router.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, controller.package.detail);
         };
        -
        1
        2
        3
        4
        5
        6
        7

        如果你有一个通配的路由映射,需注意顺序,放在后面,如:

        router.get('/user/manager', controller.user.manager);
        +

        如果你有一个通配的路由映射,需注意顺序,放在后面,如:

        router.get('/user/manager', controller.user.manager);
         router.get('/user/:id', controller.user.detail);
        -
        1
        2

        路径解析使用了 path-to-regexp 模块,更多规则可以参见其文档。

        路由中间件

        支持对特定路由挂载中间件。

        router.verb('/some-path', middleware1, ..., middlewareN, controller.action);
        -
        1

        如下示例:

        // app/router.js
        +

        路径解析使用了 path-to-regexp 模块,更多规则可以参见其文档。

        路由中间件

        支持对特定路由挂载中间件。

        router.verb('/some-path', middleware1, ..., middlewareN, controller.action);
        +

        如下示例:

        // app/router.js
         module.exports = app => {
           const { router, controller, middleware } = app;
         
        @@ -54,21 +96,21 @@
           // 仅挂载到指定的路由上
           router.get('/test', responseTime, controller.test);
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        路由别名

        支持对路由定义别名,用于生成路由链接。

        router.verb('router-name', '/some-path', controller.action);
        +

        路由别名

        支持对路由定义别名,用于生成路由链接。

        router.verb('router-name', '/some-path', controller.action);
         router.verb('router-name', '/some-path', middleware1, ..., middlewareN, controller.action);
        -
        1
        2

        然后可以通过 Helper 提供的辅助函数 pathForurlFor 来生成链接。

        // app/router.js
        +

        然后可以通过 Helper 提供的辅助函数 pathForurlFor 来生成链接。

        // app/router.js
         router.get('user', '/user', controller.user);
         
         // 使用 helper 计算指定 path
         ctx.helper.pathFor('user', { limit: 10, sort: 'name' });
         // => /user?limit=10&sort=name
        -
        1
        2
        3
        4
        5
        6

        你可以通过 ctx.routerName 获取到当前命中的路由别名。

        RESTful 风格的 URL 定义

        RESTful 是非常经典的 Web API 设计规范,如 CRUD 的路由结构。

        我们提供了 app.resources('routerName', 'pathMatch', controller) 来简化开发。

        // app/router.js
        +

        你可以通过 ctx.routerName 获取到当前命中的路由别名。

        RESTful 风格的 URL 定义

        RESTful 是非常经典的 Web API 设计规范,如 CRUD 的路由结构。

        我们提供了 app.resources('routerName', 'pathMatch', controller) 来简化开发。

        // app/router.js
         module.exports = app => {
           const { router, controller } = app;
           router.resources('posts', '/api/posts', controller.posts);
           router.resources('users', '/api/v1/users', controller.v1.users); // app/controller/v1/users.js
         };
        -
        1
        2
        3
        4
        5
        6

        如上,我们对 /posts 路径设置了映射到 app/controller/posts.js

        然后,你只需要在 Controller 里面按需提供对应的方法即可,框架会自动映射。

        Method Path Route Name Controller.Action
        GET /posts posts controller.posts.index
        GET /posts/new new_post controller.posts.new
        GET /posts/:id post controller.posts.show
        GET /posts/:id/edit edit_post controller.posts.edit
        POST /posts posts controller.posts.create
        PUT /posts/:id post controller.posts.update
        DELETE /posts/:id post controller.posts.destroy
        // app/controller/posts.js
        +

        如上,我们对 /posts 路径设置了映射到 app/controller/posts.js

        然后,你只需要在 Controller 里面按需提供对应的方法即可,框架会自动映射。

        Method Path Route Name Controller.Action
        GET /posts posts controller.posts.index
        GET /posts/new new_post controller.posts.new
        GET /posts/:id post controller.posts.show
        GET /posts/:id/edit edit_post controller.posts.edit
        POST /posts posts controller.posts.create
        PUT /posts/:id post controller.posts.update
        DELETE /posts/:id post controller.posts.destroy
        // app/controller/posts.js
         class PostController extends Controller {
           async index() {}
           async new() {}
        @@ -78,7 +120,7 @@
           async update() {}
           async destroy() {}
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        具体示例,可以参考 实现 RESTful API 文档。

        Router 实战

        下面通过更多实际的例子,来说明 Router 的用法。

        获取查询参数

        // app/router.js
        +

        具体示例,可以参考 实现 RESTful API 文档。

        Router 实战

        下面通过更多实际的例子,来说明 Router 的用法。

        获取查询参数

        // app/router.js
         module.exports = app => {
           const { router, controller } = app;
           router.get('/user/list', controller.user.list);
        @@ -92,7 +134,7 @@
             ctx.body = `name: ${ctx.query.name}`;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        获取命名参数

        // app/router.js
        +

        获取命名参数

        // app/router.js
         module.exports = app => {
           const { router, controller } = app;
           router.get('/user/:id/:name', controller.user.detail);
        @@ -106,7 +148,7 @@
             ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        重定向

        使用方式:router.redirect(source, destination, [code])

        • sourcedestination 可以是路径,也可以是路径别名。
        • code 默认 301,可选参数。
        // app/router.js
        +

        重定向

        使用方式:router.redirect(source, destination, [code])

        • sourcedestination 可以是路径,也可以是路径别名。
        • code 默认 301,可选参数。
        // app/router.js
         module.exports = app => {
           const { router, controller } = app;
           router.get('index', '/home/index', controller.home.index);
        @@ -121,7 +163,7 @@
             ctx.body = 'hello controller';
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        常见问题

        路由映射太多?

        一般来说,我们并不推荐把路由规则逻辑散落在多个地方,这会给排查问题带来困扰。

        若确实有需求,可以如下拆分:

        // app/router.js
        +

        常见问题

        路由映射太多?

        一般来说,我们并不推荐把路由规则逻辑散落在多个地方,这会给排查问题带来困扰。

        若确实有需求,可以如下拆分:

        // app/router.js
         module.exports = app => {
           require('./router/news')(app);
           require('./router/admin')(app);
        @@ -140,15 +182,9 @@
           router.get('/admin/user', controller.admin.user);
           router.get('/admin/log', controller.admin.log);
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

        也可直接使用 egg-router-plus

        另外,框架会在启动期把最终的路由映射 dump 到 run/router.json 中。

        自动映射路由?

        一般来说,如果符合 RESTful 风格的路由,直接用上述的 router.resource() 配置即可。

        如果你的业务场景中,有其他约定的规则,则可以参考对应的 resource 源码,扩展自己的方法,封装为插件。

        通过装饰器映射?

        装饰器目前还不是 ECMA 的正式规范,框架未提供该功能。

        开发者可以自行通过 TypeScriptBabel 转义对应的自定义装饰器。

        - +

        也可直接使用 egg-router-plus

        另外,框架会在启动期把最终的路由映射 dump 到 run/router.json 中。

        自动映射路由?

        一般来说,如果符合 RESTful 风格的路由,直接用上述的 router.resource() 配置即可。

        如果你的业务场景中,有其他约定的规则,则可以参考对应的 resource 源码,扩展自己的方法,封装为插件。

        通过装饰器映射?

        装饰器目前还不是 ECMA 的正式规范,框架未提供该功能。

        开发者可以自行通过 TypeScriptBabel 转义对应的自定义装饰器。

        + diff --git a/zh/guide/service.html b/zh/guide/service.html index 826a88d..028d313 100644 --- a/zh/guide/service.html +++ b/zh/guide/service.html @@ -5,13 +5,55 @@ Service | Egg + - - - + + -

        使用场景

        Service 是在复杂业务场景下用于做业务逻辑封装的一个抽象层:

        • 保持 Controller 中的逻辑更加简洁。
        • 保持业务逻辑的独立性,抽象出来的 Service 可以被多个 Controller 重复调用。
        • 将逻辑和展现分离,更容易编写测试用例。

        场景举例:

        • 复杂数据的处理,如从数据库获取信息后,需经过一定的规则计算,才能返回用户显示。
        • 第三方服务的调用,如调用后端微服务的接口。

        编写 Service

        我们约定把 Service 放置在 app/service 目录下:

        // app/service/user.js
        +    

        Service

        使用场景

        Service 是在复杂业务场景下用于做业务逻辑封装的一个抽象层:

        • 保持 Controller 中的逻辑更加简洁。
        • 保持业务逻辑的独立性,抽象出来的 Service 可以被多个 Controller 重复调用。
        • 将逻辑和展现分离,更容易编写测试用例。

        场景举例:

        • 复杂数据的处理,如从数据库获取信息后,需经过一定的规则计算,才能返回用户显示。
        • 第三方服务的调用,如调用后端微服务的接口。

        编写 Service

        我们约定把 Service 放置在 app/service 目录下:

        // app/service/user.js
         const { Service } = require('egg');
         
         class UserService extends Service {
        @@ -22,7 +64,7 @@
         }
         
         module.exports = UserService;
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        使用 Service

        框架会默认挂载到 ctx.service 上,对应的 Key 为文件名的驼峰格式。

        如上面的 Service 会挂载为 ctx.service.user

        然后就可以在 Controller 里调用:

        // app/controller/user.js
        +

        使用 Service

        框架会默认挂载到 ctx.service 上,对应的 Key 为文件名的驼峰格式。

        如上面的 Service 会挂载为 ctx.service.user

        然后就可以在 Controller 里调用:

        // app/controller/user.js
         const { Controller } = require('egg');
         
         class UserController extends Controller {
        @@ -35,10 +77,10 @@
         }
         
         module.exports = UserController;
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        生命周期

        Service 不是单例,是 请求级别 的对象,它挂载在 Context 上的。

        Service 是延迟实例化的,仅在每一次请求中,首次调用到该 Service 的时候,才会实例化。

        因此,无需担心实例化的性能损耗,经过我们大规模的实践证明,可以忽略不计。

        挂载规则

        约定放置在 app/service 目录下,支持多级目录,对应的文件名会转换为驼峰格式

        app/service/biz/user.js => ctx.service.biz.user
        +

        生命周期

        Service 不是单例,是 请求级别 的对象,它挂载在 Context 上的。

        Service 是延迟实例化的,仅在每一次请求中,首次调用到该 Service 的时候,才会实例化。

        因此,无需担心实例化的性能损耗,经过我们大规模的实践证明,可以忽略不计。

        挂载规则

        约定放置在 app/service 目录下,支持多级目录,对应的文件名会转换为驼峰格式

        app/service/biz/user.js => ctx.service.biz.user
         app/service/sync_user.js => ctx.service.syncUser
         app/service/HackerNews.js => ctx.service.hackerNews
        -
        1
        2
        3

        常用属性和方法

        Service 实例继承 egg.Service,提供以下属性:

        • this.ctx: 当前请求的上下文 Context 的实例,可以拿到各种便捷属性和方法。
        • this.app: 当前应用 Application 的实例,可以拿到全局对象和方法。
        • this.service:应用定义的 Service,可以调用其他 Service
        • this.config:应用运行时的配置项
        • this.logger:logger 对象,使用方法类似 Context Logger,不同之处是通过这个 Logger 对象记录的日志,会额外加上该日志的文件路径,以便快速定位日志打印位置。

        编写测试

        可以通过 app.mockContext() 获取到 Context 实例来测试。

        // test/service/user.test.js
        +

        常用属性和方法

        Service 实例继承 egg.Service,提供以下属性:

        • this.ctx: 当前请求的上下文 Context 的实例,可以拿到各种便捷属性和方法。
        • this.app: 当前应用 Application 的实例,可以拿到全局对象和方法。
        • this.service:应用定义的 Service,可以调用其他 Service
        • this.config:应用运行时的配置项
        • this.logger:logger 对象,使用方法类似 Context Logger,不同之处是通过这个 Logger 对象记录的日志,会额外加上该日志的文件路径,以便快速定位日志打印位置。

        编写测试

        可以通过 app.mockContext() 获取到 Context 实例来测试。

        // test/service/user.test.js
         const { app, mock, assert } = require('egg-mock');
         
         describe('test/service/user.test.js', () => {
        @@ -51,15 +93,9 @@
             assert(user.name === 'TZ');
           });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        - +

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        + diff --git a/zh/guide/session.html b/zh/guide/session.html index c385562..7d96080 100644 --- a/zh/guide/session.html +++ b/zh/guide/session.html @@ -5,13 +5,55 @@ Session | Egg + - - - + + -

        使用场景

        在 Web 应用中经常用 Cookie 来承担标识请求方身份的功能,但浏览器给每个站点分配的空间是很有限的。

        从而提出了 Session 的概念,用于用户身份识别,以及会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)。

        最佳实践

        对于 Egg 的用户来说,请不要直接操作 ctx.session,而应该:

        • 使用 用户系统 提供的统一登录方式,由它来操作 Session
        • 如果你有额外的用户信息需要存储,直接操作 ZCache(Tair) 提供的 API。

        使用 Session

        框架内置了 Session 插件,给我们提供了 ctx.session 来访问或者修改当前用户 Session

        // app/controller/home.js
        +    

        Session

        使用场景

        在 Web 应用中经常用 Cookie 来承担标识请求方身份的功能,但浏览器给每个站点分配的空间是很有限的。

        从而提出了 Session 的概念,用于用户身份识别,以及会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)。

        最佳实践

        对于 Egg 的用户来说,请不要直接操作 ctx.session,而应该:

        • 使用 用户系统 提供的统一登录方式,由它来操作 Session
        • 如果你有额外的用户信息需要存储,直接操作 ZCache(Tair) 提供的 API。

        使用 Session

        框架内置了 Session 插件,给我们提供了 ctx.session 来访问或者修改当前用户 Session

        // app/controller/home.js
         class HomeController extends Controller {
           async fetchPosts() {
             const { ctx } = this;
        @@ -26,20 +68,20 @@
             };
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15

        Session 的使用方法非常直观,直接读取或修改它就可以了,如果要删除,直接赋值为 null

        ctx.session = null;
        -
        1

        禁止使用的 Key 值

        需要 特别注意 的是:设置 session 属性时需要避免:

        • 不要以 _ 开头
        • 不能为 isNew

        否则会造成字段丢失,详见 koa-session 源码。

        // ❌ 错误的用法
        +

        Session 的使用方法非常直观,直接读取或修改它就可以了,如果要删除,直接赋值为 null

        ctx.session = null;
        +

        禁止使用的 Key 值

        需要 特别注意 的是:设置 session 属性时需要避免:

        • 不要以 _ 开头
        • 不能为 isNew

        否则会造成字段丢失,详见 koa-session 源码。

        // ❌ 错误的用法
         ctx.session._visited = 1;   //    --> 该字段会在下一次请求时丢失
         ctx.session.isNew = 'HeHe'; //    --> 为内部关键字, 不应该去更改
         
         // ✔️ 正确的用法
         ctx.session.visited = 1;    //   -->  此处没有问题
        -
        1
        2
        3
        4
        5
        6

        存储方式

        默认配置下,会把用户的 Session 加密后直接存储在 Cookie 中的一个字段中,浏览器每次请求时会带上这个 Cookie,我们在服务端解密后使用。

        Session 写入 Cookie 的默认配置如下:

        config.session = {
        +

        存储方式

        默认配置下,会把用户的 Session 加密后直接存储在 Cookie 中的一个字段中,浏览器每次请求时会带上这个 Cookie,我们在服务端解密后使用。

        Session 写入 Cookie 的默认配置如下:

        config.session = {
           key: 'EGG_SESS', // 存储 `Session` 的 `Cookie` 键值对的 key
           maxAge: 24 * 3600 * 1000, // 1 天
           httpOnly: true,
           encrypt: true,
         };
        -
        1
        2
        3
        4
        5
        6

        可以看到,默认配置下,存放 SessionCookie 将会加密存储、不可被前端 js 访问,这样可以保证用户数据是安全的。

        Redis

        默认存储在 Cookie 时,如果 Session 对象过于庞大,就会导致:

        • 浏览器通常都有限制最大的 Cookie 长度,当设置的 Session 过大时,浏览器可能拒绝保存。
        • Session 过大时,每次请求都要额外带上庞大的 Cookie 信息,影响性能。

        对于社区的用户,可以使用 egg-session-redis 插件来配置存储。

        你需要:

        • 参考 egg-redis 插件的文档,来配置对应的 Redis 地址信息。
        • 安装并开启对应的插件。
        // plugin.js
        +

        可以看到,默认配置下,存放 SessionCookie 将会加密存储、不可被前端 js 访问,这样可以保证用户数据是安全的。

        Redis

        默认存储在 Cookie 时,如果 Session 对象过于庞大,就会导致:

        • 浏览器通常都有限制最大的 Cookie 长度,当设置的 Session 过大时,浏览器可能拒绝保存。
        • Session 过大时,每次请求都要额外带上庞大的 Cookie 信息,影响性能。

        对于社区的用户,可以使用 egg-session-redis 插件来配置存储。

        你需要:

        • 参考 egg-redis 插件的文档,来配置对应的 Redis 地址信息。
        • 安装并开启对应的插件。
        // plugin.js
         exports.redis = {
           enable: true,
           package: 'egg-redis',
        @@ -49,8 +91,8 @@
           enable: true,
           package: 'egg-session-redis',
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        注意事项

        一旦选择了将 Session 存入到外部存储中,就意味着系统将强依赖于这个外部存储,当它挂了的时候,我们就完全无法使用 Session 相关的功能了。

        一般来说,建议只将必要的信息存储在 Session 中,保持 Session 的精简并使用默认的 Cookie 存储,用户级别的缓存不要存储在 Session 中。

        注意事项

        再次提醒,对于 Egg 的用户来说,请不要直接读取和写入 ctx.session

        应该使用 用户系统 提供的统一登录方式,读取 ctx.user

        Session 实战

        删除 Session

        ctx.session = null;
        -
        1

        修改失效时间

        虽然在 Session 的配置中有一项是 maxAge,但是它只能全局设置 Session 的有效期。

        我们经常可以在一些网站的登陆页上看到有 记住我 的选项框,勾选之后可以让登陆用户的 Session 有效期更长。

        这种针对特定用户的 Session 有效时间设置我们可以通过 ctx.session.maxAge= 来实现。

        // app/controller/user.js
        +

        注意事项

        一旦选择了将 Session 存入到外部存储中,就意味着系统将强依赖于这个外部存储,当它挂了的时候,我们就完全无法使用 Session 相关的功能了。

        一般来说,建议只将必要的信息存储在 Session 中,保持 Session 的精简并使用默认的 Cookie 存储,用户级别的缓存不要存储在 Session 中。

        注意事项

        再次提醒,对于 Egg 的用户来说,请不要直接读取和写入 ctx.session

        应该使用 用户系统 提供的统一登录方式,读取 ctx.user

        Session 实战

        删除 Session

        ctx.session = null;
        +

        修改失效时间

        虽然在 Session 的配置中有一项是 maxAge,但是它只能全局设置 Session 的有效期。

        我们经常可以在一些网站的登陆页上看到有 记住我 的选项框,勾选之后可以让登陆用户的 Session 有效期更长。

        这种针对特定用户的 Session 有效时间设置我们可以通过 ctx.session.maxAge= 来实现。

        // app/controller/user.js
         const ms = require('ms');
         class UserController extends Controller {
           async login() {
        @@ -64,21 +106,15 @@
             if (rememberMe) ctx.session.maxAge = ms('30d');
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        延长有效期

        默认情况下,当用户请求没有导致 Session 被修改时,框架都不会延长 Session 的有效期。

        但是在有些场景下,我们希望用户如果长时间都在访问我们的站点,则延长他们的 Session 有效期,不让用户退出登录态。

        框架提供了一个 renew 配置项用于实现此功能,它会在发现当用户 Session 的有效期仅剩下最大有效期一半的时候,重置 Session 的有效期。

        // config/config.default.js
        +

        延长有效期

        默认情况下,当用户请求没有导致 Session 被修改时,框架都不会延长 Session 的有效期。

        但是在有些场景下,我们希望用户如果长时间都在访问我们的站点,则延长他们的 Session 有效期,不让用户退出登录态。

        框架提供了一个 renew 配置项用于实现此功能,它会在发现当用户 Session 的有效期仅剩下最大有效期一半的时候,重置 Session 的有效期。

        // config/config.default.js
         module.exports = {
           session: {
             renew: true,
           },
         };
        -
        1
        2
        3
        4
        5
        6
        - +
        + diff --git a/zh/guide/upload.html b/zh/guide/upload.html index 1a186d2..826b84d 100644 --- a/zh/guide/upload.html +++ b/zh/guide/upload.html @@ -5,23 +5,65 @@ 文件上传 | Egg + - - - + + -

        使用场景

        文件上传,是 Web 应用的一个常见的功能。

        框架内置了 Multipart 插件:

        • 解析浏览器上传的 multipart/form-data 的数据。
        • 提供 filestream 两种处理接口供开发者选择。
        • 默认提供了安全的限制。

        获取到用户上传的数据后,开发者可以:

        • 存储为本地文件。
        • 提交给第三方服务,参见 通过 HttpClient 上传文件
        • 大部分情况下,我们会转存给云存储服务,在本文中我们也会一并介绍到。

        File 模式

        虽然在 Node.js 的世界里面,Stream 才是主流。

        但对于一般开发者来说,Stream 并不是很容易掌握,尤其是错误处理环节。

        因此,框架提供了 File 模式来简化开发。

        相关的示例代码参见:eggjs/example/multipart-file-mode

        配置

        // config/config.default.js
        +    

        文件上传

        使用场景

        文件上传,是 Web 应用的一个常见的功能。

        框架内置了 Multipart 插件:

        • 解析浏览器上传的 multipart/form-data 的数据。
        • 提供 filestream 两种处理接口供开发者选择。
        • 默认提供了安全的限制。

        获取到用户上传的数据后,开发者可以:

        • 存储为本地文件。
        • 提交给第三方服务,参见 通过 HttpClient 上传文件
        • 大部分情况下,我们会转存给云存储服务,在本文中我们也会一并介绍到。

        File 模式

        虽然在 Node.js 的世界里面,Stream 才是主流。

        但对于一般开发者来说,Stream 并不是很容易掌握,尤其是错误处理环节。

        因此,框架提供了 File 模式来简化开发。

        相关的示例代码参见:eggjs/example/multipart-file-mode

        配置

        // config/config.default.js
         config.multipart = {
           mode: 'file',
         };
        -
        1
        2
        3
        4

        前端代码

        前端可以通过 FormAJAX 等方式来上传文件。

        譬如:

        <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
        +

        前端代码

        前端可以通过 FormAJAX 等方式来上传文件。

        譬如:

        <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
           title: <input name="title" />
           file1: <input name="file1" type="file" />
           file2: <input name="file2" type="file" />
           <button type="submit">Upload</button>
         </form>
        -
        1
        2
        3
        4
        5
        6

        注意事项

        文件上传需要通过 POST 协议,因此会受到 CSRF 安全的管控,具体参见对应文档。

        获取上传的文件

        框架在 File 模式下,会把获取到的文件挂载到 ctx.request.files 数组上。

        关键代码:

        • ctx.request.files: 获取到的文件列表。
        • ctx.oss.put():示例代码,此处为上传到 OSS 云存储,下文会介绍到。
        • ctx.cleanupRequestFiles():处理完毕后,清理临时文件。
        // app/controller/upload.js
        +

        注意事项

        文件上传需要通过 POST 协议,因此会受到 CSRF 安全的管控,具体参见对应文档。

        获取上传的文件

        框架在 File 模式下,会把获取到的文件挂载到 ctx.request.files 数组上。

        关键代码:

        • ctx.request.files: 获取到的文件列表。
        • ctx.oss.put():示例代码,此处为上传到 OSS 云存储,下文会介绍到。
        • ctx.cleanupRequestFiles():处理完毕后,清理临时文件。
        // app/controller/upload.js
         class UploadController extends Controller {
           async upload() {
             const { ctx } = this;
        @@ -47,14 +89,14 @@
             }
           }
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26

        Stream 模式

        如果你对于 Node.js 中的 Stream 模式非常熟悉,那么你可以选择此模式。

        相关的示例代码参见:eggjs/example/multipart

        上传单个文件

        框架同样提供了简化开发的语法糖:

        • ctx.getFileStream():获取上传的文件流,仅支持上传一个文件的情况。
        • stream.fields 获取其他表单字段。

        注意事项

        由于表单解析是有时序的,因此前端代码中,文件 fileds 必须在最后面。

        否则在拿到文件流时,stream.fields 还没解析完,从而获取不到。

        因此对应的前端代码:

        <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
        +

        Stream 模式

        如果你对于 Node.js 中的 Stream 模式非常熟悉,那么你可以选择此模式。

        相关的示例代码参见:eggjs/example/multipart

        上传单个文件

        框架同样提供了简化开发的语法糖:

        • ctx.getFileStream():获取上传的文件流,仅支持上传一个文件的情况。
        • stream.fields 获取其他表单字段。

        注意事项

        由于表单解析是有时序的,因此前端代码中,文件 fileds 必须在最后面。

        否则在拿到文件流时,stream.fields 还没解析完,从而获取不到。

        因此对应的前端代码:

        <form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
           title: <input name="title" />
         
           <!-- 只能有一个 File,且必须放在最后-->
           file: <input name="file" type="file" />
           <button type="submit">Upload</button>
         </form>
        -
        1
        2
        3
        4
        5
        6
        7

        对应的后端代码:

        const path = require('path');
        +

        对应的后端代码:

        const path = require('path');
         const sendToWormhole = require('stream-wormhole');
         const Controller = require('egg').Controller;
         
        @@ -80,7 +122,7 @@
             };
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26

        上传多个文件

        同时上传多个文件的场景,不能通过 ctx.getFileStream() 来获取,只能通过以下方式:

        const sendToWormhole = require('stream-wormhole');
        +

        上传多个文件

        同时上传多个文件的场景,不能通过 ctx.getFileStream() 来获取,只能通过以下方式:

        const sendToWormhole = require('stream-wormhole');
         const Controller = require('egg').Controller;
         
         class UploadController extends Controller {
        @@ -122,7 +164,7 @@
             console.log('and we are done parsing the form!');
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42

        错误处理

        Stream 模式下,在异常处理里面,必须将上传的文件流消费掉,要不然浏览器响应会卡死

        如上示例,你可以使用 stream-wormholemz-modules/pump 模块来处理。

        友情提示

        如果你对 Stream 没有足够了解的时候,建议直接使用 File 模式。

        安全限制

        文件大小

        为了避免恶意的攻击,框架默认对文件上传接口,限制了 FileField 的个数和大小。

        默认配置如下,开发者可以根据需求修改对应的配置。

        config.multipart = {
        +

        错误处理

        Stream 模式下,在异常处理里面,必须将上传的文件流消费掉,要不然浏览器响应会卡死

        如上示例,你可以使用 stream-wormholemz-modules/pump 模块来处理。

        友情提示

        如果你对 Stream 没有足够了解的时候,建议直接使用 File 模式。

        安全限制

        文件大小

        为了避免恶意的攻击,框架默认对文件上传接口,限制了 FileField 的个数和大小。

        默认配置如下,开发者可以根据需求修改对应的配置。

        config.multipart = {
           // 表单 Field 文件名长度限制
           fieldNameSize: 100,
           // 表单 Field 内容大小
        @@ -135,12 +177,12 @@
           // 允许上传的最大文件数
           files: 10,
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

        其中,fileSize 支持 10mb 这种人性化的方式,具体参见 humanize-bytes 模块。

        文件类型

        为了保证文件上传的安全,框架限制了支持的文件格式。默认的后缀白名单参见源码

        开发者可以通过配置 fileExtensions 来新增允许的类型:

        module.exports = {
        +

        其中,fileSize 支持 10mb 这种人性化的方式,具体参见 humanize-bytes 模块。

        文件类型

        为了保证文件上传的安全,框架限制了支持的文件格式。默认的后缀白名单参见源码

        开发者可以通过配置 fileExtensions 来新增允许的类型:

        module.exports = {
           multipart: {
             fileExtensions: [ '.apk' ] // 增加对 apk 扩展名的文件支持
           },
         };
        -
        1
        2
        3
        4
        5

        如果你希望覆盖框架内置的白名单,可以配置 whitelist 属性:

        module.exports = {
        +

        如果你希望覆盖框架内置的白名单,可以配置 whitelist 属性:

        module.exports = {
           multipart: {
             // 覆盖整个白名单,只允许上传 '.png' 格式
             whitelist: [ '.png' ],
        @@ -148,9 +190,9 @@
             // whitelist: (filename) => [ '.png' ].includes(path.extname(filename) || ''),
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8

        友情提示

        当重写了 whitelist 时,fileExtensions 不生效。

        云存储

        当获得上传的文件之后,我们一般会转存到云存储服务,尤其是在集群的情况下。

        常用的服务有:

        OSS

        框架内置了 egg-oss 插件,默认未开启。

        配置

        首先需要开启插件:

        // config/plugin.js
        +

        友情提示

        当重写了 whitelist 时,fileExtensions 不生效。

        云存储

        当获得上传的文件之后,我们一般会转存到云存储服务,尤其是在集群的情况下。

        常用的服务有:

        OSS

        框架内置了 egg-oss 插件,默认未开启。

        配置

        首先需要开启插件:

        // config/plugin.js
         exports.oss = true;
        -
        1
        2

        然后配置一下你的 OSSbucket, accessKeyId, accessKeySecret 等必要信息。

        // config/config.default.js
        +

        然后配置一下你的 OSSbucket, accessKeyId, accessKeySecret 等必要信息。

        // config/config.default.js
         config.oss = {
           client: {
             accessKeyId: 'your access key',
        @@ -162,7 +204,7 @@
             // encryptPassword: false,
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        然后通过 ctx.oss.put() 方法即可上传,支持 FileStream 两种模式。

        File 模式

        class UploadController extends Controller {
        +

        然后通过 ctx.oss.put() 方法即可上传,支持 FileStream 两种模式。

        File 模式

        class UploadController extends Controller {
           async upload() {
             // ...
         
        @@ -171,7 +213,7 @@
             console.info(url); // url 即为上传后的文件链接
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        Stream 模式

        class UploadController extends Controller {
        +

        Stream 模式

        class UploadController extends Controller {
           async upload() {
             // ...
         
        @@ -180,15 +222,9 @@
             console.info(url); // url 即为上传后的文件链接
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9

        前端直接上传 OSS

        还有一种常见的需求:前端直接上传文件到 OSS,不经过我们的 Web 应用。

        OSS 提供了 STS 临时授权方式

        上述的 egg-oss 插件的底层是 ali-oss 模块,也提供了对应的支持,具体参见文档。

        - +

        前端直接上传 OSS

        还有一种常见的需求:前端直接上传文件到 OSS,不经过我们的 Web 应用。

        OSS 提供了 STS 临时授权方式

        上述的 egg-oss 插件的底层是 ali-oss 模块,也提供了对应的支持,具体参见文档。

        + diff --git a/zh/index.html b/zh/index.html index daf4db6..d9138ba 100644 --- a/zh/index.html +++ b/zh/index.html @@ -5,17 +5,19 @@ Egg + - - - + + - - +

        Egg.js

        为企业级框架和应用而生

        + 快速开始 → +

        完善的生态

        基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。

        高效自然的研发体验

        渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。

        高质量、可信赖

        高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。

        灵活、高扩展性

        约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。

        + diff --git a/zh/quickstart/egg.html b/zh/quickstart/egg.html index 74c6571..240f837 100644 --- a/zh/quickstart/egg.html +++ b/zh/quickstart/egg.html @@ -5,19 +5,27 @@ 简单的 Egg 应用 | Egg + - - - + + -

        在本章中我们先来学习如何写一个简单的 Egg 应用,通过它来了解一些基本的概念和术语。

        友情提示

        需注意的是,本文介绍的是 Egg 的基础使用。 +

        简单的 Egg 应用

        在本章中我们先来学习如何写一个简单的 Egg 应用,通过它来了解一些基本的概念和术语。

        友情提示

        需注意的是,本文介绍的是 Egg 的基础使用。 对于 Egg 的开发者而言,很多插件无需自行安装,已经内置到框架,直接开启即可。 -更多内容,在开发指南中可以了解到。

        典型场景

        我们以 TodoMVC 这个典型的前端应用场景为例,一步步从零开始搭建。

        完整的源码参见 eggjs/examples/todomvc

        逐步搭建

        环境准备

        • 操作系统:支持 macOSLinuxWindows,推荐本地开发用 macOS
        • 运行环境:仅需要 Node.js,对应的安装参见文档

        初始化项目

        通过骨架来初始化

        # 使用 `Egg` 的 `simple` 骨架来初始化
        +更多内容,在开发指南中可以了解到。

        典型场景

        我们以 TodoMVC 这个典型的前端应用场景为例,一步步从零开始搭建。

        完整的源码参见 eggjs/examples/todomvc

        逐步搭建

        环境准备

        • 操作系统:支持 macOSLinuxWindows,推荐本地开发用 macOS
        • 运行环境:仅需要 Node.js,对应的安装参见文档

        初始化项目

        通过骨架来初始化

        # 使用 `Egg` 的 `simple` 骨架来初始化
         $ mkdir demo && cd demo
         $ npm init egg --type=simple
         $ npm install
        -
        1
        2
        3
        4

        目录结构

        框架奉行『约定优于配置』,所以我们首先来看看生成的目录结构,更多可以参见目录规范

        demo
        +

        目录结构

        框架奉行『约定优于配置』,所以我们首先来看看生成的目录结构,更多可以参见目录规范

        demo
         ├── app
         │   ├── controller # 控制器
         │   │   └── home.js
        @@ -28,7 +36,7 @@
         ├── test # 单元测试
         ├── README.md
         └── package.json
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        Controller

        Controller 负责解析用户的输入,处理后返回相应的结果

        // app/controller/home.js
        +

        Controller

        Controller 负责解析用户的输入,处理后返回相应的结果

        // app/controller/home.js
         const { Controller } = require('egg');
         
         class HomeController extends Controller {
        @@ -39,7 +47,7 @@
         }
         
         module.exports = HomeController;
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        接着配置 路由 映射到对应的 URL 上。

        // app/router.js
        +

        接着配置 路由 映射到对应的 URL 上。

        // app/router.js
         /**
          * @param {Egg.Application} app - egg application
          */
        @@ -47,29 +55,29 @@
           const { router, controller } = app;
           router.get('/', controller.home.index);
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8

        本地开发

        框架提供了本地开发的辅助工具。

        • 辅助本地启动应用,监控代码变更自动重启。
        • 自动生成 d.ts 文件,提供 智能提示代码跳转 等能力。

        通过命令启动应用:

        $ npm run dev
        -
        1

        然后就可以访问 http://127.0.0.1:7001

        模板渲染

        绝大多数情况,我们都需要读取数据后渲染模板,然后呈现给用户。

        Egg 并不强制你使用某种模板引擎,故我们需要引入对应的『插件』。

        术语讲堂

        插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。 -详见开发指南 - 插件文档。

        在本章中,我们使用 Nunjucks 来渲染,先安装对应的插件 egg-view-nunjucks

        $ npm i egg-view-nunjucks --save
        -
        1

        开启插件:

        // config/plugin.js
        +

        本地开发

        框架提供了本地开发的辅助工具。

        • 辅助本地启动应用,监控代码变更自动重启。
        • 自动生成 d.ts 文件,提供 智能提示代码跳转 等能力。

        通过命令启动应用:

        $ npm run dev
        +

        然后就可以访问 http://127.0.0.1:7001

        模板渲染

        绝大多数情况,我们都需要读取数据后渲染模板,然后呈现给用户。

        Egg 并不强制你使用某种模板引擎,故我们需要引入对应的『插件』。

        术语讲堂

        插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。 +详见开发指南 - 插件文档。

        在本章中,我们使用 Nunjucks 来渲染,先安装对应的插件 egg-view-nunjucks

        $ npm i egg-view-nunjucks --save
        +

        开启插件:

        // config/plugin.js
         exports.nunjucks = {
           enable: true,
           package: 'egg-view-nunjucks'
         };
        -
        1
        2
        3
        4
        5

        按照约定,在 app/view 目录下添加对应的模板文件:

        <!-- app/view/home.tpl -->
        +

        按照约定,在 app/view 目录下添加对应的模板文件:

        <!-- app/view/home.tpl -->
         <html>
           ...
           <script src="/public/main.js"></script>
         </html>
        -
        1
        2
        3
        4
        5

        对应的 Controller 改为:

        class HomeController extends Controller {
        +

        对应的 Controller 改为:

        class HomeController extends Controller {
           async index() {
             const { ctx } = this;
             // 渲染模板 `app/view/home.tpl`
             await ctx.render('home.tpl');
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7

        静态资源

        前端代码的发布,一般有:

        • 构建后发布到 CDN。(推荐)
        • 直接在应用中托管。

        Egg 内置了 egg-static 插件,对后者提供了支持。

        默认会把 app/public 目录映射到 /public 路由上。

        在本例中,我们使用 Vue 来写对应的前端逻辑,可以直接参见示例代码。

        注意事项

        • static 插件,线上会默认设置一年的 magAge
        • 框架默认开启了 CSRF 防护,故 AJAX 请求需要带上对应的 token
        // app/public/main.js
        +

        静态资源

        前端代码的发布,一般有:

        • 构建后发布到 CDN。(推荐)
        • 直接在应用中托管。

        Egg 内置了 egg-static 插件,对后者提供了支持。

        默认会把 app/public 目录映射到 /public 路由上。

        在本例中,我们使用 Vue 来写对应的前端逻辑,可以直接参见示例代码。

        注意事项

        • static 插件,线上会默认设置一年的 magAge
        • 框架默认开启了 CSRF 防护,故 AJAX 请求需要带上对应的 token
        // app/public/main.js
         axios.defaults.headers.common['x-csrf-token'] = Cookies.get('csrfToken');
        -
        1
        2

        配置文件

        写业务的时候,不可避免的需要有配置文件

        框架提供了强大的配置合并管理功能。

        如上述的 nunjucks 插件,添加对应的配置:

        // config/config.default.js
        +

        配置文件

        写业务的时候,不可避免的需要有配置文件

        框架提供了强大的配置合并管理功能。

        如上述的 nunjucks 插件,添加对应的配置:

        // config/config.default.js
         config.view = {
           defaultViewEngine: 'nunjucks',
           mapping: {
        @@ -77,7 +85,7 @@
             '.html': 'nunjucks',
           },
         };
        -
        1
        2
        3
        4
        5
        6
        7
        8

        注意事项

        config 目录,不是 app/config!

        Service

        我们的业务逻辑一般会写在 Service 里,然后供 Controller 调用。

        // app/service/todo.js
        +

        注意事项

        config 目录,不是 app/config!

        Service

        我们的业务逻辑一般会写在 Service 里,然后供 Controller 调用。

        // app/service/todo.js
         const { Service } = require('egg');
         
         class TodoService extends Service {
        @@ -97,7 +105,7 @@
             return todo;
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20

        对应的 Controller 如下:

        // app/controller/todo.js
        +

        对应的 Controller 如下:

        // app/controller/todo.js
         class TodoController extends Controller {
           async create() {
             const { ctx, service } = this;
        @@ -109,14 +117,14 @@
             ctx.body = await service.todo.create(ctx.request.body);
           }
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        RESTful

        EggRESTful 这种常见的场景提供了内建的支持

        // app/router.js
        +

        RESTful

        EggRESTful 这种常见的场景提供了内建的支持

        // app/router.js
         module.exports = app => {
           const { router, controller } = app;
         
           // RESTful 映射
           router.resources('/api/todo', controller.todo);
         };
        -
        1
        2
        3
        4
        5
        6
        7

        对应的 Controller

        // app/controller/todo.js
        +

        对应的 Controller

        // app/controller/todo.js
         class TodoController extends Controller {
           // `GET /api/todo`
           async index() {}
        @@ -130,7 +138,7 @@
           // `DELETE /api/todo`
           async destroy() {}
         }
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        单元测试

        Web 应用中的单元测试非常重要,框架也提供了对应的单元测试能力支持

        // test/app/controller/todo.test.js
        +

        单元测试

        Web 应用中的单元测试非常重要,框架也提供了对应的单元测试能力支持

        // test/app/controller/todo.test.js
         const { app, mock, assert } = require('egg-mock/bootstrap');
         
         describe('test/app/controller/todo.test.js', () => {
        @@ -148,11 +156,8 @@
               });
           });
         });
        -
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        - +
        + diff --git a/zh/quickstart/index.html b/zh/quickstart/index.html index 0c49e72..a7f35bf 100644 --- a/zh/quickstart/index.html +++ b/zh/quickstart/index.html @@ -5,17 +5,22 @@ 快速开始 | Egg + - - - + + - - + + From 0aa639b64b421494fbd3dc8950669cfc49bcd30e Mon Sep 17 00:00:00 2001 From: Suyi Date: Fri, 12 Jul 2019 15:11:52 +0800 Subject: [PATCH 07/11] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..cca6e6f --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +eggjs.org \ No newline at end of file From c670c690d1c3d1c483e12f1014135820c07cbddc Mon Sep 17 00:00:00 2001 From: Suyi Date: Fri, 12 Jul 2019 15:12:06 +0800 Subject: [PATCH 08/11] Delete CNAME --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index cca6e6f..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -eggjs.org \ No newline at end of file From c89c586ca467980a1a13dedd7366b1fa690c4b54 Mon Sep 17 00:00:00 2001 From: Auto Doc Publisher Date: Fri, 12 Jul 2019 07:17:19 +0000 Subject: [PATCH 09/11] docs: auto generate by ci chore: update deploy branch --- 404.html | 4 ++-- assets/js/{app.77aa8c4f.js => app.2e8db10c.js} | 2 +- guide/index.html | 6 +++--- index.html | 6 +++--- quickstart/egg.html | 6 +++--- quickstart/index.html | 6 +++--- zh/guide/application.html | 6 +++--- zh/guide/config.html | 6 +++--- zh/guide/context.html | 6 +++--- zh/guide/controller.html | 6 +++--- zh/guide/cookie.html | 6 +++--- zh/guide/directory.html | 6 +++--- zh/guide/error_handler.html | 6 +++--- zh/guide/faq.html | 6 +++--- zh/guide/helper.html | 6 +++--- zh/guide/httpclient.html | 6 +++--- zh/guide/i18n.html | 6 +++--- zh/guide/index.html | 6 +++--- zh/guide/lifecycle.html | 6 +++--- zh/guide/logger.html | 6 +++--- zh/guide/middleware.html | 6 +++--- zh/guide/plugin.html | 6 +++--- zh/guide/router.html | 6 +++--- zh/guide/service.html | 6 +++--- zh/guide/session.html | 6 +++--- zh/guide/upload.html | 6 +++--- zh/index.html | 6 +++--- zh/quickstart/egg.html | 6 +++--- zh/quickstart/index.html | 6 +++--- 29 files changed, 84 insertions(+), 84 deletions(-) rename assets/js/{app.77aa8c4f.js => app.2e8db10c.js} (95%) diff --git a/404.html b/404.html index 5f81211..a6917e2 100644 --- a/404.html +++ b/404.html @@ -7,11 +7,11 @@ - +

        404

        That's a Four-Oh-Four.
        Take me home.
        - + diff --git a/assets/js/app.77aa8c4f.js b/assets/js/app.2e8db10c.js similarity index 95% rename from assets/js/app.77aa8c4f.js rename to assets/js/app.2e8db10c.js index a0b4a8f..15c2ae3 100644 --- a/assets/js/app.77aa8c4f.js +++ b/assets/js/app.2e8db10c.js @@ -11,4 +11,4 @@ var r=Object.freeze({});function i(e){return null==e}function o(e){return null!= * vue-router v3.0.7 * (c) 2019 Evan You * @license MIT - */function Ni(e){return Object.prototype.toString.call(e).indexOf("Error")>-1}function qi(e,t){for(var n in t)e[n]=t[n];return e}var Ui={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render:function(e,t){var n=t.props,r=t.children,i=t.parent,o=t.data;o.routerView=!0;for(var a=i.$createElement,l=n.name,s=i.$route,u=i._routerViewCache||(i._routerViewCache={}),c=0,f=!1;i&&i._routerRoot!==i;){var p=i.$vnode&&i.$vnode.data;p&&(p.routerView&&c++,p.keepAlive&&i._inactive&&(f=!0)),i=i.$parent}if(o.routerViewDepth=c,f)return a(u[l],o,r);var d=s.matched[c];if(!d)return u[l]=null,a();var h=u[l]=d.components[l];o.registerRouteInstance=function(e,t){var n=d.instances[l];(t&&n!==e||!t&&n===e)&&(d.instances[l]=t)},(o.hook||(o.hook={})).prepatch=function(e,t){d.instances[l]=t.componentInstance},o.hook.init=function(e){e.data.keepAlive&&e.componentInstance&&e.componentInstance!==d.instances[l]&&(d.instances[l]=e.componentInstance)};var v=o.props=function(e,t){switch(typeof t){case"undefined":return;case"object":return t;case"function":return t(e);case"boolean":return t?e.params:void 0;default:0}}(s,d.props&&d.props[l]);if(v){v=o.props=qi({},v);var g=o.attrs=o.attrs||{};for(var m in v)h.props&&m in h.props||(g[m]=v[m],delete v[m])}return a(h,o,r)}};var Fi=/[!'()*]/g,Bi=function(e){return"%"+e.charCodeAt(0).toString(16)},Hi=/%2C/g,Vi=function(e){return encodeURIComponent(e).replace(Fi,Bi).replace(Hi,",")},Wi=decodeURIComponent;function Ki(e){var t={};return(e=e.trim().replace(/^(\?|#|&)/,""))?(e.split("&").forEach(function(e){var n=e.replace(/\+/g," ").split("="),r=Wi(n.shift()),i=n.length>0?Wi(n.join("=")):null;void 0===t[r]?t[r]=i:Array.isArray(t[r])?t[r].push(i):t[r]=[t[r],i]}),t):t}function Gi(e){var t=e?Object.keys(e).map(function(t){var n=e[t];if(void 0===n)return"";if(null===n)return Vi(t);if(Array.isArray(n)){var r=[];return n.forEach(function(e){void 0!==e&&(null===e?r.push(Vi(t)):r.push(Vi(t)+"="+Vi(e)))}),r.join("&")}return Vi(t)+"="+Vi(n)}).filter(function(e){return e.length>0}).join("&"):null;return t?"?"+t:""}var Qi=/\/?$/;function Xi(e,t,n,r){var i=r&&r.options.stringifyQuery,o=t.query||{};try{o=Ji(o)}catch(e){}var a={name:t.name||e&&e.name,meta:e&&e.meta||{},path:t.path||"/",hash:t.hash||"",query:o,params:t.params||{},fullPath:eo(t,i),matched:e?Zi(e):[]};return n&&(a.redirectedFrom=eo(n,i)),Object.freeze(a)}function Ji(e){if(Array.isArray(e))return e.map(Ji);if(e&&"object"==typeof e){var t={};for(var n in e)t[n]=Ji(e[n]);return t}return e}var Yi=Xi(null,{path:"/"});function Zi(e){for(var t=[];e;)t.unshift(e),e=e.parent;return t}function eo(e,t){var n=e.path,r=e.query;void 0===r&&(r={});var i=e.hash;return void 0===i&&(i=""),(n||"/")+(t||Gi)(r)+i}function to(e,t){return t===Yi?e===t:!!t&&(e.path&&t.path?e.path.replace(Qi,"")===t.path.replace(Qi,"")&&e.hash===t.hash&&no(e.query,t.query):!(!e.name||!t.name)&&(e.name===t.name&&e.hash===t.hash&&no(e.query,t.query)&&no(e.params,t.params)))}function no(e,t){if(void 0===e&&(e={}),void 0===t&&(t={}),!e||!t)return e===t;var n=Object.keys(e),r=Object.keys(t);return n.length===r.length&&n.every(function(n){var r=e[n],i=t[n];return"object"==typeof r&&"object"==typeof i?no(r,i):String(r)===String(i)})}var ro,io=[String,Object],oo=[String,Array],ao={name:"RouterLink",props:{to:{type:io,required:!0},tag:{type:String,default:"a"},exact:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,event:{type:oo,default:"click"}},render:function(e){var t=this,n=this.$router,r=this.$route,i=n.resolve(this.to,r,this.append),o=i.location,a=i.route,l=i.href,s={},u=n.options.linkActiveClass,c=n.options.linkExactActiveClass,f=null==u?"router-link-active":u,p=null==c?"router-link-exact-active":c,d=null==this.activeClass?f:this.activeClass,h=null==this.exactActiveClass?p:this.exactActiveClass,v=o.path?Xi(null,o,null,n):a;s[h]=to(r,v),s[d]=this.exact?s[h]:function(e,t){return 0===e.path.replace(Qi,"/").indexOf(t.path.replace(Qi,"/"))&&(!t.hash||e.hash===t.hash)&&function(e,t){for(var n in t)if(!(n in e))return!1;return!0}(e.query,t.query)}(r,v);var g=function(e){lo(e)&&(t.replace?n.replace(o):n.push(o))},m={click:lo};Array.isArray(this.event)?this.event.forEach(function(e){m[e]=g}):m[this.event]=g;var y={class:s};if("a"===this.tag)y.on=m,y.attrs={href:l};else{var b=function e(t){if(t)for(var n,r=0;r=0&&(t=e.slice(r),e=e.slice(0,r));var i=e.indexOf("?");return i>=0&&(n=e.slice(i+1),e=e.slice(0,i)),{path:e,query:n,hash:t}}(i.path||""),s=t&&t.path||"/",u=l.path?uo(l.path,s,n||i.append):s,c=function(e,t,n){void 0===t&&(t={});var r,i=n||Ki;try{r=i(e||"")}catch(e){r={}}for(var o in t)r[o]=t[o];return r}(l.query,i.query,r&&r.options.parseQuery),f=i.hash||l.hash;return f&&"#"!==f.charAt(0)&&(f="#"+f),{_normalized:!0,path:u,query:c,hash:f}}function zo(e,t){var n=jo(e),r=n.pathList,i=n.pathMap,o=n.nameMap;function a(e,n,a){var l=Po(e,n,!1,t),u=l.name;if(u){var c=o[u];if(!c)return s(null,l);var f=c.regex.keys.filter(function(e){return!e.optional}).map(function(e){return e.name});if("object"!=typeof l.params&&(l.params={}),n&&"object"==typeof n.params)for(var p in n.params)!(p in l.params)&&f.indexOf(p)>-1&&(l.params[p]=n.params[p]);return l.path=Ao(c.path,l.params),s(c,l,a)}if(l.path){l.params={};for(var d=0;d=e.length?n():e[i]?t(e[i],function(){r(i+1)}):r(i+1)};r(0)}function Yo(e){return function(t,n,r){var i=!1,o=0,a=null;Zo(e,function(e,t,n,l){if("function"==typeof e&&void 0===e.cid){i=!0,o++;var s,u=na(function(t){var i;((i=t).__esModule||ta&&"Module"===i[Symbol.toStringTag])&&(t=t.default),e.resolved="function"==typeof t?t:ro.extend(t),n.components[l]=t,--o<=0&&r()}),c=na(function(e){var t="Failed to resolve async component "+l+": "+e;a||(a=Ni(e)?e:new Error(t),r(a))});try{s=e(u,c)}catch(e){c(e)}if(s)if("function"==typeof s.then)s.then(u,c);else{var f=s.component;f&&"function"==typeof f.then&&f.then(u,c)}}}),i||r()}}function Zo(e,t){return ea(e.map(function(e){return Object.keys(e.components).map(function(n){return t(e.components[n],e.instances[n],e,n)})}))}function ea(e){return Array.prototype.concat.apply([],e)}var ta="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function na(e){var t=!1;return function(){for(var n=[],r=arguments.length;r--;)n[r]=arguments[r];if(!t)return t=!0,e.apply(this,n)}}var ra=function(e,t){this.router=e,this.base=function(e){if(!e)if(so){var t=document.querySelector("base");e=(e=t&&t.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else e="/";"/"!==e.charAt(0)&&(e="/"+e);return e.replace(/\/$/,"")}(t),this.current=Yi,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[]};function ia(e,t,n,r){var i=Zo(e,function(e,r,i,o){var a=function(e,t){"function"!=typeof e&&(e=ro.extend(e));return e.options[t]}(e,t);if(a)return Array.isArray(a)?a.map(function(e){return n(e,r,i,o)}):n(a,r,i,o)});return ea(r?i.reverse():i)}function oa(e,t){if(t)return function(){return e.apply(t,arguments)}}ra.prototype.listen=function(e){this.cb=e},ra.prototype.onReady=function(e,t){this.ready?e():(this.readyCbs.push(e),t&&this.readyErrorCbs.push(t))},ra.prototype.onError=function(e){this.errorCbs.push(e)},ra.prototype.transitionTo=function(e,t,n){var r=this,i=this.router.match(e,this.current);this.confirmTransition(i,function(){r.updateRoute(i),t&&t(i),r.ensureURL(),r.ready||(r.ready=!0,r.readyCbs.forEach(function(e){e(i)}))},function(e){n&&n(e),e&&!r.ready&&(r.ready=!0,r.readyErrorCbs.forEach(function(t){t(e)}))})},ra.prototype.confirmTransition=function(e,t,n){var r=this,i=this.current,o=function(e){Ni(e)&&(r.errorCbs.length?r.errorCbs.forEach(function(t){t(e)}):console.error(e)),n&&n(e)};if(to(e,i)&&e.matched.length===i.matched.length)return this.ensureURL(),o();var a=function(e,t){var n,r=Math.max(e.length,t.length);for(n=0;n-1?decodeURI(e.slice(0,r))+e.slice(r):decodeURI(e)}else n>-1&&(e=decodeURI(e.slice(0,n))+e.slice(n));return e}function fa(e){var t=window.location.href,n=t.indexOf("#");return(n>=0?t.slice(0,n):t)+"#"+e}function pa(e){Ho?Qo(fa(e)):window.location.hash=e}function da(e){Ho?Xo(fa(e)):window.location.replace(fa(e))}var ha=function(e){function t(t,n){e.call(this,t,n),this.stack=[],this.index=-1}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.push=function(e,t,n){var r=this;this.transitionTo(e,function(e){r.stack=r.stack.slice(0,r.index+1).concat(e),r.index++,t&&t(e)},n)},t.prototype.replace=function(e,t,n){var r=this;this.transitionTo(e,function(e){r.stack=r.stack.slice(0,r.index).concat(e),t&&t(e)},n)},t.prototype.go=function(e){var t=this,n=this.index+e;if(!(n<0||n>=this.stack.length)){var r=this.stack[n];this.confirmTransition(r,function(){t.index=n,t.updateRoute(r)})}},t.prototype.getCurrentLocation=function(){var e=this.stack[this.stack.length-1];return e?e.fullPath:"/"},t.prototype.ensureURL=function(){},t}(ra),va=function(e){void 0===e&&(e={}),this.app=null,this.apps=[],this.options=e,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=zo(e.routes||[],this);var t=e.mode||"hash";switch(this.fallback="history"===t&&!Ho&&!1!==e.fallback,this.fallback&&(t="hash"),so||(t="abstract"),this.mode=t,t){case"history":this.history=new aa(this,e.base);break;case"hash":this.history=new sa(this,e.base,this.fallback);break;case"abstract":this.history=new ha(this,e.base);break;default:0}},ga={currentRoute:{configurable:!0}};function ma(e,t){return e.push(t),function(){var n=e.indexOf(t);n>-1&&e.splice(n,1)}}va.prototype.match=function(e,t,n){return this.matcher.match(e,t,n)},ga.currentRoute.get=function(){return this.history&&this.history.current},va.prototype.init=function(e){var t=this;if(this.apps.push(e),e.$once("hook:destroyed",function(){var n=t.apps.indexOf(e);n>-1&&t.apps.splice(n,1),t.app===e&&(t.app=t.apps[0]||null)}),!this.app){this.app=e;var n=this.history;if(n instanceof aa)n.transitionTo(n.getCurrentLocation());else if(n instanceof sa){var r=function(){n.setupListeners()};n.transitionTo(n.getCurrentLocation(),r,r)}n.listen(function(e){t.apps.forEach(function(t){t._route=e})})}},va.prototype.beforeEach=function(e){return ma(this.beforeHooks,e)},va.prototype.beforeResolve=function(e){return ma(this.resolveHooks,e)},va.prototype.afterEach=function(e){return ma(this.afterHooks,e)},va.prototype.onReady=function(e,t){this.history.onReady(e,t)},va.prototype.onError=function(e){this.history.onError(e)},va.prototype.push=function(e,t,n){this.history.push(e,t,n)},va.prototype.replace=function(e,t,n){this.history.replace(e,t,n)},va.prototype.go=function(e){this.history.go(e)},va.prototype.back=function(){this.go(-1)},va.prototype.forward=function(){this.go(1)},va.prototype.getMatchedComponents=function(e){var t=e?e.matched?e:this.resolve(e).route:this.currentRoute;return t?[].concat.apply([],t.matched.map(function(e){return Object.keys(e.components).map(function(t){return e.components[t]})})):[]},va.prototype.resolve=function(e,t,n){var r=Po(e,t=t||this.history.current,n,this),i=this.match(r,t),o=i.redirectedFrom||i.fullPath;return{location:r,route:i,href:function(e,t,n){var r="hash"===n?"#"+t:t;return e?co(e+"/"+r):r}(this.history.base,o,this.mode),normalizedTo:r,resolved:i}},va.prototype.addRoutes=function(e){this.matcher.addRoutes(e),this.history.current!==Yi&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(va.prototype,ga),va.install=function e(t){if(!e.installed||ro!==t){e.installed=!0,ro=t;var n=function(e){return void 0!==e},r=function(e,t){var r=e.$options._parentVnode;n(r)&&n(r=r.data)&&n(r=r.registerRouteInstance)&&r(e,t)};t.mixin({beforeCreate:function(){n(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),t.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,r(this,this)},destroyed:function(){r(this)}}),Object.defineProperty(t.prototype,"$router",{get:function(){return this._routerRoot._router}}),Object.defineProperty(t.prototype,"$route",{get:function(){return this._routerRoot._route}}),t.component("RouterView",Ui),t.component("RouterLink",ao);var i=t.config.optionMergeStrategies;i.beforeRouteEnter=i.beforeRouteLeave=i.beforeRouteUpdate=i.created}},va.version="3.0.7",so&&window.Vue&&window.Vue.use(va);var ya=va;var ba={NotFound:()=>Promise.all([n.e(0),n.e(5)]).then(n.bind(null,215)),Detector:()=>n.e(9).then(n.bind(null,216)),Ecosystem:()=>Promise.all([n.e(0),n.e(1),n.e(6)]).then(n.bind(null,217)),Layout:()=>Promise.all([n.e(0),n.e(1),n.e(3)]).then(n.bind(null,214))},_a={"v-1605d0f3":()=>n.e(10).then(n.bind(null,241)),"v-97a66c4e":()=>n.e(11).then(n.bind(null,242)),"v-3ea02d83":()=>n.e(12).then(n.bind(null,243)),"v-77fd80de":()=>n.e(13).then(n.bind(null,227)),"v-dd3f763a":()=>n.e(14).then(n.bind(null,218)),"v-3a272843":()=>n.e(15).then(n.bind(null,231)),"v-555b843c":()=>n.e(16).then(n.bind(null,244)),"v-01665048":()=>n.e(17).then(n.bind(null,219)),"v-bea6a0f0":()=>n.e(20).then(n.bind(null,220)),"v-61e12502":()=>n.e(21).then(n.bind(null,221)),"v-5138afa0":()=>n.e(23).then(n.bind(null,223)),"v-2634a070":()=>n.e(24).then(n.bind(null,226)),"v-a25f7170":()=>n.e(26).then(n.bind(null,245)),"v-1879f308":()=>n.e(27).then(n.bind(null,229)),"v-15b3b548":()=>n.e(7).then(n.bind(null,233)),"v-2f089be8":()=>n.e(29).then(n.bind(null,235)),"v-a9bc6b9c":()=>n.e(31).then(n.bind(null,237)),"v-9c1bf3a0":()=>n.e(32).then(n.bind(null,239)),"v-c73d89b6":()=>n.e(34).then(n.bind(null,238)),"v-13a2f408":()=>n.e(8).then(n.bind(null,236)),"v-45c3b484":()=>n.e(18).then(n.bind(null,234)),"v-2ca41536":()=>n.e(22).then(n.bind(null,232)),"v-26f90708":()=>n.e(28).then(n.bind(null,230)),"v-1f1ad7a8":()=>n.e(30).then(n.bind(null,228)),"v-07b4bab0":()=>n.e(33).then(n.bind(null,240)),"v-471e5888":()=>n.e(19).then(n.bind(null,225)),"v-25adca68":()=>n.e(25).then(n.bind(null,224))};function wa(e){const t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}const xa=/-(\w)/g,ka=wa(e=>e.replace(xa,(e,t)=>t?t.toUpperCase():"")),Ca=/\B([A-Z])/g,$a=wa(e=>e.replace(Ca,"-$1").toLowerCase()),Ea=wa(e=>e.charAt(0).toUpperCase()+e.slice(1));function Sa(e,t){if(!t)return;if(e(t))return e(t);return t.includes("-")?e(Ea(ka(t))):e(Ea(t))||e($a(t))}const Oa=Object.assign({},ba,_a),Aa=e=>Oa[e],ja=e=>_a[e],Ta=e=>ba[e],Pa=e=>Di.component(e);function za(e){return Sa(ja,e)}function La(e){return Sa(Ta,e)}function Ra(e){return Sa(Aa,e)}function Ma(e){return Sa(Pa,e)}function Ia(...e){return Promise.all(e.filter(e=>e).map(async e=>{if(!Ma(e)&&Ra(e)){const t=await Ra(e)();Di.component(e,t.default)}}))}function Da(e,t){"undefined"!=typeof window&&window.__VUEPRESS__&&(window.__VUEPRESS__[e]=t)}var Na={created(){this.$ssrContext&&(this.$ssrContext.title=this.$title,this.$ssrContext.lang=this.$lang,this.$ssrContext.description=this.$page.description||this.$description)},mounted(){this.currentMetaTags=new Set,this.updateMeta()},methods:{updateMeta(){document.title=this.$title,document.documentElement.lang=this.$lang;const e=this.$page.frontmatter.meta||[],t=e.slice(0);0===e.filter(e=>"description"===e.name).length&&t.push({name:"description",content:this.$description});const n=document.querySelectorAll('meta[name="description"]');n.length&&n.forEach(e=>this.currentMetaTags.add(e)),this.currentMetaTags=new Set(qa(t,this.currentMetaTags))}},watch:{$page(){this.updateMeta()}},beforeDestroy(){qa(null,this.currentMetaTags)}};function qa(e,t){if(t&&[...t].forEach(e=>{document.head.removeChild(e)}),e)return e.map(e=>{const t=document.createElement("meta");return Object.keys(e).forEach(n=>{t.setAttribute(n,e[n])}),document.head.appendChild(t),t})}var Ua=n(3),Fa={mounted(){window.addEventListener("scroll",this.onScroll)},methods:{onScroll:n.n(Ua)()(function(){this.setActiveHash()},300),setActiveHash(){const e=[].slice.call(document.querySelectorAll(".sidebar-link")),t=[].slice.call(document.querySelectorAll(".header-anchor")).filter(t=>e.some(e=>e.hash===t.hash)),n=Math.max(window.pageYOffset,document.documentElement.scrollTop,document.body.scrollTop),r=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),i=window.innerHeight+n;for(let e=0;e=o.parentElement.offsetTop+10&&(!a||n{this.$nextTick(()=>{this.$vuepress.$set("disableScrollBehavior",!1)})})}}}},beforeDestroy(){window.removeEventListener("scroll",this.onScroll)}},Ba=n(2),Ha=n.n(Ba),Va=[Na,Fa,{mounted(){Ha.a.configure({showSpinner:!1}),this.$router.beforeEach((e,t,n)=>{e.path===t.path||Di.component(e.name)||Ha.a.start(),n()}),this.$router.afterEach(()=>{Ha.a.done(),this.isSidebarOpen=!1})}}],Wa={methods:{getLayout:function(){if(this.$page.path){var e=this.$page.frontmatter.layout;return e&&(this.$vuepress.getLayoutAsyncComponent(e)||this.$vuepress.getVueComponent(e))?e:"Layout"}return"NotFound"}},computed:{layout:function(){var e=this.getLayout();return Da("layout",e),Di.component(e)}}},Ka=n(0),Ga=Object(Ka.a)(Wa,function(){var e=this.$createElement;return(this._self._c||e)(this.layout,{tag:"component"})},[],!1,null,null,null).exports;!function(e,t,n){switch(t){case"components":e[t]||(e[t]={}),Object.assign(e[t],n);break;case"mixins":e[t]||(e[t]=[]),e[t].push(...n);break;default:throw new Error("Unknown option name.")}}(Ga,"mixins",Va);const Qa=[{name:"v-1605d0f3",path:"/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-1605d0f3").then(n)}},{path:"/index.html",redirect:"/"},{name:"v-97a66c4e",path:"/guide/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-97a66c4e").then(n)}},{path:"/guide/index.html",redirect:"/guide/"},{name:"v-3ea02d83",path:"/quickstart/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-3ea02d83").then(n)}},{path:"/quickstart/index.html",redirect:"/quickstart/"},{name:"v-77fd80de",path:"/quickstart/egg.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-77fd80de").then(n)}},{name:"v-dd3f763a",path:"/zh/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-dd3f763a").then(n)}},{path:"/zh/index.html",redirect:"/zh/"},{name:"v-3a272843",path:"/zh/guide/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-3a272843").then(n)}},{path:"/zh/guide/index.html",redirect:"/zh/guide/"},{name:"v-555b843c",path:"/zh/guide/application.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-555b843c").then(n)}},{name:"v-01665048",path:"/zh/guide/config.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-01665048").then(n)}},{name:"v-bea6a0f0",path:"/zh/guide/cookie.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-bea6a0f0").then(n)}},{name:"v-61e12502",path:"/zh/guide/directory.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-61e12502").then(n)}},{name:"v-5138afa0",path:"/zh/guide/faq.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-5138afa0").then(n)}},{name:"v-2634a070",path:"/zh/guide/helper.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-2634a070").then(n)}},{name:"v-a25f7170",path:"/zh/guide/i18n.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-a25f7170").then(n)}},{name:"v-1879f308",path:"/zh/guide/lifecycle.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-1879f308").then(n)}},{name:"v-15b3b548",path:"/zh/guide/middleware.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-15b3b548").then(n)}},{name:"v-2f089be8",path:"/zh/guide/plugin.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-2f089be8").then(n)}},{name:"v-a9bc6b9c",path:"/zh/guide/service.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-a9bc6b9c").then(n)}},{name:"v-9c1bf3a0",path:"/zh/guide/session.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-9c1bf3a0").then(n)}},{name:"v-c73d89b6",path:"/zh/quickstart/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-c73d89b6").then(n)}},{path:"/zh/quickstart/index.html",redirect:"/zh/quickstart/"},{name:"v-13a2f408",path:"/zh/quickstart/egg.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-13a2f408").then(n)}},{name:"v-45c3b484",path:"/zh/guide/context.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-45c3b484").then(n)}},{name:"v-2ca41536",path:"/zh/guide/error_handler.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-2ca41536").then(n)}},{name:"v-26f90708",path:"/zh/guide/logger.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-26f90708").then(n)}},{name:"v-1f1ad7a8",path:"/zh/guide/router.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-1f1ad7a8").then(n)}},{name:"v-07b4bab0",path:"/zh/guide/upload.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-07b4bab0").then(n)}},{name:"v-471e5888",path:"/zh/guide/controller.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-471e5888").then(n)}},{name:"v-25adca68",path:"/zh/guide/httpclient.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-25adca68").then(n)}},{path:"*",component:Ga}],Xa={title:"",description:"",base:"/",pages:[{title:"Home",frontmatter:{toc:!1,home:!0,heroText:"Egg.js",banner:"/img_egg/banner.png",tagline:"Born to build better enterprise frameworks and apps with Node.js & Koa",actionText:"QuickStart →",actionLink:"/guide/",features:[{title:"完善的生态",details:"基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。",icon:"/img_egg/icon-3.png"},{title:"高效自然的研发体验",details:"渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。",icon:"/img_egg/icon-4.png"},{title:"高质量、可信赖",details:"高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。",icon:"/img_egg/icon-2.png"},{title:"灵活、高扩展性",details:"约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。",icon:"/img_egg/icon-1.png"}],whosusing:{icon:"/img_egg/whosusing.png"}},regularPath:"/",relativePath:"README.md",key:"v-1605d0f3",path:"/"},{title:"Guide",frontmatter:{title:"Guide",navTitle:"Egg Guide",toc:!1},regularPath:"/guide/",relativePath:"guide/README.md",key:"v-97a66c4e",path:"/guide/"},{title:"QuickStart",frontmatter:{title:"QuickStart"},regularPath:"/quickstart/",relativePath:"quickstart/README.md",key:"v-3ea02d83",path:"/quickstart/"},{title:"Simple Egg Application",frontmatter:{title:"Simple Egg Application"},regularPath:"/quickstart/egg.html",relativePath:"quickstart/egg.md",key:"v-77fd80de",path:"/quickstart/egg.html"},{title:"Home",frontmatter:{toc:!1,home:!0,heroText:"Egg.js",banner:"/img_egg/banner.png",tagline:"为企业级框架和应用而生",actionText:"快速开始 →",actionLink:"/zh/guide/",features:[{title:"完善的生态",details:"基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。",icon:"/img_egg/icon-3.png"},{title:"高效自然的研发体验",details:"渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。",icon:"/img_egg/icon-4.png"},{title:"高质量、可信赖",details:"高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。",icon:"/img_egg/icon-2.png"},{title:"灵活、高扩展性",details:"约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。",icon:"/img_egg/icon-1.png"}],whosusing:{icon:"/img_egg/whosusing.png"}},regularPath:"/zh/",relativePath:"zh/README.md",key:"v-dd3f763a",path:"/zh/"},{title:"概述",frontmatter:{title:"概述",navTitle:"Egg 指南",toc:!1},regularPath:"/zh/guide/",relativePath:"zh/guide/README.md",key:"v-3a272843",path:"/zh/guide/",headers:[{level:2,title:"Web 模型",slug:"web-模型"},{level:2,title:"功能模块",slug:"功能模块"}]},{title:"Application",frontmatter:{title:"Application"},regularPath:"/zh/guide/application.html",relativePath:"zh/guide/application.md",key:"v-555b843c",path:"/zh/guide/application.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"获取方式",slug:"获取方式"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:3,title:"app.config",slug:"app-config"},{level:3,title:"app.router",slug:"app-router"},{level:3,title:"app.controller",slug:"app-controller"},{level:3,title:"app.logger",slug:"app-logger"},{level:3,title:"app.middleware",slug:"app-middleware"},{level:3,title:"app.server",slug:"app-server"},{level:3,title:"app.curl()",slug:"app-curl"},{level:3,title:"app.createAnonymousContext()",slug:"app-createanonymouscontext"},{level:2,title:"如何扩展",slug:"如何扩展"},{level:3,title:"方法扩展",slug:"方法扩展"},{level:3,title:"属性扩展",slug:"属性扩展"},{level:3,title:"编写测试",slug:"编写测试"},{level:3,title:"按照环境进行扩展",slug:"按照环境进行扩展"}]},{title:"配置",frontmatter:{title:"配置"},regularPath:"/zh/guide/config.html",relativePath:"zh/guide/config.md",key:"v-01665048",path:"/zh/guide/config.html",headers:[{level:2,title:"方案选型",slug:"方案选型"},{level:2,title:"运行环境",slug:"运行环境"},{level:3,title:"env",slug:"env"},{level:2,title:"配置文件",slug:"配置文件"},{level:2,title:"配置定义",slug:"配置定义"},{level:2,title:"AppInfo",slug:"appinfo"},{level:2,title:"加载规则",slug:"加载规则"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"为什么我的配置不生效?",slug:"为什么我的配置不生效?"},{level:3,title:"如何查看最终的配置?",slug:"如何查看最终的配置?"}]},{title:"Cookie",frontmatter:{title:"Cookie"},regularPath:"/zh/guide/cookie.html",relativePath:"zh/guide/cookie.md",key:"v-bea6a0f0",path:"/zh/guide/cookie.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"使用 Cookie",slug:"使用-cookie"},{level:2,title:"术语解释",slug:"术语解释"},{level:3,title:"过期时间",slug:"过期时间"},{level:3,title:"作用域",slug:"作用域"},{level:3,title:"安全",slug:"安全"},{level:3,title:"加签 && 加密",slug:"加签-加密"},{level:2,title:"API 说明",slug:"api-说明"},{level:3,title:"set(key, value, options)",slug:"set-key-value-options"},{level:3,title:"get(key, options)",slug:"get-key-options"},{level:3,title:"options",slug:"options"},{level:3,title:"配置秘钥",slug:"配置秘钥"},{level:2,title:"Cookie 实战",slug:"cookie-实战"},{level:3,title:"读取前端写入的 Cookie",slug:"读取前端写入的-cookie"},{level:3,title:"允许前端读取 Cookie",slug:"允许前端读取-cookie"},{level:3,title:"不允许浏览器看到明文内容",slug:"不允许浏览器看到明文内容"},{level:3,title:"删除 Cookie",slug:"删除-cookie"},{level:2,title:"编写测试",slug:"编写测试"},{level:2,title:"注意事项",slug:"注意事项"}]},{title:"目录规范",frontmatter:{title:"目录规范"},regularPath:"/zh/guide/directory.html",relativePath:"zh/guide/directory.md",key:"v-61e12502",path:"/zh/guide/directory.html"},{title:"FAQ",frontmatter:{title:"FAQ",sidebarDepth:1},regularPath:"/zh/guide/faq.html",relativePath:"zh/guide/faq.md",key:"v-5138afa0",path:"/zh/guide/faq.html",headers:[{level:2,title:"如何高效的反馈问题?",slug:"如何高效的反馈问题?"},{level:2,title:"为什么我的配置不生效?",slug:"为什么我的配置不生效?"},{level:2,title:"线上的日志打印去哪里了?",slug:"线上的日志打印去哪里了?"},{level:2,title:"进程管理为什么没有选型 PM2 ?",slug:"进程管理为什么没有选型-pm2-?"},{level:2,title:"为什么会有 csrf 报错?",slug:"为什么会有-csrf-报错?"},{level:2,title:"本地开发时,修改代码后为什么 worker 进程没有自动重启?",slug:"本地开发时,修改代码后为什么-worker-进程没有自动重启?"}]},{title:"Helper",frontmatter:{title:"Helper"},regularPath:"/zh/guide/helper.html",relativePath:"zh/guide/helper.md",key:"v-2634a070",path:"/zh/guide/helper.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"访问方式",slug:"访问方式"},{level:2,title:"常用的属性和方法",slug:"常用的属性和方法"},{level:2,title:"如何扩展",slug:"如何扩展"}]},{title:"国际化",frontmatter:{title:"国际化"},regularPath:"/zh/guide/i18n.html",relativePath:"zh/guide/i18n.md",key:"v-a25f7170",path:"/zh/guide/i18n.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"定义 locale",slug:"定义-locale"},{level:3,title:"目录规范",slug:"目录规范"},{level:3,title:"文件格式",slug:"文件格式"},{level:3,title:"占位符",slug:"占位符"},{level:2,title:"使用 i18n",slug:"使用-i18n"},{level:3,title:"ctx.__(key, ...values)",slug:"ctx-key-values"},{level:3,title:"在 View 中使用",slug:"在-view-中使用"},{level:2,title:"切换语言",slug:"切换语言"},{level:3,title:"默认语言",slug:"默认语言"},{level:3,title:"切换语言",slug:"切换语言-2"},{level:2,title:"局限性",slug:"局限性"}]},{title:"生命周期",frontmatter:{title:"生命周期"},regularPath:"/zh/guide/lifecycle.html",relativePath:"zh/guide/lifecycle.md",key:"v-1879f308",path:"/zh/guide/lifecycle.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"定义生命周期",slug:"定义生命周期"},{level:2,title:"详解生命周期",slug:"详解生命周期"},{level:3,title:"configWillLoad()",slug:"configwillload"},{level:3,title:"configDidLoad()",slug:"configdidload"},{level:3,title:"async didLoad()",slug:"async-didload"},{level:3,title:"async willReady()",slug:"async-willready"},{level:3,title:"async didReady()",slug:"async-didready"},{level:3,title:"async serverDidReady()",slug:"async-serverdidready"},{level:3,title:"async beforeClose()",slug:"async-beforeclose"}]},{title:"Middleware",frontmatter:{title:"Middleware"},regularPath:"/zh/guide/middleware.html",relativePath:"zh/guide/middleware.md",key:"v-15b3b548",path:"/zh/guide/middleware.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写中间件",slug:"编写中间件"},{level:2,title:"加载规则",slug:"加载规则"},{level:2,title:"使用中间件",slug:"使用中间件"},{level:2,title:"自定义配置",slug:"自定义配置"},{level:2,title:"通用配置",slug:"通用配置"},{level:3,title:"enable",slug:"enable"},{level:3,title:"match 和 ignore",slug:"match-和-ignore"},{level:2,title:"修改内置中间件的配置",slug:"修改内置中间件的配置"},{level:2,title:"路由中间件",slug:"路由中间件"},{level:2,title:"引入 Koa 生态",slug:"引入-koa-生态"},{level:2,title:"编写测试",slug:"编写测试"}]},{title:"使用插件",frontmatter:{title:"使用插件"},regularPath:"/zh/guide/plugin.html",relativePath:"zh/guide/plugin.md",key:"v-2f089be8",path:"/zh/guide/plugin.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"使用插件",slug:"使用插件"},{level:3,title:"安装依赖",slug:"安装依赖"},{level:3,title:"挂载插件",slug:"挂载插件"},{level:3,title:"使用插件",slug:"使用插件-2"},{level:2,title:"了解插件",slug:"了解插件"},{level:3,title:"目录结构",slug:"目录结构"},{level:3,title:"Service",slug:"service"},{level:3,title:"Config",slug:"config"},{level:3,title:"Middleware",slug:"middleware"},{level:3,title:"Extend",slug:"extend"},{level:3,title:"不支持的特性",slug:"不支持的特性"},{level:2,title:"插件配置",slug:"插件配置"},{level:3,title:"参数介绍",slug:"参数介绍"},{level:3,title:"开启框架内置插件",slug:"开启框架内置插件"},{level:3,title:"package 和 path",slug:"package-和-path"},{level:3,title:"根据环境配置",slug:"根据环境配置"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"如何开发一个插件",slug:"如何开发一个插件"},{level:3,title:"插件太多,每个应用都要开启怎么办?",slug:"插件太多,每个应用都要开启怎么办?"}]},{title:"Service",frontmatter:{title:"Service"},regularPath:"/zh/guide/service.html",relativePath:"zh/guide/service.md",key:"v-a9bc6b9c",path:"/zh/guide/service.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写 Service",slug:"编写-service"},{level:2,title:"使用 Service",slug:"使用-service"},{level:2,title:"生命周期",slug:"生命周期"},{level:2,title:"挂载规则",slug:"挂载规则"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:2,title:"编写测试",slug:"编写测试"}]},{title:"Session",frontmatter:{title:"Session"},regularPath:"/zh/guide/session.html",relativePath:"zh/guide/session.md",key:"v-9c1bf3a0",path:"/zh/guide/session.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"使用 Session",slug:"使用-session"},{level:2,title:"禁止使用的 Key 值",slug:"禁止使用的-key-值"},{level:2,title:"存储方式",slug:"存储方式"},{level:3,title:"Cookie",slug:"cookie"},{level:3,title:"Redis",slug:"redis"},{level:3,title:"注意事项",slug:"注意事项"},{level:2,title:"Session 实战",slug:"session-实战"},{level:3,title:"删除 Session",slug:"删除-session"},{level:3,title:"修改失效时间",slug:"修改失效时间"},{level:3,title:"延长有效期",slug:"延长有效期"}]},{title:"快速开始",frontmatter:{title:"快速开始"},regularPath:"/zh/quickstart/",relativePath:"zh/quickstart/README.md",key:"v-c73d89b6",path:"/zh/quickstart/"},{title:"简单的 Egg 应用",frontmatter:{title:"简单的 Egg 应用"},regularPath:"/zh/quickstart/egg.html",relativePath:"zh/quickstart/egg.md",key:"v-13a2f408",path:"/zh/quickstart/egg.html",headers:[{level:2,title:"典型场景",slug:"典型场景"},{level:2,title:"逐步搭建",slug:"逐步搭建"},{level:3,title:"环境准备",slug:"环境准备"},{level:3,title:"初始化项目",slug:"初始化项目"},{level:3,title:"目录结构",slug:"目录结构"},{level:3,title:"Controller",slug:"controller"},{level:3,title:"本地开发",slug:"本地开发"},{level:3,title:"模板渲染",slug:"模板渲染"},{level:3,title:"静态资源",slug:"静态资源"},{level:3,title:"配置文件",slug:"配置文件"},{level:3,title:"Service",slug:"service"},{level:3,title:"RESTful",slug:"restful"},{level:3,title:"单元测试",slug:"单元测试"}]},{title:"Context",frontmatter:{title:"Context"},regularPath:"/zh/guide/context.html",relativePath:"zh/guide/context.md",key:"v-45c3b484",path:"/zh/guide/context.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"获取方式",slug:"获取方式"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:3,title:"ctx.app",slug:"ctx-app"},{level:3,title:"ctx.service",slug:"ctx-service"},{level:3,title:"ctx.logger",slug:"ctx-logger"},{level:3,title:"ctx.curl()",slug:"ctx-curl"},{level:3,title:"ctx.runInBackground()",slug:"ctx-runinbackground"},{level:3,title:"ctx.query",slug:"ctx-query"},{level:3,title:"ctx.queries",slug:"ctx-queries"},{level:3,title:"ctx.params",slug:"ctx-params"},{level:3,title:"ctx.routerPath",slug:"ctx-routerpath"},{level:3,title:"ctx.routerName",slug:"ctx-routername"},{level:3,title:"ctx.request.body",slug:"ctx-request-body"},{level:3,title:"ctx.request.files",slug:"ctx-request-files"},{level:3,title:"ctx.get(name)",slug:"ctx-get-name"},{level:3,title:"ctx.cookies",slug:"ctx-cookies"},{level:3,title:"ctx.status =",slug:"ctx-status"},{level:3,title:"ctx.body =",slug:"ctx-body"},{level:3,title:"ctx.set(name, value)",slug:"ctx-set-name-value"},{level:3,title:"ctx.type =",slug:"ctx-type"},{level:3,title:"ctx.render()",slug:"ctx-render"},{level:3,title:"ctx.redirect()",slug:"ctx-redirect"},{level:3,title:"ctx.request",slug:"ctx-request"},{level:3,title:"ctx.response",slug:"ctx-response"},{level:3,title:"更多",slug:"更多"},{level:2,title:"如何扩展",slug:"如何扩展"},{level:3,title:"属性扩展",slug:"属性扩展"},{level:3,title:"编写测试",slug:"编写测试"}]},{title:"异常处理",frontmatter:{title:"异常处理"},regularPath:"/zh/guide/error_handler.html",relativePath:"zh/guide/error_handler.md",key:"v-2ca41536",path:"/zh/guide/error_handler.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"Node.js 异常处理",slug:"node-js-异常处理"},{level:2,title:"框架内置支持",slug:"框架内置支持"},{level:2,title:"业务错误处理",slug:"业务错误处理"},{level:2,title:"框架兜底处理",slug:"框架兜底处理"},{level:3,title:"errorPageUrl",slug:"errorpageurl"},{level:3,title:"自定义统一异常处理",slug:"自定义统一异常处理"},{level:2,title:"404",slug:"_404"},{level:3,title:"默认返回值",slug:"默认返回值"},{level:3,title:"重定向",slug:"重定向"},{level:3,title:"自定义 404 响应",slug:"自定义-404-响应"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"该不该 Catch",slug:"该不该-catch"},{level:3,title:"回调错误无法捕获",slug:"回调错误无法捕获"}]},{title:"日志",frontmatter:{title:"日志"},regularPath:"/zh/guide/logger.html",relativePath:"zh/guide/logger.md",key:"v-26f90708",path:"/zh/guide/logger.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"打印日志",slug:"打印日志"},{level:3,title:"app.logger",slug:"app-logger"},{level:3,title:"ctx.logger",slug:"ctx-logger"},{level:3,title:"this.logger",slug:"this-logger"},{level:2,title:"日志级别",slug:"日志级别"},{level:2,title:"错误日志",slug:"错误日志"},{level:2,title:"输出方式",slug:"输出方式"},{level:3,title:"文件日志",slug:"文件日志"},{level:3,title:"终端日志",slug:"终端日志"},{level:2,title:"正式环境",slug:"正式环境"},{level:3,title:"落盘方式",slug:"落盘方式"},{level:3,title:"日志文件输出位置",slug:"日志文件输出位置"},{level:3,title:"禁止输出 DEBUG 日志",slug:"禁止输出-debug-日志"},{level:3,title:"禁止输出终端日志",slug:"禁止输出终端日志"},{level:2,title:"自定义日志",slug:"自定义日志"},{level:3,title:"框架内置日志",slug:"框架内置日志"},{level:3,title:"增加自定义日志",slug:"增加自定义日志"},{level:3,title:"日志输出格式",slug:"日志输出格式"},{level:3,title:"高级自定义日志",slug:"高级自定义日志"},{level:2,title:"日志切割",slug:"日志切割"},{level:3,title:"按天切割",slug:"按天切割"},{level:3,title:"按照文件大小切割",slug:"按照文件大小切割"},{level:3,title:"按照小时切割",slug:"按照小时切割"},{level:2,title:"编写测试",slug:"编写测试"}]},{title:"Router",frontmatter:{title:"Router"},regularPath:"/zh/guide/router.html",relativePath:"zh/guide/router.md",key:"v-1f1ad7a8",path:"/zh/guide/router.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写路由",slug:"编写路由"},{level:2,title:"路由定义",slug:"路由定义"},{level:3,title:"路由方法",slug:"路由方法"},{level:3,title:"路由路径",slug:"路由路径"},{level:3,title:"路由中间件",slug:"路由中间件"},{level:3,title:"路由别名",slug:"路由别名"},{level:2,title:"RESTful 风格的 URL 定义",slug:"restful-风格的-url-定义"},{level:2,title:"Router 实战",slug:"router-实战"},{level:3,title:"获取查询参数",slug:"获取查询参数"},{level:3,title:"获取命名参数",slug:"获取命名参数"},{level:3,title:"重定向",slug:"重定向"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"路由映射太多?",slug:"路由映射太多?"},{level:3,title:"自动映射路由?",slug:"自动映射路由?"},{level:3,title:"通过装饰器映射?",slug:"通过装饰器映射?"}]},{title:"文件上传",frontmatter:{title:"文件上传"},regularPath:"/zh/guide/upload.html",relativePath:"zh/guide/upload.md",key:"v-07b4bab0",path:"/zh/guide/upload.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"File 模式",slug:"file-模式"},{level:3,title:"配置",slug:"配置"},{level:3,title:"前端代码",slug:"前端代码"},{level:3,title:"获取上传的文件",slug:"获取上传的文件"},{level:2,title:"Stream 模式",slug:"stream-模式"},{level:3,title:"上传单个文件",slug:"上传单个文件"},{level:3,title:"上传多个文件",slug:"上传多个文件"},{level:3,title:"错误处理",slug:"错误处理"},{level:2,title:"安全限制",slug:"安全限制"},{level:3,title:"文件大小",slug:"文件大小"},{level:3,title:"文件类型",slug:"文件类型"},{level:2,title:"云存储",slug:"云存储"},{level:3,title:"OSS",slug:"oss"},{level:3,title:"前端直接上传 OSS",slug:"前端直接上传-oss"}]},{title:"Controller",frontmatter:{title:"Controller"},regularPath:"/zh/guide/controller.html",relativePath:"zh/guide/controller.md",key:"v-471e5888",path:"/zh/guide/controller.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写 Controller",slug:"编写-controller"},{level:2,title:"生命周期",slug:"生命周期"},{level:2,title:"挂载规则",slug:"挂载规则"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:2,title:"Controller 实战",slug:"controller-实战"},{level:3,title:"HTTP 基础",slug:"http-基础"},{level:3,title:"获取请求参数",slug:"获取请求参数"},{level:3,title:"获取请求 body",slug:"获取请求-body"},{level:3,title:"解析 JSON / Form 请求",slug:"解析-json-form-请求"},{level:3,title:"解析 XML 请求",slug:"解析-xml-请求"},{level:3,title:"解析自定义类型",slug:"解析自定义类型"},{level:3,title:"文件上传",slug:"文件上传"},{level:3,title:"获取 Header",slug:"获取-header"},{level:3,title:"代理服务器",slug:"代理服务器"},{level:3,title:"读写 Cookie",slug:"读写-cookie"},{level:3,title:"参数校验",slug:"参数校验"},{level:3,title:"调用 Service",slug:"调用-service"},{level:3,title:"发送 HTTP 响应",slug:"发送-http-响应"},{level:3,title:"模板渲染",slug:"模板渲染"},{level:3,title:"JSONP",slug:"jsonp"},{level:3,title:"重定向",slug:"重定向"},{level:2,title:"编写测试",slug:"编写测试"},{level:3,title:"测试 GET 请求",slug:"测试-get-请求"},{level:3,title:"测试 POST 请求",slug:"测试-post-请求"},{level:3,title:"测试文件上传",slug:"测试文件上传"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"missing csrf token",slug:"missing-csrf-token"},{level:3,title:"redirection is prohibited",slug:"redirection-is-prohibited"}]},{title:"HttpClient",frontmatter:{title:"HttpClient"},regularPath:"/zh/guide/httpclient.html",relativePath:"zh/guide/httpclient.md",key:"v-25adca68",path:"/zh/guide/httpclient.html",headers:[{level:2,title:"使用背景",slug:"使用背景"},{level:2,title:"获取方式",slug:"获取方式"},{level:3,title:"app.httpclient",slug:"app-httpclient"},{level:3,title:"app.curl(http://github.com/url,%20options)",slug:"app-curl-url-options"},{level:3,title:"ctx.curl(http://github.com/url,%20options)",slug:"ctx-curl-url-options"},{level:2,title:"常用参数及响应",slug:"常用参数及响应"},{level:3,title:"请求参数",slug:"请求参数"},{level:3,title:"响应数据",slug:"响应数据"},{level:2,title:"HttpClient 实战",slug:"httpclient-实战"},{level:3,title:"发起 GET 请求",slug:"发起-get-请求"},{level:3,title:"通过 POST 发送 JSON",slug:"通过-post-发送-json"},{level:3,title:"提交 Form 表单",slug:"提交-form-表单"},{level:3,title:"文件上传(Multipart)",slug:"文件上传-multipart"},{level:3,title:"文件上传(Stream)",slug:"文件上传-stream"},{level:3,title:"发送 XML",slug:"发送-xml"},{level:3,title:"超时时间",slug:"超时时间"},{level:3,title:"处理重定向",slug:"处理重定向"},{level:3,title:"抓包调试",slug:"抓包调试"},{level:3,title:"事件监听",slug:"事件监听"},{level:2,title:"如何扩展",slug:"如何扩展"},{level:2,title:"编写测试",slug:"编写测试"},{level:2,title:"常见错误码",slug:"常见错误码"},{level:3,title:"ConnectionTimeoutError",slug:"connectiontimeouterror"},{level:3,title:"ResponseTimeoutError",slug:"responsetimeouterror"},{level:3,title:"ECONNRESET",slug:"econnreset"},{level:3,title:"ECONNREFUSED",slug:"econnrefused"},{level:3,title:"ENOTFOUND",slug:"enotfound"},{level:3,title:"JSONResponseFormatError",slug:"jsonresponseformaterror"},{level:2,title:"Options 参数详解",slug:"options-参数详解"},{level:3,title:"默认全局配置",slug:"默认全局配置"},{level:3,title:"method: String",slug:"method-string"},{level:3,title:"data: Object",slug:"data-object"},{level:3,title:"contentType: String",slug:"contenttype-string"},{level:3,title:"dataType: String",slug:"datatype-string"},{level:3,title:"dataAsQueryString: Boolean",slug:"dataasquerystring-boolean"},{level:3,title:"content: String|Buffer",slug:"content-string-buffer"},{level:3,title:"headers: Object",slug:"headers-object"},{level:3,title:"timeout: Number|Array",slug:"timeout-number-array"},{level:3,title:"files: Mixed",slug:"files-mixed"},{level:3,title:"stream: ReadStream",slug:"stream-readstream"},{level:3,title:"writeStream: WriteStream",slug:"writestream-writestream"},{level:3,title:"streaming: Boolean",slug:"streaming-boolean"},{level:3,title:"beforeRequest: Function(options)",slug:"beforerequest-function-options"},{level:3,title:"gzip: Boolean",slug:"gzip-boolean"},{level:3,title:"timing: Boolean",slug:"timing-boolean"},{level:3,title:"HTTPS 相关参数",slug:"https-相关参数"},{level:2,title:"示例代码",slug:"示例代码"}]}],themeConfig:{logo:"/logo.svg",repo:"https://github.com/eggjs/eggjs.github.io",docsBranch:"docs",docsDir:"docs",editLinks:!0,locales:{"/":{label:"English",selectText:"Languages",editLinks:!0,editLinkText:"Edit this page on GitHub",serviceWorker:{updatePopup:{message:"New content is available.",buttonText:"Refresh"}},nav:[{text:"Home",link:"/"},{text:"QuickStart",link:"/quickstart/"},{text:"Guide",link:"/guide/"}],sidebar:{"/quickstart/":[["./","QuickStart"],"egg"],"/guide/":[["./","Description"]]},foot:{friendList:[{title:"Github",list:[{name:"Organization",url:"https://github.com/eggjs"},{name:"Example",url:"https://github.com/eggjs/examples"}]},{title:"Links",list:[{name:"Ant Design",url:"https://ant.design/"},{name:"Ant Motion",url:"http://motion.ant.design/"},{name:"Antv",url:"https://antv.alipay.com/"},{name:"Umi.js",url:"https://umijs.org/"}]},{title:"Community",list:[{name:"FAQ",url:"https://eggjs.org/zh-cn/faq.html"},{name:"Node.js Column",url:"https://www.yuque.com/egg/nodejs"}]},{title:"Egg.js Dingtalk",qrcode:"/egg/qrcode_dingtalk.png"}],copyright:[{text:"Copyright © 2019 Egg.js"}]}},"/zh/":{label:"简体中文",selectText:"选择语言",editLinks:!0,editLinkText:"在 GitHub 上编辑此页",serviceWorker:{updatePopup:{message:"发现新内容可用.",buttonText:"刷新"}},nav:[{text:"首页",link:"/zh/"},{text:"快速开始",link:"/zh/quickstart/"},{text:"教程",link:"/zh/guide/"}],sidebar:{"/zh/quickstart/":[["./","快速开始"],"egg"],"/zh/guide/":[["./","概述"],"directory","middleware","controller","router","service","application","context","config","logger","cookie","session","helper","httpclient","error_handler","lifecycle","plugin","upload","i18n"]},foot:{friendList:[{title:"Github",list:[{name:"Organization",url:"https://github.com/eggjs"},{name:"Example",url:"https://github.com/eggjs/examples"}]},{title:"Links",list:[{name:"Ant Design",url:"https://ant.design/"},{name:"Ant Motion",url:"http://motion.ant.design/"},{name:"Antv",url:"https://antv.alipay.com/"},{name:"Umi.js",url:"https://umijs.org/"}]},{title:"Community",list:[{name:"FAQ",url:"https://eggjs.org/zh-cn/faq.html"},{name:"Node.js Column",url:"https://www.yuque.com/egg/nodejs"}]},{title:"Egg.js Dingtalk",qrcode:"/egg/qrcode_dingtalk.png"}],copyright:[{text:"Copyright © 2019 Egg.js"}]}}}},locales:{"/":{lang:"en-US",title:"Egg",description:"Born to build better enterprise frameworks and apps with Node.js & Koa",path:"/"},"/zh/":{lang:"zh-CN",title:"Egg",description:"为企业级框架和应用而生",path:"/zh/"}}};n(5);Di.component("Badge",()=>Promise.all([n.e(0),n.e(4)]).then(n.bind(null,222)));n(6);var Ja=[{},({Vue:e})=>{e.mixin({computed:{$dataBlock(){return this.$options.__data__block__}}})},{},{}],Ya=[];class Za{constructor(){this.store=new Di({data:{state:{}}})}$get(e){return this.store.state[e]}$set(e,t){Di.set(this.store.state,e,t)}$emit(...e){this.store.$emit(...e)}$on(...e){this.store.$on(...e)}}class el extends Za{}Object.assign(el.prototype,{getPageAsyncComponent:za,getLayoutAsyncComponent:La,getAsyncComponent:Ra,getVueComponent:Ma});var tl={install(e){const t=new el;e.$vuepress=t,e.prototype.$vuepress=t}};function nl(e,t){return e.options.routes.filter(e=>e.path.toLowerCase()===t.toLowerCase()).length>0}var rl={props:{pageKey:String,slotKey:{type:String,default:"default"}},render(e){const t=this.pageKey||this.$parent.$page.key;return Da("pageKey",t),Di.component(t)||Di.component(t,za(t)),e(t||"")}},il={functional:!0,props:{slotKey:String,required:!0},render:(e,{props:t,slots:n})=>e("div",{class:[`content__${t.slotKey}`]},n()[t.slotKey])},ol=(n(7),Object(Ka.a)({},function(e,t){var n=t._c;return n("svg",{staticClass:"icon outbound",attrs:{xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"}},[n("path",{attrs:{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}}),t._v(" "),n("polygon",{attrs:{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"}})])},[],!0,null,null,null).exports),al={functional:!0,render(e,{parent:t,children:n}){if(t._isMounted)return n;t.$once("hook:mounted",()=>{t.$forceUpdate()})}};Di.config.productionTip=!1,Di.use(ya),Di.use(tl),Di.mixin(function(e,t,n=Di){!function(e){e.locales&&Object.keys(e.locales).forEach(t=>{e.locales[t].path=t}),Object.freeze(e)}(t),n.$vuepress.$set("siteData",t);const r=new(e(n.$vuepress.$get("siteData"))),i=Object.getOwnPropertyDescriptors(Object.getPrototypeOf(r)),o={};return Object.keys(i).reduce((e,t)=>(t.startsWith("$")&&(e[t]=i[t].get),e),o),{computed:o}}(e=>(class{setPage(e){this.__page=e}get $site(){return e}get $themeConfig(){return this.$site.themeConfig}get $frontmatter(){return this.$page.frontmatter}get $localeConfig(){const{locales:e={}}=this.$site;let t,n;for(const r in e)"/"===r?n=e[r]:0===this.$page.path.indexOf(r)&&(t=e[r]);return t||n||{}}get $siteTitle(){return this.$localeConfig.title||this.$site.title||""}get $title(){const e=this.$page,{metaTitle:t}=this.$page.frontmatter;if("string"==typeof t)return t;const n=this.$siteTitle,r=e.frontmatter.home?null:e.frontmatter.title||e.title;return n?r?r+" | "+n:n:r||"VuePress"}get $description(){const e=function(e){if(e){const t=e.filter(e=>"description"===e.name)[0];if(t)return t.content}}(this.$page.frontmatter.meta);return e||this.$page.frontmatter.description||this.$localeConfig.description||this.$site.description||""}get $lang(){return this.$page.frontmatter.lang||this.$localeConfig.lang||"en-US"}get $localePath(){return this.$localeConfig.path||"/"}get $themeLocaleConfig(){return(this.$site.themeConfig.locales||{})[this.$localePath]||{}}get $page(){return this.__page?this.__page:function(e,t){for(let n=0;nn||(e.hash?!Di.$vuepress.$get("disableScrollBehavior")&&{selector:e.hash}:{x:0,y:0})});!function(e){e.beforeEach((t,n,r)=>{if(nl(e,t.path))r();else if(/(\/|\.html)$/.test(t.path))if(/\/$/.test(t.path)){const n=t.path.replace(/\/$/,"")+".html";nl(e,n)?r(n):r()}else r();else{const n=t.path+"/",i=t.path+".html";nl(e,i)?r(i):nl(e,n)?r(n):r()}})}(n);const r={};try{Ja.forEach(t=>{"function"==typeof t&&t({Vue:Di,options:r,router:n,siteData:Xa,isServer:e})})}catch(e){console.error(e)}return{app:new Di(Object.assign(r,{router:n,render:e=>e("div",{attrs:{id:"app"}},[e("router-view",{ref:"layout"}),e("div",{class:"global-ui"},Ya.map(t=>e(t)))])})),router:n}}(!1);window.__VUEPRESS__={version:"1.0.2",hash:"b0f4ebf"},sl.onReady(()=>{ll.$mount("#app")})}]); \ No newline at end of file + */function Ni(e){return Object.prototype.toString.call(e).indexOf("Error")>-1}function qi(e,t){for(var n in t)e[n]=t[n];return e}var Ui={name:"RouterView",functional:!0,props:{name:{type:String,default:"default"}},render:function(e,t){var n=t.props,r=t.children,i=t.parent,o=t.data;o.routerView=!0;for(var a=i.$createElement,l=n.name,s=i.$route,u=i._routerViewCache||(i._routerViewCache={}),c=0,f=!1;i&&i._routerRoot!==i;){var p=i.$vnode&&i.$vnode.data;p&&(p.routerView&&c++,p.keepAlive&&i._inactive&&(f=!0)),i=i.$parent}if(o.routerViewDepth=c,f)return a(u[l],o,r);var d=s.matched[c];if(!d)return u[l]=null,a();var h=u[l]=d.components[l];o.registerRouteInstance=function(e,t){var n=d.instances[l];(t&&n!==e||!t&&n===e)&&(d.instances[l]=t)},(o.hook||(o.hook={})).prepatch=function(e,t){d.instances[l]=t.componentInstance},o.hook.init=function(e){e.data.keepAlive&&e.componentInstance&&e.componentInstance!==d.instances[l]&&(d.instances[l]=e.componentInstance)};var v=o.props=function(e,t){switch(typeof t){case"undefined":return;case"object":return t;case"function":return t(e);case"boolean":return t?e.params:void 0;default:0}}(s,d.props&&d.props[l]);if(v){v=o.props=qi({},v);var g=o.attrs=o.attrs||{};for(var m in v)h.props&&m in h.props||(g[m]=v[m],delete v[m])}return a(h,o,r)}};var Fi=/[!'()*]/g,Bi=function(e){return"%"+e.charCodeAt(0).toString(16)},Hi=/%2C/g,Vi=function(e){return encodeURIComponent(e).replace(Fi,Bi).replace(Hi,",")},Wi=decodeURIComponent;function Ki(e){var t={};return(e=e.trim().replace(/^(\?|#|&)/,""))?(e.split("&").forEach(function(e){var n=e.replace(/\+/g," ").split("="),r=Wi(n.shift()),i=n.length>0?Wi(n.join("=")):null;void 0===t[r]?t[r]=i:Array.isArray(t[r])?t[r].push(i):t[r]=[t[r],i]}),t):t}function Gi(e){var t=e?Object.keys(e).map(function(t){var n=e[t];if(void 0===n)return"";if(null===n)return Vi(t);if(Array.isArray(n)){var r=[];return n.forEach(function(e){void 0!==e&&(null===e?r.push(Vi(t)):r.push(Vi(t)+"="+Vi(e)))}),r.join("&")}return Vi(t)+"="+Vi(n)}).filter(function(e){return e.length>0}).join("&"):null;return t?"?"+t:""}var Qi=/\/?$/;function Xi(e,t,n,r){var i=r&&r.options.stringifyQuery,o=t.query||{};try{o=Ji(o)}catch(e){}var a={name:t.name||e&&e.name,meta:e&&e.meta||{},path:t.path||"/",hash:t.hash||"",query:o,params:t.params||{},fullPath:eo(t,i),matched:e?Zi(e):[]};return n&&(a.redirectedFrom=eo(n,i)),Object.freeze(a)}function Ji(e){if(Array.isArray(e))return e.map(Ji);if(e&&"object"==typeof e){var t={};for(var n in e)t[n]=Ji(e[n]);return t}return e}var Yi=Xi(null,{path:"/"});function Zi(e){for(var t=[];e;)t.unshift(e),e=e.parent;return t}function eo(e,t){var n=e.path,r=e.query;void 0===r&&(r={});var i=e.hash;return void 0===i&&(i=""),(n||"/")+(t||Gi)(r)+i}function to(e,t){return t===Yi?e===t:!!t&&(e.path&&t.path?e.path.replace(Qi,"")===t.path.replace(Qi,"")&&e.hash===t.hash&&no(e.query,t.query):!(!e.name||!t.name)&&(e.name===t.name&&e.hash===t.hash&&no(e.query,t.query)&&no(e.params,t.params)))}function no(e,t){if(void 0===e&&(e={}),void 0===t&&(t={}),!e||!t)return e===t;var n=Object.keys(e),r=Object.keys(t);return n.length===r.length&&n.every(function(n){var r=e[n],i=t[n];return"object"==typeof r&&"object"==typeof i?no(r,i):String(r)===String(i)})}var ro,io=[String,Object],oo=[String,Array],ao={name:"RouterLink",props:{to:{type:io,required:!0},tag:{type:String,default:"a"},exact:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,event:{type:oo,default:"click"}},render:function(e){var t=this,n=this.$router,r=this.$route,i=n.resolve(this.to,r,this.append),o=i.location,a=i.route,l=i.href,s={},u=n.options.linkActiveClass,c=n.options.linkExactActiveClass,f=null==u?"router-link-active":u,p=null==c?"router-link-exact-active":c,d=null==this.activeClass?f:this.activeClass,h=null==this.exactActiveClass?p:this.exactActiveClass,v=o.path?Xi(null,o,null,n):a;s[h]=to(r,v),s[d]=this.exact?s[h]:function(e,t){return 0===e.path.replace(Qi,"/").indexOf(t.path.replace(Qi,"/"))&&(!t.hash||e.hash===t.hash)&&function(e,t){for(var n in t)if(!(n in e))return!1;return!0}(e.query,t.query)}(r,v);var g=function(e){lo(e)&&(t.replace?n.replace(o):n.push(o))},m={click:lo};Array.isArray(this.event)?this.event.forEach(function(e){m[e]=g}):m[this.event]=g;var y={class:s};if("a"===this.tag)y.on=m,y.attrs={href:l};else{var b=function e(t){if(t)for(var n,r=0;r=0&&(t=e.slice(r),e=e.slice(0,r));var i=e.indexOf("?");return i>=0&&(n=e.slice(i+1),e=e.slice(0,i)),{path:e,query:n,hash:t}}(i.path||""),s=t&&t.path||"/",u=l.path?uo(l.path,s,n||i.append):s,c=function(e,t,n){void 0===t&&(t={});var r,i=n||Ki;try{r=i(e||"")}catch(e){r={}}for(var o in t)r[o]=t[o];return r}(l.query,i.query,r&&r.options.parseQuery),f=i.hash||l.hash;return f&&"#"!==f.charAt(0)&&(f="#"+f),{_normalized:!0,path:u,query:c,hash:f}}function zo(e,t){var n=jo(e),r=n.pathList,i=n.pathMap,o=n.nameMap;function a(e,n,a){var l=Po(e,n,!1,t),u=l.name;if(u){var c=o[u];if(!c)return s(null,l);var f=c.regex.keys.filter(function(e){return!e.optional}).map(function(e){return e.name});if("object"!=typeof l.params&&(l.params={}),n&&"object"==typeof n.params)for(var p in n.params)!(p in l.params)&&f.indexOf(p)>-1&&(l.params[p]=n.params[p]);return l.path=Ao(c.path,l.params),s(c,l,a)}if(l.path){l.params={};for(var d=0;d=e.length?n():e[i]?t(e[i],function(){r(i+1)}):r(i+1)};r(0)}function Yo(e){return function(t,n,r){var i=!1,o=0,a=null;Zo(e,function(e,t,n,l){if("function"==typeof e&&void 0===e.cid){i=!0,o++;var s,u=na(function(t){var i;((i=t).__esModule||ta&&"Module"===i[Symbol.toStringTag])&&(t=t.default),e.resolved="function"==typeof t?t:ro.extend(t),n.components[l]=t,--o<=0&&r()}),c=na(function(e){var t="Failed to resolve async component "+l+": "+e;a||(a=Ni(e)?e:new Error(t),r(a))});try{s=e(u,c)}catch(e){c(e)}if(s)if("function"==typeof s.then)s.then(u,c);else{var f=s.component;f&&"function"==typeof f.then&&f.then(u,c)}}}),i||r()}}function Zo(e,t){return ea(e.map(function(e){return Object.keys(e.components).map(function(n){return t(e.components[n],e.instances[n],e,n)})}))}function ea(e){return Array.prototype.concat.apply([],e)}var ta="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function na(e){var t=!1;return function(){for(var n=[],r=arguments.length;r--;)n[r]=arguments[r];if(!t)return t=!0,e.apply(this,n)}}var ra=function(e,t){this.router=e,this.base=function(e){if(!e)if(so){var t=document.querySelector("base");e=(e=t&&t.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else e="/";"/"!==e.charAt(0)&&(e="/"+e);return e.replace(/\/$/,"")}(t),this.current=Yi,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[]};function ia(e,t,n,r){var i=Zo(e,function(e,r,i,o){var a=function(e,t){"function"!=typeof e&&(e=ro.extend(e));return e.options[t]}(e,t);if(a)return Array.isArray(a)?a.map(function(e){return n(e,r,i,o)}):n(a,r,i,o)});return ea(r?i.reverse():i)}function oa(e,t){if(t)return function(){return e.apply(t,arguments)}}ra.prototype.listen=function(e){this.cb=e},ra.prototype.onReady=function(e,t){this.ready?e():(this.readyCbs.push(e),t&&this.readyErrorCbs.push(t))},ra.prototype.onError=function(e){this.errorCbs.push(e)},ra.prototype.transitionTo=function(e,t,n){var r=this,i=this.router.match(e,this.current);this.confirmTransition(i,function(){r.updateRoute(i),t&&t(i),r.ensureURL(),r.ready||(r.ready=!0,r.readyCbs.forEach(function(e){e(i)}))},function(e){n&&n(e),e&&!r.ready&&(r.ready=!0,r.readyErrorCbs.forEach(function(t){t(e)}))})},ra.prototype.confirmTransition=function(e,t,n){var r=this,i=this.current,o=function(e){Ni(e)&&(r.errorCbs.length?r.errorCbs.forEach(function(t){t(e)}):console.error(e)),n&&n(e)};if(to(e,i)&&e.matched.length===i.matched.length)return this.ensureURL(),o();var a=function(e,t){var n,r=Math.max(e.length,t.length);for(n=0;n-1?decodeURI(e.slice(0,r))+e.slice(r):decodeURI(e)}else n>-1&&(e=decodeURI(e.slice(0,n))+e.slice(n));return e}function fa(e){var t=window.location.href,n=t.indexOf("#");return(n>=0?t.slice(0,n):t)+"#"+e}function pa(e){Ho?Qo(fa(e)):window.location.hash=e}function da(e){Ho?Xo(fa(e)):window.location.replace(fa(e))}var ha=function(e){function t(t,n){e.call(this,t,n),this.stack=[],this.index=-1}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.push=function(e,t,n){var r=this;this.transitionTo(e,function(e){r.stack=r.stack.slice(0,r.index+1).concat(e),r.index++,t&&t(e)},n)},t.prototype.replace=function(e,t,n){var r=this;this.transitionTo(e,function(e){r.stack=r.stack.slice(0,r.index).concat(e),t&&t(e)},n)},t.prototype.go=function(e){var t=this,n=this.index+e;if(!(n<0||n>=this.stack.length)){var r=this.stack[n];this.confirmTransition(r,function(){t.index=n,t.updateRoute(r)})}},t.prototype.getCurrentLocation=function(){var e=this.stack[this.stack.length-1];return e?e.fullPath:"/"},t.prototype.ensureURL=function(){},t}(ra),va=function(e){void 0===e&&(e={}),this.app=null,this.apps=[],this.options=e,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=zo(e.routes||[],this);var t=e.mode||"hash";switch(this.fallback="history"===t&&!Ho&&!1!==e.fallback,this.fallback&&(t="hash"),so||(t="abstract"),this.mode=t,t){case"history":this.history=new aa(this,e.base);break;case"hash":this.history=new sa(this,e.base,this.fallback);break;case"abstract":this.history=new ha(this,e.base);break;default:0}},ga={currentRoute:{configurable:!0}};function ma(e,t){return e.push(t),function(){var n=e.indexOf(t);n>-1&&e.splice(n,1)}}va.prototype.match=function(e,t,n){return this.matcher.match(e,t,n)},ga.currentRoute.get=function(){return this.history&&this.history.current},va.prototype.init=function(e){var t=this;if(this.apps.push(e),e.$once("hook:destroyed",function(){var n=t.apps.indexOf(e);n>-1&&t.apps.splice(n,1),t.app===e&&(t.app=t.apps[0]||null)}),!this.app){this.app=e;var n=this.history;if(n instanceof aa)n.transitionTo(n.getCurrentLocation());else if(n instanceof sa){var r=function(){n.setupListeners()};n.transitionTo(n.getCurrentLocation(),r,r)}n.listen(function(e){t.apps.forEach(function(t){t._route=e})})}},va.prototype.beforeEach=function(e){return ma(this.beforeHooks,e)},va.prototype.beforeResolve=function(e){return ma(this.resolveHooks,e)},va.prototype.afterEach=function(e){return ma(this.afterHooks,e)},va.prototype.onReady=function(e,t){this.history.onReady(e,t)},va.prototype.onError=function(e){this.history.onError(e)},va.prototype.push=function(e,t,n){this.history.push(e,t,n)},va.prototype.replace=function(e,t,n){this.history.replace(e,t,n)},va.prototype.go=function(e){this.history.go(e)},va.prototype.back=function(){this.go(-1)},va.prototype.forward=function(){this.go(1)},va.prototype.getMatchedComponents=function(e){var t=e?e.matched?e:this.resolve(e).route:this.currentRoute;return t?[].concat.apply([],t.matched.map(function(e){return Object.keys(e.components).map(function(t){return e.components[t]})})):[]},va.prototype.resolve=function(e,t,n){var r=Po(e,t=t||this.history.current,n,this),i=this.match(r,t),o=i.redirectedFrom||i.fullPath;return{location:r,route:i,href:function(e,t,n){var r="hash"===n?"#"+t:t;return e?co(e+"/"+r):r}(this.history.base,o,this.mode),normalizedTo:r,resolved:i}},va.prototype.addRoutes=function(e){this.matcher.addRoutes(e),this.history.current!==Yi&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(va.prototype,ga),va.install=function e(t){if(!e.installed||ro!==t){e.installed=!0,ro=t;var n=function(e){return void 0!==e},r=function(e,t){var r=e.$options._parentVnode;n(r)&&n(r=r.data)&&n(r=r.registerRouteInstance)&&r(e,t)};t.mixin({beforeCreate:function(){n(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),t.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,r(this,this)},destroyed:function(){r(this)}}),Object.defineProperty(t.prototype,"$router",{get:function(){return this._routerRoot._router}}),Object.defineProperty(t.prototype,"$route",{get:function(){return this._routerRoot._route}}),t.component("RouterView",Ui),t.component("RouterLink",ao);var i=t.config.optionMergeStrategies;i.beforeRouteEnter=i.beforeRouteLeave=i.beforeRouteUpdate=i.created}},va.version="3.0.7",so&&window.Vue&&window.Vue.use(va);var ya=va;var ba={NotFound:()=>Promise.all([n.e(0),n.e(5)]).then(n.bind(null,215)),Detector:()=>n.e(9).then(n.bind(null,216)),Ecosystem:()=>Promise.all([n.e(0),n.e(1),n.e(6)]).then(n.bind(null,217)),Layout:()=>Promise.all([n.e(0),n.e(1),n.e(3)]).then(n.bind(null,214))},_a={"v-1605d0f3":()=>n.e(10).then(n.bind(null,241)),"v-97a66c4e":()=>n.e(11).then(n.bind(null,242)),"v-3ea02d83":()=>n.e(12).then(n.bind(null,243)),"v-77fd80de":()=>n.e(13).then(n.bind(null,227)),"v-dd3f763a":()=>n.e(14).then(n.bind(null,218)),"v-3a272843":()=>n.e(15).then(n.bind(null,231)),"v-555b843c":()=>n.e(16).then(n.bind(null,244)),"v-01665048":()=>n.e(17).then(n.bind(null,219)),"v-bea6a0f0":()=>n.e(20).then(n.bind(null,220)),"v-61e12502":()=>n.e(21).then(n.bind(null,221)),"v-5138afa0":()=>n.e(23).then(n.bind(null,223)),"v-2634a070":()=>n.e(24).then(n.bind(null,226)),"v-a25f7170":()=>n.e(26).then(n.bind(null,245)),"v-1879f308":()=>n.e(27).then(n.bind(null,229)),"v-15b3b548":()=>n.e(7).then(n.bind(null,233)),"v-2f089be8":()=>n.e(29).then(n.bind(null,235)),"v-a9bc6b9c":()=>n.e(31).then(n.bind(null,237)),"v-9c1bf3a0":()=>n.e(32).then(n.bind(null,239)),"v-c73d89b6":()=>n.e(34).then(n.bind(null,238)),"v-13a2f408":()=>n.e(8).then(n.bind(null,236)),"v-45c3b484":()=>n.e(18).then(n.bind(null,234)),"v-2ca41536":()=>n.e(22).then(n.bind(null,232)),"v-26f90708":()=>n.e(28).then(n.bind(null,230)),"v-1f1ad7a8":()=>n.e(30).then(n.bind(null,228)),"v-07b4bab0":()=>n.e(33).then(n.bind(null,240)),"v-471e5888":()=>n.e(19).then(n.bind(null,225)),"v-25adca68":()=>n.e(25).then(n.bind(null,224))};function wa(e){const t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}const xa=/-(\w)/g,ka=wa(e=>e.replace(xa,(e,t)=>t?t.toUpperCase():"")),Ca=/\B([A-Z])/g,$a=wa(e=>e.replace(Ca,"-$1").toLowerCase()),Ea=wa(e=>e.charAt(0).toUpperCase()+e.slice(1));function Sa(e,t){if(!t)return;if(e(t))return e(t);return t.includes("-")?e(Ea(ka(t))):e(Ea(t))||e($a(t))}const Oa=Object.assign({},ba,_a),Aa=e=>Oa[e],ja=e=>_a[e],Ta=e=>ba[e],Pa=e=>Di.component(e);function za(e){return Sa(ja,e)}function La(e){return Sa(Ta,e)}function Ra(e){return Sa(Aa,e)}function Ma(e){return Sa(Pa,e)}function Ia(...e){return Promise.all(e.filter(e=>e).map(async e=>{if(!Ma(e)&&Ra(e)){const t=await Ra(e)();Di.component(e,t.default)}}))}function Da(e,t){"undefined"!=typeof window&&window.__VUEPRESS__&&(window.__VUEPRESS__[e]=t)}var Na={created(){this.$ssrContext&&(this.$ssrContext.title=this.$title,this.$ssrContext.lang=this.$lang,this.$ssrContext.description=this.$page.description||this.$description)},mounted(){this.currentMetaTags=new Set,this.updateMeta()},methods:{updateMeta(){document.title=this.$title,document.documentElement.lang=this.$lang;const e=this.$page.frontmatter.meta||[],t=e.slice(0);0===e.filter(e=>"description"===e.name).length&&t.push({name:"description",content:this.$description});const n=document.querySelectorAll('meta[name="description"]');n.length&&n.forEach(e=>this.currentMetaTags.add(e)),this.currentMetaTags=new Set(qa(t,this.currentMetaTags))}},watch:{$page(){this.updateMeta()}},beforeDestroy(){qa(null,this.currentMetaTags)}};function qa(e,t){if(t&&[...t].forEach(e=>{document.head.removeChild(e)}),e)return e.map(e=>{const t=document.createElement("meta");return Object.keys(e).forEach(n=>{t.setAttribute(n,e[n])}),document.head.appendChild(t),t})}var Ua=n(3),Fa={mounted(){window.addEventListener("scroll",this.onScroll)},methods:{onScroll:n.n(Ua)()(function(){this.setActiveHash()},300),setActiveHash(){const e=[].slice.call(document.querySelectorAll(".sidebar-link")),t=[].slice.call(document.querySelectorAll(".header-anchor")).filter(t=>e.some(e=>e.hash===t.hash)),n=Math.max(window.pageYOffset,document.documentElement.scrollTop,document.body.scrollTop),r=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),i=window.innerHeight+n;for(let e=0;e=o.parentElement.offsetTop+10&&(!a||n{this.$nextTick(()=>{this.$vuepress.$set("disableScrollBehavior",!1)})})}}}},beforeDestroy(){window.removeEventListener("scroll",this.onScroll)}},Ba=n(2),Ha=n.n(Ba),Va=[Na,Fa,{mounted(){Ha.a.configure({showSpinner:!1}),this.$router.beforeEach((e,t,n)=>{e.path===t.path||Di.component(e.name)||Ha.a.start(),n()}),this.$router.afterEach(()=>{Ha.a.done(),this.isSidebarOpen=!1})}}],Wa={methods:{getLayout:function(){if(this.$page.path){var e=this.$page.frontmatter.layout;return e&&(this.$vuepress.getLayoutAsyncComponent(e)||this.$vuepress.getVueComponent(e))?e:"Layout"}return"NotFound"}},computed:{layout:function(){var e=this.getLayout();return Da("layout",e),Di.component(e)}}},Ka=n(0),Ga=Object(Ka.a)(Wa,function(){var e=this.$createElement;return(this._self._c||e)(this.layout,{tag:"component"})},[],!1,null,null,null).exports;!function(e,t,n){switch(t){case"components":e[t]||(e[t]={}),Object.assign(e[t],n);break;case"mixins":e[t]||(e[t]=[]),e[t].push(...n);break;default:throw new Error("Unknown option name.")}}(Ga,"mixins",Va);const Qa=[{name:"v-1605d0f3",path:"/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-1605d0f3").then(n)}},{path:"/index.html",redirect:"/"},{name:"v-97a66c4e",path:"/guide/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-97a66c4e").then(n)}},{path:"/guide/index.html",redirect:"/guide/"},{name:"v-3ea02d83",path:"/quickstart/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-3ea02d83").then(n)}},{path:"/quickstart/index.html",redirect:"/quickstart/"},{name:"v-77fd80de",path:"/quickstart/egg.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-77fd80de").then(n)}},{name:"v-dd3f763a",path:"/zh/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-dd3f763a").then(n)}},{path:"/zh/index.html",redirect:"/zh/"},{name:"v-3a272843",path:"/zh/guide/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-3a272843").then(n)}},{path:"/zh/guide/index.html",redirect:"/zh/guide/"},{name:"v-555b843c",path:"/zh/guide/application.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-555b843c").then(n)}},{name:"v-01665048",path:"/zh/guide/config.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-01665048").then(n)}},{name:"v-bea6a0f0",path:"/zh/guide/cookie.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-bea6a0f0").then(n)}},{name:"v-61e12502",path:"/zh/guide/directory.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-61e12502").then(n)}},{name:"v-5138afa0",path:"/zh/guide/faq.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-5138afa0").then(n)}},{name:"v-2634a070",path:"/zh/guide/helper.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-2634a070").then(n)}},{name:"v-a25f7170",path:"/zh/guide/i18n.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-a25f7170").then(n)}},{name:"v-1879f308",path:"/zh/guide/lifecycle.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-1879f308").then(n)}},{name:"v-15b3b548",path:"/zh/guide/middleware.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-15b3b548").then(n)}},{name:"v-2f089be8",path:"/zh/guide/plugin.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-2f089be8").then(n)}},{name:"v-a9bc6b9c",path:"/zh/guide/service.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-a9bc6b9c").then(n)}},{name:"v-9c1bf3a0",path:"/zh/guide/session.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-9c1bf3a0").then(n)}},{name:"v-c73d89b6",path:"/zh/quickstart/",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-c73d89b6").then(n)}},{path:"/zh/quickstart/index.html",redirect:"/zh/quickstart/"},{name:"v-13a2f408",path:"/zh/quickstart/egg.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-13a2f408").then(n)}},{name:"v-45c3b484",path:"/zh/guide/context.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-45c3b484").then(n)}},{name:"v-2ca41536",path:"/zh/guide/error_handler.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-2ca41536").then(n)}},{name:"v-26f90708",path:"/zh/guide/logger.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-26f90708").then(n)}},{name:"v-1f1ad7a8",path:"/zh/guide/router.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-1f1ad7a8").then(n)}},{name:"v-07b4bab0",path:"/zh/guide/upload.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-07b4bab0").then(n)}},{name:"v-471e5888",path:"/zh/guide/controller.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-471e5888").then(n)}},{name:"v-25adca68",path:"/zh/guide/httpclient.html",component:Ga,beforeEnter:(e,t,n)=>{Ia("Layout","v-25adca68").then(n)}},{path:"*",component:Ga}],Xa={title:"",description:"",base:"/",pages:[{title:"Home",frontmatter:{toc:!1,home:!0,heroText:"Egg.js",banner:"/img_egg/banner.png",tagline:"Born to build better enterprise frameworks and apps with Node.js & Koa",actionText:"QuickStart →",actionLink:"/guide/",features:[{title:"完善的生态",details:"基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。",icon:"/img_egg/icon-3.png"},{title:"高效自然的研发体验",details:"渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。",icon:"/img_egg/icon-4.png"},{title:"高质量、可信赖",details:"高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。",icon:"/img_egg/icon-2.png"},{title:"灵活、高扩展性",details:"约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。",icon:"/img_egg/icon-1.png"}],whosusing:{icon:"/img_egg/whosusing.png"}},regularPath:"/",relativePath:"README.md",key:"v-1605d0f3",path:"/"},{title:"Guide",frontmatter:{title:"Guide",navTitle:"Egg Guide",toc:!1},regularPath:"/guide/",relativePath:"guide/README.md",key:"v-97a66c4e",path:"/guide/"},{title:"QuickStart",frontmatter:{title:"QuickStart"},regularPath:"/quickstart/",relativePath:"quickstart/README.md",key:"v-3ea02d83",path:"/quickstart/"},{title:"Simple Egg Application",frontmatter:{title:"Simple Egg Application"},regularPath:"/quickstart/egg.html",relativePath:"quickstart/egg.md",key:"v-77fd80de",path:"/quickstart/egg.html"},{title:"Home",frontmatter:{toc:!1,home:!0,heroText:"Egg.js",banner:"/img_egg/banner.png",tagline:"为企业级框架和应用而生",actionText:"快速开始 →",actionLink:"/zh/guide/",features:[{title:"完善的生态",details:"基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。",icon:"/img_egg/icon-3.png"},{title:"高效自然的研发体验",details:"渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。",icon:"/img_egg/icon-4.png"},{title:"高质量、可信赖",details:"高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。",icon:"/img_egg/icon-2.png"},{title:"灵活、高扩展性",details:"约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。",icon:"/img_egg/icon-1.png"}],whosusing:{icon:"/img_egg/whosusing.png"}},regularPath:"/zh/",relativePath:"zh/README.md",key:"v-dd3f763a",path:"/zh/"},{title:"概述",frontmatter:{title:"概述",navTitle:"Egg 指南",toc:!1},regularPath:"/zh/guide/",relativePath:"zh/guide/README.md",key:"v-3a272843",path:"/zh/guide/",headers:[{level:2,title:"Web 模型",slug:"web-模型"},{level:2,title:"功能模块",slug:"功能模块"}]},{title:"Application",frontmatter:{title:"Application"},regularPath:"/zh/guide/application.html",relativePath:"zh/guide/application.md",key:"v-555b843c",path:"/zh/guide/application.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"获取方式",slug:"获取方式"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:3,title:"app.config",slug:"app-config"},{level:3,title:"app.router",slug:"app-router"},{level:3,title:"app.controller",slug:"app-controller"},{level:3,title:"app.logger",slug:"app-logger"},{level:3,title:"app.middleware",slug:"app-middleware"},{level:3,title:"app.server",slug:"app-server"},{level:3,title:"app.curl()",slug:"app-curl"},{level:3,title:"app.createAnonymousContext()",slug:"app-createanonymouscontext"},{level:2,title:"如何扩展",slug:"如何扩展"},{level:3,title:"方法扩展",slug:"方法扩展"},{level:3,title:"属性扩展",slug:"属性扩展"},{level:3,title:"编写测试",slug:"编写测试"},{level:3,title:"按照环境进行扩展",slug:"按照环境进行扩展"}]},{title:"配置",frontmatter:{title:"配置"},regularPath:"/zh/guide/config.html",relativePath:"zh/guide/config.md",key:"v-01665048",path:"/zh/guide/config.html",headers:[{level:2,title:"方案选型",slug:"方案选型"},{level:2,title:"运行环境",slug:"运行环境"},{level:3,title:"env",slug:"env"},{level:2,title:"配置文件",slug:"配置文件"},{level:2,title:"配置定义",slug:"配置定义"},{level:2,title:"AppInfo",slug:"appinfo"},{level:2,title:"加载规则",slug:"加载规则"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"为什么我的配置不生效?",slug:"为什么我的配置不生效?"},{level:3,title:"如何查看最终的配置?",slug:"如何查看最终的配置?"}]},{title:"Cookie",frontmatter:{title:"Cookie"},regularPath:"/zh/guide/cookie.html",relativePath:"zh/guide/cookie.md",key:"v-bea6a0f0",path:"/zh/guide/cookie.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"使用 Cookie",slug:"使用-cookie"},{level:2,title:"术语解释",slug:"术语解释"},{level:3,title:"过期时间",slug:"过期时间"},{level:3,title:"作用域",slug:"作用域"},{level:3,title:"安全",slug:"安全"},{level:3,title:"加签 && 加密",slug:"加签-加密"},{level:2,title:"API 说明",slug:"api-说明"},{level:3,title:"set(key, value, options)",slug:"set-key-value-options"},{level:3,title:"get(key, options)",slug:"get-key-options"},{level:3,title:"options",slug:"options"},{level:3,title:"配置秘钥",slug:"配置秘钥"},{level:2,title:"Cookie 实战",slug:"cookie-实战"},{level:3,title:"读取前端写入的 Cookie",slug:"读取前端写入的-cookie"},{level:3,title:"允许前端读取 Cookie",slug:"允许前端读取-cookie"},{level:3,title:"不允许浏览器看到明文内容",slug:"不允许浏览器看到明文内容"},{level:3,title:"删除 Cookie",slug:"删除-cookie"},{level:2,title:"编写测试",slug:"编写测试"},{level:2,title:"注意事项",slug:"注意事项"}]},{title:"目录规范",frontmatter:{title:"目录规范"},regularPath:"/zh/guide/directory.html",relativePath:"zh/guide/directory.md",key:"v-61e12502",path:"/zh/guide/directory.html"},{title:"FAQ",frontmatter:{title:"FAQ",sidebarDepth:1},regularPath:"/zh/guide/faq.html",relativePath:"zh/guide/faq.md",key:"v-5138afa0",path:"/zh/guide/faq.html",headers:[{level:2,title:"如何高效的反馈问题?",slug:"如何高效的反馈问题?"},{level:2,title:"为什么我的配置不生效?",slug:"为什么我的配置不生效?"},{level:2,title:"线上的日志打印去哪里了?",slug:"线上的日志打印去哪里了?"},{level:2,title:"进程管理为什么没有选型 PM2 ?",slug:"进程管理为什么没有选型-pm2-?"},{level:2,title:"为什么会有 csrf 报错?",slug:"为什么会有-csrf-报错?"},{level:2,title:"本地开发时,修改代码后为什么 worker 进程没有自动重启?",slug:"本地开发时,修改代码后为什么-worker-进程没有自动重启?"}]},{title:"Helper",frontmatter:{title:"Helper"},regularPath:"/zh/guide/helper.html",relativePath:"zh/guide/helper.md",key:"v-2634a070",path:"/zh/guide/helper.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"访问方式",slug:"访问方式"},{level:2,title:"常用的属性和方法",slug:"常用的属性和方法"},{level:2,title:"如何扩展",slug:"如何扩展"}]},{title:"国际化",frontmatter:{title:"国际化"},regularPath:"/zh/guide/i18n.html",relativePath:"zh/guide/i18n.md",key:"v-a25f7170",path:"/zh/guide/i18n.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"定义 locale",slug:"定义-locale"},{level:3,title:"目录规范",slug:"目录规范"},{level:3,title:"文件格式",slug:"文件格式"},{level:3,title:"占位符",slug:"占位符"},{level:2,title:"使用 i18n",slug:"使用-i18n"},{level:3,title:"ctx.__(key, ...values)",slug:"ctx-key-values"},{level:3,title:"在 View 中使用",slug:"在-view-中使用"},{level:2,title:"切换语言",slug:"切换语言"},{level:3,title:"默认语言",slug:"默认语言"},{level:3,title:"切换语言",slug:"切换语言-2"},{level:2,title:"局限性",slug:"局限性"}]},{title:"生命周期",frontmatter:{title:"生命周期"},regularPath:"/zh/guide/lifecycle.html",relativePath:"zh/guide/lifecycle.md",key:"v-1879f308",path:"/zh/guide/lifecycle.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"定义生命周期",slug:"定义生命周期"},{level:2,title:"详解生命周期",slug:"详解生命周期"},{level:3,title:"configWillLoad()",slug:"configwillload"},{level:3,title:"configDidLoad()",slug:"configdidload"},{level:3,title:"async didLoad()",slug:"async-didload"},{level:3,title:"async willReady()",slug:"async-willready"},{level:3,title:"async didReady()",slug:"async-didready"},{level:3,title:"async serverDidReady()",slug:"async-serverdidready"},{level:3,title:"async beforeClose()",slug:"async-beforeclose"}]},{title:"Middleware",frontmatter:{title:"Middleware"},regularPath:"/zh/guide/middleware.html",relativePath:"zh/guide/middleware.md",key:"v-15b3b548",path:"/zh/guide/middleware.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写中间件",slug:"编写中间件"},{level:2,title:"加载规则",slug:"加载规则"},{level:2,title:"使用中间件",slug:"使用中间件"},{level:2,title:"自定义配置",slug:"自定义配置"},{level:2,title:"通用配置",slug:"通用配置"},{level:3,title:"enable",slug:"enable"},{level:3,title:"match 和 ignore",slug:"match-和-ignore"},{level:2,title:"修改内置中间件的配置",slug:"修改内置中间件的配置"},{level:2,title:"路由中间件",slug:"路由中间件"},{level:2,title:"引入 Koa 生态",slug:"引入-koa-生态"},{level:2,title:"编写测试",slug:"编写测试"}]},{title:"使用插件",frontmatter:{title:"使用插件"},regularPath:"/zh/guide/plugin.html",relativePath:"zh/guide/plugin.md",key:"v-2f089be8",path:"/zh/guide/plugin.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"使用插件",slug:"使用插件"},{level:3,title:"安装依赖",slug:"安装依赖"},{level:3,title:"挂载插件",slug:"挂载插件"},{level:3,title:"使用插件",slug:"使用插件-2"},{level:2,title:"了解插件",slug:"了解插件"},{level:3,title:"目录结构",slug:"目录结构"},{level:3,title:"Service",slug:"service"},{level:3,title:"Config",slug:"config"},{level:3,title:"Middleware",slug:"middleware"},{level:3,title:"Extend",slug:"extend"},{level:3,title:"不支持的特性",slug:"不支持的特性"},{level:2,title:"插件配置",slug:"插件配置"},{level:3,title:"参数介绍",slug:"参数介绍"},{level:3,title:"开启框架内置插件",slug:"开启框架内置插件"},{level:3,title:"package 和 path",slug:"package-和-path"},{level:3,title:"根据环境配置",slug:"根据环境配置"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"如何开发一个插件",slug:"如何开发一个插件"},{level:3,title:"插件太多,每个应用都要开启怎么办?",slug:"插件太多,每个应用都要开启怎么办?"}]},{title:"Service",frontmatter:{title:"Service"},regularPath:"/zh/guide/service.html",relativePath:"zh/guide/service.md",key:"v-a9bc6b9c",path:"/zh/guide/service.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写 Service",slug:"编写-service"},{level:2,title:"使用 Service",slug:"使用-service"},{level:2,title:"生命周期",slug:"生命周期"},{level:2,title:"挂载规则",slug:"挂载规则"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:2,title:"编写测试",slug:"编写测试"}]},{title:"Session",frontmatter:{title:"Session"},regularPath:"/zh/guide/session.html",relativePath:"zh/guide/session.md",key:"v-9c1bf3a0",path:"/zh/guide/session.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"使用 Session",slug:"使用-session"},{level:2,title:"禁止使用的 Key 值",slug:"禁止使用的-key-值"},{level:2,title:"存储方式",slug:"存储方式"},{level:3,title:"Cookie",slug:"cookie"},{level:3,title:"Redis",slug:"redis"},{level:3,title:"注意事项",slug:"注意事项"},{level:2,title:"Session 实战",slug:"session-实战"},{level:3,title:"删除 Session",slug:"删除-session"},{level:3,title:"修改失效时间",slug:"修改失效时间"},{level:3,title:"延长有效期",slug:"延长有效期"}]},{title:"快速开始",frontmatter:{title:"快速开始"},regularPath:"/zh/quickstart/",relativePath:"zh/quickstart/README.md",key:"v-c73d89b6",path:"/zh/quickstart/"},{title:"简单的 Egg 应用",frontmatter:{title:"简单的 Egg 应用"},regularPath:"/zh/quickstart/egg.html",relativePath:"zh/quickstart/egg.md",key:"v-13a2f408",path:"/zh/quickstart/egg.html",headers:[{level:2,title:"典型场景",slug:"典型场景"},{level:2,title:"逐步搭建",slug:"逐步搭建"},{level:3,title:"环境准备",slug:"环境准备"},{level:3,title:"初始化项目",slug:"初始化项目"},{level:3,title:"目录结构",slug:"目录结构"},{level:3,title:"Controller",slug:"controller"},{level:3,title:"本地开发",slug:"本地开发"},{level:3,title:"模板渲染",slug:"模板渲染"},{level:3,title:"静态资源",slug:"静态资源"},{level:3,title:"配置文件",slug:"配置文件"},{level:3,title:"Service",slug:"service"},{level:3,title:"RESTful",slug:"restful"},{level:3,title:"单元测试",slug:"单元测试"}]},{title:"Context",frontmatter:{title:"Context"},regularPath:"/zh/guide/context.html",relativePath:"zh/guide/context.md",key:"v-45c3b484",path:"/zh/guide/context.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"获取方式",slug:"获取方式"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:3,title:"ctx.app",slug:"ctx-app"},{level:3,title:"ctx.service",slug:"ctx-service"},{level:3,title:"ctx.logger",slug:"ctx-logger"},{level:3,title:"ctx.curl()",slug:"ctx-curl"},{level:3,title:"ctx.runInBackground()",slug:"ctx-runinbackground"},{level:3,title:"ctx.query",slug:"ctx-query"},{level:3,title:"ctx.queries",slug:"ctx-queries"},{level:3,title:"ctx.params",slug:"ctx-params"},{level:3,title:"ctx.routerPath",slug:"ctx-routerpath"},{level:3,title:"ctx.routerName",slug:"ctx-routername"},{level:3,title:"ctx.request.body",slug:"ctx-request-body"},{level:3,title:"ctx.request.files",slug:"ctx-request-files"},{level:3,title:"ctx.get(name)",slug:"ctx-get-name"},{level:3,title:"ctx.cookies",slug:"ctx-cookies"},{level:3,title:"ctx.status =",slug:"ctx-status"},{level:3,title:"ctx.body =",slug:"ctx-body"},{level:3,title:"ctx.set(name, value)",slug:"ctx-set-name-value"},{level:3,title:"ctx.type =",slug:"ctx-type"},{level:3,title:"ctx.render()",slug:"ctx-render"},{level:3,title:"ctx.redirect()",slug:"ctx-redirect"},{level:3,title:"ctx.request",slug:"ctx-request"},{level:3,title:"ctx.response",slug:"ctx-response"},{level:3,title:"更多",slug:"更多"},{level:2,title:"如何扩展",slug:"如何扩展"},{level:3,title:"属性扩展",slug:"属性扩展"},{level:3,title:"编写测试",slug:"编写测试"}]},{title:"异常处理",frontmatter:{title:"异常处理"},regularPath:"/zh/guide/error_handler.html",relativePath:"zh/guide/error_handler.md",key:"v-2ca41536",path:"/zh/guide/error_handler.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"Node.js 异常处理",slug:"node-js-异常处理"},{level:2,title:"框架内置支持",slug:"框架内置支持"},{level:2,title:"业务错误处理",slug:"业务错误处理"},{level:2,title:"框架兜底处理",slug:"框架兜底处理"},{level:3,title:"errorPageUrl",slug:"errorpageurl"},{level:3,title:"自定义统一异常处理",slug:"自定义统一异常处理"},{level:2,title:"404",slug:"_404"},{level:3,title:"默认返回值",slug:"默认返回值"},{level:3,title:"重定向",slug:"重定向"},{level:3,title:"自定义 404 响应",slug:"自定义-404-响应"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"该不该 Catch",slug:"该不该-catch"},{level:3,title:"回调错误无法捕获",slug:"回调错误无法捕获"}]},{title:"日志",frontmatter:{title:"日志"},regularPath:"/zh/guide/logger.html",relativePath:"zh/guide/logger.md",key:"v-26f90708",path:"/zh/guide/logger.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"打印日志",slug:"打印日志"},{level:3,title:"app.logger",slug:"app-logger"},{level:3,title:"ctx.logger",slug:"ctx-logger"},{level:3,title:"this.logger",slug:"this-logger"},{level:2,title:"日志级别",slug:"日志级别"},{level:2,title:"错误日志",slug:"错误日志"},{level:2,title:"输出方式",slug:"输出方式"},{level:3,title:"文件日志",slug:"文件日志"},{level:3,title:"终端日志",slug:"终端日志"},{level:2,title:"正式环境",slug:"正式环境"},{level:3,title:"落盘方式",slug:"落盘方式"},{level:3,title:"日志文件输出位置",slug:"日志文件输出位置"},{level:3,title:"禁止输出 DEBUG 日志",slug:"禁止输出-debug-日志"},{level:3,title:"禁止输出终端日志",slug:"禁止输出终端日志"},{level:2,title:"自定义日志",slug:"自定义日志"},{level:3,title:"框架内置日志",slug:"框架内置日志"},{level:3,title:"增加自定义日志",slug:"增加自定义日志"},{level:3,title:"日志输出格式",slug:"日志输出格式"},{level:3,title:"高级自定义日志",slug:"高级自定义日志"},{level:2,title:"日志切割",slug:"日志切割"},{level:3,title:"按天切割",slug:"按天切割"},{level:3,title:"按照文件大小切割",slug:"按照文件大小切割"},{level:3,title:"按照小时切割",slug:"按照小时切割"},{level:2,title:"编写测试",slug:"编写测试"}]},{title:"Router",frontmatter:{title:"Router"},regularPath:"/zh/guide/router.html",relativePath:"zh/guide/router.md",key:"v-1f1ad7a8",path:"/zh/guide/router.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写路由",slug:"编写路由"},{level:2,title:"路由定义",slug:"路由定义"},{level:3,title:"路由方法",slug:"路由方法"},{level:3,title:"路由路径",slug:"路由路径"},{level:3,title:"路由中间件",slug:"路由中间件"},{level:3,title:"路由别名",slug:"路由别名"},{level:2,title:"RESTful 风格的 URL 定义",slug:"restful-风格的-url-定义"},{level:2,title:"Router 实战",slug:"router-实战"},{level:3,title:"获取查询参数",slug:"获取查询参数"},{level:3,title:"获取命名参数",slug:"获取命名参数"},{level:3,title:"重定向",slug:"重定向"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"路由映射太多?",slug:"路由映射太多?"},{level:3,title:"自动映射路由?",slug:"自动映射路由?"},{level:3,title:"通过装饰器映射?",slug:"通过装饰器映射?"}]},{title:"文件上传",frontmatter:{title:"文件上传"},regularPath:"/zh/guide/upload.html",relativePath:"zh/guide/upload.md",key:"v-07b4bab0",path:"/zh/guide/upload.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"File 模式",slug:"file-模式"},{level:3,title:"配置",slug:"配置"},{level:3,title:"前端代码",slug:"前端代码"},{level:3,title:"获取上传的文件",slug:"获取上传的文件"},{level:2,title:"Stream 模式",slug:"stream-模式"},{level:3,title:"上传单个文件",slug:"上传单个文件"},{level:3,title:"上传多个文件",slug:"上传多个文件"},{level:3,title:"错误处理",slug:"错误处理"},{level:2,title:"安全限制",slug:"安全限制"},{level:3,title:"文件大小",slug:"文件大小"},{level:3,title:"文件类型",slug:"文件类型"},{level:2,title:"云存储",slug:"云存储"},{level:3,title:"OSS",slug:"oss"},{level:3,title:"前端直接上传 OSS",slug:"前端直接上传-oss"}]},{title:"Controller",frontmatter:{title:"Controller"},regularPath:"/zh/guide/controller.html",relativePath:"zh/guide/controller.md",key:"v-471e5888",path:"/zh/guide/controller.html",headers:[{level:2,title:"使用场景",slug:"使用场景"},{level:2,title:"编写 Controller",slug:"编写-controller"},{level:2,title:"生命周期",slug:"生命周期"},{level:2,title:"挂载规则",slug:"挂载规则"},{level:2,title:"常用属性和方法",slug:"常用属性和方法"},{level:2,title:"Controller 实战",slug:"controller-实战"},{level:3,title:"HTTP 基础",slug:"http-基础"},{level:3,title:"获取请求参数",slug:"获取请求参数"},{level:3,title:"获取请求 body",slug:"获取请求-body"},{level:3,title:"解析 JSON / Form 请求",slug:"解析-json-form-请求"},{level:3,title:"解析 XML 请求",slug:"解析-xml-请求"},{level:3,title:"解析自定义类型",slug:"解析自定义类型"},{level:3,title:"文件上传",slug:"文件上传"},{level:3,title:"获取 Header",slug:"获取-header"},{level:3,title:"代理服务器",slug:"代理服务器"},{level:3,title:"读写 Cookie",slug:"读写-cookie"},{level:3,title:"参数校验",slug:"参数校验"},{level:3,title:"调用 Service",slug:"调用-service"},{level:3,title:"发送 HTTP 响应",slug:"发送-http-响应"},{level:3,title:"模板渲染",slug:"模板渲染"},{level:3,title:"JSONP",slug:"jsonp"},{level:3,title:"重定向",slug:"重定向"},{level:2,title:"编写测试",slug:"编写测试"},{level:3,title:"测试 GET 请求",slug:"测试-get-请求"},{level:3,title:"测试 POST 请求",slug:"测试-post-请求"},{level:3,title:"测试文件上传",slug:"测试文件上传"},{level:2,title:"常见问题",slug:"常见问题"},{level:3,title:"missing csrf token",slug:"missing-csrf-token"},{level:3,title:"redirection is prohibited",slug:"redirection-is-prohibited"}]},{title:"HttpClient",frontmatter:{title:"HttpClient"},regularPath:"/zh/guide/httpclient.html",relativePath:"zh/guide/httpclient.md",key:"v-25adca68",path:"/zh/guide/httpclient.html",headers:[{level:2,title:"使用背景",slug:"使用背景"},{level:2,title:"获取方式",slug:"获取方式"},{level:3,title:"app.httpclient",slug:"app-httpclient"},{level:3,title:"app.curl(http://github.com/url,%20options)",slug:"app-curl-url-options"},{level:3,title:"ctx.curl(http://github.com/url,%20options)",slug:"ctx-curl-url-options"},{level:2,title:"常用参数及响应",slug:"常用参数及响应"},{level:3,title:"请求参数",slug:"请求参数"},{level:3,title:"响应数据",slug:"响应数据"},{level:2,title:"HttpClient 实战",slug:"httpclient-实战"},{level:3,title:"发起 GET 请求",slug:"发起-get-请求"},{level:3,title:"通过 POST 发送 JSON",slug:"通过-post-发送-json"},{level:3,title:"提交 Form 表单",slug:"提交-form-表单"},{level:3,title:"文件上传(Multipart)",slug:"文件上传-multipart"},{level:3,title:"文件上传(Stream)",slug:"文件上传-stream"},{level:3,title:"发送 XML",slug:"发送-xml"},{level:3,title:"超时时间",slug:"超时时间"},{level:3,title:"处理重定向",slug:"处理重定向"},{level:3,title:"抓包调试",slug:"抓包调试"},{level:3,title:"事件监听",slug:"事件监听"},{level:2,title:"如何扩展",slug:"如何扩展"},{level:2,title:"编写测试",slug:"编写测试"},{level:2,title:"常见错误码",slug:"常见错误码"},{level:3,title:"ConnectionTimeoutError",slug:"connectiontimeouterror"},{level:3,title:"ResponseTimeoutError",slug:"responsetimeouterror"},{level:3,title:"ECONNRESET",slug:"econnreset"},{level:3,title:"ECONNREFUSED",slug:"econnrefused"},{level:3,title:"ENOTFOUND",slug:"enotfound"},{level:3,title:"JSONResponseFormatError",slug:"jsonresponseformaterror"},{level:2,title:"Options 参数详解",slug:"options-参数详解"},{level:3,title:"默认全局配置",slug:"默认全局配置"},{level:3,title:"method: String",slug:"method-string"},{level:3,title:"data: Object",slug:"data-object"},{level:3,title:"contentType: String",slug:"contenttype-string"},{level:3,title:"dataType: String",slug:"datatype-string"},{level:3,title:"dataAsQueryString: Boolean",slug:"dataasquerystring-boolean"},{level:3,title:"content: String|Buffer",slug:"content-string-buffer"},{level:3,title:"headers: Object",slug:"headers-object"},{level:3,title:"timeout: Number|Array",slug:"timeout-number-array"},{level:3,title:"files: Mixed",slug:"files-mixed"},{level:3,title:"stream: ReadStream",slug:"stream-readstream"},{level:3,title:"writeStream: WriteStream",slug:"writestream-writestream"},{level:3,title:"streaming: Boolean",slug:"streaming-boolean"},{level:3,title:"beforeRequest: Function(options)",slug:"beforerequest-function-options"},{level:3,title:"gzip: Boolean",slug:"gzip-boolean"},{level:3,title:"timing: Boolean",slug:"timing-boolean"},{level:3,title:"HTTPS 相关参数",slug:"https-相关参数"},{level:2,title:"示例代码",slug:"示例代码"}]}],themeConfig:{logo:"/logo.svg",repo:"https://github.com/eggjs/eggjs.github.io",docsBranch:"docs",docsDir:"docs",editLinks:!0,locales:{"/":{label:"English",selectText:"Languages",editLinks:!0,editLinkText:"Edit this page on GitHub",serviceWorker:{updatePopup:{message:"New content is available.",buttonText:"Refresh"}},nav:[{text:"Home",link:"/"},{text:"QuickStart",link:"/quickstart/"},{text:"Guide",link:"/guide/"}],sidebar:{"/quickstart/":[["./","QuickStart"],"egg"],"/guide/":[["./","Description"]]},foot:{friendList:[{title:"Github",list:[{name:"Organization",url:"https://github.com/eggjs"},{name:"Example",url:"https://github.com/eggjs/examples"}]},{title:"Links",list:[{name:"Ant Design",url:"https://ant.design/"},{name:"Ant Motion",url:"http://motion.ant.design/"},{name:"Antv",url:"https://antv.alipay.com/"},{name:"Umi.js",url:"https://umijs.org/"}]},{title:"Community",list:[{name:"FAQ",url:"https://eggjs.org/zh-cn/faq.html"},{name:"Node.js Column",url:"https://www.yuque.com/egg/nodejs"}]},{title:"Egg.js Dingtalk",qrcode:"/img_egg/qrcode_dingtalk.png"}],copyright:[{text:"Copyright © 2019 Egg.js"}]}},"/zh/":{label:"简体中文",selectText:"选择语言",editLinks:!0,editLinkText:"在 GitHub 上编辑此页",serviceWorker:{updatePopup:{message:"发现新内容可用.",buttonText:"刷新"}},nav:[{text:"首页",link:"/zh/"},{text:"快速开始",link:"/zh/quickstart/"},{text:"教程",link:"/zh/guide/"}],sidebar:{"/zh/quickstart/":[["./","快速开始"],"egg"],"/zh/guide/":[["./","概述"],"directory","middleware","controller","router","service","application","context","config","logger","cookie","session","helper","httpclient","error_handler","lifecycle","plugin","upload","i18n"]},foot:{friendList:[{title:"Github",list:[{name:"Organization",url:"https://github.com/eggjs"},{name:"Example",url:"https://github.com/eggjs/examples"}]},{title:"Links",list:[{name:"Ant Design",url:"https://ant.design/"},{name:"Ant Motion",url:"http://motion.ant.design/"},{name:"Antv",url:"https://antv.alipay.com/"},{name:"Umi.js",url:"https://umijs.org/"}]},{title:"Community",list:[{name:"FAQ",url:"https://eggjs.org/zh-cn/faq.html"},{name:"Node.js Column",url:"https://www.yuque.com/egg/nodejs"}]},{title:"Egg.js Dingtalk",qrcode:"/img_egg/qrcode_dingtalk.png"}],copyright:[{text:"Copyright © 2019 Egg.js"}]}}}},locales:{"/":{lang:"en-US",title:"Egg",description:"Born to build better enterprise frameworks and apps with Node.js & Koa",path:"/"},"/zh/":{lang:"zh-CN",title:"Egg",description:"为企业级框架和应用而生",path:"/zh/"}}};n(5);Di.component("Badge",()=>Promise.all([n.e(0),n.e(4)]).then(n.bind(null,222)));n(6);var Ja=[{},({Vue:e})=>{e.mixin({computed:{$dataBlock(){return this.$options.__data__block__}}})},{},{}],Ya=[];class Za{constructor(){this.store=new Di({data:{state:{}}})}$get(e){return this.store.state[e]}$set(e,t){Di.set(this.store.state,e,t)}$emit(...e){this.store.$emit(...e)}$on(...e){this.store.$on(...e)}}class el extends Za{}Object.assign(el.prototype,{getPageAsyncComponent:za,getLayoutAsyncComponent:La,getAsyncComponent:Ra,getVueComponent:Ma});var tl={install(e){const t=new el;e.$vuepress=t,e.prototype.$vuepress=t}};function nl(e,t){return e.options.routes.filter(e=>e.path.toLowerCase()===t.toLowerCase()).length>0}var rl={props:{pageKey:String,slotKey:{type:String,default:"default"}},render(e){const t=this.pageKey||this.$parent.$page.key;return Da("pageKey",t),Di.component(t)||Di.component(t,za(t)),e(t||"")}},il={functional:!0,props:{slotKey:String,required:!0},render:(e,{props:t,slots:n})=>e("div",{class:[`content__${t.slotKey}`]},n()[t.slotKey])},ol=(n(7),Object(Ka.a)({},function(e,t){var n=t._c;return n("svg",{staticClass:"icon outbound",attrs:{xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"}},[n("path",{attrs:{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}}),t._v(" "),n("polygon",{attrs:{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"}})])},[],!0,null,null,null).exports),al={functional:!0,render(e,{parent:t,children:n}){if(t._isMounted)return n;t.$once("hook:mounted",()=>{t.$forceUpdate()})}};Di.config.productionTip=!1,Di.use(ya),Di.use(tl),Di.mixin(function(e,t,n=Di){!function(e){e.locales&&Object.keys(e.locales).forEach(t=>{e.locales[t].path=t}),Object.freeze(e)}(t),n.$vuepress.$set("siteData",t);const r=new(e(n.$vuepress.$get("siteData"))),i=Object.getOwnPropertyDescriptors(Object.getPrototypeOf(r)),o={};return Object.keys(i).reduce((e,t)=>(t.startsWith("$")&&(e[t]=i[t].get),e),o),{computed:o}}(e=>(class{setPage(e){this.__page=e}get $site(){return e}get $themeConfig(){return this.$site.themeConfig}get $frontmatter(){return this.$page.frontmatter}get $localeConfig(){const{locales:e={}}=this.$site;let t,n;for(const r in e)"/"===r?n=e[r]:0===this.$page.path.indexOf(r)&&(t=e[r]);return t||n||{}}get $siteTitle(){return this.$localeConfig.title||this.$site.title||""}get $title(){const e=this.$page,{metaTitle:t}=this.$page.frontmatter;if("string"==typeof t)return t;const n=this.$siteTitle,r=e.frontmatter.home?null:e.frontmatter.title||e.title;return n?r?r+" | "+n:n:r||"VuePress"}get $description(){const e=function(e){if(e){const t=e.filter(e=>"description"===e.name)[0];if(t)return t.content}}(this.$page.frontmatter.meta);return e||this.$page.frontmatter.description||this.$localeConfig.description||this.$site.description||""}get $lang(){return this.$page.frontmatter.lang||this.$localeConfig.lang||"en-US"}get $localePath(){return this.$localeConfig.path||"/"}get $themeLocaleConfig(){return(this.$site.themeConfig.locales||{})[this.$localePath]||{}}get $page(){return this.__page?this.__page:function(e,t){for(let n=0;nn||(e.hash?!Di.$vuepress.$get("disableScrollBehavior")&&{selector:e.hash}:{x:0,y:0})});!function(e){e.beforeEach((t,n,r)=>{if(nl(e,t.path))r();else if(/(\/|\.html)$/.test(t.path))if(/\/$/.test(t.path)){const n=t.path.replace(/\/$/,"")+".html";nl(e,n)?r(n):r()}else r();else{const n=t.path+"/",i=t.path+".html";nl(e,i)?r(i):nl(e,n)?r(n):r()}})}(n);const r={};try{Ja.forEach(t=>{"function"==typeof t&&t({Vue:Di,options:r,router:n,siteData:Xa,isServer:e})})}catch(e){console.error(e)}return{app:new Di(Object.assign(r,{router:n,render:e=>e("div",{attrs:{id:"app"}},[e("router-view",{ref:"layout"}),e("div",{class:"global-ui"},Ya.map(t=>e(t)))])})),router:n}}(!1);window.__VUEPRESS__={version:"1.0.2",hash:"48ee5b6"},sl.onReady(()=>{ll.$mount("#app")})}]); \ No newline at end of file diff --git a/guide/index.html b/guide/index.html index 62a1d95..b909bf3 100644 --- a/guide/index.html +++ b/guide/index.html @@ -7,7 +7,7 @@ - + @@ -17,7 +17,7 @@ GitHub

        Guide

        guide

        - +

        Guide

        guide

        + diff --git a/index.html b/index.html index bb81226..8bade04 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ - + @@ -17,7 +17,7 @@ GitHub

        Egg.js

        Born to build better enterprise frameworks and apps with Node.js & Koa

        QuickStart → -

        完善的生态

        基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。

        高效自然的研发体验

        渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。

        高质量、可信赖

        高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。

        灵活、高扩展性

        约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。

        - +

        完善的生态

        基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。

        高效自然的研发体验

        渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。

        高质量、可信赖

        高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。

        灵活、高扩展性

        约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。

        + diff --git a/quickstart/egg.html b/quickstart/egg.html index dab8397..8c7e621 100644 --- a/quickstart/egg.html +++ b/quickstart/egg.html @@ -7,7 +7,7 @@ - + @@ -20,7 +20,7 @@
      1. Simple Egg Application

        - + 上一篇
        QuickStart

        + diff --git a/quickstart/index.html b/quickstart/index.html index 4e953e2..7c1cf85 100644 --- a/quickstart/index.html +++ b/quickstart/index.html @@ -7,7 +7,7 @@ - + @@ -20,7 +20,7 @@
      2. QuickStart

        QuickStart

        - + 下一篇
        Simple Egg Application

        + diff --git a/zh/guide/application.html b/zh/guide/application.html index ed59df3..5422d57 100644 --- a/zh/guide/application.html +++ b/zh/guide/application.html @@ -7,7 +7,7 @@ - + @@ -111,7 +111,7 @@ };

        这个文件只会在 unittest 环境加载。

        同理,对于下文中的 ApplicationContextRequestResponseHelper 都可以使用这种方式针对某个环境进行扩展。

        - + 下一篇
        Context

        + diff --git a/zh/guide/config.html b/zh/guide/config.html index 7193879..0377e2f 100644 --- a/zh/guide/config.html +++ b/zh/guide/config.html @@ -7,7 +7,7 @@ - + @@ -109,7 +109,7 @@ };

        其次,参考下一条 FAQ 来排查问题。

        如何查看最终的配置?

        框架的配置功能比较强大,有不同环境变量,又有框架、插件、应用等很多地方配置。

        如果你分析问题时,想知道当前运行时使用的最终配置,框架提供了:

        • run/application_config.json 文件:最终的配置合并结果,可以用来分析问题。
        • run/application_config_meta.json 文件:用来排查属性的来源。

        另外,基于安全的考虑,dump 出的文件中会对一些字段进行脱敏处理,主要包括两类:

        • 如密码、密钥等安全字段,可以通过 config.dump.ignore 配置。
        • 如函数、Buffer 等类型,JSON.stringify 后的内容特别大。

        友情提示

        注意:run 目录是每次启动期都会 dump 的信息,用于问题排查。

        开发者修改该目录的文件将不会有任何效果,应该把该目录加到 gitignore 中。

        - + 下一篇
        日志

        + diff --git a/zh/guide/context.html b/zh/guide/context.html index e3dd980..911e134 100644 --- a/zh/guide/context.html +++ b/zh/guide/context.html @@ -7,7 +7,7 @@ - + @@ -307,7 +307,7 @@ });

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        - + 下一篇
        配置

        + diff --git a/zh/guide/controller.html b/zh/guide/controller.html index 448b4ab..4c4dfdb 100644 --- a/zh/guide/controller.html +++ b/zh/guide/controller.html @@ -7,7 +7,7 @@ - + @@ -259,7 +259,7 @@

        如何处理可以阅读上述文档。

        redirection is prohibited

        nodejs.InternalServerError: a security problem has been detected for url "http://www.baidu.com/", redirection is prohibited.
         

        如上所述,不允许重定向到非白名单的域名,具体处理参见安全域名

        - + 下一篇
        Router

        + diff --git a/zh/guide/cookie.html b/zh/guide/cookie.html index 119b794..3573c5b 100644 --- a/zh/guide/cookie.html +++ b/zh/guide/cookie.html @@ -7,7 +7,7 @@ - + @@ -106,7 +106,7 @@ });

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        注意事项

        1. 由于浏览器和其他客户端实现的不确定性,为了保证 Cookie 可以写入成功,建议 value 通过 base64 编码或者其他形式 encode 之后再写入。
        2. 由于浏览器对 Cookie 有长度限制限制,所以尽量不要设置太长的 Cookie。一般来说不要超过 4093 bytes。当设置的 Cookie value 大于这个值时,框架会打印一条警告日志。
        3. 尽可能少写入数据到 Cookie
        - + 下一篇
        Session

        + diff --git a/zh/guide/directory.html b/zh/guide/directory.html index df4f671..c966e21 100644 --- a/zh/guide/directory.html +++ b/zh/guide/directory.html @@ -7,7 +7,7 @@ - + @@ -79,7 +79,7 @@

        如上,为一个常见的应用目录结构:

        • app: 为主要的逻辑代码目录。
          • 常规 MVC 如: app/controllerapp/serviceapp/router.js 等。
          • 某些插件也会自定义加载规范,如 app/rpc 等目录的自动挂载。
        • config: 为配置目录,包含不同环境的配置文件,以及插件挂载声明。
        • test: 为单元测试目录。
        • run:每次启动期都会 dump 的相关信息,用于问题排查,建议加入 gitignore

        文件挂载如下:

        • app/controller/home.js 会被自动挂载到 app.controller.home
        • app/service/user.js 会被自动挂载到 ctx.service.user

        注意事项

        需要注意的是,加载文件时会进行驼峰转换,因此文件名和挂载的属性名可能会存在差异:

        • 默认情况下,连字符和下划线均会被转换为驼峰格式。
        • app/middleware/response_time.js 挂载为 app.middleware.responseTime
        • 部分插件,如 mongoose 插件有特殊约定,会挂载为类格式,如 app.model.User

        在后面的章节中,我们会逐步介绍具体的目录约定。

        如果需要自定义加载规则,可以参见 Loader 相关文档。

        - + 下一篇
        Middleware

        + diff --git a/zh/guide/error_handler.html b/zh/guide/error_handler.html index d2412bc..12f84bb 100644 --- a/zh/guide/error_handler.html +++ b/zh/guide/error_handler.html @@ -7,7 +7,7 @@ - + @@ -197,7 +197,7 @@ }
        - + 下一篇
        生命周期

        + diff --git a/zh/guide/faq.html b/zh/guide/faq.html index e2e70db..448dfae 100644 --- a/zh/guide/faq.html +++ b/zh/guide/faq.html @@ -7,7 +7,7 @@ - + @@ -69,7 +69,7 @@ baseDir: __dirname, });

        这样,我们就可以通过 PM2 进行启动了:

        pm2 start server.js
        -

        为什么会有 csrf 报错?

        通常有两种 csrf 报错:

        • missing csrf token
        • invalid csrf token

        Egg 内置的 egg-security 插件默认对所有『非安全』的方法,例如 POSTPUTDELETE 都进行 CSRF 校验。

        请求遇到 csrf 报错通常是因为没有加正确的 csrf token 导致,具体实现方式,请阅读安全威胁 CSRF 的防范

        本地开发时,修改代码后为什么 worker 进程没有自动重启?

        没有自动重启的情况一般是在使用 Jetbrains 旗下软件(IntelliJ IDEA, WebStorm..),并且开启了 Safe Write 选项。

        Jetbrains Safe Write 文档中有提到:

        If this check box is selected, a changed file is first saved in a temporary file. If the save operation succeeds, the file being saved is replaced with the saved file. (Technically, the original file is deleted and the temporary file is renamed.)

        由于使用了重命名导致文件监听的失效。解决办法是关掉 Safe Write 选项。(Settings | Appearance & Behavior | System Settings | Use "safe write" 路径可能根据版本有所不同)

        - +

        为什么会有 csrf 报错?

        通常有两种 csrf 报错:

        • missing csrf token
        • invalid csrf token

        Egg 内置的 egg-security 插件默认对所有『非安全』的方法,例如 POSTPUTDELETE 都进行 CSRF 校验。

        请求遇到 csrf 报错通常是因为没有加正确的 csrf token 导致,具体实现方式,请阅读安全威胁 CSRF 的防范

        本地开发时,修改代码后为什么 worker 进程没有自动重启?

        没有自动重启的情况一般是在使用 Jetbrains 旗下软件(IntelliJ IDEA, WebStorm..),并且开启了 Safe Write 选项。

        Jetbrains Safe Write 文档中有提到:

        If this check box is selected, a changed file is first saved in a temporary file. If the save operation succeeds, the file being saved is replaced with the saved file. (Technically, the original file is deleted and the temporary file is renamed.)

        由于使用了重命名导致文件监听的失效。解决办法是关掉 Safe Write 选项。(Settings | Appearance & Behavior | System Settings | Use "safe write" 路径可能根据版本有所不同)

        + diff --git a/zh/guide/helper.html b/zh/guide/helper.html index d10b2df..c7e5765 100644 --- a/zh/guide/helper.html +++ b/zh/guide/helper.html @@ -7,7 +7,7 @@ - + @@ -98,7 +98,7 @@ });

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        - + 下一篇
        HttpClient

        + diff --git a/zh/guide/httpclient.html b/zh/guide/httpclient.html index 72336f1..a87c3de 100644 --- a/zh/guide/httpclient.html +++ b/zh/guide/httpclient.html @@ -7,7 +7,7 @@ - + @@ -495,7 +495,7 @@ // }

        HTTPS 相关参数

        包括 keycertpassphrase 等参数,都将透传给 HTTPS 模块。

        其中 rejectUnauthorized 用于在本地调试时忽略无效的 HTTPS 证书。

        具体请查看 https.request() 文档。

        示例代码

        完整示例代码可以在 eggjs/examples/httpclient 找到。

        - + 下一篇
        异常处理

        + diff --git a/zh/guide/i18n.html b/zh/guide/i18n.html index c6cfcee..7892830 100644 --- a/zh/guide/i18n.html +++ b/zh/guide/i18n.html @@ -7,7 +7,7 @@ - + @@ -121,7 +121,7 @@ cookieMaxAge: '1y', };

        局限性

        一般来说,国际化是需要有配套的运营后台的,该插件只是一个简化的实现,开发者根据具体情况选择使用。

        - + 上一篇
        文件上传

        + diff --git a/zh/guide/index.html b/zh/guide/index.html index f127f15..d163e06 100644 --- a/zh/guide/index.html +++ b/zh/guide/index.html @@ -7,7 +7,7 @@ - + @@ -54,7 +54,7 @@
      3. 概述

        在本篇中,我们会对每一个术语概念,逐一进行详细的讲解。

        包括它的适用场景、如何使用、常用的方法和属性、如何扩展、如何测试等等。

        Web 模型

        框架奉行『约定优于配置』,因此我们首先需要了解下 目录规范 的约定。

        其次,对于一个 Web 应用来说,一般会采用 MVC 模型。

        对应的概念有:

        • MiddlewareKoa 的洋葱模型,类似 JavaFilter
        • Controller:控制器,处理和校验用户请求,然后调用业务逻辑层,最终发送响应给用户。
        • Router:路由,对用户请求进行分派。
        • Service:业务逻辑层。
        • Application:全局应用对象,通过它可以获取 配置文件 等信息。
        • Context:用户请求的上下文,用于获取请求信息和设置响应信息。
        • 此外,还有 CookieSessionHelper 等等。

        功能模块

        除此之外,还提供了很多研发过程中需要的 Utils

        • 使用插件:生态共建的基础,一分钟即可通过插件接入各自基础中间件服务。
        • 生命周期:方便开发者做一些初始化工作。
        • 日志:对应用的运行状态监控、问题排查等都有非常重要的意义。
        • 异常处理:程序健壮性的保障。
        • 安全:安全无小事。
        • 还有 文件上传国际化 等等。
        - + 下一篇
        目录规范

        + diff --git a/zh/guide/lifecycle.html b/zh/guide/lifecycle.html index 71c5136..a55db0d 100644 --- a/zh/guide/lifecycle.html +++ b/zh/guide/lifecycle.html @@ -7,7 +7,7 @@ - + @@ -177,7 +177,7 @@ }

        注意事项

        框架默认最多只会等到 5s 就会退出,不保证会等待所有的该 Hook 执行完毕。

        - + 下一篇
        使用插件

        + diff --git a/zh/guide/logger.html b/zh/guide/logger.html index bc1fc90..b74349f 100644 --- a/zh/guide/logger.html +++ b/zh/guide/logger.html @@ -7,7 +7,7 @@ - + @@ -193,7 +193,7 @@ });
        - + 下一篇
        Cookie

        + diff --git a/zh/guide/middleware.html b/zh/guide/middleware.html index 22a26e5..10e47e9 100644 --- a/zh/guide/middleware.html +++ b/zh/guide/middleware.html @@ -7,7 +7,7 @@ - + @@ -163,7 +163,7 @@ });

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        - + 下一篇
        Controller

        + diff --git a/zh/guide/plugin.html b/zh/guide/plugin.html index 1f07b4e..0caa005 100644 --- a/zh/guide/plugin.html +++ b/zh/guide/plugin.html @@ -7,7 +7,7 @@ - + @@ -126,7 +126,7 @@ };

        这样在生产环境可以 npm i --production 不需要下载 egg-development-proxyagent 的包了。

        注意:

        • 不存在 plugin.default.js
        • 只能在应用层使用,在框架层请勿使用。

        常见问题

        如何开发一个插件

        恭喜你迈出这一步,可以回馈社区。

        具体可以参见文档:

        插件太多,每个应用都要开启怎么办?

        此时应该考虑包装为一个上层框架

        - + 下一篇
        文件上传

        + diff --git a/zh/guide/router.html b/zh/guide/router.html index 4b3c9e9..da7ce18 100644 --- a/zh/guide/router.html +++ b/zh/guide/router.html @@ -7,7 +7,7 @@ - + @@ -184,7 +184,7 @@ };

        也可直接使用 egg-router-plus

        另外,框架会在启动期把最终的路由映射 dump 到 run/router.json 中。

        自动映射路由?

        一般来说,如果符合 RESTful 风格的路由,直接用上述的 router.resource() 配置即可。

        如果你的业务场景中,有其他约定的规则,则可以参考对应的 resource 源码,扩展自己的方法,封装为插件。

        通过装饰器映射?

        装饰器目前还不是 ECMA 的正式规范,框架未提供该功能。

        开发者可以自行通过 TypeScriptBabel 转义对应的自定义装饰器。

        - + 下一篇
        Service

        + diff --git a/zh/guide/service.html b/zh/guide/service.html index 028d313..cd7cdb5 100644 --- a/zh/guide/service.html +++ b/zh/guide/service.html @@ -7,7 +7,7 @@ - + @@ -95,7 +95,7 @@ });

        具体的单元测试运行方式,参见 研发流程 - 单元测试 文档。

        - + 下一篇
        Application

        + diff --git a/zh/guide/session.html b/zh/guide/session.html index 7d96080..c5051cc 100644 --- a/zh/guide/session.html +++ b/zh/guide/session.html @@ -7,7 +7,7 @@ - + @@ -114,7 +114,7 @@ };
        - + 下一篇
        Helper

        + diff --git a/zh/guide/upload.html b/zh/guide/upload.html index 826b84d..530f7e0 100644 --- a/zh/guide/upload.html +++ b/zh/guide/upload.html @@ -7,7 +7,7 @@ - + @@ -224,7 +224,7 @@ }

        前端直接上传 OSS

        还有一种常见的需求:前端直接上传文件到 OSS,不经过我们的 Web 应用。

        OSS 提供了 STS 临时授权方式

        上述的 egg-oss 插件的底层是 ali-oss 模块,也提供了对应的支持,具体参见文档。

        - + 下一篇
        国际化

        + diff --git a/zh/index.html b/zh/index.html index d9138ba..113aab7 100644 --- a/zh/index.html +++ b/zh/index.html @@ -7,7 +7,7 @@ - + @@ -17,7 +17,7 @@ GitHub

        Egg.js

        为企业级框架和应用而生

        快速开始 → -

        完善的生态

        基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。

        高效自然的研发体验

        渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。

        高质量、可信赖

        高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。

        灵活、高扩展性

        约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。

        - +

        完善的生态

        基于开源生态,专为泛蚂蚁生态定制,一分钟接入后端服务中间件,支持多种部署环境。

        高效自然的研发体验

        渐进式开发,学习曲线平滑,提供一站式开发套件,为研发全流程保驾护航。

        高质量、可信赖

        高质量,完备的测试,内置集团安全策略,双十一等线上大规模顶级流量压力考验。

        灵活、高扩展性

        约定优于配置,高度灵活的定制性,业界领先的插件机制和上层业务框架机制。

        + diff --git a/zh/quickstart/egg.html b/zh/quickstart/egg.html index 240f837..d0f2b45 100644 --- a/zh/quickstart/egg.html +++ b/zh/quickstart/egg.html @@ -7,7 +7,7 @@ - + @@ -157,7 +157,7 @@ }); });
        - + 上一篇
        快速开始

        + diff --git a/zh/quickstart/index.html b/zh/quickstart/index.html index a7f35bf..f4f3d04 100644 --- a/zh/quickstart/index.html +++ b/zh/quickstart/index.html @@ -7,7 +7,7 @@ - + @@ -20,7 +20,7 @@
      4. 快速开始

        快速开始

        - + 下一篇
        简单的 Egg 应用

        + From 3029dc4cf90cd0b85b2ba6c6c7f05c7dd042a5ef Mon Sep 17 00:00:00 2001 From: Suyi Date: Fri, 12 Jul 2019 15:17:56 +0800 Subject: [PATCH 10/11] Create CNAME --- CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 CNAME diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..cca6e6f --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +eggjs.org \ No newline at end of file From f7ae645c03728db0b4c6afe09124cdbdaa66f494 Mon Sep 17 00:00:00 2001 From: Suyi Date: Fri, 12 Jul 2019 15:18:08 +0800 Subject: [PATCH 11/11] Delete CNAME --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index cca6e6f..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -eggjs.org \ No newline at end of file
        hero

        EggJs

        - Born to build better enterprise frameworks and apps with Node.js & Koa -

        QuickStart →

        Egg

        Egg Detail