From 967bde9444c8d23ccfd15a0cacd329835f0ddf16 Mon Sep 17 00:00:00 2001 From: ChenSino <462488588@qq.com> Date: Sat, 2 Nov 2024 13:31:49 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20ChenSino?= =?UTF-8?q?/ChenSino.github.io@1e14f2452cfbf39972543e833e5e6b9b13537d64=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 404.html | 8 +- article/index.html | 8 +- ...1.html-BpsuZlEh.js => 01.html-jkQdEnIh.js} | 2 +- ...2.html-9y4kTt4c.js => 02.html-B3UlmDxL.js} | 2 +- ...3.html-_Fpqszha.js => 03.html-CK1TN99Q.js} | 2 +- ...4.html-BA8_N2LW.js => 04.html-CeKJQimw.js} | 2 +- ...5.html-C12aFhzW.js => 05.html-CMuhT-M0.js} | 2 +- ...6.html-fv-ofJNx.js => 06.html-BX6nV64C.js} | 2 +- ...7.html-C8FkfsLs.js => 07.html-CCBD9WTC.js} | 2 +- ...8.html-DoM55lqI.js => 08.html-BJMKXxmH.js} | 2 +- ...9.html-Aa1WASXU.js => 09.html-7MXGKP1a.js} | 2 +- ...{1.html-BsLOzPLz.js => 1.html-CeguXEpG.js} | 2 +- ...0.html-D50UJxV3.js => 10.html-IqVcln-C.js} | 2 +- ...1.html-DLQhthQQ.js => 11.html-kw9nHLna.js} | 2 +- ...2.html-By4kPY-J.js => 12.html-DxvMj_JN.js} | 2 +- ...3.html-DbVAkfja.js => 13.html-CBYdZLrV.js} | 2 +- ...4.html-DC_xS0f7.js => 14.html-B-d6pKTK.js} | 2 +- ...{2.html-CDG7SSJK.js => 2.html-Dp6E9lgb.js} | 2 +- ...nI2oaJK.js => 2022-04-12.html-omw7Q29H.js} | 2 +- ....html-2xAmqrLX.js => 404.html-CFV1KPyG.js} | 2 +- ...ml-jSrFg30s.js => AOPLog.html-WkAcygqg.js} | 2 +- ...u-Jo73Z.js => Annotation.html-CN1Pgot9.js} | 2 +- ...ml-D1k7ux4i.js => Arthas.html-BDHUU6lY.js} | 2 +- ...v7f3.js => Authorization.html-DYO-IVFw.js} | 2 +- ...tml-CuSxbXvJ.js => BTree.html-DJQoEL8B.js} | 2 +- ...=> BatchDeleteGitHubRepo.html-DtqMXGl3.js} | 2 +- ....js => BeanPostProcessor.html-CLtCpKMO.js} | 2 +- ...up.js => BuildWebProject.html-D4yumRMO.js} | 2 +- ....html-rIf3-Wbw.js => CDN.html-Cd7ildNJ.js} | 2 +- ....html-D5a-tI0p.js => CPU.html-BmK1dlqs.js} | 2 +- ...4MUidN.js => CPUOverLoad.html-B_JZj2Bd.js} | 2 +- ...ml-C59_Q0zj.js => CentOS.html-CSukTNYu.js} | 2 +- ...RzK.js => ChromeDevTools.html-jvUjLE8a.js} | 2 +- ...js => CircularDependency.html-DRv79HhX.js} | 2 +- ...p_fs5y.js => ClassLoader.html-fNJjFm9m.js} | 2 +- ...DK1Z4.js => CloudService.html-DKHWbJgc.js} | 2 +- ... => CloudServiceTraining.html-CsW1-SEa.js} | 2 +- ...eAHa-2T.js => Collection.html-D-nzOS-a.js} | 2 +- ...m.js => CollectionInject.html-Wk5anYr4.js} | 2 +- ...454f.js => CommonUsedCMD.html-CHwLkMH3.js} | 2 +- ...9XJev.js => CompileJdk11.html-k8rk73iW.js} | 2 +- ...lwuWlCD.js => Concurrent.html-DQRS-IpH.js} | 2 +- ...pO7p_.js => ConstantPool.html-C5zSxd7l.js} | 2 +- ...ml-ffctXEz0.js => Cookie.html-BobniOCU.js} | 2 +- ...html-DlrJSikw.js => Curl.html-BO77gaot.js} | 2 +- ...omAuthenticationProvider.html-C05ZFsDP.js} | 2 +- ...CX9zw1r2.js => CustomLRU.html-DHBi8rnr.js} | 2 +- ...7K.js => CustomLoginPage.html-R1ammlY9.js} | 2 +- ...ustomTokenAuthentication.html-DcEKKC-z.js} | 2 +- ...=> DelegatingFilterProxy.html-CK8Yiklp.js} | 2 +- ...P.js => DeployGithubPage.html-COQNI509.js} | 2 +- ...=> DesignPatternInSpring.html-B1tCH6O1.js} | 2 +- ...Q21.js => DistributeLock.html-C1ihBiBe.js} | 2 +- ...ml-BaiyWfkU.js => Docker.html-DzlBwcZ1.js} | 2 +- ...ml-C-IGHrDE.js => Future.html-CI2mkHT2.js} | 2 +- ...8ZQnH_.js => GitCommands.html-BB60xrRT.js} | 2 +- ...html-C3TqYNG0.js => Http.html-Cx1NIixk.js} | 2 +- ...tml-yFvJ3oRy.js => Http2.html-B3cAbYAj.js} | 2 +- ...\351\200\211\345\236\213.html-B2_U3umv.js" | 2 +- ...-BPdWwFrU.js => IO-Model.html-O-6ElLZv.js} | 2 +- ...DbFAWkDL.js => IO-model1.html-Bko-tL29.js} | 2 +- ...html-N3gMCtcp.js => Idea.html-DFw9lvM7.js} | 2 +- ...> ImplementSameInterface.html-iv1dGOUt.js} | 2 +- ...> InstallMysqlWithDocker.html-Dlzu3km3.js} | 2 +- ...s => IntegerConstantPool.html-B-ijklmd.js} | 2 +- ...vKsTRiv.js => JdkVersion.html-CS9P09_u.js} | 2 +- ...\346\220\255\345\273\272.html-B4JCtBiz.js" | 2 +- ...l-D5aTJlwz.js => Jenkins.html-e7mi9H_K.js} | 2 +- ....html-BI9W3_p_.js => Jwt.html-CP1cMj9m.js} | 2 +- ...l-CTuhb7rc.js => Manjaro.html-Dy2e0Hrp.js} | 2 +- ...www5iG.js => MemoryModel.html-CRsJwAOr.js} | 2 +- ...AWo0fohf.js => MountDisk.html-BqspOYo5.js} | 2 +- ...y.js => MultiNetworkCard.html-BdZVkWRH.js} | 2 +- ...=> MybatisPlusDataSource.html-J7-G54ls.js} | 2 +- ...xtc4N.js => MysqlCollate.html-BXuIpRKi.js} | 2 +- ...D.js => MysqlMasterSlave.html-CPoF9taw.js} | 2 +- ...C-U71JLc.js => MysqlNote.html-Cy1AGfO1.js} | 2 +- ...js => MysqlRemoteConnect.html-B6Rz_Dfl.js} | 2 +- ...tml-D6kbUFLh.js => Nacos.html-DC2XGbQ_.js} | 2 +- ...H8rB4.js => NativeMethod.html-CW2rOE3c.js} | 2 +- ..._zRwVPgF.js => NewObject.html-DNLlDT5H.js} | 2 +- ...5KbJsg.js => OAUTH_LOGIN.html-DZ9PB-Wb.js} | 2 +- ... => OAuth2Authentication.html-ChmdjSKd.js} | 2 +- ...ld.js => ObjectReference.html-CG8KX2PY.js} | 2 +- ... => OncePerRequestFilter.html-DIikqGft.js} | 2 +- ... => OncePerRequestFilter.html-DvJ1C4ir.js} | 2 +- ...entDelegationClassLoader.html-BE03CURY.js} | 2 +- ...WAtGw.js => PreAuthorize.html-DFU2o_nN.js} | 2 +- ...ibZtz7.js => ProxyInJava.html-Craly_CB.js} | 2 +- ...l-6jai2Fmm.js => Recurse.html-CzvVNt5P.js} | 2 +- ...kbthk.js => RefreshToken.html-B7uT6L3n.js} | 2 +- ...l-X9thQzel.js => Restful.html-BeZrEgn9.js} | 2 +- ...Pq.js => SQLOptimization.html-DB9Wuz97.js} | 2 +- ....html-DgwliJOO.js => SSO.html-DB2D0akT.js} | 2 +- ....html-DBoqluIC.js => SSO.html-DKOv-tJ8.js} | 2 +- ....html-BJ48MGxE.js => SUC.html-DtVpWo_q.js} | 2 +- ...tml-DATE_iKU.js => Samba.html-D8HLrsJP.js} | 2 +- ...s => SecurityFilterChain.html-j_4ZmVEf.js} | 2 +- ...pG-l.js => Serialization.html-BJVNALhI.js} | 2 +- ...tbe.js => ServiceInstall.html-BaooIkHM.js} | 2 +- ...l-PVXvBLSr.js => Session.html-CKk3uN_p.js} | 2 +- ...Gj1n.js => SetObjectNull.html-BA6rF5JL.js} | 2 +- ...reBetweenWindowsAndLinux.html-CJ7U7icg.js} | 2 +- ...-DwRE_esc.js => SoftWare.html-6EROxq9x.js} | 2 +- ...0F2yJUPF.js => SpringAOP.html-BR3lenMn.js} | 2 +- ...ingBootAutoConfiguration.html-DyJd0iCn.js} | 2 +- ...T_2YyF.js => SpringCache.html-B2PNt9GB.js} | 2 +- ...js => SpringCloudGateway.html-Dc9fg5OX.js} | 2 +- ... => SpringExtensionPoint.html-BdwK_BjA.js} | 2 +- ...D2ZKIq-l.js => SpringIOC.html-BqsqDvdX.js} | 2 +- ...CW7qiD-c.js => SpringMVC.html-D8cETPq8.js} | 2 +- ...s => SpringSourceAnalize.html-D0vztr7E.js} | 2 +- ...ml-CI6Ty1q7.js => String.html-Ca6nK_a0.js} | 2 +- ...BihzcLHW.js => StringAdd.html-fGViknMr.js} | 2 +- ...l-BrC3rGwr.js => Swagger.html-QZsEFOI2.js} | 2 +- ...9EviB.js => Synchronized.html-Bz2_U7rD.js} | 2 +- ...ml-DSyGo702.js => TCP-IP.html-obsJBTgk.js} | 2 +- ...l-cL04DF4f.js => TcpDump.html-DFEN5LYg.js} | 2 +- ...q5iQUn.js => ThreadLocal.html-YdsPoeQ1.js} | 2 +- ...4bZt9rf.js => ThreadPool.html-D9vbBMnV.js} | 2 +- ...6.js => TooManyOpenFiles.html-DBlFfNwX.js} | 2 +- ...31qBXz.js => TyporaPicgo.html-DyxSZmxq.js} | 2 +- ... => Undertow.xxxNotFount.html-X1F463A1.js} | 2 +- ...CvrJ26C9.js => Validator.html-DOW_vPxb.js} | 2 +- ...ml-o-J8uPWj.js => VsCode.html-BCn8Egxl.js} | 2 +- ....html-irNHs4ez.js => WSL.html-BIz7aujd.js} | 2 +- ...html-D7OcL7nb.js => Wifi.html-DCwxVnq6.js} | 2 +- ...Vw3VyIR.js => aboutAsync.html-CukGl2Bf.js} | 2 +- ...fGRZu30.js => aboutEvent.html-CvhiVca8.js} | 2 +- ...DlclfKVB.js => aboutThis.html-B-2Ir7Kw.js} | 2 +- ...lIzb_.js => action-usage.html-DFNFrJyT.js} | 2 +- assets/{app-CVMfKeWw.js => app-_Oi5YZFn.js} | 466 +++++++++--------- ...xV4KJUL.js => asyncError.html-npKDZnPq.js} | 2 +- ...tml-DIiEtqYW.js => axios.html-BXoNbwVV.js} | 2 +- ...UsK9ry.js => basic-usage.html-CBqhMzrq.js} | 2 +- ...--1ri9Qlh.js => branch01.html-CEgmUaaW.js} | 2 +- ...-CScQPDBP.js => branch02.html-CkbKOtQo.js} | 2 +- ...\346\240\207\347\255\276.html-CyXt4kH2.js" | 2 +- ...iVSBRz.js => crossDomain.html-CSDExzG7.js} | 2 +- ...hraX3.js => crossDomain2.html-CA7WfkLl.js} | 2 +- ...\350\257\201\344\271\246.html-ykZMVUjH.js" | 2 +- ...l-D2cQj4ic.js => disable.html-D_vnyg33.js} | 2 +- ...html-3iC56TfH.js => draw.html-DWXvV2Rt.js} | 2 +- ...ml-D9FixpLr.js => ebooks.html-BgNEqnIF.js} | 2 +- ...\346\223\215\344\275\234.html-DivcwrqA.js" | 2 +- ...\346\240\241\351\252\214.html-BDHhWiHk.js" | 2 +- ...\351\203\250\347\275\262.html-hYHFl7Qx.js" | 2 +- ...l-BEDsFZkv.js => encrypt.html-BVOYksPN.js} | 2 +- ...-DxETLIkG.js => eventBus.html-B0dTFe1w.js} | 2 +- ...l-DHpjy03Z.js => fanType.html-34EbXHhU.js} | 2 +- ...tml-CLx7xyLl.js => fatal.html-CuBowvDc.js} | 2 +- ...-axsN7QwQ.js => firewall.html-Bcf4dLh0.js} | 2 +- ...-CDBaOBQ8.js => firewall.html-CnVk-61G.js} | 2 +- ...xwXqnJ.js => gitConflict.html-CD4vs1Ct.js} | 2 +- ...Bh0TFroo.js => gitRebase.html-DF3bIliz.js} | 2 +- ...l-DCYwt7rE.js => gitwork.html-BenGZ5gF.js} | 2 +- ...html-CdI4hI0D.js => home.html-Ds_ny-Cd.js} | 2 +- ...ml-DtYCD-Aq.js => import.html-CNG84pM6.js} | 2 +- ...\351\234\200\346\261\202.html-Disuf4MT.js" | 2 +- ...tml-DxrpWTG5.js => index.html--jCoRugf.js} | 2 +- ...tml-CrtTLfwo.js => index.html-0gfJBSgv.js} | 2 +- ...tml-zTuDnnQ9.js => index.html-1bodzj_2.js} | 2 +- ...tml-BX9tTXXW.js => index.html-2E2sJd9T.js} | 2 +- ...tml-D3LGsC8Z.js => index.html-3y6s76rT.js} | 2 +- ...tml-AC98BttZ.js => index.html-43Da7xtg.js} | 2 +- ...tml-SQqXSiBs.js => index.html-52PFUvm3.js} | 2 +- assets/index.html-Ah6mlgvk.js | 1 - ...tml-CPxFcKAG.js => index.html-B0HiH-Xn.js} | 2 +- ...tml-Dhyapq1H.js => index.html-BA11w6YR.js} | 2 +- ...tml-BQhu6ulG.js => index.html-BAA1oPlT.js} | 2 +- ...tml-B35-Vn16.js => index.html-BB0RPG_h.js} | 2 +- ...tml-EDXVLWDR.js => index.html-BBsSpY4m.js} | 2 +- ...tml-XxdR190C.js => index.html-BDDCxsP9.js} | 2 +- ...tml-Cnhk4PBp.js => index.html-BGEnglY6.js} | 2 +- ...tml-Ds23VMOY.js => index.html-BGmSAx78.js} | 2 +- ...tml-B2inZhc8.js => index.html-BIoY637M.js} | 2 +- ...tml-BKgtb9Ah.js => index.html-BJ_XZI-3.js} | 2 +- ...tml-BUg70Vrr.js => index.html-BJxwK7AA.js} | 2 +- ...tml-DaPOtDhI.js => index.html-BLiK3lZA.js} | 2 +- ...tml-pad8GR4o.js => index.html-BNCitO52.js} | 2 +- ...tml-Cu9v8LOw.js => index.html-BOFOt1os.js} | 2 +- ...tml-CF8Xpy4A.js => index.html-BR_hmahK.js} | 2 +- ...tml-xL8R2rvI.js => index.html-Be9YOXiD.js} | 2 +- ...tml-DZIt25hg.js => index.html-BgeMLVVF.js} | 2 +- ...tml-CpRQKI06.js => index.html-BgghxhP9.js} | 2 +- ...tml-C6jwACfV.js => index.html-Bk4Xl8z2.js} | 2 +- ...tml-CUh5IBbk.js => index.html-BkurDFO4.js} | 2 +- ...tml-RGleUAuC.js => index.html-BluLs2ps.js} | 2 +- ...tml-DKR14bqO.js => index.html-Bm2QW3Yx.js} | 2 +- ...tml-COzc4rHJ.js => index.html-Bn0M7tTB.js} | 2 +- ...tml-DqHDZd6R.js => index.html-BoICU8sT.js} | 2 +- ...tml-Ci4VSHuy.js => index.html-Bq73GOIG.js} | 2 +- ...tml-uyHOrigb.js => index.html-Bt1B_fZ7.js} | 2 +- ...tml-BMhOFPmp.js => index.html-BtN-4LZa.js} | 2 +- ...tml-CJbe2oEP.js => index.html-BwnKcSJN.js} | 2 +- ...tml-DiiTcF6s.js => index.html-Bx1FwJMs.js} | 2 +- ...tml-EMVH5RI8.js => index.html-Bxi5AXy-.js} | 2 +- ...tml-dkKrYb2Y.js => index.html-ByKY6u5T.js} | 2 +- ...tml-CL8Rn6LN.js => index.html-C2vp8KMJ.js} | 2 +- ...tml-DZIE4r_x.js => index.html-C4Z9a5GS.js} | 2 +- ...tml-BOWROSmg.js => index.html-C6FMNcFL.js} | 2 +- ...tml-BtjOnGEp.js => index.html-C6kiZ_HF.js} | 2 +- ...tml-DKQaObvm.js => index.html-C8vCtcrV.js} | 2 +- ...tml-BnfXAjNS.js => index.html-CEilrxfK.js} | 2 +- ...tml-DQDf-iB8.js => index.html-CJQlCK8P.js} | 2 +- ...tml-BfisB8tx.js => index.html-CNk3LVBO.js} | 2 +- ...tml-CLj8LoVt.js => index.html-CONpmD-V.js} | 2 +- ...tml-CmHVopsM.js => index.html-CTGnNX68.js} | 2 +- ...tml-DCau-SSn.js => index.html-CV_dUHJe.js} | 2 +- ...tml-Dv16igET.js => index.html-CXTrskFo.js} | 2 +- ...tml-E2fKYy3S.js => index.html-C_jbP7SN.js} | 2 +- ...tml-CFkGmFia.js => index.html-CbyN5TWa.js} | 2 +- ...tml-76c_E0sL.js => index.html-ChsojBeT.js} | 2 +- ...tml-CzP4p5XK.js => index.html-CiPcuEin.js} | 2 +- ...tml-DAKYBthR.js => index.html-Cjv6EKnU.js} | 2 +- ...tml-BHP5lSWG.js => index.html-ClRCgRtJ.js} | 2 +- ...tml-CeTjFoZy.js => index.html-CntKYRgb.js} | 2 +- ...tml-HvLMGTgp.js => index.html-CrqLYD8s.js} | 2 +- ...tml-f8Jf88Pj.js => index.html-CtpU9Ex-.js} | 2 +- ...tml-CTOJOPSY.js => index.html-CuULl194.js} | 2 +- ...tml-CJudebxY.js => index.html-CvVo_Iy8.js} | 2 +- ...tml-Bo2PC9xb.js => index.html-Cwy-RPtK.js} | 2 +- ...tml-FIqAJepD.js => index.html-D0WBxxKk.js} | 2 +- ...tml-DJeEzsTa.js => index.html-D1zTD58M.js} | 2 +- ...tml-B6iZ__OM.js => index.html-D3l9AP0G.js} | 2 +- ...tml-hGAHmgqz.js => index.html-D4BNQ3HG.js} | 2 +- ...tml-oz23N8Lk.js => index.html-D5Bas5eh.js} | 2 +- ...tml-_iWg3Z3W.js => index.html-D5egMLMa.js} | 2 +- ...tml-CHghYPfg.js => index.html-DAGR5a1Q.js} | 2 +- ...tml-C3kmdsDq.js => index.html-DDdnWKF-.js} | 2 +- ...tml-BvXx7kZb.js => index.html-DERaj909.js} | 2 +- ...tml-BJvieq0-.js => index.html-DESeP-r7.js} | 2 +- ...tml-D5JX-dxI.js => index.html-DEubCK6b.js} | 2 +- ...tml-dhU_SREp.js => index.html-DFsL5gjr.js} | 2 +- ...tml-DKhBFgRg.js => index.html-DH61zfDk.js} | 2 +- ...tml-CToWg7cr.js => index.html-DHWvYkOD.js} | 2 +- ...tml-BOnFN1d7.js => index.html-DI5pE721.js} | 2 +- ...tml-iq2vqpLb.js => index.html-DLU057mi.js} | 2 +- ...tml-Dn4DJ2Jv.js => index.html-DNyKapJd.js} | 2 +- ...tml-D2gpmgi4.js => index.html-DYNHttZu.js} | 2 +- ...tml-7hihg5cE.js => index.html-DZ7gwFx0.js} | 2 +- ...tml-B9UffgOy.js => index.html-D_iYE-Ij.js} | 2 +- ...tml-Beu4CvPJ.js => index.html-DaiGMbWY.js} | 2 +- ...tml-0QvH_nXT.js => index.html-DcPH7P5y.js} | 2 +- ...tml-DD8LSjG9.js => index.html-De67gXK7.js} | 2 +- ...tml-3CT6OGMP.js => index.html-DfN87M_p.js} | 2 +- assets/index.html-Di3gSe-7.js | 1 + ...tml-BDPTsTEv.js => index.html-DmeObyHP.js} | 2 +- ...tml-BvSZt_Zx.js => index.html-DnWLnH1N.js} | 2 +- ...tml-C7LrpzTI.js => index.html-DpIMle5T.js} | 2 +- ...tml-CCNjkOtl.js => index.html-DqEIAryj.js} | 2 +- ...tml-LnP3a8gR.js => index.html-DsIag3k6.js} | 2 +- ...tml-DDz84u40.js => index.html-DtMYUjJK.js} | 2 +- ...tml-B1xDsJG7.js => index.html-Du2EK4qm.js} | 2 +- ...tml-B-rzW1p0.js => index.html-DyGScxDV.js} | 2 +- ...tml-CuWpjIbb.js => index.html-Gf4fmU9c.js} | 2 +- ...tml-B-zCQLtF.js => index.html-KBuRJKDT.js} | 2 +- ...tml-BQbSFfwU.js => index.html-QOoLQt7U.js} | 2 +- ...tml-CxAtUmc7.js => index.html-UDfVv4MI.js} | 2 +- ...tml-09kdO8uk.js => index.html-UPH_9_-C.js} | 2 +- ...tml-vvugCgoe.js => index.html-YlcxYzGW.js} | 2 +- ...tml-BqSgUMIT.js => index.html-ZWa93oKC.js} | 2 +- ...tml-aM10vDis.js => index.html-ZenQwYfE.js} | 2 +- ...tml-DYLEdXjZ.js => index.html-Zxb4dqDx.js} | 2 +- ...tml-BXBwg_XP.js => index.html-_Hk-UKaP.js} | 2 +- ...tml-CDsKkWs-.js => index.html-bCycLo4M.js} | 2 +- ...tml-CeUSh-YY.js => index.html-da2hiqxj.js} | 2 +- ...tml-CY45JW6T.js => index.html-gaUfJQSN.js} | 2 +- ...tml-eHCo69H8.js => index.html-jAf7-iM1.js} | 2 +- ...tml-B3FdepDG.js => index.html-k__z7vRJ.js} | 2 +- ...tml-Dvn_QmO3.js => index.html-l0ianYW6.js} | 2 +- ...tml-BXMP0bPq.js => index.html-l2dMz0nJ.js} | 2 +- ...tml-D38QGC2C.js => index.html-n0MBVKbb.js} | 2 +- ...tml-COH-CgE0.js => index.html-raRbuKfh.js} | 2 +- ...tml-CaPxFNNf.js => index.html-tdfwMWEj.js} | 2 +- ...tml-jpPjIq77.js => index.html-ukDkHwB4.js} | 2 +- ...tml-hsfyfYXC.js => index.html-yLnx4YcF.js} | 2 +- ...o.html-DYLJUoUT.js => io.html-C1zkJgLS.js} | 2 +- ...\345\244\247\345\200\274.html-BiKvCKWl.js" | 2 +- ...-Cdfyboon.js => lazyLoad.html-B0wYE_Yw.js} | 2 +- ...l-DOiHN3-e.js => logback.html-bWzTkpCR.js} | 2 +- ...-Ku2vuX_R.js => markdown.html-DnotFn9F.js} | 2 +- ...gMZ_ic.js => mergeBranch.html-CCnBpahJ.js} | 2 +- ...P4UAvN.js => multiModule.html-vU5Z_GHP.js} | 2 +- ...l-ZRAnLlvk.js => mybatis.html-CU1fMlIQ.js} | 2 +- ...html-CoTL_cZp.js => note.html-CZMFgVaM.js} | 2 +- ...html-CNLLwtGV.js => page.html-CalA3aZV.js} | 2 +- ...l-dW-7sW20.js => problem.html-220H-omj.js} | 2 +- ...l-BUt4IEbi.js => promise.html-DlbJ5Sxz.js} | 2 +- ...tml-DRPn2ULh.js => proxy.html-CIpuLV1M.js} | 2 +- ...MMt.js => rebaseAndMerge.html-DZipbfd7.js} | 2 +- ...tml-CjbSN2yZ.js => reset.html-JinN7PbC.js} | 2 +- ...tml-_uY0duH1.js => slide.html-gu2cHfI1.js} | 2 +- ...th2-authorization-server.html-o1Bz8Yim.js} | 2 +- ...tml-DJPmBi5v.js => stash.html-D1rEWs9W.js} | 2 +- ...-BVURcdFS.js => throttle.html-CDssB4Vw.js} | 2 +- ...DuJXkf-s.js => tsAndvue3.html-8xWV3_Wf.js} | 2 +- ...C54V2441.js => useModule.html-BHtXy4j3.js} | 2 +- ...ml-DJJdqwXN.js => useNpm.html-CKLfIIZn.js} | 2 +- ...l-mIezI9dx.js => usePnpm.html-C3QlcelL.js} | 2 +- ...l-DUPdL_R5.js => useYarn.html-BrgPHvAk.js} | 2 +- ...-ChArXn5X.js => volatile.html-B-u5KWIK.js} | 2 +- ...xXL.js => vue-Direactive.html-CwIH1XaN.js} | 2 +- ...TJj0.js => vue-authority.html-HH15bglq.js} | 2 +- ...li8S.js => vue-in-action.html-DaPOjPap.js} | 2 +- ...AWrxQ.js => vue-nextTick.html-BAWrVlUH.js} | 2 +- ...l-CpOJwwqn.js => vue-pic.html-BGsMqmKX.js} | 2 +- ...I04ly7.js => vue-router1.html-DZlcHgjO.js} | 2 +- ...QGqnFR.js => vue-router2.html-DNXG8jNK.js} | 2 +- ...-DJy1ivmL.js => vue3Emit.html-C3GB9oZp.js} | 2 +- ...HBDaz.js => vue3LifeTime.html-L4zQOdiA.js} | 2 +- ...Dj4ZUdmc.js => vueExtend.html-DnRRc5Za.js} | 2 +- ...\346\263\250\345\206\214.html-CH2QvxT3.js" | 2 +- ...l-Do0j3LhC.js => wrapper.html-aaFCqiMF.js} | 2 +- ....html-DoVf1_uE.js => wsl.html-DiCvzzYG.js} | 2 +- ...p5hWIu.js => x86_openwrt.html-CASth0J6.js} | 2 +- ...\347\232\204\345\235\221.html-CpfYP_fL.js" | 2 +- ...\351\227\256\351\242\230.html-BVVorpF6.js" | 2 +- ...\350\247\243\351\207\212.html-B5GJmRJ1.js" | 2 +- ...\351\227\256\351\242\230.html-B-h8Zfob.js" | 2 +- ...\346\250\241\345\274\217.html-CPvB1dQQ.js" | 2 +- ...\346\210\252\345\233\276.html-DV7uCl6s.js" | 2 +- ...\345\274\225\345\257\274.html-BKBwfgmF.js" | 2 +- ...\345\244\207\344\273\275.html-rdxyp_Zo.js" | 2 +- ...\347\275\221\345\205\263.html-JyFgPLU6.js" | 2 +- ...\346\250\241\345\274\217.html-D86ZbVNZ.js" | 2 +- ...\345\221\250\346\234\237.html-CGxeIKAF.js" | 2 +- ...\350\275\257\344\273\266.html-DO8LI6dF.js" | 2 +- ...\346\250\241\345\274\217.html-1pJjZLuA.js" | 2 +- ...\350\256\276\347\275\256.html-CT2typrG.js" | 2 +- ...\345\256\236\350\267\265.html-dDt_1gUV.js" | 2 +- ...0\207\252\345\273\272nas.html-D-Ky5W6L.js" | 2 +- ...\346\250\241\345\274\217.html-Bq_Ohlm8.js" | 2 +- ...\346\250\241\345\274\217.html-C6zRijOG.js" | 2 +- ...\351\203\250\347\275\262.html-Cup61qP7.js" | 2 +- category/docker/index.html | 8 +- .../git-\346\223\215\344\275\234/index.html" | 8 +- category/index.html | 8 +- category/java/index.html | 8 +- .../java\345\237\272\347\241\200/index.html" | 8 +- category/jdk/index.html | 8 +- .../js\345\237\272\347\241\200/index.html" | 8 +- category/jvm/index.html | 8 +- category/linux/index.html | 8 +- category/markdown/index.html | 8 +- category/maven/index.html | 8 +- .../index.html" | 8 +- category/oauth/index.html | 8 +- category/open-source/index.html | 8 +- category/security/index.html | 8 +- category/spring/index.html | 8 +- category/vite/index.html | 8 +- .../index.html" | 8 +- category/web/index.html | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../\345\277\205\344\274\232/index.html" | 8 +- .../index.html" | 8 +- .../\346\241\206\346\236\266/index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../\350\277\220\347\273\264/index.html" | 8 +- .../index.html" | 8 +- .../\351\233\206\345\220\210/index.html" | 8 +- cpp/index.html | 8 +- cpp/other/1.html | 8 +- cpp/other/2.html | 8 +- cpp/other/index.html | 8 +- cpp/study/index.html | 8 +- designpattern/index.html | 8 +- ...\350\200\205\346\250\241\345\274\217.html" | 8 +- ...\346\263\225\346\250\241\345\274\217.html" | 8 +- ...\347\225\245\346\250\241\345\274\217.html" | 8 +- ...\351\245\260\346\250\241\345\274\217.html" | 8 +- ...\351\223\276\346\250\241\345\274\217.html" | 8 +- frontweb/es5/aboutAsync.html | 8 +- frontweb/es5/aboutEvent.html | 8 +- frontweb/es5/aboutThis.html | 8 +- frontweb/es5/asyncError.html | 8 +- frontweb/es5/crossDomain.html | 8 +- frontweb/es5/crossDomain2.html | 8 +- frontweb/es5/index.html | 8 +- frontweb/es5/lazyLoad.html | 8 +- frontweb/es5/throttle.html | 8 +- frontweb/es6/index.html | 8 +- ...\346\234\200\345\244\247\345\200\274.html" | 8 +- frontweb/es6/promise.html | 8 +- frontweb/es6/useModule.html | 8 +- frontweb/es6/useNpm.html | 8 +- frontweb/es6/usePnpm.html | 8 +- frontweb/es6/useYarn.html | 8 +- frontweb/index.html | 8 +- frontweb/nodejs/index.html | 8 +- ...\350\243\205\351\227\256\351\242\230.html" | 8 +- frontweb/typeScript/action-usage.html | 8 +- frontweb/typeScript/axios.html | 8 +- frontweb/typeScript/basic-usage.html | 8 +- frontweb/typeScript/fanType.html | 8 +- frontweb/typeScript/index.html | 8 +- frontweb/typeScript/tsAndvue3.html | 8 +- frontweb/vite/index.html | 8 +- ...\344\271\211\346\240\241\351\252\214.html" | 8 +- frontweb/vue/eventBus.html | 8 +- frontweb/vue/index.html | 8 +- frontweb/vue/vue-Direactive.html | 8 +- frontweb/vue/vue-authority.html | 8 +- frontweb/vue/vue-in-action.html | 8 +- frontweb/vue/vue-nextTick.html | 8 +- frontweb/vue/vue-pic.html | 8 +- frontweb/vue/vue-router1.html | 8 +- frontweb/vue/vue-router2.html | 8 +- frontweb/vue/vue3Emit.html | 8 +- frontweb/vue/vue3LifeTime.html | 8 +- frontweb/vue/vueExtend.html | 8 +- ...\347\232\204\345\256\236\350\267\265.html" | 8 +- guide/disable.html | 6 +- guide/encrypt.html | 8 +- guide/index.html | 8 +- guide/markdown.html | 8 +- guide/page.html | 6 +- home.html | 10 +- index.html | 12 +- java/advance/Arthas.html | 8 +- java/advance/Collection.html | 8 +- java/advance/CompileJdk11.html | 8 +- java/advance/Concurrent.html | 8 +- java/advance/Future.html | 8 +- java/advance/IO-Model.html | 8 +- java/advance/IO-model1.html | 8 +- java/advance/ImplementSameInterface.html | 8 +- java/advance/MysqlMasterSlave.html | 8 +- java/advance/NativeMethod.html | 8 +- java/advance/ParentDelegationClassLoader.html | 8 +- java/advance/ProxyInJava.html | 8 +- java/advance/Synchronized.html | 8 +- java/advance/ThreadLocal.html | 8 +- java/advance/ThreadPool.html | 8 +- java/advance/index.html | 8 +- java/advance/io.html | 8 +- java/base/ConstantPool.html | 8 +- java/base/CustomLRU.html | 8 +- java/base/IntegerConstantPool.html | 8 +- java/base/Serialization.html | 8 +- java/base/String.html | 8 +- java/base/index.html | 8 +- java/framework/index.html | 8 +- .../mybatis/MybatisPlusDataSource.html | 8 +- java/framework/mybatis/index.html | 8 +- java/framework/mybatis/mybatis.html | 8 +- java/framework/security/Authorization.html | 8 +- .../CustomAuthenticationProvider.html | 8 +- java/framework/security/CustomLoginPage.html | 8 +- .../security/CustomTokenAuthentication.html | 8 +- .../security/DelegatingFilterProxy.html | 8 +- .../security/OAuth2Authentication.html | 8 +- .../security/OncePerRequestFilter.html | 8 +- java/framework/security/PreAuthorize.html | 8 +- java/framework/security/SSO.html | 8 +- .../security/SecurityFilterChain.html | 8 +- java/framework/security/Session.html | 8 +- java/framework/security/index.html | 8 +- java/framework/security/note.html | 8 +- ...-security-oauth2-authorization-server.html | 8 +- java/framework/spring/Annotation.html | 8 +- java/framework/spring/BeanPostProcessor.html | 8 +- java/framework/spring/CircularDependency.html | 8 +- .../spring/DesignPatternInSpring.html | 8 +- .../spring/OncePerRequestFilter.html | 8 +- java/framework/spring/SpringAOP.html | 8 +- java/framework/spring/SpringCache.html | 8 +- .../spring/SpringExtensionPoint.html | 8 +- java/framework/spring/SpringIOC.html | 8 +- java/framework/spring/SpringMVC.html | 8 +- .../framework/spring/SpringSourceAnalize.html | 8 +- java/framework/spring/Validator.html | 8 +- java/framework/spring/index.html | 8 +- java/framework/springboot/AOPLog.html | 8 +- .../springboot/CollectionInject.html | 8 +- java/framework/springboot/Http2.html | 8 +- .../SpringBootAutoConfiguration.html | 8 +- java/framework/springboot/Swagger.html | 8 +- java/framework/springboot/index.html | 8 +- .../springboot/\351\203\250\347\275\262.html" | 8 +- .../springcloud/SpringCloudGateway.html | 8 +- java/framework/springcloud/index.html | 8 +- java/index.html | 8 +- java/jvm/ClassLoader.html | 8 +- java/jvm/MemoryModel.html | 8 +- java/jvm/NewObject.html | 8 +- java/jvm/ObjectReference.html | 8 +- java/jvm/SetObjectNull.html | 8 +- java/jvm/StringAdd.html | 8 +- java/jvm/index.html | 8 +- java/jvm/volatile.html | 8 +- ...\350\257\215\350\247\243\351\207\212.html" | 8 +- java/other/JdkVersion.html | 8 +- java/other/gradle/index.html | 8 +- java/other/gradle/wrapper.html | 8 +- java/other/index.html | 8 +- .../other/locateproblem/TooManyOpenFiles.html | 9 +- .../locateproblem/Undertow.xxxNotFount.html | 8 +- java/other/locateproblem/index.html | 8 +- ...\346\234\254\347\232\204\345\235\221.html" | 8 +- ...\347\247\215\351\227\256\351\242\230.html" | 8 +- java/other/log/index.html | 8 +- java/other/log/logback.html | 8 +- .../maven/build\346\240\207\347\255\276.html" | 8 +- java/other/maven/import.html | 8 +- java/other/maven/index.html | 8 +- java/other/maven/multiModule.html | 8 +- java/other/maven/problem.html | 8 +- ...\345\221\275\345\221\250\346\234\237.html" | 8 +- .../Jellyfin\346\220\255\345\273\272.html" | 8 +- myserver/index.html | 8 +- myserver/x86_openwrt.html | 8 +- ...\347\224\261\347\275\221\345\205\263.html" | 8 +- ...\347\273\234\350\256\276\347\275\256.html" | 8 +- "myserver/\350\207\252\345\273\272nas.html" | 8 +- other/books/ebooks.html | 8 +- other/books/index.html | 8 +- other/computerprinciple/TCP-IP.html | 8 +- other/computerprinciple/index.html | 8 +- other/database/CPUOverLoad.html | 8 +- other/database/MysqlCollate.html | 8 +- other/database/MysqlNote.html | 8 +- other/database/MysqlRemoteConnect.html | 8 +- other/database/Recurse.html | 8 +- other/database/SQLOptimization.html | 8 +- other/database/index.html | 8 +- ...\345\272\223\345\244\207\344\273\275.html" | 8 +- other/distributeservice/DistributeLock.html | 8 +- other/distributeservice/Nacos.html | 8 +- other/distributeservice/index.html | 8 +- other/docker/Docker.html | 8 +- other/docker/ServiceInstall.html | 8 +- other/docker/index.html | 8 +- other/essay/2022-04-12.html | 8 +- other/essay/BTree.html | 8 +- other/essay/CDN.html | 8 +- other/essay/ChromeDevTools.html | 8 +- other/essay/CloudService.html | 8 +- other/essay/DeployGithubPage.html | 8 +- ...\346\234\257\351\200\211\345\236\213.html" | 8 +- other/essay/Jenkins.html | 8 +- other/essay/TyporaPicgo.html | 8 +- ...lasticSearch\346\223\215\344\275\234.html" | 8 +- .../essay/elk\351\203\250\347\275\262.html" | 8 +- ...\347\232\204\351\234\200\346\261\202.html" | 8 +- other/essay/index.html | 8 +- ...\345\212\241\346\263\250\345\206\214.html" | 8 +- ...\347\273\237\345\274\225\345\257\274.html" | 8 +- other/git/BatchDeleteGitHubRepo.html | 8 +- other/git/GitCommands.html | 8 +- other/git/branch01.html | 8 +- other/git/branch02.html | 8 +- other/git/fatal.html | 8 +- other/git/gitConflict.html | 8 +- other/git/gitRebase.html | 8 +- other/git/gitwork.html | 8 +- other/git/index.html | 8 +- other/git/mergeBranch.html | 8 +- other/git/proxy.html | 8 +- other/git/rebaseAndMerge.html | 8 +- other/git/reset.html | 8 +- other/git/stash.html | 8 +- other/hardware/CPU.html | 8 +- other/hardware/index.html | 8 +- other/index.html | 8 +- other/linux/CentOS.html | 8 +- other/linux/CommonUsedCMD.html | 8 +- other/linux/Curl.html | 8 +- other/linux/InstallMysqlWithDocker.html | 8 +- other/linux/Manjaro.html | 8 +- other/linux/MountDisk.html | 8 +- other/linux/MultiNetworkCard.html | 8 +- other/linux/Samba.html | 8 +- other/linux/ShareBetweenWindowsAndLinux.html | 8 +- other/linux/TcpDump.html | 8 +- other/linux/Wifi.html | 8 +- other/linux/firewall.html | 8 +- other/linux/index.html | 8 +- other/linux/wsl.html | 8 +- "other/linux/\346\210\252\345\233\276.html" | 8 +- other/markdown/index.html | 8 +- other/oauth2/01.html | 8 +- other/oauth2/02.html | 8 +- other/oauth2/03.html | 8 +- other/oauth2/04.html | 8 +- other/oauth2/05.html | 8 +- other/oauth2/06.html | 8 +- other/oauth2/07.html | 8 +- other/oauth2/08.html | 8 +- other/oauth2/09.html | 8 +- other/oauth2/10.html | 8 +- other/oauth2/11.html | 8 +- other/oauth2/12.html | 8 +- other/oauth2/13.html | 8 +- other/oauth2/14.html | 8 +- other/oauth2/index.html | 8 +- ...\350\257\267\350\257\201\344\271\246.html" | 8 +- other/pve/firewall.html | 8 +- other/pve/index.html | 8 +- other/rabbitmq/index.html | 8 +- other/software/index.html | 8 +- ...\350\247\243\350\275\257\344\273\266.html" | 8 +- other/sono/SUC.html | 8 +- other/sono/index.html | 8 +- other/tools/Idea.html | 8 +- other/tools/SoftWare.html | 8 +- other/tools/VsCode.html | 8 +- other/tools/index.html | 8 +- other/training/CloudServiceTraining.html | 8 +- other/training/SSO.html | 8 +- other/training/draw.html | 8 +- other/training/index.html | 8 +- other/web/BuildWebProject.html | 8 +- other/web/Cookie.html | 8 +- other/web/Http.html | 8 +- other/web/Jwt.html | 8 +- other/web/OAUTH_LOGIN.html | 8 +- other/web/RefreshToken.html | 8 +- other/web/Restful.html | 8 +- other/web/index.html | 8 +- other/windows/WSL.html | 8 +- other/windows/index.html | 8 +- sitemap.xml | 2 +- slide.html | 6 +- star/index.html | 8 +- "tag/---\345\271\266\345\217\221/index.html" | 8 +- tag/index.html | 8 +- tag/linux/index.html | 8 +- tag/markdown/index.html | 8 +- tag/mysql/index.html | 8 +- tag/oauth/index.html | 8 +- tag/springboot/index.html | 8 +- tag/sso/index.html | 8 +- tag/ts/index.html | 8 +- tag/vite/index.html | 8 +- tag/vue3/index.html | 8 +- .../index.html" | 8 +- tag/web/index.html | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- "tag/\345\277\205\344\274\232/index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- .../index.html" | 8 +- "tag/\346\227\245\345\277\227/index.html" | 8 +- "tag/\346\241\206\346\236\266/index.html" | 8 +- "tag/\347\246\201\347\224\250/index.html" | 8 +- .../index.html" | 8 +- "tag/\350\277\220\347\273\264/index.html" | 8 +- .../index.html" | 8 +- "tag/\351\233\206\345\220\210/index.html" | 8 +- .../index.html" | 8 +- timeline/index.html | 8 +- 665 files changed, 1889 insertions(+), 1890 deletions(-) rename assets/{01.html-BpsuZlEh.js => 01.html-jkQdEnIh.js} (99%) rename assets/{02.html-9y4kTt4c.js => 02.html-B3UlmDxL.js} (99%) rename assets/{03.html-_Fpqszha.js => 03.html-CK1TN99Q.js} (99%) rename assets/{04.html-BA8_N2LW.js => 04.html-CeKJQimw.js} (99%) rename assets/{05.html-C12aFhzW.js => 05.html-CMuhT-M0.js} (99%) rename assets/{06.html-fv-ofJNx.js => 06.html-BX6nV64C.js} (99%) rename assets/{07.html-C8FkfsLs.js => 07.html-CCBD9WTC.js} (99%) rename assets/{08.html-DoM55lqI.js => 08.html-BJMKXxmH.js} (99%) rename assets/{09.html-Aa1WASXU.js => 09.html-7MXGKP1a.js} (99%) rename assets/{1.html-BsLOzPLz.js => 1.html-CeguXEpG.js} (99%) rename assets/{10.html-D50UJxV3.js => 10.html-IqVcln-C.js} (99%) rename assets/{11.html-DLQhthQQ.js => 11.html-kw9nHLna.js} (99%) rename assets/{12.html-By4kPY-J.js => 12.html-DxvMj_JN.js} (99%) rename assets/{13.html-DbVAkfja.js => 13.html-CBYdZLrV.js} (99%) rename assets/{14.html-DC_xS0f7.js => 14.html-B-d6pKTK.js} (99%) rename assets/{2.html-CDG7SSJK.js => 2.html-Dp6E9lgb.js} (97%) rename assets/{2022-04-12.html-BnI2oaJK.js => 2022-04-12.html-omw7Q29H.js} (99%) rename assets/{404.html-2xAmqrLX.js => 404.html-CFV1KPyG.js} (94%) rename assets/{AOPLog.html-jSrFg30s.js => AOPLog.html-WkAcygqg.js} (99%) rename assets/{Annotation.html-Bu-Jo73Z.js => Annotation.html-CN1Pgot9.js} (99%) rename assets/{Arthas.html-D1k7ux4i.js => Arthas.html-BDHUU6lY.js} (99%) rename assets/{Authorization.html-BqA7v7f3.js => Authorization.html-DYO-IVFw.js} (98%) rename assets/{BTree.html-CuSxbXvJ.js => BTree.html-DJQoEL8B.js} (97%) rename assets/{BatchDeleteGitHubRepo.html-WNdAC_un.js => BatchDeleteGitHubRepo.html-DtqMXGl3.js} (98%) rename assets/{BeanPostProcessor.html-2fGhuRBC.js => BeanPostProcessor.html-CLtCpKMO.js} (99%) rename assets/{BuildWebProject.html-D1F1nzup.js => BuildWebProject.html-D4yumRMO.js} (99%) rename assets/{CDN.html-rIf3-Wbw.js => CDN.html-Cd7ildNJ.js} (99%) rename assets/{CPU.html-D5a-tI0p.js => CPU.html-BmK1dlqs.js} (97%) rename assets/{CPUOverLoad.html-Cl4MUidN.js => CPUOverLoad.html-B_JZj2Bd.js} (99%) rename assets/{CentOS.html-C59_Q0zj.js => CentOS.html-CSukTNYu.js} (96%) rename assets/{ChromeDevTools.html-BoMI5RzK.js => ChromeDevTools.html-jvUjLE8a.js} (97%) rename assets/{CircularDependency.html-Cy-x1Wkb.js => CircularDependency.html-DRv79HhX.js} (99%) rename assets/{ClassLoader.html-9dp_fs5y.js => ClassLoader.html-fNJjFm9m.js} (99%) rename assets/{CloudService.html-DjGDK1Z4.js => CloudService.html-DKHWbJgc.js} (99%) rename assets/{CloudServiceTraining.html-C8NinniP.js => CloudServiceTraining.html-CsW1-SEa.js} (99%) rename assets/{Collection.html-CeAHa-2T.js => Collection.html-D-nzOS-a.js} (97%) rename assets/{CollectionInject.html-Ds4tphSm.js => CollectionInject.html-Wk5anYr4.js} (99%) rename assets/{CommonUsedCMD.html-WpgS454f.js => CommonUsedCMD.html-CHwLkMH3.js} (99%) rename assets/{CompileJdk11.html-DQ_9XJev.js => CompileJdk11.html-k8rk73iW.js} (99%) rename assets/{Concurrent.html-ClwuWlCD.js => Concurrent.html-DQRS-IpH.js} (99%) rename assets/{ConstantPool.html-BlmpO7p_.js => ConstantPool.html-C5zSxd7l.js} (99%) rename assets/{Cookie.html-ffctXEz0.js => Cookie.html-BobniOCU.js} (99%) rename assets/{Curl.html-DlrJSikw.js => Curl.html-BO77gaot.js} (99%) rename assets/{CustomAuthenticationProvider.html-D7qaxkkr.js => CustomAuthenticationProvider.html-C05ZFsDP.js} (99%) rename assets/{CustomLRU.html-CX9zw1r2.js => CustomLRU.html-DHBi8rnr.js} (99%) rename assets/{CustomLoginPage.html-DheOzz7K.js => CustomLoginPage.html-R1ammlY9.js} (99%) rename assets/{CustomTokenAuthentication.html-BAwukKcd.js => CustomTokenAuthentication.html-DcEKKC-z.js} (99%) rename assets/{DelegatingFilterProxy.html-BfP7rcad.js => DelegatingFilterProxy.html-CK8Yiklp.js} (99%) rename assets/{DeployGithubPage.html-2HbSyCiP.js => DeployGithubPage.html-COQNI509.js} (99%) rename assets/{DesignPatternInSpring.html-Cz-ZqYXH.js => DesignPatternInSpring.html-B1tCH6O1.js} (99%) rename assets/{DistributeLock.html-PlGmUQ21.js => DistributeLock.html-C1ihBiBe.js} (99%) rename assets/{Docker.html-BaiyWfkU.js => Docker.html-DzlBwcZ1.js} (97%) rename assets/{Future.html-C-IGHrDE.js => Future.html-CI2mkHT2.js} (98%) rename assets/{GitCommands.html-_C8ZQnH_.js => GitCommands.html-BB60xrRT.js} (99%) rename assets/{Http.html-C3TqYNG0.js => Http.html-Cx1NIixk.js} (99%) rename assets/{Http2.html-yFvJ3oRy.js => Http2.html-B3cAbYAj.js} (97%) rename "assets/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html-DiDYCl7r.js" => "assets/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html-B2_U3umv.js" (99%) rename assets/{IO-Model.html-BPdWwFrU.js => IO-Model.html-O-6ElLZv.js} (99%) rename assets/{IO-model1.html-DbFAWkDL.js => IO-model1.html-Bko-tL29.js} (99%) rename assets/{Idea.html-N3gMCtcp.js => Idea.html-DFw9lvM7.js} (98%) rename assets/{ImplementSameInterface.html-8aOTv4So.js => ImplementSameInterface.html-iv1dGOUt.js} (99%) rename assets/{InstallMysqlWithDocker.html-cGZHFWnE.js => InstallMysqlWithDocker.html-Dlzu3km3.js} (99%) rename assets/{IntegerConstantPool.html-BgqTmJIN.js => IntegerConstantPool.html-B-ijklmd.js} (99%) rename assets/{JdkVersion.html-BvKsTRiv.js => JdkVersion.html-CS9P09_u.js} (99%) rename "assets/Jellyfin\346\220\255\345\273\272.html-CLPGwkSq.js" => "assets/Jellyfin\346\220\255\345\273\272.html-B4JCtBiz.js" (97%) rename assets/{Jenkins.html-D5aTJlwz.js => Jenkins.html-e7mi9H_K.js} (99%) rename assets/{Jwt.html-BI9W3_p_.js => Jwt.html-CP1cMj9m.js} (99%) rename assets/{Manjaro.html-CTuhb7rc.js => Manjaro.html-Dy2e0Hrp.js} (99%) rename assets/{MemoryModel.html-Bzwww5iG.js => MemoryModel.html-CRsJwAOr.js} (99%) rename assets/{MountDisk.html-AWo0fohf.js => MountDisk.html-BqspOYo5.js} (99%) rename assets/{MultiNetworkCard.html-ZHqfhuQy.js => MultiNetworkCard.html-BdZVkWRH.js} (99%) rename assets/{MybatisPlusDataSource.html-2C9lgUhC.js => MybatisPlusDataSource.html-J7-G54ls.js} (99%) rename assets/{MysqlCollate.html-D6Cxtc4N.js => MysqlCollate.html-BXuIpRKi.js} (99%) rename assets/{MysqlMasterSlave.html-CfozUk_D.js => MysqlMasterSlave.html-CPoF9taw.js} (99%) rename assets/{MysqlNote.html-C-U71JLc.js => MysqlNote.html-Cy1AGfO1.js} (99%) rename assets/{MysqlRemoteConnect.html-DGYiJFHo.js => MysqlRemoteConnect.html-B6Rz_Dfl.js} (98%) rename assets/{Nacos.html-D6kbUFLh.js => Nacos.html-DC2XGbQ_.js} (98%) rename assets/{NativeMethod.html-BICH8rB4.js => NativeMethod.html-CW2rOE3c.js} (99%) rename assets/{NewObject.html-_zRwVPgF.js => NewObject.html-DNLlDT5H.js} (99%) rename assets/{OAUTH_LOGIN.html-Dt5KbJsg.js => OAUTH_LOGIN.html-DZ9PB-Wb.js} (99%) rename assets/{OAuth2Authentication.html-BPOz3SI3.js => OAuth2Authentication.html-ChmdjSKd.js} (99%) rename assets/{ObjectReference.html-BttKKJld.js => ObjectReference.html-CG8KX2PY.js} (99%) rename assets/{OncePerRequestFilter.html-BEOxLqvE.js => OncePerRequestFilter.html-DIikqGft.js} (99%) rename assets/{OncePerRequestFilter.html-C_TVU5f6.js => OncePerRequestFilter.html-DvJ1C4ir.js} (99%) rename assets/{ParentDelegationClassLoader.html-lWjNd7F6.js => ParentDelegationClassLoader.html-BE03CURY.js} (99%) rename assets/{PreAuthorize.html-B_BWAtGw.js => PreAuthorize.html-DFU2o_nN.js} (97%) rename assets/{ProxyInJava.html-C0ibZtz7.js => ProxyInJava.html-Craly_CB.js} (99%) rename assets/{Recurse.html-6jai2Fmm.js => Recurse.html-CzvVNt5P.js} (99%) rename assets/{RefreshToken.html-CFwkbthk.js => RefreshToken.html-B7uT6L3n.js} (99%) rename assets/{Restful.html-X9thQzel.js => Restful.html-BeZrEgn9.js} (97%) rename assets/{SQLOptimization.html-CzzwcOPq.js => SQLOptimization.html-DB9Wuz97.js} (98%) rename assets/{SSO.html-DgwliJOO.js => SSO.html-DB2D0akT.js} (99%) rename assets/{SSO.html-DBoqluIC.js => SSO.html-DKOv-tJ8.js} (98%) rename assets/{SUC.html-BJ48MGxE.js => SUC.html-DtVpWo_q.js} (99%) rename assets/{Samba.html-DATE_iKU.js => Samba.html-D8HLrsJP.js} (99%) rename assets/{SecurityFilterChain.html-D0yc0vvl.js => SecurityFilterChain.html-j_4ZmVEf.js} (99%) rename assets/{Serialization.html-BPyqpG-l.js => Serialization.html-BJVNALhI.js} (99%) rename assets/{ServiceInstall.html-CCIiUtbe.js => ServiceInstall.html-BaooIkHM.js} (99%) rename assets/{Session.html-PVXvBLSr.js => Session.html-CKk3uN_p.js} (98%) rename assets/{SetObjectNull.html-pva3Gj1n.js => SetObjectNull.html-BA6rF5JL.js} (99%) rename assets/{ShareBetweenWindowsAndLinux.html-Vr4o6QxT.js => ShareBetweenWindowsAndLinux.html-CJ7U7icg.js} (97%) rename assets/{SoftWare.html-DwRE_esc.js => SoftWare.html-6EROxq9x.js} (97%) rename assets/{SpringAOP.html-0F2yJUPF.js => SpringAOP.html-BR3lenMn.js} (99%) rename assets/{SpringBootAutoConfiguration.html-B2Ew9z9M.js => SpringBootAutoConfiguration.html-DyJd0iCn.js} (99%) rename assets/{SpringCache.html-BcT_2YyF.js => SpringCache.html-B2PNt9GB.js} (99%) rename assets/{SpringCloudGateway.html-DptX-4Hc.js => SpringCloudGateway.html-Dc9fg5OX.js} (99%) rename assets/{SpringExtensionPoint.html-BY3OGoZJ.js => SpringExtensionPoint.html-BdwK_BjA.js} (99%) rename assets/{SpringIOC.html-D2ZKIq-l.js => SpringIOC.html-BqsqDvdX.js} (99%) rename assets/{SpringMVC.html-CW7qiD-c.js => SpringMVC.html-D8cETPq8.js} (99%) rename assets/{SpringSourceAnalize.html-C86lj3k3.js => SpringSourceAnalize.html-D0vztr7E.js} (96%) rename assets/{String.html-CI6Ty1q7.js => String.html-Ca6nK_a0.js} (99%) rename assets/{StringAdd.html-BihzcLHW.js => StringAdd.html-fGViknMr.js} (99%) rename assets/{Swagger.html-BrC3rGwr.js => Swagger.html-QZsEFOI2.js} (99%) rename assets/{Synchronized.html-Dhr9EviB.js => Synchronized.html-Bz2_U7rD.js} (97%) rename assets/{TCP-IP.html-DSyGo702.js => TCP-IP.html-obsJBTgk.js} (99%) rename assets/{TcpDump.html-cL04DF4f.js => TcpDump.html-DFEN5LYg.js} (99%) rename assets/{ThreadLocal.html-CHq5iQUn.js => ThreadLocal.html-YdsPoeQ1.js} (99%) rename assets/{ThreadPool.html-B4bZt9rf.js => ThreadPool.html-D9vbBMnV.js} (99%) rename assets/{TooManyOpenFiles.html-ChBajVz6.js => TooManyOpenFiles.html-DBlFfNwX.js} (99%) rename assets/{TyporaPicgo.html-4B31qBXz.js => TyporaPicgo.html-DyxSZmxq.js} (99%) rename assets/{Undertow.xxxNotFount.html-CjycD9Jx.js => Undertow.xxxNotFount.html-X1F463A1.js} (99%) rename assets/{Validator.html-CvrJ26C9.js => Validator.html-DOW_vPxb.js} (99%) rename assets/{VsCode.html-o-J8uPWj.js => VsCode.html-BCn8Egxl.js} (99%) rename assets/{WSL.html-irNHs4ez.js => WSL.html-BIz7aujd.js} (98%) rename assets/{Wifi.html-D7OcL7nb.js => Wifi.html-DCwxVnq6.js} (98%) rename assets/{aboutAsync.html-CVw3VyIR.js => aboutAsync.html-CukGl2Bf.js} (99%) rename assets/{aboutEvent.html-1fGRZu30.js => aboutEvent.html-CvhiVca8.js} (99%) rename assets/{aboutThis.html-DlclfKVB.js => aboutThis.html-B-2Ir7Kw.js} (99%) rename assets/{action-usage.html-xDUlIzb_.js => action-usage.html-DFNFrJyT.js} (99%) rename assets/{app-CVMfKeWw.js => app-_Oi5YZFn.js} (63%) rename assets/{asyncError.html-WxV4KJUL.js => asyncError.html-npKDZnPq.js} (99%) rename assets/{axios.html-DIiEtqYW.js => axios.html-BXoNbwVV.js} (99%) rename assets/{basic-usage.html-CEUsK9ry.js => basic-usage.html-CBqhMzrq.js} (99%) rename assets/{branch01.html--1ri9Qlh.js => branch01.html-CEgmUaaW.js} (98%) rename assets/{branch02.html-CScQPDBP.js => branch02.html-CkbKOtQo.js} (99%) rename "assets/build\346\240\207\347\255\276.html-uLUm0P1E.js" => "assets/build\346\240\207\347\255\276.html-CyXt4kH2.js" (99%) rename assets/{crossDomain.html-DCiVSBRz.js => crossDomain.html-CSDExzG7.js} (99%) rename assets/{crossDomain2.html-DTphraX3.js => crossDomain2.html-CA7WfkLl.js} (99%) rename "assets/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html-D3MLskiS.js" => "assets/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html-ykZMVUjH.js" (99%) rename assets/{disable.html-D2cQj4ic.js => disable.html-D_vnyg33.js} (97%) rename assets/{draw.html-3iC56TfH.js => draw.html-DWXvV2Rt.js} (99%) rename assets/{ebooks.html-D9FixpLr.js => ebooks.html-BgNEqnIF.js} (98%) rename "assets/elasticSearch\346\223\215\344\275\234.html-B7hezSq2.js" => "assets/elasticSearch\346\223\215\344\275\234.html-DivcwrqA.js" (99%) rename "assets/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html-Bl7vnRpA.js" => "assets/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html-BDHhWiHk.js" (99%) rename "assets/elk\351\203\250\347\275\262.html-DF5G_BDL.js" => "assets/elk\351\203\250\347\275\262.html-hYHFl7Qx.js" (99%) rename assets/{encrypt.html-BEDsFZkv.js => encrypt.html-BVOYksPN.js} (95%) rename assets/{eventBus.html-DxETLIkG.js => eventBus.html-B0dTFe1w.js} (98%) rename assets/{fanType.html-DHpjy03Z.js => fanType.html-34EbXHhU.js} (99%) rename assets/{fatal.html-CLx7xyLl.js => fatal.html-CuBowvDc.js} (99%) rename assets/{firewall.html-axsN7QwQ.js => firewall.html-Bcf4dLh0.js} (99%) rename assets/{firewall.html-CDBaOBQ8.js => firewall.html-CnVk-61G.js} (98%) rename assets/{gitConflict.html-XWxwXqnJ.js => gitConflict.html-CD4vs1Ct.js} (99%) rename assets/{gitRebase.html-Bh0TFroo.js => gitRebase.html-DF3bIliz.js} (99%) rename assets/{gitwork.html-DCYwt7rE.js => gitwork.html-BenGZ5gF.js} (99%) rename assets/{home.html-CdI4hI0D.js => home.html-Ds_ny-Cd.js} (52%) rename assets/{import.html-DtYCD-Aq.js => import.html-CNG84pM6.js} (99%) rename "assets/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html-qDdBmZbJ.js" => "assets/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html-Disuf4MT.js" (99%) rename assets/{index.html-DxrpWTG5.js => index.html--jCoRugf.js} (96%) rename assets/{index.html-CrtTLfwo.js => index.html-0gfJBSgv.js} (99%) rename assets/{index.html-zTuDnnQ9.js => index.html-1bodzj_2.js} (94%) rename assets/{index.html-BX9tTXXW.js => index.html-2E2sJd9T.js} (94%) rename assets/{index.html-D3LGsC8Z.js => index.html-3y6s76rT.js} (93%) rename assets/{index.html-AC98BttZ.js => index.html-43Da7xtg.js} (94%) rename assets/{index.html-SQqXSiBs.js => index.html-52PFUvm3.js} (96%) delete mode 100644 assets/index.html-Ah6mlgvk.js rename assets/{index.html-CPxFcKAG.js => index.html-B0HiH-Xn.js} (94%) rename assets/{index.html-Dhyapq1H.js => index.html-BA11w6YR.js} (94%) rename assets/{index.html-BQhu6ulG.js => index.html-BAA1oPlT.js} (94%) rename assets/{index.html-B35-Vn16.js => index.html-BB0RPG_h.js} (95%) rename assets/{index.html-EDXVLWDR.js => index.html-BBsSpY4m.js} (96%) rename assets/{index.html-XxdR190C.js => index.html-BDDCxsP9.js} (94%) rename assets/{index.html-Cnhk4PBp.js => index.html-BGEnglY6.js} (97%) rename assets/{index.html-Ds23VMOY.js => index.html-BGmSAx78.js} (94%) rename assets/{index.html-B2inZhc8.js => index.html-BIoY637M.js} (96%) rename assets/{index.html-BKgtb9Ah.js => index.html-BJ_XZI-3.js} (94%) rename assets/{index.html-BUg70Vrr.js => index.html-BJxwK7AA.js} (94%) rename assets/{index.html-DaPOtDhI.js => index.html-BLiK3lZA.js} (96%) rename assets/{index.html-pad8GR4o.js => index.html-BNCitO52.js} (94%) rename assets/{index.html-Cu9v8LOw.js => index.html-BOFOt1os.js} (94%) rename assets/{index.html-CF8Xpy4A.js => index.html-BR_hmahK.js} (94%) rename assets/{index.html-xL8R2rvI.js => index.html-Be9YOXiD.js} (94%) rename assets/{index.html-DZIt25hg.js => index.html-BgeMLVVF.js} (97%) rename assets/{index.html-CpRQKI06.js => index.html-BgghxhP9.js} (94%) rename assets/{index.html-C6jwACfV.js => index.html-Bk4Xl8z2.js} (94%) rename assets/{index.html-CUh5IBbk.js => index.html-BkurDFO4.js} (94%) rename assets/{index.html-RGleUAuC.js => index.html-BluLs2ps.js} (94%) rename assets/{index.html-DKR14bqO.js => index.html-Bm2QW3Yx.js} (96%) rename assets/{index.html-COzc4rHJ.js => index.html-Bn0M7tTB.js} (96%) rename assets/{index.html-DqHDZd6R.js => index.html-BoICU8sT.js} (93%) rename assets/{index.html-Ci4VSHuy.js => index.html-Bq73GOIG.js} (94%) rename assets/{index.html-uyHOrigb.js => index.html-Bt1B_fZ7.js} (94%) rename assets/{index.html-BMhOFPmp.js => index.html-BtN-4LZa.js} (96%) rename assets/{index.html-CJbe2oEP.js => index.html-BwnKcSJN.js} (94%) rename assets/{index.html-DiiTcF6s.js => index.html-Bx1FwJMs.js} (97%) rename assets/{index.html-EMVH5RI8.js => index.html-Bxi5AXy-.js} (93%) rename assets/{index.html-dkKrYb2Y.js => index.html-ByKY6u5T.js} (93%) rename assets/{index.html-CL8Rn6LN.js => index.html-C2vp8KMJ.js} (94%) rename assets/{index.html-DZIE4r_x.js => index.html-C4Z9a5GS.js} (94%) rename assets/{index.html-BOWROSmg.js => index.html-C6FMNcFL.js} (94%) rename assets/{index.html-BtjOnGEp.js => index.html-C6kiZ_HF.js} (96%) rename assets/{index.html-DKQaObvm.js => index.html-C8vCtcrV.js} (94%) rename assets/{index.html-BnfXAjNS.js => index.html-CEilrxfK.js} (94%) rename assets/{index.html-DQDf-iB8.js => index.html-CJQlCK8P.js} (98%) rename assets/{index.html-BfisB8tx.js => index.html-CNk3LVBO.js} (94%) rename assets/{index.html-CLj8LoVt.js => index.html-CONpmD-V.js} (93%) rename assets/{index.html-CmHVopsM.js => index.html-CTGnNX68.js} (93%) rename assets/{index.html-DCau-SSn.js => index.html-CV_dUHJe.js} (94%) rename assets/{index.html-Dv16igET.js => index.html-CXTrskFo.js} (94%) rename assets/{index.html-E2fKYy3S.js => index.html-C_jbP7SN.js} (94%) rename assets/{index.html-CFkGmFia.js => index.html-CbyN5TWa.js} (93%) rename assets/{index.html-76c_E0sL.js => index.html-ChsojBeT.js} (94%) rename assets/{index.html-CzP4p5XK.js => index.html-CiPcuEin.js} (94%) rename assets/{index.html-DAKYBthR.js => index.html-Cjv6EKnU.js} (96%) rename assets/{index.html-BHP5lSWG.js => index.html-ClRCgRtJ.js} (94%) rename assets/{index.html-CeTjFoZy.js => index.html-CntKYRgb.js} (96%) rename assets/{index.html-HvLMGTgp.js => index.html-CrqLYD8s.js} (94%) rename assets/{index.html-f8Jf88Pj.js => index.html-CtpU9Ex-.js} (94%) rename assets/{index.html-CTOJOPSY.js => index.html-CuULl194.js} (97%) rename assets/{index.html-CJudebxY.js => index.html-CvVo_Iy8.js} (94%) rename assets/{index.html-Bo2PC9xb.js => index.html-Cwy-RPtK.js} (94%) rename assets/{index.html-FIqAJepD.js => index.html-D0WBxxKk.js} (94%) rename assets/{index.html-DJeEzsTa.js => index.html-D1zTD58M.js} (94%) rename assets/{index.html-B6iZ__OM.js => index.html-D3l9AP0G.js} (94%) rename assets/{index.html-hGAHmgqz.js => index.html-D4BNQ3HG.js} (96%) rename assets/{index.html-oz23N8Lk.js => index.html-D5Bas5eh.js} (96%) rename assets/{index.html-_iWg3Z3W.js => index.html-D5egMLMa.js} (96%) rename assets/{index.html-CHghYPfg.js => index.html-DAGR5a1Q.js} (96%) rename assets/{index.html-C3kmdsDq.js => index.html-DDdnWKF-.js} (94%) rename assets/{index.html-BvXx7kZb.js => index.html-DERaj909.js} (94%) rename assets/{index.html-BJvieq0-.js => index.html-DESeP-r7.js} (96%) rename assets/{index.html-D5JX-dxI.js => index.html-DEubCK6b.js} (96%) rename assets/{index.html-dhU_SREp.js => index.html-DFsL5gjr.js} (93%) rename assets/{index.html-DKhBFgRg.js => index.html-DH61zfDk.js} (94%) rename assets/{index.html-CToWg7cr.js => index.html-DHWvYkOD.js} (96%) rename assets/{index.html-BOnFN1d7.js => index.html-DI5pE721.js} (94%) rename assets/{index.html-iq2vqpLb.js => index.html-DLU057mi.js} (96%) rename assets/{index.html-Dn4DJ2Jv.js => index.html-DNyKapJd.js} (94%) rename assets/{index.html-D2gpmgi4.js => index.html-DYNHttZu.js} (94%) rename assets/{index.html-7hihg5cE.js => index.html-DZ7gwFx0.js} (96%) rename assets/{index.html-B9UffgOy.js => index.html-D_iYE-Ij.js} (96%) rename assets/{index.html-Beu4CvPJ.js => index.html-DaiGMbWY.js} (96%) rename assets/{index.html-0QvH_nXT.js => index.html-DcPH7P5y.js} (93%) rename assets/{index.html-DD8LSjG9.js => index.html-De67gXK7.js} (93%) rename assets/{index.html-3CT6OGMP.js => index.html-DfN87M_p.js} (94%) create mode 100644 assets/index.html-Di3gSe-7.js rename assets/{index.html-BDPTsTEv.js => index.html-DmeObyHP.js} (94%) rename assets/{index.html-BvSZt_Zx.js => index.html-DnWLnH1N.js} (94%) rename assets/{index.html-C7LrpzTI.js => index.html-DpIMle5T.js} (94%) rename assets/{index.html-CCNjkOtl.js => index.html-DqEIAryj.js} (94%) rename assets/{index.html-LnP3a8gR.js => index.html-DsIag3k6.js} (94%) rename assets/{index.html-DDz84u40.js => index.html-DtMYUjJK.js} (94%) rename assets/{index.html-B1xDsJG7.js => index.html-Du2EK4qm.js} (94%) rename assets/{index.html-B-rzW1p0.js => index.html-DyGScxDV.js} (96%) rename assets/{index.html-CuWpjIbb.js => index.html-Gf4fmU9c.js} (93%) rename assets/{index.html-B-zCQLtF.js => index.html-KBuRJKDT.js} (94%) rename assets/{index.html-BQbSFfwU.js => index.html-QOoLQt7U.js} (94%) rename assets/{index.html-CxAtUmc7.js => index.html-UDfVv4MI.js} (94%) rename assets/{index.html-09kdO8uk.js => index.html-UPH_9_-C.js} (97%) rename assets/{index.html-vvugCgoe.js => index.html-YlcxYzGW.js} (94%) rename assets/{index.html-BqSgUMIT.js => index.html-ZWa93oKC.js} (95%) rename assets/{index.html-aM10vDis.js => index.html-ZenQwYfE.js} (93%) rename assets/{index.html-DYLEdXjZ.js => index.html-Zxb4dqDx.js} (98%) rename assets/{index.html-BXBwg_XP.js => index.html-_Hk-UKaP.js} (95%) rename assets/{index.html-CDsKkWs-.js => index.html-bCycLo4M.js} (94%) rename assets/{index.html-CeUSh-YY.js => index.html-da2hiqxj.js} (94%) rename assets/{index.html-CY45JW6T.js => index.html-gaUfJQSN.js} (94%) rename assets/{index.html-eHCo69H8.js => index.html-jAf7-iM1.js} (94%) rename assets/{index.html-B3FdepDG.js => index.html-k__z7vRJ.js} (94%) rename assets/{index.html-Dvn_QmO3.js => index.html-l0ianYW6.js} (97%) rename assets/{index.html-BXMP0bPq.js => index.html-l2dMz0nJ.js} (94%) rename assets/{index.html-D38QGC2C.js => index.html-n0MBVKbb.js} (94%) rename assets/{index.html-COH-CgE0.js => index.html-raRbuKfh.js} (97%) rename assets/{index.html-CaPxFNNf.js => index.html-tdfwMWEj.js} (94%) rename assets/{index.html-jpPjIq77.js => index.html-ukDkHwB4.js} (94%) rename assets/{index.html-hsfyfYXC.js => index.html-yLnx4YcF.js} (96%) rename assets/{io.html-DYLJUoUT.js => io.html-C1zkJgLS.js} (99%) rename "assets/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html-BzW3aeox.js" => "assets/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html-BiKvCKWl.js" (99%) rename assets/{lazyLoad.html-Cdfyboon.js => lazyLoad.html-B0wYE_Yw.js} (99%) rename assets/{logback.html-DOiHN3-e.js => logback.html-bWzTkpCR.js} (99%) rename assets/{markdown.html-Ku2vuX_R.js => markdown.html-DnotFn9F.js} (99%) rename assets/{mergeBranch.html-4sgMZ_ic.js => mergeBranch.html-CCnBpahJ.js} (99%) rename assets/{multiModule.html-k-P4UAvN.js => multiModule.html-vU5Z_GHP.js} (99%) rename assets/{mybatis.html-ZRAnLlvk.js => mybatis.html-CU1fMlIQ.js} (99%) rename assets/{note.html-CoTL_cZp.js => note.html-CZMFgVaM.js} (99%) rename assets/{page.html-CNLLwtGV.js => page.html-CalA3aZV.js} (97%) rename assets/{problem.html-dW-7sW20.js => problem.html-220H-omj.js} (99%) rename assets/{promise.html-BUt4IEbi.js => promise.html-DlbJ5Sxz.js} (99%) rename assets/{proxy.html-DRPn2ULh.js => proxy.html-CIpuLV1M.js} (97%) rename assets/{rebaseAndMerge.html-LfOBbMMt.js => rebaseAndMerge.html-DZipbfd7.js} (98%) rename assets/{reset.html-CjbSN2yZ.js => reset.html-JinN7PbC.js} (99%) rename assets/{slide.html-_uY0duH1.js => slide.html-gu2cHfI1.js} (99%) rename assets/{spring-security-oauth2-authorization-server.html-BU0wlxJK.js => spring-security-oauth2-authorization-server.html-o1Bz8Yim.js} (99%) rename assets/{stash.html-DJPmBi5v.js => stash.html-D1rEWs9W.js} (99%) rename assets/{throttle.html-BVURcdFS.js => throttle.html-CDssB4Vw.js} (99%) rename assets/{tsAndvue3.html-DuJXkf-s.js => tsAndvue3.html-8xWV3_Wf.js} (99%) rename assets/{useModule.html-C54V2441.js => useModule.html-BHtXy4j3.js} (99%) rename assets/{useNpm.html-DJJdqwXN.js => useNpm.html-CKLfIIZn.js} (99%) rename assets/{usePnpm.html-mIezI9dx.js => usePnpm.html-C3QlcelL.js} (98%) rename assets/{useYarn.html-DUPdL_R5.js => useYarn.html-BrgPHvAk.js} (98%) rename assets/{volatile.html-ChArXn5X.js => volatile.html-B-u5KWIK.js} (99%) rename assets/{vue-Direactive.html-CrUvqxXL.js => vue-Direactive.html-CwIH1XaN.js} (99%) rename assets/{vue-authority.html-hGxiTJj0.js => vue-authority.html-HH15bglq.js} (99%) rename assets/{vue-in-action.html-6hfWli8S.js => vue-in-action.html-DaPOjPap.js} (99%) rename assets/{vue-nextTick.html-D03AWrxQ.js => vue-nextTick.html-BAWrVlUH.js} (99%) rename assets/{vue-pic.html-CpOJwwqn.js => vue-pic.html-BGsMqmKX.js} (99%) rename assets/{vue-router1.html-DrI04ly7.js => vue-router1.html-DZlcHgjO.js} (99%) rename assets/{vue-router2.html-C4QGqnFR.js => vue-router2.html-DNXG8jNK.js} (99%) rename assets/{vue3Emit.html-DJy1ivmL.js => vue3Emit.html-C3GB9oZp.js} (99%) rename assets/{vue3LifeTime.html-CgbHBDaz.js => vue3LifeTime.html-L4zQOdiA.js} (99%) rename assets/{vueExtend.html-Dj4ZUdmc.js => vueExtend.html-DnRRc5Za.js} (99%) rename "assets/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html-DuNb2TND.js" => "assets/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html-CH2QvxT3.js" (99%) rename assets/{wrapper.html-Do0j3LhC.js => wrapper.html-aaFCqiMF.js} (96%) rename assets/{wsl.html-DoVf1_uE.js => wsl.html-DiCvzzYG.js} (99%) rename assets/{x86_openwrt.html-Rfp5hWIu.js => x86_openwrt.html-CASth0J6.js} (98%) rename "assets/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html-H7SaEEcF.js" => "assets/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html-CpfYP_fL.js" (99%) rename "assets/\345\220\204\347\247\215\351\227\256\351\242\230.html-DVSHYPDc.js" => "assets/\345\220\204\347\247\215\351\227\256\351\242\230.html-BVVorpF6.js" (99%) rename "assets/\345\220\215\350\257\215\350\247\243\351\207\212.html-D6DWrpIk.js" => "assets/\345\220\215\350\257\215\350\247\243\351\207\212.html-B5GJmRJ1.js" (98%) rename "assets/\345\256\211\350\243\205\351\227\256\351\242\230.html-BUB5ckKH.js" => "assets/\345\256\211\350\243\205\351\227\256\351\242\230.html-B-h8Zfob.js" (97%) rename "assets/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html-BpLKsWf9.js" => "assets/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html-CPvB1dQQ.js" (99%) rename "assets/\346\210\252\345\233\276.html-BRM_4DWV.js" => "assets/\346\210\252\345\233\276.html-DV7uCl6s.js" (98%) rename "assets/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html-DD7Rd6wj.js" => "assets/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html-BKBwfgmF.js" (99%) rename "assets/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html-UG8-nnQB.js" => "assets/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html-rdxyp_Zo.js" (99%) rename "assets/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html-2p0UA79e.js" => "assets/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html-JyFgPLU6.js" (96%) rename "assets/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html-DLubeSKF.js" => "assets/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html-D86ZbVNZ.js" (99%) rename "assets/\347\224\237\345\221\275\345\221\250\346\234\237.html-DmTGecRc.js" => "assets/\347\224\237\345\221\275\345\221\250\346\234\237.html-CGxeIKAF.js" (99%) rename "assets/\347\240\264\350\247\243\350\275\257\344\273\266.html-CYd3SkT3.js" => "assets/\347\240\264\350\247\243\350\275\257\344\273\266.html-DO8LI6dF.js" (98%) rename "assets/\347\255\226\347\225\245\346\250\241\345\274\217.html-BzaYdrRx.js" => "assets/\347\255\226\347\225\245\346\250\241\345\274\217.html-1pJjZLuA.js" (99%) rename "assets/\347\275\221\347\273\234\350\256\276\347\275\256.html-DbRAJ3uC.js" => "assets/\347\275\221\347\273\234\350\256\276\347\275\256.html-CT2typrG.js" (99%) rename "assets/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html-C1dSRMpn.js" => "assets/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html-dDt_1gUV.js" (99%) rename "assets/\350\207\252\345\273\272nas.html-BhQRnqm0.js" => "assets/\350\207\252\345\273\272nas.html-D-Ky5W6L.js" (97%) rename "assets/\350\243\205\351\245\260\346\250\241\345\274\217.html-NVA1JlXh.js" => "assets/\350\243\205\351\245\260\346\250\241\345\274\217.html-Bq_Ohlm8.js" (99%) rename "assets/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html-WcnUM2LR.js" => "assets/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html-C6zRijOG.js" (99%) rename "assets/\351\203\250\347\275\262.html-FCiywWGM.js" => "assets/\351\203\250\347\275\262.html-Cup61qP7.js" (97%) diff --git a/404.html b/404.html index 5605501843..6304a76fbd 100644 --- a/404.html +++ b/404.html @@ -30,11 +30,11 @@ ChenSino - - + + -
跳至主要內容

404

页面不存在

看起来你访问了一个失效的链接

- +
跳至主要內容

404

页面不存在

这 是 四 零 四 !

+ diff --git a/article/index.html b/article/index.html index ac429b34a8..89284b849c 100644 --- a/article/index.html +++ b/article/index.html @@ -30,8 +30,8 @@ 文章 | ChenSino - - + +
跳至主要內容
Spring缓存

Spring缓存大揭秘

@@ -142,7 +142,7 @@

1、什么是双亲委派?

​双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现却异常简单,用以实现双亲委 派的代码只有短短十余行,全部集中在java.lang.ClassLoader的loadClass()方法之中。


chenkun大约 13 分钟
- +
ChenSino大约 8 分钟java
+ diff --git a/assets/01.html-BpsuZlEh.js b/assets/01.html-jkQdEnIh.js similarity index 99% rename from assets/01.html-BpsuZlEh.js rename to assets/01.html-jkQdEnIh.js index 4868be57c9..2b7daaed4d 100644 --- a/assets/01.html-BpsuZlEh.js +++ b/assets/01.html-jkQdEnIh.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as p,e as a}from"./app-CVMfKeWw.js";const n={},o=a('

你好,我是王新栋。 在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。

到这里,我估计你会问,这是怎么实现的?微信把我的个人信息给了极客时间,它又是怎么保证我的数据安全的呢?

其实,微信这一系列授权背后的原理都可以归到一个词上,那就是 OAuth 2.0。今天这节课,我们就来看看 OAuth 2.0 到底是什么、能干什么以及它是怎么干的。

OAuth 2.0 是什么?

用一句话总结来说,OAuth 2.0 就是一种授权协议。那如何理解这里的“授权”呢?

我举个咱们生活中的例子。假如你是一名销售人员,你想去百度拜访你的大客户王总。到了百度的大楼之后,保安拦住了你,问你要工牌。你说:“保安大哥啊,我是来拜访王总的,哪里有什么工牌”。保安大哥说:“那你要去前台做个登记”。

然后你就赶紧来到前台,前台美女问你是不是做了登记。你说王总秘书昨天有要你的手机号,说是已经做过预约。小姐姐确认之后往你的手机发了个验证码,你把验证码告诉了前台小姐姐之后,她给了你一张门禁卡,于是你就可以开心地去见王总了。

你看,这个例子里面就有一次授权。本来你是没有权限进入百度大楼的,但是经过前台小姐姐一系列的验证之后,她发现你确实是来拜访客户的,于是给了你一张临时工牌。这整个过程就是授权。

我再举一个电商的场景,你估计更有感觉。假如你是一个卖家,在京东商城开了一个店铺,日常运营中你要将订单打印出来以便给用户发货。但打印这事儿也挺繁琐的,之前你总是手工操作,后来发现有个叫“小兔”的第三方软件,它可以帮你高效率地处理这事。

但你想想,小兔是怎么访问到这些订单数据的呢?其实是这样,京东商城提供了开放平台,小兔通过京东商家开放平台的 API 就能访问到用户的订单数据。只要你在软件里点击同意,小兔就可以拿到一个访问令牌,通过访问令牌来获取到你的订单数据帮你干活儿了。你看,这里也是有一次授权。你要是不同意,平台肯定不敢把这些数据给到第三方软件。

为什么用 OAuth 2.0?

基于上面两种场景的解决方案,关于授权我们最容易想到的方案就是提供钥匙。比如,你要去百度拜访王总,那前台小姐姐就给你张百度的工牌;小兔要获取你的订单信息,那你就把你的用户名和密码给它。但稍微有些安全意识,我们都不会这样做。

因为你有了百度工牌,那以后都可以随时自由地进出了,这显然不是百度想要的。所以,百度有一套完整的机制,通过给你一张临时工牌,实现在保证安全的情况下,还能让你去大楼里面见到王总。相应地,小兔软件请求访问你的订单数据的过程,也会涉及这样一套授权机制,那就是 OAuth 2.0。它通过给小兔软件一个访问令牌,而不是让小兔软件拿着你的用户名和密码,去获取你的订单数据帮你干活儿。

其实,除了小兔软件这个场景,在如今的互联网世界里用到 OAuth 2.0 的地方非常多,只是因为它隐藏了实现细节,需要我们多做分析才能发现它。比如,当你使用微信登录其他网站或者 App 的时候,当你开始使用某个小程序的时候,你都在无感知的情况下用到了 OAuth 2.0。

那总结来说, ==OAuth 2.0 这种授权协议,就是保证第三方(软件)只有在获得授权之后,才可以进一步访问授权者的数据。== 因此,我们常常还会听到一种说法,OAuth 2.0 是一种安全协议。现在你知道了,这种说法也是正确的。

现在访问授权者的数据主要是通过 Web API,所以凡是要保护这种对外的 API 时,都需要这样授权的方式。而 OAuth 2.0 的这种颁发访问令牌的机制,是再合适不过的方法了。同时,这样的 Web API 还在持续增加,所以 OAuth 2.0 是目前 Web 上重要的安全手段之一了。

OAuth 2.0 是怎样运转的?

现在,我相信你已经对 OAuth 2.0 有了一个整体印象,接下来咱们再看看它是怎么运转的。

我们还是来看上面提到的小兔打单软件的例子吧。假如小明在京东上面开了一个店铺,小明要管理他的店铺里面的订单,于是选择了使用小兔软件。

现在,让我们把“小明”“小兔软件”“京东商家开放平台”放到一个对话里面,看看“他们”是怎么沟通的吧。

小明:“你好,小兔软件。我正在 Google 浏览器上面,需要访问你来帮我处理我在京东商城店铺的订单。

”小兔软件:“好的,小明,我需要你给我授权。现在我把你引导到京东商家开放平台上,你在那里给我授权吧。

”京东商家开放平台:“你好,小明。我收到了小兔软件跳转过来的请求,现在已经准备好了一个授权页面。你登录并确认后,点击授权页面上面的授权按钮即可。

小明:“好的,京东商家开放平台。我看到了这个授权页面,已经点授权按钮啦😄”

京东商家开放平台:“你好,小兔打单软件。我收到了小明的授权,现在要给你生成一个授权码 code 值,我通过浏览器重定向到你的回调 URL 地址上面了。

”小兔软件:“好的,京东商家开放平台。我现在从浏览器上拿到了授权码,现在就用这个授权码来请求你,请给我一个访问令牌 access_token 吧。”

京东商家开放平台:“好的,小兔打单软件,访问令牌已经发送给你了。”

小兔打单软件:“太好了,我现在就可以使用访问令牌来获取小明店铺的订单了。”

小明:“我已经能够看到我的订单了,现在就开始打单操作了。”下

面,为了帮助你理解,我再用一张图来描述整个过程:

20221108112829

再分析下这个流程,我们不难发现小兔软件最终的目的,是要获取一个叫做“访问令牌”的东西。从最后一步也能够看出来,在小兔软件获取到访问令牌之后,才有足够的 “能力” 去请求小明的店铺的订单,也就是才能够帮助小明打印订单。

那么,小兔软件是怎么获取访问令牌的值的呢?我们会发现还有一个叫做“授权码”的东西,也就是说小兔软件是拿授权码换取的访问令牌。

小兔软件又是怎么拿到授权码的呢?从图中流程刚开始的那一步,我们就会发现,是在小明授权之后,才产生的授权码,上面流程中后续的一切动作,实际上都是在小明对小兔软件授权发生以后才产生的。其中主要的动作,就是生成授权码–> 生成访问令牌–> 使用访问令牌。

到这里,我们不难发现,OAuth 2.0 授权的核心就是颁发访问令牌、使用访问令牌,而且不管是哪种类型的授权流程都是这样。你一定要理解,或者记住这句话,它是整个流程的核心。你也可以再回想下,去百度拜访王总的例子。如果你是百度这套机制的设计者的话,会怎么设计这套授权机制呢。想清楚了这个问题,你再去理解令牌、授权码啥的也就简单了。

在小兔软件这个例子中呢,我们使用的就是授权码许可(Authorization Code)类型。它是 OAuth 2.0 中最经典、最完备、最安全、应用最广泛的许可类型。除了授权码许可类型外,OAuth 2.0 针对不同的使用场景,还有 3 种基础的许可类型,分别是隐式许可(Implicit)、客户端凭据许可(Client Credentials)、资源拥有者凭据许可(Resource Owner Password Credentials)。相对而言,这 3 种授权许可类型的流程,在流程复杂度和安全性上都有所减弱(我会在第 6 讲,与你详细分析)。

因此,在这个课程中,我会频繁用授权码许可类型来举例。至于为什么称它为授权码许可,为什么有两次重定向,以及这种许可类型更详细的通信流程又是怎样的,我会在第 2 讲给你深入分析,你可以先不用关注。

总结

好了,今天这节课就到这里。这节课咱们知识点不多,我来回给你举例子,其实就是希望你能理解 OAuth 到底是什么,为什么需要它,以及它大概的运行逻辑是怎样的。总结来说,我需要你记住以下这 3 个关键点:

  1. OAuth 2.0 的核心是授权许可,更进一步说就是令牌机制。也就是说,像小兔软件这样的第三方软件只有拿到了京东商家开放平台颁发的访问令牌,也就是得到了授权许可,然后才可以代表用户访问他们的数据。
  2. 互联网中所有的受保护资源,几乎都是以 Web API 的形式来提供访问的,比如极客时间 App 要获取用户的头像、昵称,小兔软件要获取用户的店铺订单,我们说 OAuth 2.0 与安全相关,是用来保护 Web API 的。另外,第三方软件通过 OAuth 2.0 取得访问权限之后,用户便把这些权限委托给了第三方软件,我们说 OAuth 2.0 是一种委托协议,也没问题。
  3. 也正因为像小兔这样的第三方软件,每次都是用访问令牌而不是用户名和密码来请求用户的数据,才大大减少了安全风险上的“攻击面”。不然,我们试想一下,每次都带着用户名和密码来访问数量众多的 Web API ,是不是增加了这个“攻击面”。因此,我们说 OAuth 2.0 的核心,就是颁发访问令牌和使用访问令牌。

思考题

好了,今天这一讲我们马上要结束了,我给你留个思考题。你可以再花时间想下小兔软件获取用户订单信息的那个场景,如果让你来设计整个的授权流程,你会怎么设计?还有没有更好的方式?欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。

',42),h=[o];function i(r,l){return e(),p("div",null,h)}const u=t(n,[["render",i],["__file","01.html.vue"]]),d=JSON.parse('{"path":"/other/oauth2/01.html","title":"01 | OAuth 2.0是要通过什么方式解决什么问题?","lang":"zh-CN","frontmatter":{"title":"01 | OAuth 2.0是要通过什么方式解决什么问题?","date":"2022-11-08T00:00:00.000Z","author":"王新栋","publish":true,"description":"你好,我是王新栋。 在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。 到这里,我估计你...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/01.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"01 | OAuth 2.0是要通过什么方式解决什么问题?"}],["meta",{"property":"og:description","content":"你好,我是王新栋。 在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。 到这里,我估计你..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221108112829.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"王新栋"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"01 | OAuth 2.0是要通过什么方式解决什么问题?\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221108112829.png\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"王新栋\\"}]}"]]},"headers":[{"level":3,"title":"OAuth 2.0 是什么?","slug":"oauth-2-0-是什么","link":"#oauth-2-0-是什么","children":[]},{"level":3,"title":"为什么用 OAuth 2.0?","slug":"为什么用-oauth-2-0","link":"#为什么用-oauth-2-0","children":[]},{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]},{"level":3,"title":"思考题","slug":"思考题","link":"#思考题","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":10.36,"words":3109},"filePathRelative":"other/oauth2/01.md","localizedDate":"2022年11月8日","excerpt":"

你好,我是王新栋。\\n在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。

\\n

到这里,我估计你会问,这是怎么实现的?微信把我的个人信息给了极客时间,它又是怎么保证我的数据安全的呢?

\\n

其实,微信这一系列授权背后的原理都可以归到一个词上,那就是 OAuth 2.0。今天这节课,我们就来看看 OAuth 2.0 到底是什么、能干什么以及它是怎么干的。

","autoDesc":true}');export{u as comp,d as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as p,e as a}from"./app-_Oi5YZFn.js";const n={},o=a('

你好,我是王新栋。 在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。

到这里,我估计你会问,这是怎么实现的?微信把我的个人信息给了极客时间,它又是怎么保证我的数据安全的呢?

其实,微信这一系列授权背后的原理都可以归到一个词上,那就是 OAuth 2.0。今天这节课,我们就来看看 OAuth 2.0 到底是什么、能干什么以及它是怎么干的。

OAuth 2.0 是什么?

用一句话总结来说,OAuth 2.0 就是一种授权协议。那如何理解这里的“授权”呢?

我举个咱们生活中的例子。假如你是一名销售人员,你想去百度拜访你的大客户王总。到了百度的大楼之后,保安拦住了你,问你要工牌。你说:“保安大哥啊,我是来拜访王总的,哪里有什么工牌”。保安大哥说:“那你要去前台做个登记”。

然后你就赶紧来到前台,前台美女问你是不是做了登记。你说王总秘书昨天有要你的手机号,说是已经做过预约。小姐姐确认之后往你的手机发了个验证码,你把验证码告诉了前台小姐姐之后,她给了你一张门禁卡,于是你就可以开心地去见王总了。

你看,这个例子里面就有一次授权。本来你是没有权限进入百度大楼的,但是经过前台小姐姐一系列的验证之后,她发现你确实是来拜访客户的,于是给了你一张临时工牌。这整个过程就是授权。

我再举一个电商的场景,你估计更有感觉。假如你是一个卖家,在京东商城开了一个店铺,日常运营中你要将订单打印出来以便给用户发货。但打印这事儿也挺繁琐的,之前你总是手工操作,后来发现有个叫“小兔”的第三方软件,它可以帮你高效率地处理这事。

但你想想,小兔是怎么访问到这些订单数据的呢?其实是这样,京东商城提供了开放平台,小兔通过京东商家开放平台的 API 就能访问到用户的订单数据。只要你在软件里点击同意,小兔就可以拿到一个访问令牌,通过访问令牌来获取到你的订单数据帮你干活儿了。你看,这里也是有一次授权。你要是不同意,平台肯定不敢把这些数据给到第三方软件。

为什么用 OAuth 2.0?

基于上面两种场景的解决方案,关于授权我们最容易想到的方案就是提供钥匙。比如,你要去百度拜访王总,那前台小姐姐就给你张百度的工牌;小兔要获取你的订单信息,那你就把你的用户名和密码给它。但稍微有些安全意识,我们都不会这样做。

因为你有了百度工牌,那以后都可以随时自由地进出了,这显然不是百度想要的。所以,百度有一套完整的机制,通过给你一张临时工牌,实现在保证安全的情况下,还能让你去大楼里面见到王总。相应地,小兔软件请求访问你的订单数据的过程,也会涉及这样一套授权机制,那就是 OAuth 2.0。它通过给小兔软件一个访问令牌,而不是让小兔软件拿着你的用户名和密码,去获取你的订单数据帮你干活儿。

其实,除了小兔软件这个场景,在如今的互联网世界里用到 OAuth 2.0 的地方非常多,只是因为它隐藏了实现细节,需要我们多做分析才能发现它。比如,当你使用微信登录其他网站或者 App 的时候,当你开始使用某个小程序的时候,你都在无感知的情况下用到了 OAuth 2.0。

那总结来说, ==OAuth 2.0 这种授权协议,就是保证第三方(软件)只有在获得授权之后,才可以进一步访问授权者的数据。== 因此,我们常常还会听到一种说法,OAuth 2.0 是一种安全协议。现在你知道了,这种说法也是正确的。

现在访问授权者的数据主要是通过 Web API,所以凡是要保护这种对外的 API 时,都需要这样授权的方式。而 OAuth 2.0 的这种颁发访问令牌的机制,是再合适不过的方法了。同时,这样的 Web API 还在持续增加,所以 OAuth 2.0 是目前 Web 上重要的安全手段之一了。

OAuth 2.0 是怎样运转的?

现在,我相信你已经对 OAuth 2.0 有了一个整体印象,接下来咱们再看看它是怎么运转的。

我们还是来看上面提到的小兔打单软件的例子吧。假如小明在京东上面开了一个店铺,小明要管理他的店铺里面的订单,于是选择了使用小兔软件。

现在,让我们把“小明”“小兔软件”“京东商家开放平台”放到一个对话里面,看看“他们”是怎么沟通的吧。

小明:“你好,小兔软件。我正在 Google 浏览器上面,需要访问你来帮我处理我在京东商城店铺的订单。

”小兔软件:“好的,小明,我需要你给我授权。现在我把你引导到京东商家开放平台上,你在那里给我授权吧。

”京东商家开放平台:“你好,小明。我收到了小兔软件跳转过来的请求,现在已经准备好了一个授权页面。你登录并确认后,点击授权页面上面的授权按钮即可。

小明:“好的,京东商家开放平台。我看到了这个授权页面,已经点授权按钮啦😄”

京东商家开放平台:“你好,小兔打单软件。我收到了小明的授权,现在要给你生成一个授权码 code 值,我通过浏览器重定向到你的回调 URL 地址上面了。

”小兔软件:“好的,京东商家开放平台。我现在从浏览器上拿到了授权码,现在就用这个授权码来请求你,请给我一个访问令牌 access_token 吧。”

京东商家开放平台:“好的,小兔打单软件,访问令牌已经发送给你了。”

小兔打单软件:“太好了,我现在就可以使用访问令牌来获取小明店铺的订单了。”

小明:“我已经能够看到我的订单了,现在就开始打单操作了。”下

面,为了帮助你理解,我再用一张图来描述整个过程:

20221108112829

再分析下这个流程,我们不难发现小兔软件最终的目的,是要获取一个叫做“访问令牌”的东西。从最后一步也能够看出来,在小兔软件获取到访问令牌之后,才有足够的 “能力” 去请求小明的店铺的订单,也就是才能够帮助小明打印订单。

那么,小兔软件是怎么获取访问令牌的值的呢?我们会发现还有一个叫做“授权码”的东西,也就是说小兔软件是拿授权码换取的访问令牌。

小兔软件又是怎么拿到授权码的呢?从图中流程刚开始的那一步,我们就会发现,是在小明授权之后,才产生的授权码,上面流程中后续的一切动作,实际上都是在小明对小兔软件授权发生以后才产生的。其中主要的动作,就是生成授权码–> 生成访问令牌–> 使用访问令牌。

到这里,我们不难发现,OAuth 2.0 授权的核心就是颁发访问令牌、使用访问令牌,而且不管是哪种类型的授权流程都是这样。你一定要理解,或者记住这句话,它是整个流程的核心。你也可以再回想下,去百度拜访王总的例子。如果你是百度这套机制的设计者的话,会怎么设计这套授权机制呢。想清楚了这个问题,你再去理解令牌、授权码啥的也就简单了。

在小兔软件这个例子中呢,我们使用的就是授权码许可(Authorization Code)类型。它是 OAuth 2.0 中最经典、最完备、最安全、应用最广泛的许可类型。除了授权码许可类型外,OAuth 2.0 针对不同的使用场景,还有 3 种基础的许可类型,分别是隐式许可(Implicit)、客户端凭据许可(Client Credentials)、资源拥有者凭据许可(Resource Owner Password Credentials)。相对而言,这 3 种授权许可类型的流程,在流程复杂度和安全性上都有所减弱(我会在第 6 讲,与你详细分析)。

因此,在这个课程中,我会频繁用授权码许可类型来举例。至于为什么称它为授权码许可,为什么有两次重定向,以及这种许可类型更详细的通信流程又是怎样的,我会在第 2 讲给你深入分析,你可以先不用关注。

总结

好了,今天这节课就到这里。这节课咱们知识点不多,我来回给你举例子,其实就是希望你能理解 OAuth 到底是什么,为什么需要它,以及它大概的运行逻辑是怎样的。总结来说,我需要你记住以下这 3 个关键点:

  1. OAuth 2.0 的核心是授权许可,更进一步说就是令牌机制。也就是说,像小兔软件这样的第三方软件只有拿到了京东商家开放平台颁发的访问令牌,也就是得到了授权许可,然后才可以代表用户访问他们的数据。
  2. 互联网中所有的受保护资源,几乎都是以 Web API 的形式来提供访问的,比如极客时间 App 要获取用户的头像、昵称,小兔软件要获取用户的店铺订单,我们说 OAuth 2.0 与安全相关,是用来保护 Web API 的。另外,第三方软件通过 OAuth 2.0 取得访问权限之后,用户便把这些权限委托给了第三方软件,我们说 OAuth 2.0 是一种委托协议,也没问题。
  3. 也正因为像小兔这样的第三方软件,每次都是用访问令牌而不是用户名和密码来请求用户的数据,才大大减少了安全风险上的“攻击面”。不然,我们试想一下,每次都带着用户名和密码来访问数量众多的 Web API ,是不是增加了这个“攻击面”。因此,我们说 OAuth 2.0 的核心,就是颁发访问令牌和使用访问令牌。

思考题

好了,今天这一讲我们马上要结束了,我给你留个思考题。你可以再花时间想下小兔软件获取用户订单信息的那个场景,如果让你来设计整个的授权流程,你会怎么设计?还有没有更好的方式?欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。

',42),h=[o];function i(r,l){return e(),p("div",null,h)}const u=t(n,[["render",i],["__file","01.html.vue"]]),d=JSON.parse('{"path":"/other/oauth2/01.html","title":"01 | OAuth 2.0是要通过什么方式解决什么问题?","lang":"zh-CN","frontmatter":{"title":"01 | OAuth 2.0是要通过什么方式解决什么问题?","date":"2022-11-08T00:00:00.000Z","author":"王新栋","publish":true,"description":"你好,我是王新栋。 在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。 到这里,我估计你...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/01.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"01 | OAuth 2.0是要通过什么方式解决什么问题?"}],["meta",{"property":"og:description","content":"你好,我是王新栋。 在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。 到这里,我估计你..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221108112829.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"王新栋"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"01 | OAuth 2.0是要通过什么方式解决什么问题?\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221108112829.png\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"王新栋\\"}]}"]]},"headers":[{"level":3,"title":"OAuth 2.0 是什么?","slug":"oauth-2-0-是什么","link":"#oauth-2-0-是什么","children":[]},{"level":3,"title":"为什么用 OAuth 2.0?","slug":"为什么用-oauth-2-0","link":"#为什么用-oauth-2-0","children":[]},{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]},{"level":3,"title":"思考题","slug":"思考题","link":"#思考题","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":10.36,"words":3109},"filePathRelative":"other/oauth2/01.md","localizedDate":"2022年11月8日","excerpt":"

你好,我是王新栋。\\n在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。

\\n

到这里,我估计你会问,这是怎么实现的?微信把我的个人信息给了极客时间,它又是怎么保证我的数据安全的呢?

\\n

其实,微信这一系列授权背后的原理都可以归到一个词上,那就是 OAuth 2.0。今天这节课,我们就来看看 OAuth 2.0 到底是什么、能干什么以及它是怎么干的。

","autoDesc":true}');export{u as comp,d as data}; diff --git a/assets/02.html-9y4kTt4c.js b/assets/02.html-B3UlmDxL.js similarity index 99% rename from assets/02.html-9y4kTt4c.js rename to assets/02.html-B3UlmDxL.js index 3dfcad58fd..f6447c4b5b 100644 --- a/assets/02.html-9y4kTt4c.js +++ b/assets/02.html-B3UlmDxL.js @@ -1,2 +1,2 @@ -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o,c as i,a as t,b as e,d as c,e as a}from"./app-CVMfKeWw.js";const l={},r=a('

你好,我是王新栋。

在上一讲,我提到了 OAuth 2.0 的授权码许可类型,在小兔打单软件的例子里面,小兔最终是通过访问令牌请求到小明的店铺里的订单数据。同时呢,我还提到了,这个访问令牌是通过授权码换来的。到这里估计你会问了,为什么要用授权码来换令牌?为什么不能直接颁发访问令牌呢?

你可以先停下来想想这个问题。今天咱们这节课,我会带着你深入探究下其中的逻辑。

为什么需要授权码?

在讲这个问题之前,我先要和你同步下,在 OAuth 2.0 的体系里面有 4 种角色,按照官方的称呼它们分别是资源拥有者、客户端、授权服务和受保护资源。不过,这里的客户端,我更愿意称其为第三方软件,而且在咱们这个课程中,都是以第三方软件在举例子。所以,在后续的讲解中我统一把它称为第三方软件。

所以,你在看官方资料的时候,可以自己对应下。为了便于你理解,我还是拿小兔软件来举例子,将官方的称呼 “照进现实”,对应关系就是,资源拥有者 -> 小明,第三方软件 -> 小兔软件,授权服务 -> 京东商家开放平台的授权服务,受保护资源 -> 小明店铺在京东上面的订单。

在理解了这些概念以后,让我们继续。

你知道,OAuth 诞生之初就是为了解决 Web 浏览器场景下的授权问题,所以我基于浏览器的场景,在上一讲的小明使用小兔软件打印订单的整体流程的基础上,画了一个授权码许可类型的序列图。

当然了,这里还是有小兔软件来继续陪伴着我们,不过这次为了能够更好地表述授权码许可流程,我会把小兔软件的前端和后端分开展示,并把京东商家开放平台的系统按照 OAuth 2.0 的组件拆分成了授权服务和受保护资源服务。如下图所示: 图1 以小兔软件为例,授权码许可类型的序列图

突然看到这个序列图增加了这么多步骤的时候,你是不是有些紧张?那如果我告诉你再细分的话步骤还要更多,你是不是就更困惑了?

不过,别紧张,这没啥关系。一方面,咱们这一讲的重点就是跟授权码相关的流程,你只需关注这里的重点步骤,也就是两次重定向相关的步骤就够了。在下一讲中,我再教你如何将这些步骤进一步拆解。另一方面,我接下来还会用另一种视角来帮助你分析这个流程。

我们继续来看这张序列图。从图中看到,在第 4 步授权服务生成了授权码 code,按照一开始我们提出来的问题,如果不要授权码,这一步实际上就可以直接返回访问令牌 access_token 了。

按着这个没有授权码的思路继续想,如果这里直接返回访问令牌,那我们肯定不能使用重定向的方式。因为这样会把安全保密性要求极高的访问令牌暴露在浏览器上,从而将会面临访问令牌失窃的安全风险。显然,这是不能被允许的。

也就是说,如果没有授权码的话,我们就只能把访问令牌发送给第三方软件小兔的后端服务。按照这样的逻辑,上面的流程图就会变成下面这样: 图2 如果没有授权码,直接把访问令牌发送给第三方软件小兔的后端服务

到这里,看起来天衣无缝。小明访问小兔软件,小兔软件说要打单你得给我授权,不然京东不干,然后小兔软件就引导小明跳转到了京东的授权服务。到授权服务之后,京东商家开放平台验证了小兔的合法性以及小明的登录状态后,生成了授权页面。紧接着,小明赶紧点击同意授权,这时候,京东商家开放平台知道可以把小明的订单数据给小兔软件。

于是,京东商家开放平台没含糊,赶紧生成访问令牌 access_token,并且通过后端服务的方式返回给了小兔软件。这时候,小兔软件就能正常工作了。

这样,问题就来了,什么问题呢?当小明被浏览器重定向到授权服务上之后,小明跟小兔软件之间的 “连接” 就断了,相当于此时此刻小明跟授权服务建立了“连接”后,将一直“停留在授权服务的页面上”。你会看到图 2 中问号处的时序上,小明再也没有重新“连接”到小兔软件。

但是,这个时候小兔软件已经拿到了小明授权之后的访问令牌,也使用访问令牌获取到了小明店铺里的订单数据。这时,考虑到“小明的感受”,小兔软件应该要通知到小明,但是如何做呢?现在“连接断了”,这事儿恐怕就没那么容易了。

OK,为了让小兔软件能很容易地通知到小明,还必须让小明跟小兔软件重新建立起 “连接”。这就是我们看到的第二次重定向,小明授权之后,又重新重定向回到了小兔软件的地址上,这样小明就跟小兔软件有了新的 “连接”。

到这里,你就能理解在授权码许可的流程中,为什么需要两次重定向了吧。

为了重新建立起这样的一次连接,我们又不能让访问令牌暴露出去,就有了这样一个临时的、间接的凭证:授权码。因为小兔软件最终要拿到的是安全保密性要求极高的访问令牌,并不是授权码,而授权码是可以暴露在浏览器上面的。这样有了授权码的参与,访问令牌可以在后端服务之间传输,同时呢还可以重新建立小明与小兔软件之间的“连接”。这样通过一个授权码,既“照顾”到了小明的体验,又“照顾”了通信的安全。

这下,你就知道为什么要有授权码了吧。

那么,在执行授权码流程的时候,授权码和访问令牌在小兔软件和授权服务之间到底是怎么流转的呢?要回答这个问题,就需要继续分析一下授权码许可类型的通信过程了。

授权码许可类型的通信过程

图 1 的通信过程中标识出来的步骤就有 9 个,一步步地去分析看似会很复杂,所以我会用另一个维度来分析以帮助你理解,也就是从直接通信和间接通信的维度来分析。这里所谓的间接通信就是指获取授权码的交互,而直接通信就是指通过授权码换取访问令牌的交互。

接下来,我们就一起分析下吧,看看哪些是间接通信,哪些又是直接通信。

间接通信我们先分析下为什么是“间接”。我们把图 1 中获取授权码 code 的流程 “放大”,并换个角度来看一看,也就是将浏览器这个代理放到第三方软件小兔和授权服务中间。于是,我们来到了下面这张图: 图3 获取授权码的交互过程

这个过程,仿佛有这样的一段对话。

小明:“你好,小兔软件,我要访问你了。 ”小兔软件:“好的,我把你引到授权服务那里,我需要授权服务给我一个授权码。” 授权服务:“小兔软件,我把授权码发给浏览器了。” 小兔软件:“好的,我从浏览器拿到了授权码。”

不知道你注意到没有,第三方软件小兔和授权服务之间,并没有发生直接的通信,而是 ==通过浏览器这个“中间人” 来 “搭线”的。== 因此,我们说这是一个间接通信的方式。

直接通信

那我们再分析下,授权码换取访问令牌的交互,为什么是“直接”的。我们再把图 1 中获取访问令牌的流程“放大”,就得到了下面的图示: 图4 授权码换取访问令牌的交互过程

相比获取授权码过程的间接通信,获取访问令牌的直接通信就比较容易理解了,就是第三方软件小兔获取到授权码 code 值后,向授权服务发起获取访问令牌 access_token 的通信请求。这个请求是第三方软件服务器跟授权服务的服务器之间的通信,都是在后端服务器之间的请求和响应,因此也叫作后端通信。

两个 “一伙”

了解了上面的通信方式之后,不知道你有没有意识到,OAuth 2.0 中的 4 个角色是 “两两站队” 的:资源拥有者和第三方软件“站在一起”,因为第三方软件要代表资源拥有者去访问受保护资源;授权服务和受保护资源“站在一起”,因为授权服务负责颁发访问令牌,受保护资源负责接收并验证访问令牌。 图5 OAuth 2.0 中的4个角色是“两两站队”

讲到这里的时候,你会发现在这一讲,介绍授权码流程的时候我都是以浏览器参与的场景来讲的,那么浏览器一定要参与到这个流程中吗?其实,授权码许可流程,不一定要有浏览器的参与。接下来,我们就继续分析下其中的逻辑。

一定要有浏览器吗?

OAuth 2.0 发展之初,开放生态环境相对单薄,以浏览器为代理的 Web 应用居多,授权码许可类型 “理所当然” 地被应用到了通过浏览器才能访问的 Web 应用中。

',38),h={href:"https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html",target:"_blank",rel:"noopener noreferrer"},d=a(`

根据微信官方文档描述,开发者获取用户登录态信息的过程正是一个授权码的许可流程:

你可以看到,这个过程并没有使用到浏览器,但确实按照授权码许可的思想走了一个完整的授权码许可流程。也就是说,先通过小程序前端获取到 code 值,再通过小程序的后端服务使用 code 值换取 session_key 等信息,只不过是访问令牌 access_token 的值被换成了 session_key。

GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
+import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o,c as i,a as t,b as e,d as c,e as a}from"./app-_Oi5YZFn.js";const l={},r=a('

你好,我是王新栋。

在上一讲,我提到了 OAuth 2.0 的授权码许可类型,在小兔打单软件的例子里面,小兔最终是通过访问令牌请求到小明的店铺里的订单数据。同时呢,我还提到了,这个访问令牌是通过授权码换来的。到这里估计你会问了,为什么要用授权码来换令牌?为什么不能直接颁发访问令牌呢?

你可以先停下来想想这个问题。今天咱们这节课,我会带着你深入探究下其中的逻辑。

为什么需要授权码?

在讲这个问题之前,我先要和你同步下,在 OAuth 2.0 的体系里面有 4 种角色,按照官方的称呼它们分别是资源拥有者、客户端、授权服务和受保护资源。不过,这里的客户端,我更愿意称其为第三方软件,而且在咱们这个课程中,都是以第三方软件在举例子。所以,在后续的讲解中我统一把它称为第三方软件。

所以,你在看官方资料的时候,可以自己对应下。为了便于你理解,我还是拿小兔软件来举例子,将官方的称呼 “照进现实”,对应关系就是,资源拥有者 -> 小明,第三方软件 -> 小兔软件,授权服务 -> 京东商家开放平台的授权服务,受保护资源 -> 小明店铺在京东上面的订单。

在理解了这些概念以后,让我们继续。

你知道,OAuth 诞生之初就是为了解决 Web 浏览器场景下的授权问题,所以我基于浏览器的场景,在上一讲的小明使用小兔软件打印订单的整体流程的基础上,画了一个授权码许可类型的序列图。

当然了,这里还是有小兔软件来继续陪伴着我们,不过这次为了能够更好地表述授权码许可流程,我会把小兔软件的前端和后端分开展示,并把京东商家开放平台的系统按照 OAuth 2.0 的组件拆分成了授权服务和受保护资源服务。如下图所示: 图1 以小兔软件为例,授权码许可类型的序列图

突然看到这个序列图增加了这么多步骤的时候,你是不是有些紧张?那如果我告诉你再细分的话步骤还要更多,你是不是就更困惑了?

不过,别紧张,这没啥关系。一方面,咱们这一讲的重点就是跟授权码相关的流程,你只需关注这里的重点步骤,也就是两次重定向相关的步骤就够了。在下一讲中,我再教你如何将这些步骤进一步拆解。另一方面,我接下来还会用另一种视角来帮助你分析这个流程。

我们继续来看这张序列图。从图中看到,在第 4 步授权服务生成了授权码 code,按照一开始我们提出来的问题,如果不要授权码,这一步实际上就可以直接返回访问令牌 access_token 了。

按着这个没有授权码的思路继续想,如果这里直接返回访问令牌,那我们肯定不能使用重定向的方式。因为这样会把安全保密性要求极高的访问令牌暴露在浏览器上,从而将会面临访问令牌失窃的安全风险。显然,这是不能被允许的。

也就是说,如果没有授权码的话,我们就只能把访问令牌发送给第三方软件小兔的后端服务。按照这样的逻辑,上面的流程图就会变成下面这样: 图2 如果没有授权码,直接把访问令牌发送给第三方软件小兔的后端服务

到这里,看起来天衣无缝。小明访问小兔软件,小兔软件说要打单你得给我授权,不然京东不干,然后小兔软件就引导小明跳转到了京东的授权服务。到授权服务之后,京东商家开放平台验证了小兔的合法性以及小明的登录状态后,生成了授权页面。紧接着,小明赶紧点击同意授权,这时候,京东商家开放平台知道可以把小明的订单数据给小兔软件。

于是,京东商家开放平台没含糊,赶紧生成访问令牌 access_token,并且通过后端服务的方式返回给了小兔软件。这时候,小兔软件就能正常工作了。

这样,问题就来了,什么问题呢?当小明被浏览器重定向到授权服务上之后,小明跟小兔软件之间的 “连接” 就断了,相当于此时此刻小明跟授权服务建立了“连接”后,将一直“停留在授权服务的页面上”。你会看到图 2 中问号处的时序上,小明再也没有重新“连接”到小兔软件。

但是,这个时候小兔软件已经拿到了小明授权之后的访问令牌,也使用访问令牌获取到了小明店铺里的订单数据。这时,考虑到“小明的感受”,小兔软件应该要通知到小明,但是如何做呢?现在“连接断了”,这事儿恐怕就没那么容易了。

OK,为了让小兔软件能很容易地通知到小明,还必须让小明跟小兔软件重新建立起 “连接”。这就是我们看到的第二次重定向,小明授权之后,又重新重定向回到了小兔软件的地址上,这样小明就跟小兔软件有了新的 “连接”。

到这里,你就能理解在授权码许可的流程中,为什么需要两次重定向了吧。

为了重新建立起这样的一次连接,我们又不能让访问令牌暴露出去,就有了这样一个临时的、间接的凭证:授权码。因为小兔软件最终要拿到的是安全保密性要求极高的访问令牌,并不是授权码,而授权码是可以暴露在浏览器上面的。这样有了授权码的参与,访问令牌可以在后端服务之间传输,同时呢还可以重新建立小明与小兔软件之间的“连接”。这样通过一个授权码,既“照顾”到了小明的体验,又“照顾”了通信的安全。

这下,你就知道为什么要有授权码了吧。

那么,在执行授权码流程的时候,授权码和访问令牌在小兔软件和授权服务之间到底是怎么流转的呢?要回答这个问题,就需要继续分析一下授权码许可类型的通信过程了。

授权码许可类型的通信过程

图 1 的通信过程中标识出来的步骤就有 9 个,一步步地去分析看似会很复杂,所以我会用另一个维度来分析以帮助你理解,也就是从直接通信和间接通信的维度来分析。这里所谓的间接通信就是指获取授权码的交互,而直接通信就是指通过授权码换取访问令牌的交互。

接下来,我们就一起分析下吧,看看哪些是间接通信,哪些又是直接通信。

间接通信我们先分析下为什么是“间接”。我们把图 1 中获取授权码 code 的流程 “放大”,并换个角度来看一看,也就是将浏览器这个代理放到第三方软件小兔和授权服务中间。于是,我们来到了下面这张图: 图3 获取授权码的交互过程

这个过程,仿佛有这样的一段对话。

小明:“你好,小兔软件,我要访问你了。 ”小兔软件:“好的,我把你引到授权服务那里,我需要授权服务给我一个授权码。” 授权服务:“小兔软件,我把授权码发给浏览器了。” 小兔软件:“好的,我从浏览器拿到了授权码。”

不知道你注意到没有,第三方软件小兔和授权服务之间,并没有发生直接的通信,而是 ==通过浏览器这个“中间人” 来 “搭线”的。== 因此,我们说这是一个间接通信的方式。

直接通信

那我们再分析下,授权码换取访问令牌的交互,为什么是“直接”的。我们再把图 1 中获取访问令牌的流程“放大”,就得到了下面的图示: 图4 授权码换取访问令牌的交互过程

相比获取授权码过程的间接通信,获取访问令牌的直接通信就比较容易理解了,就是第三方软件小兔获取到授权码 code 值后,向授权服务发起获取访问令牌 access_token 的通信请求。这个请求是第三方软件服务器跟授权服务的服务器之间的通信,都是在后端服务器之间的请求和响应,因此也叫作后端通信。

两个 “一伙”

了解了上面的通信方式之后,不知道你有没有意识到,OAuth 2.0 中的 4 个角色是 “两两站队” 的:资源拥有者和第三方软件“站在一起”,因为第三方软件要代表资源拥有者去访问受保护资源;授权服务和受保护资源“站在一起”,因为授权服务负责颁发访问令牌,受保护资源负责接收并验证访问令牌。 图5 OAuth 2.0 中的4个角色是“两两站队”

讲到这里的时候,你会发现在这一讲,介绍授权码流程的时候我都是以浏览器参与的场景来讲的,那么浏览器一定要参与到这个流程中吗?其实,授权码许可流程,不一定要有浏览器的参与。接下来,我们就继续分析下其中的逻辑。

一定要有浏览器吗?

OAuth 2.0 发展之初,开放生态环境相对单薄,以浏览器为代理的 Web 应用居多,授权码许可类型 “理所当然” 地被应用到了通过浏览器才能访问的 Web 应用中。

',38),h={href:"https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html",target:"_blank",rel:"noopener noreferrer"},d=a(`

根据微信官方文档描述,开发者获取用户登录态信息的过程正是一个授权码的许可流程:

  • 首先,开发者通过 wx.login(Object object) 方法获取到登录凭证 code 值,这一步的流程是在小程序内部通过调用微信提供的 SDK 实现;
  • 然后,再通过该 code 值换取用户的 session_key 等信息,也就是官方文档的 auth.code2Session 方法,同时该方法也是被强烈建议通过开发者的后端服务来调用的。

你可以看到,这个过程并没有使用到浏览器,但确实按照授权码许可的思想走了一个完整的授权码许可流程。也就是说,先通过小程序前端获取到 code 值,再通过小程序的后端服务使用 code 值换取 session_key 等信息,只不过是访问令牌 access_token 的值被换成了 session_key。

GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
 

你看,这整个过程体现的就是授权码许可流程的思想。

总结

这节课又接近尾声了,我再带你回顾下重点内容。

今天,我从为什么需要授权码这个问题开始讲起,并通过授权码把授权码许可流程整体的通信过程串了一遍,提到了授权码这种方式解决的问题,也提到了授权码流程的通信方式。总结来说,我需要你记住以下两点。

  1. 授权码许可流程有两种通信方式。一种是前端通信,因为它通过浏览器促成了授权码的交互流程,比如京东商家开放平台的授权服务生成授权码发送到浏览器,第三方软件小兔从浏览器获取授权码。正因为获取授权码的时候小兔软件和授权服务并没有发生直接的联系,也叫做间接通信。另外一种是后端通信,在小兔软件获取到授权码之后,在后端服务直接发起换取访问令牌的请求,也叫做直接通信。
  2. 在 OAuth 2.0 中,访问令牌被要求有极高的安全保密性,因此我们不能让它暴露在浏览器上面,只能通过第三方软件(比如小兔)的后端服务来获取和使用,以最大限度地保障访问令牌的安全性。正因为访问令牌的这种安全要求特性,当需要前端通信,比如浏览器上面的流转的时候,OAuth 2.0 才又提供了一个临时的凭证:授权码。通过授权码的方式,可以让用户小明在授权服务上给小兔授权之后,还能重新回到小兔的操作页面上。这样,在保障安全性的情况下,提升了小明在小兔上的体验。

从授权码许可流程中就可以看出来,它完美地将 OAuth 2.0 的 4 个角色组织了起来,并保证了它们之间的顺畅通信。它提出的这种结构和思想都可以被迁移到其他环境或者协议上,比如在微信小程序中使用授权码许可。

不过,也正是因为有了授权码的参与,才使得授权码许可要比其他授权许可类型,在授权的流程上多出了好多步骤,让授权码许可类型成为了 OAuth 2.0 体系中迄今流程最完备、安全性最高的授权流程。在接下来的两讲中,我还会为你重点讲解授权码许可类型下的授权服务。

思考题

好了,今天这一讲我们马上要结束了,我给你留个思考题。关于不需要浏览器参与的授权码许可流程,你还能列举出更多的应用场景吗?

`,13);function g(m,u){const n=s("ExternalLinkIcon");return o(),i("div",null,[r,t("p",null,[e("但实际上,OAuth 2.0 是一个授权理念,或者说是一种授权思维。它的授权码模式的思维可以移植到很多场景中,比如微信小程序。在开发微信小程序应用时,我们通过授权码模式获取用户登录信息,"),t("a",h,[e("官方文档的地址示例"),c(n)]),e("中给出的 grant_type=authorization_code ,就没有用到浏览器。")]),d])}const f=p(l,[["render",g],["__file","02.html.vue"]]),k=JSON.parse('{"path":"/other/oauth2/02.html","title":"02 | 授权码许可类型中,为什么一定要有授权码?","lang":"zh-CN","frontmatter":{"title":"02 | 授权码许可类型中,为什么一定要有授权码?","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","publish":true,"description":"你好,我是王新栋。 在上一讲,我提到了 OAuth 2.0 的授权码许可类型,在小兔打单软件的例子里面,小兔最终是通过访问令牌请求到小明的店铺里的订单数据。同时呢,我还提到了,这个访问令牌是通过授权码换来的。到这里估计你会问了,为什么要用授权码来换令牌?为什么不能直接颁发访问令牌呢? 你可以先停下来想想这个问题。今天咱们这节课,我会带着你深入探究下其中...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/02.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"02 | 授权码许可类型中,为什么一定要有授权码?"}],["meta",{"property":"og:description","content":"你好,我是王新栋。 在上一讲,我提到了 OAuth 2.0 的授权码许可类型,在小兔打单软件的例子里面,小兔最终是通过访问令牌请求到小明的店铺里的订单数据。同时呢,我还提到了,这个访问令牌是通过授权码换来的。到这里估计你会问了,为什么要用授权码来换令牌?为什么不能直接颁发访问令牌呢? 你可以先停下来想想这个问题。今天咱们这节课,我会带着你深入探究下其中..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221108113248.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"02 | 授权码许可类型中,为什么一定要有授权码?\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221108113248.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221108113402.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221108113557.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221108113739.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221108113820.png\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"为什么需要授权码?","slug":"为什么需要授权码","link":"#为什么需要授权码","children":[]},{"level":3,"title":"授权码许可类型的通信过程","slug":"授权码许可类型的通信过程","link":"#授权码许可类型的通信过程","children":[]},{"level":3,"title":"直接通信","slug":"直接通信","link":"#直接通信","children":[]},{"level":3,"title":"两个 “一伙”","slug":"两个-一伙","link":"#两个-一伙","children":[]},{"level":3,"title":"一定要有浏览器吗?","slug":"一定要有浏览器吗","link":"#一定要有浏览器吗","children":[]},{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]},{"level":3,"title":"思考题","slug":"思考题","link":"#思考题","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":3},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":12.79,"words":3838},"filePathRelative":"other/oauth2/02.md","localizedDate":"2022年11月8日","excerpt":"

你好,我是王新栋。

\\n

在上一讲,我提到了 OAuth 2.0 的授权码许可类型,在小兔打单软件的例子里面,小兔最终是通过访问令牌请求到小明的店铺里的订单数据。同时呢,我还提到了,这个访问令牌是通过授权码换来的。到这里估计你会问了,为什么要用授权码来换令牌?为什么不能直接颁发访问令牌呢?

\\n

你可以先停下来想想这个问题。今天咱们这节课,我会带着你深入探究下其中的逻辑。

\\n

为什么需要授权码?

\\n

在讲这个问题之前,我先要和你同步下,在 OAuth 2.0 的体系里面有 4 种角色,按照官方的称呼它们分别是资源拥有者、客户端、授权服务和受保护资源。不过,这里的客户端,我更愿意称其为第三方软件,而且在咱们这个课程中,都是以第三方软件在举例子。所以,在后续的讲解中我统一把它称为第三方软件。

","autoDesc":true}');export{f as comp,k as data}; diff --git a/assets/03.html-_Fpqszha.js b/assets/03.html-CK1TN99Q.js similarity index 99% rename from assets/03.html-_Fpqszha.js rename to assets/03.html-CK1TN99Q.js index 75b5961033..34ca09a992 100644 --- a/assets/03.html-_Fpqszha.js +++ b/assets/03.html-CK1TN99Q.js @@ -1,4 +1,4 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as e,c as o,a as n,b as s,d as c,e as i}from"./app-CVMfKeWw.js";const l={},u=i(`

你好,我是王新栋。

在上一讲,我从为什么需要授权码这个问题开始,为你串了一遍授权码许可流程整体的通信过程。在接下来的三讲中,我会着重为你讲解关于授权服务的工作流程、授权过程中的令牌,以及如何接入 OAuth 2.0。这样一来,你就可以吃透授权码许可这一最经典、最完备、最常用的授权流程了,以后再处理授权相关的逻辑就更得心应手了。现在呢,让我们开始这一讲。

在介绍授权码许可类型时,我提到了很多次 “授权服务”。一句话概括,授权服务就是负责颁发访问令牌的服务。更进一步地讲,OAuth 2.0 的核心是授权服务,而授权服务的核心

为什么这么说呢?当第三方软件比如小兔,要想获取小明在京东店铺的订单,就必须先从京东商家开放平台的授权服务那里获取访问令牌,进而通过访问令牌来 “代表” 小明去请求小明的订单数据。这不恰恰就是整个 OAuth 2.0 授权体系的核心吗?

那么,授权服务到底是怎么生成访问令牌的,这其中包含了哪些操作呢?还有一个问题是,访问令牌过期了而用户又不在场的情况下,又如何重新生成访问令牌呢?

带着这两个问题,我们就以授权码许可类型为例,一起深入探索下授权服务这个核心组件吧。

授权服务的工作过程

开始之前,你还是要先回想下小明给小兔软件授权订单数据的整个流程。

我们说小兔软件先要让小明去京东商家开放平台那里给它授权数据,那这里是不是你觉得很奇怪?你总不能说,“嘿,京东,你把数据给小兔用吧”,那京东肯定会回复说,“小明,小兔是谁啊,没在咱家备过案,我不能给他,万一是骗子呢?”

对吧,你想想是不是这个逻辑。所以,授权这个大动作的前提,肯定是小兔要去平台那里“备案”,也就是注册。注册完后,京东商家开放平台就会给小兔软件 app_id 和 app_secret 等信息,以方便后面授权时的各种身份校验。

同时,注册的时候,第三方软件也会请求受保护资源的可访问范围。比如,小兔能否获取小明店铺 3 个月以前的订单,能否获取每条订单的所有字段信息等等。这个权限范围,就是 scope。后面呢,我还会详细讲述范围控制。

文字说起来有点抽象,咱们还是直接上代码吧。关于注册后的数据存储,我们使用如下 Java 代码来模拟:


+import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as e,c as o,a as n,b as s,d as c,e as i}from"./app-_Oi5YZFn.js";const l={},u=i(`

你好,我是王新栋。

在上一讲,我从为什么需要授权码这个问题开始,为你串了一遍授权码许可流程整体的通信过程。在接下来的三讲中,我会着重为你讲解关于授权服务的工作流程、授权过程中的令牌,以及如何接入 OAuth 2.0。这样一来,你就可以吃透授权码许可这一最经典、最完备、最常用的授权流程了,以后再处理授权相关的逻辑就更得心应手了。现在呢,让我们开始这一讲。

在介绍授权码许可类型时,我提到了很多次 “授权服务”。一句话概括,授权服务就是负责颁发访问令牌的服务。更进一步地讲,OAuth 2.0 的核心是授权服务,而授权服务的核心

为什么这么说呢?当第三方软件比如小兔,要想获取小明在京东店铺的订单,就必须先从京东商家开放平台的授权服务那里获取访问令牌,进而通过访问令牌来 “代表” 小明去请求小明的订单数据。这不恰恰就是整个 OAuth 2.0 授权体系的核心吗?

那么,授权服务到底是怎么生成访问令牌的,这其中包含了哪些操作呢?还有一个问题是,访问令牌过期了而用户又不在场的情况下,又如何重新生成访问令牌呢?

带着这两个问题,我们就以授权码许可类型为例,一起深入探索下授权服务这个核心组件吧。

授权服务的工作过程

开始之前,你还是要先回想下小明给小兔软件授权订单数据的整个流程。

我们说小兔软件先要让小明去京东商家开放平台那里给它授权数据,那这里是不是你觉得很奇怪?你总不能说,“嘿,京东,你把数据给小兔用吧”,那京东肯定会回复说,“小明,小兔是谁啊,没在咱家备过案,我不能给他,万一是骗子呢?”

对吧,你想想是不是这个逻辑。所以,授权这个大动作的前提,肯定是小兔要去平台那里“备案”,也就是注册。注册完后,京东商家开放平台就会给小兔软件 app_id 和 app_secret 等信息,以方便后面授权时的各种身份校验。

同时,注册的时候,第三方软件也会请求受保护资源的可访问范围。比如,小兔能否获取小明店铺 3 个月以前的订单,能否获取每条订单的所有字段信息等等。这个权限范围,就是 scope。后面呢,我还会详细讲述范围控制。

文字说起来有点抽象,咱们还是直接上代码吧。关于注册后的数据存储,我们使用如下 Java 代码来模拟:


 Map<String,String> appMap =  new HashMap<String, String>();//模拟第三方软件注册之后的数据库存储
 
 appMap.put("app_id","APPID_RABBIT");
diff --git a/assets/04.html-BA8_N2LW.js b/assets/04.html-CeKJQimw.js
similarity index 99%
rename from assets/04.html-BA8_N2LW.js
rename to assets/04.html-CeKJQimw.js
index dcb7ea401a..14ef7d7f7d 100644
--- a/assets/04.html-BA8_N2LW.js
+++ b/assets/04.html-CeKJQimw.js
@@ -1,4 +1,4 @@
-import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as i,a as n,b as a,d as t,e}from"./app-CVMfKeWw.js";const l={},u=e(`

在上一讲,我们讲到了授权服务的核心就是颁发访问令牌,而 OAuth 2.0 规范并没有约束访问令牌内容的生成规则,只要符合唯一性、不连续性、不可猜性就够了。这就意味着,我们可以灵活选择令牌的形式,既可以是没有内部结构且不包含任何信息含义的随机字符串,也可以是具有内部结构且包含有信息含义的字符串。

随机字符串这样的方式我就不再介绍了,之前课程中我们生成令牌的方式都是默认一个随机字符串。而在结构化令牌这方面,目前用得最多的就是 JWT 令牌了。

接下来,我就要和你详细讲讲,JWT 是什么、原理是怎样的、优势是什么,以及怎么使用,同时我还会讲到令牌生命周期的问题。

JWT 结构化令牌

关于什么是 JWT,官方定义是这样描述的:

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。

这个定义是不是很费解?我们简单理解下,JWT 就是用一种结构化封装的方式来生成 token 的技术。结构化后的 token 可以被赋予非常丰富的含义,这也是它与原先毫无意义的、随机的字符串形式 token 的最大区别。

结构化之后,令牌本身就可以被“塞进”一些有用的信息,比如小明为小兔软件进行了授权的信息、授权的范围信息等。或者,你可以形象地将其理解为这是一种“自编码”的能力,而这些恰恰是无结构化令牌所不具备的。

JWT 这种结构化体可以分为 HEADER(头部)、PAYLOAD(数据体)和 SIGNATURE(签名)三部分。经过签名之后的 JWT 的整体结构,是被句点符号分割的三段内容,结构为 header.payload.signature 。比如下面这个示例:


+import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as i,a as n,b as a,d as t,e}from"./app-_Oi5YZFn.js";const l={},u=e(`

在上一讲,我们讲到了授权服务的核心就是颁发访问令牌,而 OAuth 2.0 规范并没有约束访问令牌内容的生成规则,只要符合唯一性、不连续性、不可猜性就够了。这就意味着,我们可以灵活选择令牌的形式,既可以是没有内部结构且不包含任何信息含义的随机字符串,也可以是具有内部结构且包含有信息含义的字符串。

随机字符串这样的方式我就不再介绍了,之前课程中我们生成令牌的方式都是默认一个随机字符串。而在结构化令牌这方面,目前用得最多的就是 JWT 令牌了。

接下来,我就要和你详细讲讲,JWT 是什么、原理是怎样的、优势是什么,以及怎么使用,同时我还会讲到令牌生命周期的问题。

JWT 结构化令牌

关于什么是 JWT,官方定义是这样描述的:

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。

这个定义是不是很费解?我们简单理解下,JWT 就是用一种结构化封装的方式来生成 token 的技术。结构化后的 token 可以被赋予非常丰富的含义,这也是它与原先毫无意义的、随机的字符串形式 token 的最大区别。

结构化之后,令牌本身就可以被“塞进”一些有用的信息,比如小明为小兔软件进行了授权的信息、授权的范围信息等。或者,你可以形象地将其理解为这是一种“自编码”的能力,而这些恰恰是无结构化令牌所不具备的。

JWT 这种结构化体可以分为 HEADER(头部)、PAYLOAD(数据体)和 SIGNATURE(签名)三部分。经过签名之后的 JWT 的整体结构,是被句点符号分割的三段内容,结构为 header.payload.signature 。比如下面这个示例:


 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
 eyJzdWIiOiJVU0VSVEVTVCIsImV4cCI6MTU4NDEwNTc5MDcwMywiaWF0IjoxNTg0MTA1OTQ4MzcyfQ.
 1HbleXbvJ_2SW8ry30cXOBGR9FW4oSWBd3PWaWKsEXE
diff --git a/assets/05.html-C12aFhzW.js b/assets/05.html-CMuhT-M0.js
similarity index 99%
rename from assets/05.html-C12aFhzW.js
rename to assets/05.html-CMuhT-M0.js
index 7b08c76b5d..1f6df5b18c 100644
--- a/assets/05.html-C12aFhzW.js
+++ b/assets/05.html-CMuhT-M0.js
@@ -1,4 +1,4 @@
-import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as e,c as p,a as o,b as n,d as c,w as i,e as l}from"./app-CVMfKeWw.js";const u={},r=l(`

虽然授权服务做了大部分工作,但是呢,在 OAuth 2.0 的体系里面,除了资源拥有者是作为用户参与,还有另外两个系统角色,也就是第三方软件和受保护资源服务。那么今天这一讲,我们就站在这两个角色的角度,看看它们应该做哪些工作,才能接入到 OAuth 2.0 的体系里面呢?

现在,就让我们来看看,作为第三方软件的小兔和京东的受保护资源服务,具体需要着重处理哪些工作吧。

注:另外说明一点,为了脱敏的需要,在下面的讲述中,我只是把京东商家开放平台作为一个角色使用,以便有场景感,来帮助你理解。

构建第三方软件应用

我们先来思考一下:如果要基于京东商家开放平台构建一个小兔打单软件的应用,小兔软件的研发人员应该做哪些工作?

是不是要到京东商家开放平台申请注册为开发者,在成为开发者以后再创建一个应用,之后我们就开始开发了,对吧?没错,一定是这样的流程。那么,开发第三方软件应用的过程中,我们需要重点关注哪些内容呢?

我先来和你总结下,这些内容包括 4 部分,分别是:注册信息、引导授权、使用访问令牌、使用刷新令牌。

图1 开发第三方软件应用,应该关注的内容

第一点,注册信息。

首先,小兔软件只有先有了身份,才可以参与到 OAuth 2.0 的流程中去。也就是说,小兔软件需要先拥有自己的 app_id 和 app_serect 等信息,同时还要填写自己的回调地址 redirect_uri、申请权限等信息。这种方式的注册呢,我们有时候也称它为静态注册,也就是小兔软件的研发人员提前登录到京东商家开放平台进行手动注册,以便后续使用这些注册的相关信息来请求访问令牌。

第二点,引导授权。

当用户需要使用第三方软件,来操作其在受保护资源上的数据,就需要第三方软件来引导授权。比如,小明要使用小兔打单软件来对店铺里面的订单发货打印,那小明首先访问的一定是小兔软件(原则上是直接访问第三方软件,不过我们在后面讲到服务市场这种场景的时候,会有稍微不同),不会是授权服务,更不会是受保护资源服务。

但是呢,小兔软件需要小明的授权,只有授权服务才能允许小明这样做。所以呢,小兔软件需要 “配合” 小明做的第一件事儿,就是将小明引导至授权服务,如下面代码所示。那去做什么呢?其实就是让用户为第三方软件授权,得到了授权之后,第三方软件才可以代表用户去访问数据。也就是说,小兔打单软件获得授权之后,才能够代表小明处理其在京东店铺上的订单数据。


+import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as e,c as p,a as o,b as n,d as c,w as i,e as l}from"./app-_Oi5YZFn.js";const u={},r=l(`

虽然授权服务做了大部分工作,但是呢,在 OAuth 2.0 的体系里面,除了资源拥有者是作为用户参与,还有另外两个系统角色,也就是第三方软件和受保护资源服务。那么今天这一讲,我们就站在这两个角色的角度,看看它们应该做哪些工作,才能接入到 OAuth 2.0 的体系里面呢?

现在,就让我们来看看,作为第三方软件的小兔和京东的受保护资源服务,具体需要着重处理哪些工作吧。

注:另外说明一点,为了脱敏的需要,在下面的讲述中,我只是把京东商家开放平台作为一个角色使用,以便有场景感,来帮助你理解。

构建第三方软件应用

我们先来思考一下:如果要基于京东商家开放平台构建一个小兔打单软件的应用,小兔软件的研发人员应该做哪些工作?

是不是要到京东商家开放平台申请注册为开发者,在成为开发者以后再创建一个应用,之后我们就开始开发了,对吧?没错,一定是这样的流程。那么,开发第三方软件应用的过程中,我们需要重点关注哪些内容呢?

我先来和你总结下,这些内容包括 4 部分,分别是:注册信息、引导授权、使用访问令牌、使用刷新令牌。

图1 开发第三方软件应用,应该关注的内容

第一点,注册信息。

首先,小兔软件只有先有了身份,才可以参与到 OAuth 2.0 的流程中去。也就是说,小兔软件需要先拥有自己的 app_id 和 app_serect 等信息,同时还要填写自己的回调地址 redirect_uri、申请权限等信息。这种方式的注册呢,我们有时候也称它为静态注册,也就是小兔软件的研发人员提前登录到京东商家开放平台进行手动注册,以便后续使用这些注册的相关信息来请求访问令牌。

第二点,引导授权。

当用户需要使用第三方软件,来操作其在受保护资源上的数据,就需要第三方软件来引导授权。比如,小明要使用小兔打单软件来对店铺里面的订单发货打印,那小明首先访问的一定是小兔软件(原则上是直接访问第三方软件,不过我们在后面讲到服务市场这种场景的时候,会有稍微不同),不会是授权服务,更不会是受保护资源服务。

但是呢,小兔软件需要小明的授权,只有授权服务才能允许小明这样做。所以呢,小兔软件需要 “配合” 小明做的第一件事儿,就是将小明引导至授权服务,如下面代码所示。那去做什么呢?其实就是让用户为第三方软件授权,得到了授权之后,第三方软件才可以代表用户去访问数据。也就是说,小兔打单软件获得授权之后,才能够代表小明处理其在京东店铺上的订单数据。


 String oauthUrl = "http://localhost:8081/OauthServlet-ch03?reqType=oauth";
 
 response.sendRedirect(toOauthUrl);
diff --git a/assets/06.html-fv-ofJNx.js b/assets/06.html-BX6nV64C.js
similarity index 99%
rename from assets/06.html-fv-ofJNx.js
rename to assets/06.html-BX6nV64C.js
index 88cb489d7d..c02041cdd8 100644
--- a/assets/06.html-fv-ofJNx.js
+++ b/assets/06.html-BX6nV64C.js
@@ -1,4 +1,4 @@
-import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as e,c as o,a as n,b as s,d as c,e as u}from"./app-CVMfKeWw.js";const i={},l=u(`

在前面几讲学习授权码许可类型的原理与工作流程时,不知道你是不是一直有这样一个疑问:授权码许可的流程最完备、最安全没错儿,但它适合所有的授权场景吗?在有些场景下使用授权码许可授权,是不是过于复杂了,是不是根本就没必要这样?

比如,小兔打单软件是京东官方开发的一款软件,那么小明在使用小兔的时候,还需要小兔再走一遍授权码许可类型的流程吗?估计你也猜到答案了,肯定是不需要了。

你还记得授权码许可流程的特点么?它通过授权码这种临时的中间值,让小明这样的用户参与进来,从而让小兔软件和京东之间建立联系,进而让小兔代表小明去访问他在京东店铺的订单数据。

现在小兔被“招安”了,是京东自家的了,是被京东充分信任的,没有“第三方软件”的概念了。同时,小明也是京东店铺的商家,也就是说软件和用户都是京东的资产。这时,显然没有必要再使用授权码许可类型进行授权了。但是呢,小兔依然要通过互联网访问订单数据的 Web API,来提供为小明打单的功能。

于是,为了保护这些场景下的 Web API,又为了让 OAuth 2.0 更好地适应现实世界的更多场景,来解决比如上述小兔软件这样的案例,OAuth 2.0 体系中还提供了资源拥有者凭据许可类型。

资源拥有者凭据许可

从“资源拥有者凭据许可”这个命名上,你可能就已经理解它的含义了。没错,资源拥有者的凭据,就是用户的凭据,就是用户名和密码。可见,这是最糟糕的一种方式。那为什么 OAuth 2.0 还支持这种许可类型,而且编入了 OAuth 2.0 的规范呢?

我们先来思考一下。正如上面我提到的,小兔此时就是京东官方出品的一款软件,小明也是京东的用户,那么小明其实是可以使用用户名和密码来直接使用小兔这款软件的。原因很简单,那就是这里不再有“第三方”的概念了。

但是呢,如果每次小兔都是拿着小明的用户名和密码来通过调用 Web API 的方式,来访问小明店铺的订单数据,甚至还有商品信息等,在调用这么多 API 的情况下,无疑增加了用户名和密码等敏感信息的攻击面。

如果是使用了 token 来代替这些“满天飞”的敏感信息,不就能很大程度上保护敏感信息数据了吗?这样,小兔软件只需要使用一次用户名和密码数据来换回一个 token,进而通过 token 来访问小明店铺的数据,以后就不会再使用用户名和密码了。

接下来,我们一起看下这种许可类型的流程,如下图所示:

图1 资源拥有者凭据许可类型的流程

步骤 1:当用户访问第三方软件小兔时,会提示输入用户名和密码。索要用户名和密码,就是资源拥有者凭据许可类型的特点。

步骤 2:这里的 grant_type 的值为 password,告诉授权服务使用资源拥有者凭据许可凭据的方式去请求访问。


+import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as e,c as o,a as n,b as s,d as c,e as u}from"./app-_Oi5YZFn.js";const i={},l=u(`

在前面几讲学习授权码许可类型的原理与工作流程时,不知道你是不是一直有这样一个疑问:授权码许可的流程最完备、最安全没错儿,但它适合所有的授权场景吗?在有些场景下使用授权码许可授权,是不是过于复杂了,是不是根本就没必要这样?

比如,小兔打单软件是京东官方开发的一款软件,那么小明在使用小兔的时候,还需要小兔再走一遍授权码许可类型的流程吗?估计你也猜到答案了,肯定是不需要了。

你还记得授权码许可流程的特点么?它通过授权码这种临时的中间值,让小明这样的用户参与进来,从而让小兔软件和京东之间建立联系,进而让小兔代表小明去访问他在京东店铺的订单数据。

现在小兔被“招安”了,是京东自家的了,是被京东充分信任的,没有“第三方软件”的概念了。同时,小明也是京东店铺的商家,也就是说软件和用户都是京东的资产。这时,显然没有必要再使用授权码许可类型进行授权了。但是呢,小兔依然要通过互联网访问订单数据的 Web API,来提供为小明打单的功能。

于是,为了保护这些场景下的 Web API,又为了让 OAuth 2.0 更好地适应现实世界的更多场景,来解决比如上述小兔软件这样的案例,OAuth 2.0 体系中还提供了资源拥有者凭据许可类型。

资源拥有者凭据许可

从“资源拥有者凭据许可”这个命名上,你可能就已经理解它的含义了。没错,资源拥有者的凭据,就是用户的凭据,就是用户名和密码。可见,这是最糟糕的一种方式。那为什么 OAuth 2.0 还支持这种许可类型,而且编入了 OAuth 2.0 的规范呢?

我们先来思考一下。正如上面我提到的,小兔此时就是京东官方出品的一款软件,小明也是京东的用户,那么小明其实是可以使用用户名和密码来直接使用小兔这款软件的。原因很简单,那就是这里不再有“第三方”的概念了。

但是呢,如果每次小兔都是拿着小明的用户名和密码来通过调用 Web API 的方式,来访问小明店铺的订单数据,甚至还有商品信息等,在调用这么多 API 的情况下,无疑增加了用户名和密码等敏感信息的攻击面。

如果是使用了 token 来代替这些“满天飞”的敏感信息,不就能很大程度上保护敏感信息数据了吗?这样,小兔软件只需要使用一次用户名和密码数据来换回一个 token,进而通过 token 来访问小明店铺的数据,以后就不会再使用用户名和密码了。

接下来,我们一起看下这种许可类型的流程,如下图所示:

图1 资源拥有者凭据许可类型的流程

步骤 1:当用户访问第三方软件小兔时,会提示输入用户名和密码。索要用户名和密码,就是资源拥有者凭据许可类型的特点。

步骤 2:这里的 grant_type 的值为 password,告诉授权服务使用资源拥有者凭据许可凭据的方式去请求访问。


 Map<String, String> params = new HashMap<String, String>();
 params.put("grant_type","password");
 params.put("app_id","APPIDTEST");
diff --git a/assets/07.html-C8FkfsLs.js b/assets/07.html-CCBD9WTC.js
similarity index 99%
rename from assets/07.html-C8FkfsLs.js
rename to assets/07.html-CCBD9WTC.js
index db44f27d69..d52201c349 100644
--- a/assets/07.html-C8FkfsLs.js
+++ b/assets/07.html-CCBD9WTC.js
@@ -1,4 +1,4 @@
-import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as r,c as o,a as p,b as e,d as c,e as a}from"./app-CVMfKeWw.js";const i={},l=a(`

在前面几讲中,我都是基于 Web 应用的场景来讲解的 OAuth 2.0。除了 Web 应用外,现实环境中还有非常多的移动 App。那么,在移动 App 中,能不能使用 OAuth 2.0 ,又该如何使用 OAuth 2.0 呢?

没错,OAuth 2.0 最初的应用场景确实是 Web 应用,但是它的伟大之处就在于,它把自己的核心协议定位成了一个框架而不是单个的协议。这样做的好处是,我们可以基于这个基本的框架协议,在一些特定的领域进行扩展。

因此,到了桌面或者移动的场景下,OAuth 2.0 的协议一样适用。考虑到授权码许可是最完备、最安全的许可类型,所以我在讲移动 App 如何使用 OAuth 2.0 的时候,依然会用授权码许可来讲解,毕竟“要用就用最好的”。

当我们开发一款移动 App 的时候,可以选择没有 Server 端的 “纯 App” 架构,比如这款 App 不需要跟自己的 Server 端通信,或者可以调用其它开放的 HTTP 接口;当然也可以选择有服务端的架构,比如这款 App 还想把用户的操作日志记录下来并保存到 Server 端的数据库中。

那总结下来呢,移动 App 可以分为两类,一类是没有 Server 端的 App 应用,一类是有 Server 端的 App 应用。

图1 两类移动App

这两类 App 在使用 OAuth 2.0 时的最大区别,在于获取访问令牌的方式:

  • 如果有 Server 端,就建议通过 Server 端和授权服务做交互来换取访问令牌;
  • 如果没有 Server 端,那么只能通过前端通信来跟授权服务做交互,比如在上一讲中提到的隐式许可授权类型。当然,这种方式的安全性就降低了很多。

有些时候,我们可能觉得自己开发一个 App 不需要一个 Server 端。那好,就让我们先来看看没有 Server 端的 App 应用如何使用授权码许可类型。

没有 Server 端的 App

在一个没有 Server 端支持的纯 App 应用中,我们首先想到的是,如何可以像 Web 服务那样,让请求和响应“来去自如”呢。

你可能会想,我是不是可以将一个“迷你”的 Web 服务器嵌入到 App 里面去,这样不就可以

这样的 App 通过监听运行在 localhost 上的 Web 服务器 URI,就可以做到跟普通的 Web 应用一样的通信机制。但这种方式不是我们这次要讲的重点,如果你想深入了解可以去查些资料。因为当使用这种方式的时候,请求访问令牌时需要的 app_secret 就只能保存在用户本地设备上,而这并不是我们所建议的。

到这里,你应该猜到了,问题的关键在于如何保存 app_secret,因为 App 会被安装在成千上万个终端设备上,app_secret 一旦被破解,就将会造成灾难性的后果。这时,有的同学突发奇想,如果不用 app_secret,也能在授权码流程里换回访问令牌 access_token,不就可以了吗?

确实可以,但新的问题也来了。在授权码许可类型的流程中,如果没有了 app_secret 这一层的保护,那么通过授权码 code 换取访问令牌的时候,就只有授权码 code 在“冲锋陷阵”了。这时,授权码 code 一旦失窃,就会带来严重的安全问题。那么,我既不使用 app_secret,还要防止授权码 code 失窃,有什么好的方法吗?

有,OAuth 2.0 里面就有这样的指导方法。这个方法就是我们将要介绍的 PKCE 协议,全称是 Proof Key for Code Exchange by OAuth Public Clients。

在下面的流程图中,为了突出第三方软件使用 PKCE 协议时与授权服务之间的通信过程,我省略了受保护资源服务和资源拥有者的角色:

图2 使用PKCE协议的流程图

我来和你分析下这个流程中的重点。

首先,App 自己要生成一个随机的、长度在 43~128 字符之间的、参数为 code_verifier 的字符串验证码;接着,我们再利用这个 code_verifier,来生成一个被称为“挑战码”的参数code_challenge。

那怎么生成这个 code_challenge 的值呢?OAuth 2.0 规范里面给出了两种方法,就是看 code_challenge_method 这个参数的值:

  • 一种 code_challenge_method=plain,此时 code_verifier 的值就是 code_challenge 的值;
  • 另外一种 code_challenge_method=S256,就是将 code_verifier 值进行 ASCII 编码之后再进行哈希,然后再将哈希之后的值进行 BASE64-URL 编码,如下代码所示。
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
+import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as r,c as o,a as p,b as e,d as c,e as a}from"./app-_Oi5YZFn.js";const i={},l=a(`

在前面几讲中,我都是基于 Web 应用的场景来讲解的 OAuth 2.0。除了 Web 应用外,现实环境中还有非常多的移动 App。那么,在移动 App 中,能不能使用 OAuth 2.0 ,又该如何使用 OAuth 2.0 呢?

没错,OAuth 2.0 最初的应用场景确实是 Web 应用,但是它的伟大之处就在于,它把自己的核心协议定位成了一个框架而不是单个的协议。这样做的好处是,我们可以基于这个基本的框架协议,在一些特定的领域进行扩展。

因此,到了桌面或者移动的场景下,OAuth 2.0 的协议一样适用。考虑到授权码许可是最完备、最安全的许可类型,所以我在讲移动 App 如何使用 OAuth 2.0 的时候,依然会用授权码许可来讲解,毕竟“要用就用最好的”。

当我们开发一款移动 App 的时候,可以选择没有 Server 端的 “纯 App” 架构,比如这款 App 不需要跟自己的 Server 端通信,或者可以调用其它开放的 HTTP 接口;当然也可以选择有服务端的架构,比如这款 App 还想把用户的操作日志记录下来并保存到 Server 端的数据库中。

那总结下来呢,移动 App 可以分为两类,一类是没有 Server 端的 App 应用,一类是有 Server 端的 App 应用。

图1 两类移动App

这两类 App 在使用 OAuth 2.0 时的最大区别,在于获取访问令牌的方式:

  • 如果有 Server 端,就建议通过 Server 端和授权服务做交互来换取访问令牌;
  • 如果没有 Server 端,那么只能通过前端通信来跟授权服务做交互,比如在上一讲中提到的隐式许可授权类型。当然,这种方式的安全性就降低了很多。

有些时候,我们可能觉得自己开发一个 App 不需要一个 Server 端。那好,就让我们先来看看没有 Server 端的 App 应用如何使用授权码许可类型。

没有 Server 端的 App

在一个没有 Server 端支持的纯 App 应用中,我们首先想到的是,如何可以像 Web 服务那样,让请求和响应“来去自如”呢。

你可能会想,我是不是可以将一个“迷你”的 Web 服务器嵌入到 App 里面去,这样不就可以

这样的 App 通过监听运行在 localhost 上的 Web 服务器 URI,就可以做到跟普通的 Web 应用一样的通信机制。但这种方式不是我们这次要讲的重点,如果你想深入了解可以去查些资料。因为当使用这种方式的时候,请求访问令牌时需要的 app_secret 就只能保存在用户本地设备上,而这并不是我们所建议的。

到这里,你应该猜到了,问题的关键在于如何保存 app_secret,因为 App 会被安装在成千上万个终端设备上,app_secret 一旦被破解,就将会造成灾难性的后果。这时,有的同学突发奇想,如果不用 app_secret,也能在授权码流程里换回访问令牌 access_token,不就可以了吗?

确实可以,但新的问题也来了。在授权码许可类型的流程中,如果没有了 app_secret 这一层的保护,那么通过授权码 code 换取访问令牌的时候,就只有授权码 code 在“冲锋陷阵”了。这时,授权码 code 一旦失窃,就会带来严重的安全问题。那么,我既不使用 app_secret,还要防止授权码 code 失窃,有什么好的方法吗?

有,OAuth 2.0 里面就有这样的指导方法。这个方法就是我们将要介绍的 PKCE 协议,全称是 Proof Key for Code Exchange by OAuth Public Clients。

在下面的流程图中,为了突出第三方软件使用 PKCE 协议时与授权服务之间的通信过程,我省略了受保护资源服务和资源拥有者的角色:

图2 使用PKCE协议的流程图

我来和你分析下这个流程中的重点。

首先,App 自己要生成一个随机的、长度在 43~128 字符之间的、参数为 code_verifier 的字符串验证码;接着,我们再利用这个 code_verifier,来生成一个被称为“挑战码”的参数code_challenge。

那怎么生成这个 code_challenge 的值呢?OAuth 2.0 规范里面给出了两种方法,就是看 code_challenge_method 这个参数的值:

  • 一种 code_challenge_method=plain,此时 code_verifier 的值就是 code_challenge 的值;
  • 另外一种 code_challenge_method=S256,就是将 code_verifier 值进行 ASCII 编码之后再进行哈希,然后再将哈希之后的值进行 BASE64-URL 编码,如下代码所示。
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
 

好了,我知道有这样两个值,也知道它们的生成方法了,但这两个值跟我们的授权码流程有什么关系呢,又怎么利用它们呢?不用着急,我们接着讲。

授权码流程简单概括起来不是有两步吗,第一步是获取授权码 code,第二步是用 app_id+app_secret+code 获取访问令牌 access_token。刚才我们的“梦想”不是设想不使用 app_secret,但同时又能保证授权码流程的安全性么?

没错。code_verifier 和 code_challenge 这两个参数,就是来帮我们实现这个“梦想”的。

在第一步获取授权码 code 的时候,我们使用 code_challenge 参数。需要注意的是,我们要同时将 code_challenge_method 参数也传过去,目的是让授权服务知道生成 code_challenge 值的方法是 plain 还是 S256。https://authorization-server.com/auth?response_type=code&app_id=APP_ID&redirect_uri=REDIRECT_URI&code_challenge=CODE_CHALLENGE&code_challenge_method=S256


 https://authorization-server.com/auth?
 response_type=code&
diff --git a/assets/08.html-DoM55lqI.js b/assets/08.html-BJMKXxmH.js
similarity index 99%
rename from assets/08.html-DoM55lqI.js
rename to assets/08.html-BJMKXxmH.js
index 47d70d1eaf..0f84b5954d 100644
--- a/assets/08.html-DoM55lqI.js
+++ b/assets/08.html-BJMKXxmH.js
@@ -1,4 +1,4 @@
-import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o,c as i,a as e,b as t,d as n,e as s}from"./app-CVMfKeWw.js";const l={},h=s(`

当知道这一讲的主题是 OAuth 2.0 的安全漏洞时,你可能要问了:“OAuth 2.0 不是一种安全协议吗,不是保护 Web API 的吗?为啥 OAuth 2.0 自己还有安全的问题了呢?”

首先,OAuth 2.0 的确是一种安全协议。这没啥问题,但是它有很多使用规范,比如授权码是一个临时凭据只能被使用一次,要对重定向 URI 做校验等。那么,如果使用的时候你没有按照这样的规范来实施,就会有安全漏洞了。

其次,OAuth 2.0 既然是“生长”在互联网这个大环境中,就一样会面对互联网上常见安全风险的攻击,比如跨站请求伪造(Cross-site request forgery,CSRF)、跨站脚本攻击(Cross Site Scripting,XSS)。最后,除了这些常见攻击类型外,OAuth 2.0 自身也有可被利用的安全漏洞,比如授权码失窃、重定向 URI 伪造。

所以,我们在实践 OAuth 2.0 的过程中,安全问题一定是重中之重。接下来,我挑选了 5 个典型的安全问题,其中 CSRF、XSS、水平越权这三种是互联网环境下常见的安全风险,授权码失窃和重定向 URI 被篡改属于 OAuth2.0“专属”的安全风险。接下来,我就和你一起看看这些安全风险的由来,以及如何应对吧。

CSRF 攻击

对于 CSRF 的定义,《OAuth 2 in Action》这本书里的解释,是我目前看到的最为贴切的解释:恶意软件让浏览器向已完成用户身份认证的网站发起请求,并执行有害的操作,就是跨站请求伪造攻击。

它是互联网上最为常见的攻击之一。我们在实践 OAuth2.0 的过程,其实就是在构建一次互联网的应用。因此,OAuth 2.0 同样也会面临这个攻击。接下来,我通过一个案例和你说明这个攻击类型。

有一个软件 A,我们让它来扮演攻击者,让它的开发者按照正常的流程使用极客时间。当该攻击者授权后,拿到授权码的值 codeA 之后,“立即按下了暂停键”,不继续往下走了。那它想干啥呢,我们继续往下看。

这时,有一个第三方软件 B,比如咱们的 Web 版极客时间,来扮演受害者吧。当然最终的受害者是用户,这里是用 Web 版极客时间来作为被软件 A 攻击的对象。

极客时间用于接收授权码的回调地址为 https://time.geekbang.org/callback。有一个用户 G 已经在极客时间的平台登录,且对极客时间进行了授权,也就是用户 G 已经在极客时间平台上有登录态了。如果此时攻击者软件 A,在自己的网站上构造了一个恶意页面:


+import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o,c as i,a as e,b as t,d as n,e as s}from"./app-_Oi5YZFn.js";const l={},h=s(`

当知道这一讲的主题是 OAuth 2.0 的安全漏洞时,你可能要问了:“OAuth 2.0 不是一种安全协议吗,不是保护 Web API 的吗?为啥 OAuth 2.0 自己还有安全的问题了呢?”

首先,OAuth 2.0 的确是一种安全协议。这没啥问题,但是它有很多使用规范,比如授权码是一个临时凭据只能被使用一次,要对重定向 URI 做校验等。那么,如果使用的时候你没有按照这样的规范来实施,就会有安全漏洞了。

其次,OAuth 2.0 既然是“生长”在互联网这个大环境中,就一样会面对互联网上常见安全风险的攻击,比如跨站请求伪造(Cross-site request forgery,CSRF)、跨站脚本攻击(Cross Site Scripting,XSS)。最后,除了这些常见攻击类型外,OAuth 2.0 自身也有可被利用的安全漏洞,比如授权码失窃、重定向 URI 伪造。

所以,我们在实践 OAuth 2.0 的过程中,安全问题一定是重中之重。接下来,我挑选了 5 个典型的安全问题,其中 CSRF、XSS、水平越权这三种是互联网环境下常见的安全风险,授权码失窃和重定向 URI 被篡改属于 OAuth2.0“专属”的安全风险。接下来,我就和你一起看看这些安全风险的由来,以及如何应对吧。

CSRF 攻击

对于 CSRF 的定义,《OAuth 2 in Action》这本书里的解释,是我目前看到的最为贴切的解释:恶意软件让浏览器向已完成用户身份认证的网站发起请求,并执行有害的操作,就是跨站请求伪造攻击。

它是互联网上最为常见的攻击之一。我们在实践 OAuth2.0 的过程,其实就是在构建一次互联网的应用。因此,OAuth 2.0 同样也会面临这个攻击。接下来,我通过一个案例和你说明这个攻击类型。

有一个软件 A,我们让它来扮演攻击者,让它的开发者按照正常的流程使用极客时间。当该攻击者授权后,拿到授权码的值 codeA 之后,“立即按下了暂停键”,不继续往下走了。那它想干啥呢,我们继续往下看。

这时,有一个第三方软件 B,比如咱们的 Web 版极客时间,来扮演受害者吧。当然最终的受害者是用户,这里是用 Web 版极客时间来作为被软件 A 攻击的对象。

极客时间用于接收授权码的回调地址为 https://time.geekbang.org/callback。有一个用户 G 已经在极客时间的平台登录,且对极客时间进行了授权,也就是用户 G 已经在极客时间平台上有登录态了。如果此时攻击者软件 A,在自己的网站上构造了一个恶意页面:


 <html>
 <img src ="https://time.geekbang.org/callback?code=codeA">
 </html>
diff --git a/assets/09.html-Aa1WASXU.js b/assets/09.html-7MXGKP1a.js
similarity index 99%
rename from assets/09.html-Aa1WASXU.js
rename to assets/09.html-7MXGKP1a.js
index d1030a26ed..1aaf8e993b 100644
--- a/assets/09.html-Aa1WASXU.js
+++ b/assets/09.html-7MXGKP1a.js
@@ -1,4 +1,4 @@
-import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as e,c as o,a as c,b as n,d as u,w as l,e as s}from"./app-CVMfKeWw.js";const i={},k=s(`

如果你是一个第三方软件开发者,在实现用户登录的逻辑时,除了可以让用户新注册一个账号再登录外,还可以接入微信、微博等平台,让用户使用自己的微信、微博账号去登录。同时,如果你的应用下面又有多个子应用,还可以让用户只登录一次就能访问所有的子应用,来提升用户体验。这就是联合登录和单点登录了。再继续深究,它们其实都是 OpenID Connect(简称 OIDC)的应用场景的实现。那 OIDC 又是什么呢?今天,我们就来学习下 OIDC 和 OAuth 2.0 的关系,以及如何用 OAuth 2.0 来实现一个 OIDC 用户身份认证协议。

OIDC 是什么?

OIDC 其实就是一种用户身份认证的开放标准。使用微信账号登录极客时间的场景,就是这种开放标准的实践。

说到这里,你可能要发问了:“不对呀,使用微信登录第三方 App 用的不是 OAuth 2.0 开放协议吗,怎么又扯上 OIDC 了呢?”

没错,用微信登录某第三方软件,确实使用的是 OAuth 2.0。但 OAuth2.0 是一种授权协议,而不是身份认证协议。OIDC 才是身份认证协议,而且是基于 OAuth 2.0 来执行用户身份认证的互通协议。更概括地说,OIDC 就是直接基于 OAuth 2.0 构建的身份认证框架协议。

换种表述方式,OIDC= 授权协议 + 身份认证,是 OAuth 2.0 的超集。为方便理解,我们可以把 OAuth 2.0 理解为面粉,把 OIDC 理解为面包。这下,你是不是就理解它们的关系了?因此,我们说“第三方 App 使用微信登录用到了 OAuth 2.0”没有错,说“使用到了 OIDC”更没有错。

考虑到单点登录、联合登录,都遵循的是 OIDC 的标准流程,因此今天我们就讲讲如何利用 OAuth2.0 来实现一个 OIDC,“高屋建瓴” 地去看问题。掌握了这一点,我们再去做单点登录、联合登录的场景,以及其他更多关于身份认证的场景,就都不再是问题了。

OIDC 和 OAuth 2.0 的角色对应关系

说到“如何利用 OAuth 2.0 来构建 OIDC 这样的认证协议”,我们可以想到一个切入点,这个切入点就是 OAuth 2.0 的四种角色。

OAuth 2.0 的授权码许可流程的运转,需要资源拥有者、第三方软件、授权服务、受保护资源这 4 个角色间的顺畅通信、配合才能够完成。如果我们要想在 OAuth 2.0 的授权码许可类型的基础上,来构建 OIDC 的话,这 4 个角色仍然要继续发挥 “它们的价值”。那么,这 4 个角色又是怎么对应到 OIDC 中的参与方的呢?

那么,我们就先想想一个关于身份认证的协议框架,应该有什么角色。你可能已经想出来了,它需要一个登录第三方软件的最终用户、一个第三方软件,以及一个认证服务来为这个用户提供身份证明的验证判断。

没错,这就是 OIDC 的三个主要角色了。在 OIDC 的官方标准框架中,这三个角色的名字是:

  • EU(End User),代表最终用户。
  • RP(Relying Party),代表认证服务的依赖方,就是上面我提到的第三方软件。
  • OP(OpenID Provider),代表提供身份认证服务方。

EU、RP 和 OP 这三个角色对于 OIDC 非常重要,我后面也会时常使用简称来描述,希望你能先记住。

现在很多 App 都接入了微信登录,那么微信登录就是一个大的身份认证服务(OP)。一旦我们有了微信账号,就可以登录所有接入了微信登录体系的 App(RP),这就是我们常说的联合登录。

现在,我们就借助极客时间的例子,来看一下 OAuth 2.0 的 4 个角色和 OIDC 的 3 个角色之间的对应关系:

图1 OAuth 2.0和OIDC的角色对应关系

OIDC 和 OAuth 2.0 的关键区别

看到这张角色对应关系图,你是不是有点 “恍然大悟” 的感觉:要实现一个 OIDC 协议,不就是直接实现一个 OAuth 2.0 协议吗。没错,我在这一讲的开始也说了,OIDC 就是基于 OAuth 2.0 来实现的一个身份认证协议框架。

我再继续给你画一张 OIDC 的通信流程图,你就更清楚 OIDC 和 OAuth 2.0 的关系了:

图2 基于授权码流程的OIDC通信流程

可以发现,一个基于授权码流程的 OIDC 协议流程,跟 OAuth 2.0 中的授权码许可的流程几乎完全一致,唯一的区别就是多返回了一个 ID_TOKEN,我们称之为 ID 令牌。这个令牌是身份认证的关键。所以,接下来我就着重和你讲一下这个令牌,而不再细讲 OIDC 的整个流程。

OIDC 中的 ID 令牌生成和解析方法

在图 2 的 OIDC 通信流程的第 6 步,我们可以看到 ID 令牌(ID_TOKEN)和访问令牌(ACCESS_TOKEN)是一起返回的。关于为什么要同时返回两个令牌,我后面再和你分析。我们先把焦点放在 ID 令牌上。

我们知道,访问令牌不需要被第三方软件解析,因为它对第三方软件来说是不透明的。但 ID 令牌需要能够被第三方软件解析出来,因为第三方软件需要获取 ID 令牌里面的内容,来处理用户的登录

那 ID 令牌的内容是什么呢?

首先,ID 令牌是一个 JWT 格式的令牌。你可以到第 4 讲中复习下 JWT 的相关内容。这里需要强调的是,虽然 JWT 令牌是一种自包含信息体的令牌,为将其作为 ID 令牌带来了方便性,但是因为 ID 令牌需要能够标识出用户、失效时间等属性来达到身份认证的目的,所以要将其作为 OIDC 的 ID 令牌时,下面这 5 个 JWT 声明参数也是必须要有的。

  • iss,令牌的颁发者,其值就是身份认证服务(OP)的 URL。
  • sub,令牌的主题,其值是一个能够代表最终用户(EU)的全局唯一标识符。
  • aud,令牌的目标受众,其值是三方软件(RP)的 app_id。
  • exp,令牌的到期时间戳,所有的 ID 令牌都会有一个过期时间。
  • iat,颁发令牌的时间戳。

生成 ID 令牌这部分的示例代码如下:


+import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as e,c as o,a as c,b as n,d as u,w as l,e as s}from"./app-_Oi5YZFn.js";const i={},k=s(`

如果你是一个第三方软件开发者,在实现用户登录的逻辑时,除了可以让用户新注册一个账号再登录外,还可以接入微信、微博等平台,让用户使用自己的微信、微博账号去登录。同时,如果你的应用下面又有多个子应用,还可以让用户只登录一次就能访问所有的子应用,来提升用户体验。这就是联合登录和单点登录了。再继续深究,它们其实都是 OpenID Connect(简称 OIDC)的应用场景的实现。那 OIDC 又是什么呢?今天,我们就来学习下 OIDC 和 OAuth 2.0 的关系,以及如何用 OAuth 2.0 来实现一个 OIDC 用户身份认证协议。

OIDC 是什么?

OIDC 其实就是一种用户身份认证的开放标准。使用微信账号登录极客时间的场景,就是这种开放标准的实践。

说到这里,你可能要发问了:“不对呀,使用微信登录第三方 App 用的不是 OAuth 2.0 开放协议吗,怎么又扯上 OIDC 了呢?”

没错,用微信登录某第三方软件,确实使用的是 OAuth 2.0。但 OAuth2.0 是一种授权协议,而不是身份认证协议。OIDC 才是身份认证协议,而且是基于 OAuth 2.0 来执行用户身份认证的互通协议。更概括地说,OIDC 就是直接基于 OAuth 2.0 构建的身份认证框架协议。

换种表述方式,OIDC= 授权协议 + 身份认证,是 OAuth 2.0 的超集。为方便理解,我们可以把 OAuth 2.0 理解为面粉,把 OIDC 理解为面包。这下,你是不是就理解它们的关系了?因此,我们说“第三方 App 使用微信登录用到了 OAuth 2.0”没有错,说“使用到了 OIDC”更没有错。

考虑到单点登录、联合登录,都遵循的是 OIDC 的标准流程,因此今天我们就讲讲如何利用 OAuth2.0 来实现一个 OIDC,“高屋建瓴” 地去看问题。掌握了这一点,我们再去做单点登录、联合登录的场景,以及其他更多关于身份认证的场景,就都不再是问题了。

OIDC 和 OAuth 2.0 的角色对应关系

说到“如何利用 OAuth 2.0 来构建 OIDC 这样的认证协议”,我们可以想到一个切入点,这个切入点就是 OAuth 2.0 的四种角色。

OAuth 2.0 的授权码许可流程的运转,需要资源拥有者、第三方软件、授权服务、受保护资源这 4 个角色间的顺畅通信、配合才能够完成。如果我们要想在 OAuth 2.0 的授权码许可类型的基础上,来构建 OIDC 的话,这 4 个角色仍然要继续发挥 “它们的价值”。那么,这 4 个角色又是怎么对应到 OIDC 中的参与方的呢?

那么,我们就先想想一个关于身份认证的协议框架,应该有什么角色。你可能已经想出来了,它需要一个登录第三方软件的最终用户、一个第三方软件,以及一个认证服务来为这个用户提供身份证明的验证判断。

没错,这就是 OIDC 的三个主要角色了。在 OIDC 的官方标准框架中,这三个角色的名字是:

  • EU(End User),代表最终用户。
  • RP(Relying Party),代表认证服务的依赖方,就是上面我提到的第三方软件。
  • OP(OpenID Provider),代表提供身份认证服务方。

EU、RP 和 OP 这三个角色对于 OIDC 非常重要,我后面也会时常使用简称来描述,希望你能先记住。

现在很多 App 都接入了微信登录,那么微信登录就是一个大的身份认证服务(OP)。一旦我们有了微信账号,就可以登录所有接入了微信登录体系的 App(RP),这就是我们常说的联合登录。

现在,我们就借助极客时间的例子,来看一下 OAuth 2.0 的 4 个角色和 OIDC 的 3 个角色之间的对应关系:

图1 OAuth 2.0和OIDC的角色对应关系

OIDC 和 OAuth 2.0 的关键区别

看到这张角色对应关系图,你是不是有点 “恍然大悟” 的感觉:要实现一个 OIDC 协议,不就是直接实现一个 OAuth 2.0 协议吗。没错,我在这一讲的开始也说了,OIDC 就是基于 OAuth 2.0 来实现的一个身份认证协议框架。

我再继续给你画一张 OIDC 的通信流程图,你就更清楚 OIDC 和 OAuth 2.0 的关系了:

图2 基于授权码流程的OIDC通信流程

可以发现,一个基于授权码流程的 OIDC 协议流程,跟 OAuth 2.0 中的授权码许可的流程几乎完全一致,唯一的区别就是多返回了一个 ID_TOKEN,我们称之为 ID 令牌。这个令牌是身份认证的关键。所以,接下来我就着重和你讲一下这个令牌,而不再细讲 OIDC 的整个流程。

OIDC 中的 ID 令牌生成和解析方法

在图 2 的 OIDC 通信流程的第 6 步,我们可以看到 ID 令牌(ID_TOKEN)和访问令牌(ACCESS_TOKEN)是一起返回的。关于为什么要同时返回两个令牌,我后面再和你分析。我们先把焦点放在 ID 令牌上。

我们知道,访问令牌不需要被第三方软件解析,因为它对第三方软件来说是不透明的。但 ID 令牌需要能够被第三方软件解析出来,因为第三方软件需要获取 ID 令牌里面的内容,来处理用户的登录

那 ID 令牌的内容是什么呢?

首先,ID 令牌是一个 JWT 格式的令牌。你可以到第 4 讲中复习下 JWT 的相关内容。这里需要强调的是,虽然 JWT 令牌是一种自包含信息体的令牌,为将其作为 ID 令牌带来了方便性,但是因为 ID 令牌需要能够标识出用户、失效时间等属性来达到身份认证的目的,所以要将其作为 OIDC 的 ID 令牌时,下面这 5 个 JWT 声明参数也是必须要有的。

  • iss,令牌的颁发者,其值就是身份认证服务(OP)的 URL。
  • sub,令牌的主题,其值是一个能够代表最终用户(EU)的全局唯一标识符。
  • aud,令牌的目标受众,其值是三方软件(RP)的 app_id。
  • exp,令牌的到期时间戳,所有的 ID 令牌都会有一个过期时间。
  • iat,颁发令牌的时间戳。

生成 ID 令牌这部分的示例代码如下:


 //GENATE ID TOKEN
 String id_token=genrateIdToken(appId,user);
 
diff --git a/assets/1.html-BsLOzPLz.js b/assets/1.html-CeguXEpG.js
similarity index 99%
rename from assets/1.html-BsLOzPLz.js
rename to assets/1.html-CeguXEpG.js
index a5247cb44a..2717e350d2 100644
--- a/assets/1.html-BsLOzPLz.js
+++ b/assets/1.html-CeguXEpG.js
@@ -1 +1 @@
-import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as t,e as c}from"./app-CVMfKeWw.js";const n={},i=c('

文章来源:https://zhuanlan.zhihu.com/p/111110992

gcc

它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。

我们的程序只有一个源文件时,直接就可以用gcc命令编译它。

可是,如果我们的程序包含很多个源文件时,该咋整?用gcc命令逐个去编译时,就发现很容易混乱而且工作量大,所以出现了下面make工具。

gcc命令在链接时默认使用C的库,只有添加了-lstdc++选项才会使用 C++ 的库。 不过 GCC 中还有一个g++命令,它专门用来编译 C++ 程序,广大 C++ 开发人员也都使用这个命令。g++命令和gcc命令的用法是一样的。

make

make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式—通过调用makefile文件中用户指定的命令来进行编译和链接的。

makefile

这个是啥东西?

简单的说就像一首歌的乐谱,make工具就像指挥家,指挥家根据乐谱指挥整个乐团怎么样演奏,make工具就根据makefile中的命令进行编译和链接的。makefile命令中就包含了调用gcc(也可以是别的编译器)去编译某个源文件的命令。

makefile在一些简单的工程完全可以人工拿下,但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改,这时候就出现了下面的Cmake这个工具。

cmake

cmake就可以更加简单的生成makefile文件给上面那个make用。当然cmake还有其他更牛X功能,就是可以跨平台生成对应平台能用的makefile,我们就不用再自己去修改了。

可是cmake根据什么生成makefile呢?它又要根据一个叫CMakeLists.txt文件(学名:组态档)去生成makefile。

CMakeList.txt

到最后CMakeLists.txt文件谁写啊?亲,是你自己手写的。

nmake[1]

nmake又是啥东西?

nmake是Microsoft Visual Studio中的附带命令,需要安装VS,实际上可以说相当于linux的make,明白了么?

20230113135511

总结一下大体流程[2] 1.用编辑器编写源代码,如.c文件。

2.用编译器编译代码生成目标文件,如.o。

3.用链接器连接目标代码生成可执行文件,如.exe。

但如果源文件太多,一个一个编译那得多麻烦啊?于是人们想到,为啥不设计一种类似批处理的程序,来批处理编译源文件呢?

于是就有了make工具,它是一个自动化编译工具,你可以使用一条命令实现完全编译。但是你需要编写一个规则文件,make依据它来批处理编译,这个文件就是makefile,所以编写makefile文件也是一个程序员所必备的技能。

对于一个大工程,编写makefile实在是件复杂的事,于是人们又想,为什么不设计一个工具,读入所有源文件之后,自动生成makefile呢,于是就出现了cmake工具,它能够输出各种各样的makefile或者project文件,从而帮助程序员减轻负担。但是随之而来也就是编写cmakelist文件,它是cmake所依据的规则。(cmake中有很多设置库的,此时还不是可执行文件,而make生成后才是二进制可执行文件。)

',27),p=[i];function l(o,m){return a(),t("div",null,p)}const h=e(n,[["render",l],["__file","1.html.vue"]]),k=JSON.parse('{"path":"/cpp/other/1.html","title":"c++中使用的编译工具介绍","lang":"zh-CN","frontmatter":{"title":"c++中使用的编译工具介绍","date":"2023-01-13T00:00:00.000Z","description":"文章来源:https://zhuanlan.zhihu.com/p/111110992 gcc 它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。 我们的程序只有一个源文件时,直接就可以用gcc命令编译它。 ...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/cpp/other/1.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"c++中使用的编译工具介绍"}],["meta",{"property":"og:description","content":"文章来源:https://zhuanlan.zhihu.com/p/111110992 gcc 它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。 我们的程序只有一个源文件时,直接就可以用gcc命令编译它。 ..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20230113135511.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-01-13T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"c++中使用的编译工具介绍\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20230113135511.png\\"],\\"datePublished\\":\\"2023-01-13T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":3,"title":"gcc","slug":"gcc","link":"#gcc","children":[]},{"level":3,"title":"make","slug":"make","link":"#make","children":[]},{"level":3,"title":"makefile","slug":"makefile","link":"#makefile","children":[]},{"level":3,"title":"cmake","slug":"cmake","link":"#cmake","children":[]},{"level":3,"title":"CMakeList.txt","slug":"cmakelist-txt","link":"#cmakelist-txt","children":[]},{"level":3,"title":"nmake[1]","slug":"nmake-1","link":"#nmake-1","children":[]}],"git":{"createdTime":1673589476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2}]},"readingTime":{"minutes":3.17,"words":951},"filePathRelative":"cpp/other/1.md","localizedDate":"2023年1月13日","excerpt":"

文章来源:https://zhuanlan.zhihu.com/p/111110992

\\n

gcc

\\n

它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。

\\n

我们的程序只有一个源文件时,直接就可以用gcc命令编译它。

\\n

可是,如果我们的程序包含很多个源文件时,该咋整?用gcc命令逐个去编译时,就发现很容易混乱而且工作量大,所以出现了下面make工具。

\\n

gcc命令在链接时默认使用C的库,只有添加了-lstdc++选项才会使用 C++ 的库。\\n不过 GCC 中还有一个g++命令,它专门用来编译 C++ 程序,广大 C++ 开发人员也都使用这个命令。g++命令和gcc命令的用法是一样的。

","autoDesc":true}');export{h as comp,k as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as t,e as c}from"./app-_Oi5YZFn.js";const n={},i=c('

文章来源:https://zhuanlan.zhihu.com/p/111110992

gcc

它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。

我们的程序只有一个源文件时,直接就可以用gcc命令编译它。

可是,如果我们的程序包含很多个源文件时,该咋整?用gcc命令逐个去编译时,就发现很容易混乱而且工作量大,所以出现了下面make工具。

gcc命令在链接时默认使用C的库,只有添加了-lstdc++选项才会使用 C++ 的库。 不过 GCC 中还有一个g++命令,它专门用来编译 C++ 程序,广大 C++ 开发人员也都使用这个命令。g++命令和gcc命令的用法是一样的。

make

make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式—通过调用makefile文件中用户指定的命令来进行编译和链接的。

makefile

这个是啥东西?

简单的说就像一首歌的乐谱,make工具就像指挥家,指挥家根据乐谱指挥整个乐团怎么样演奏,make工具就根据makefile中的命令进行编译和链接的。makefile命令中就包含了调用gcc(也可以是别的编译器)去编译某个源文件的命令。

makefile在一些简单的工程完全可以人工拿下,但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改,这时候就出现了下面的Cmake这个工具。

cmake

cmake就可以更加简单的生成makefile文件给上面那个make用。当然cmake还有其他更牛X功能,就是可以跨平台生成对应平台能用的makefile,我们就不用再自己去修改了。

可是cmake根据什么生成makefile呢?它又要根据一个叫CMakeLists.txt文件(学名:组态档)去生成makefile。

CMakeList.txt

到最后CMakeLists.txt文件谁写啊?亲,是你自己手写的。

nmake[1]

nmake又是啥东西?

nmake是Microsoft Visual Studio中的附带命令,需要安装VS,实际上可以说相当于linux的make,明白了么?

20230113135511

总结一下大体流程[2] 1.用编辑器编写源代码,如.c文件。

2.用编译器编译代码生成目标文件,如.o。

3.用链接器连接目标代码生成可执行文件,如.exe。

但如果源文件太多,一个一个编译那得多麻烦啊?于是人们想到,为啥不设计一种类似批处理的程序,来批处理编译源文件呢?

于是就有了make工具,它是一个自动化编译工具,你可以使用一条命令实现完全编译。但是你需要编写一个规则文件,make依据它来批处理编译,这个文件就是makefile,所以编写makefile文件也是一个程序员所必备的技能。

对于一个大工程,编写makefile实在是件复杂的事,于是人们又想,为什么不设计一个工具,读入所有源文件之后,自动生成makefile呢,于是就出现了cmake工具,它能够输出各种各样的makefile或者project文件,从而帮助程序员减轻负担。但是随之而来也就是编写cmakelist文件,它是cmake所依据的规则。(cmake中有很多设置库的,此时还不是可执行文件,而make生成后才是二进制可执行文件。)

',27),p=[i];function l(o,m){return a(),t("div",null,p)}const h=e(n,[["render",l],["__file","1.html.vue"]]),k=JSON.parse('{"path":"/cpp/other/1.html","title":"c++中使用的编译工具介绍","lang":"zh-CN","frontmatter":{"title":"c++中使用的编译工具介绍","date":"2023-01-13T00:00:00.000Z","description":"文章来源:https://zhuanlan.zhihu.com/p/111110992 gcc 它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。 我们的程序只有一个源文件时,直接就可以用gcc命令编译它。 ...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/cpp/other/1.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"c++中使用的编译工具介绍"}],["meta",{"property":"og:description","content":"文章来源:https://zhuanlan.zhihu.com/p/111110992 gcc 它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。 我们的程序只有一个源文件时,直接就可以用gcc命令编译它。 ..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20230113135511.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-01-13T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"c++中使用的编译工具介绍\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20230113135511.png\\"],\\"datePublished\\":\\"2023-01-13T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":3,"title":"gcc","slug":"gcc","link":"#gcc","children":[]},{"level":3,"title":"make","slug":"make","link":"#make","children":[]},{"level":3,"title":"makefile","slug":"makefile","link":"#makefile","children":[]},{"level":3,"title":"cmake","slug":"cmake","link":"#cmake","children":[]},{"level":3,"title":"CMakeList.txt","slug":"cmakelist-txt","link":"#cmakelist-txt","children":[]},{"level":3,"title":"nmake[1]","slug":"nmake-1","link":"#nmake-1","children":[]}],"git":{"createdTime":1673589476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2}]},"readingTime":{"minutes":3.17,"words":951},"filePathRelative":"cpp/other/1.md","localizedDate":"2023年1月13日","excerpt":"

文章来源:https://zhuanlan.zhihu.com/p/111110992

\\n

gcc

\\n

它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。

\\n

我们的程序只有一个源文件时,直接就可以用gcc命令编译它。

\\n

可是,如果我们的程序包含很多个源文件时,该咋整?用gcc命令逐个去编译时,就发现很容易混乱而且工作量大,所以出现了下面make工具。

\\n

gcc命令在链接时默认使用C的库,只有添加了-lstdc++选项才会使用 C++ 的库。\\n不过 GCC 中还有一个g++命令,它专门用来编译 C++ 程序,广大 C++ 开发人员也都使用这个命令。g++命令和gcc命令的用法是一样的。

","autoDesc":true}');export{h as comp,k as data}; diff --git a/assets/10.html-D50UJxV3.js b/assets/10.html-IqVcln-C.js similarity index 99% rename from assets/10.html-D50UJxV3.js rename to assets/10.html-IqVcln-C.js index 84f1955d21..bf8df98fff 100644 --- a/assets/10.html-D50UJxV3.js +++ b/assets/10.html-IqVcln-C.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as p,e as h}from"./app-CVMfKeWw.js";const a={},n=h('

今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。

当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。

好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。

OAuth 2.0 工作流程串讲

img

我们一直在讲 OAuth 2.0 是一种授权协议,这种协议可以让第三方软件代表用户去执行被允许的操作。那么,第三方软件就需要向用户索取授权来获得那个令牌。

我们回想下第 1 讲拜访百度王总的例子。只有拿到前台小姐姐给你的门禁卡,你才能够进入百度大楼。这个过程就相当于前台小姐姐给你做了一次授权,而这个授权的凭证就是门禁卡。对应到我们的系统中,门禁卡便相当于访问令牌。

通过“代表”“授权”这样的关键词,我们可以认识到,OAuth 2.0 是一个授权协议,也是一个安全协议。那么,如果我说它也是一种委托协议,你也不要吃惊。

试想一下,用户在微信平台上有修改昵称、修改头像、修改个人兴趣的权限,当第三方软件请求让自己代表用户来操作这些权限的时候,就是第三方软件请求用户把这些权限委托给自己,用户在批准了委托请求之后,才可以代表用户去执行这些操作。

这时,我们细想一下,委托才是 OAuth 2.0 授权概念的根基,因为没有“委托”之意就不会有“代表”行为的发生。

在整个课程讲述授权的过程中,我频繁举例和强调的就是授权码许可流程。在学习授权码流程的时候,你最困惑的一点恐怕莫过于 “为什么要多此一举,非得通过一个授权码 code 来换取访问令牌 access_token”了吧。这个问题我在讲授权码许可的整体流程时也做过分析了,你现在回想起来应该不会再为此“痛苦不堪” 了吧。

我们再来分析下,第三方软件要获取访问令牌,只能通过两个渠道:

  • 一个渠道是第三方软件的前端页面。但是,如果直接返回到前端页面上,访问令牌是很容易被通过浏览器截获的,所以显然不可取。
  • 另外一个渠道是通过后端传输。第三方软件的后端和授权服务的后端之间通信,这样就可以避免令牌被直接暴露的问题。

再往深了想,第三方软件的后端总不能向授权服务的后端 “硬要” 吧,总要告诉授权服务是要哪个用户的 access_token 吧,所以还需要用户的参与。

用户一旦参与进来,访问的第一个页面是第三方软件,用户要授权,第三方软件就需要把用户引导到授权服务页面。但这个时候,用户就跟第三方软件之间没有任何“通信连接”了。如果授权服务通过后端通信直接将令牌给了第三方软件的后端,那第三方软件该如何通知用户呢,恐怕就不太好实现了。

这种情况下就很巧妙地引入了授权码 code:先把 code 通过重定向返回到第三方软件的页面;第三方软件通过浏览器获取到 code 后,再通过后端通信换取 access_token;待拿到 token 之后,由于此时用户已经在第三方软件的服务上,所以可以很容易地通知到用户。

以上,就是授权码许可的整体工作流程了。我们说,这是 OAuth 2.0 授权体系中最完备的流程,其他的授权许可类型,比如资源拥有者凭据许可、客户端凭据许可、隐式许可,都是以此为基础。因此,只要你能理解授权码许可的流程,也就掌握了整个 OAuth 2.0 中所有许可类型的运转机制,在实际工作场景中用上 OAuth 2.0 将不再是问题。

OAuth 2.0 安全问题串讲

但是,到这里并没有万事大吉,我们只是解决了 OAuth 2.0 的基础使用的问题。要想用好、用对这个协议,成长为这个协议的应用专家,我们还必须关注 OAu

我们在实践 OAuth 2.0 的过程中,还必须按照规范建议来执行,否则便会引发一系列的安全问题。这也往往导致有的同学会发出这样的疑问,OAuth 2.0 不是安全的吗?它不是一直在保护着互联网上成千上万个 Web API 吗,我们不也说它是一种安全协议吗?

首先我们说 OAuth 2.0 是安全协议没问题,但如果使用不当也会引起安全上的问题。比如,我们在第 8 讲中提到了一个很广泛的跨站请求伪造问题。之所以出现这样的安全问题,就是因为我们没有遵循 OAuth 2.0 的使用建议,比如没有使用 state 这样的参数来做请求的校验,或者是没有遵循授权码 code 值只能使用一次,并且还要清除使用过的 code 值跟 token 值之间的绑定关系的建议。

在安全问题上,其实我们一直都没有特别说明一点,那就是在使用 OAuth 2.0 的流程中,我们的 HTTP 通信要使用 HTTPS 协议来保护数据传输的安全性。这是因为 OAuth 2.0 支持的 bearer 令牌类型,也就是任意字符串格式的令牌,并没有提供且没有要求使用信息签名的机制。

你可能会说,JWT 令牌有这样的加密机制啊。但其实,这也正说明了 OAuth 2.0 是一个没有约束普通令牌的规则,所以才有了 JWT 这样对 OAuth 2.0 的额外补充。

实际上,JWT 跟 OAuth 2.0 并没有直接关系,它只是一种结构化的信息存储,可以被用在除了 OAuth 2.0 以外的任何地方。比如,重置密码的时候,会给你的邮箱发送一个链接,这个链接就需要能够标识出用户是谁、不能篡改、有效期 5 分钟,这些特征都跟 JWT 相符合。也就是说,JWT 并不是 OAuth 2.0 协议规范所涵盖的内容。

OAuth 2.0 似乎没有自己的规则约束机制,或者说只有比较弱的约束,但其实不是不约束,而是它就致力于做好授权框架这一件事儿。通过我们前面的学习,也可以验证出它的确很好地胜任了这项工作。

也许正是因为 OAuth 2.0 可以支持类似 OIDC 这样的身份认证协议,导致我们总是“坚持”认为 OAuth 2.0 是一种身份认证协议。当然了,OAuth 2.0 并不是身份认证协议,我在第 9 讲中用“面粉”和“面包”来类比 OAuth 2.0 和 OIDC 的关系。

这里我再解释一下。究竟是什么原因导致了我们对 OAuth 2.0 有这样的 “误解” 呢?我想大概原因是,OAuth 2.0 中确实包含了身份认证的内容,即授权服务需要让用户登录以后才可以进行用户确认授权的操作。

但这样的流程,仅仅是 OAuth 2.0 涉及到了身份认证的行为,还不足以让 OAuth 2.0 成为一个真正的用户身份认证协议。因为 OAuth 2.0 关心的只有两点,颁发令牌和使用令牌,并且令牌对第三方软件是不透明的;同时,受保护资源服务也不关心是哪个用户来请求,只要有合法的令牌 “递” 过来,就会给出正确的响应,把数据返回给第三方软件。

以上,就是与 OAuth 2.0 安全问题息息相关的内容了。讲到这里,希望你可以按照自己的理解,融会贯通 OAuth 2.0 的这些核心知识了。接下来,我再和你分享一个我在实践 OAuth 2.0 过程中感触最深的一个问题吧。

再强调都不为过的安全意识

根据我在开放平台上这些年的工作经验,安全意识是实践 OAuth 2.0 过程中,再怎么强调都不为过的问题。

因为总结起来,要说使用 OAuth 2.0 的过程中如果能有哪个机会让你“栽个大跟头”的话,那这个机会一定是在安全上:OAuth 2.0 本就是致力于保护开放的 Web API,保护用户在平台上的资源,如果因为 OAuth 2.0 使用不当而造成安全问题,确实是一件非常 “丢人” 的事情。

而 OAuth2.0 的流程里面能够为安全做贡献的只有两方,一方是第三方软件,一方是平台方。在安全性这个问题上,第三方软件开发者的安全意识参差不齐。那针对这一点,就需要平台方在其官方文档上重笔描述,并给出常见安全漏洞相应的解决方案。同时,作为平台方的内部开发人员,对安全的问题同样不能忽视,而且要有更高的安全意识和认知。

只有第三方软件开发者和平台方的研发人员共同保有较高的安全意识,才能让“安全的墙”垒得越来越高,让攻击者的成本越来越高。因为安全的本质就是成本问题。

你看,我花了这么大的篇幅来和你讲解 OAuth 2.0 的安全问题,并单独分析了安全意识,是不是足以凸显安全性这个问题的重要程度了。没错儿,这也是你能用好 OAuth 2.0 的一个关键标志。

总结

好了,以上就是我们今天的主要内容了。我希望你能记住以下三点:

  1. OAuth 2.0 是一个授权协议,它通过访问令牌来表示这种授权。第三软件拿到访问令牌之后,就可以使用访问令牌来代表用户去访问用户的数据了。所以,我们说授权的核心就是获取访问令牌和使用访问令牌。

  2. OAuth 2.0 是一个安全协议,但是如果你使用不当,它并不能保证一定是安全的。如果你不按照 OAuth 2.0 规范中的建议来实施,就会有安全风险。比如,你没有遵循授权服务中的授权码只能使用一次、第三方软件的重定向 URL 要精确匹配等建议。

  3. 安全防护的过程一直都是“魔高一尺道高一丈”,相互攀升的过程。因此,在使用 OAuth 2.0 的过程中,第三方软件和平台方都要有足够的安全意识,来把“安全的墙”筑得更高。

    最后我想说的是,无论你使用 OAuth 2.0 目的是保护 API,还是作为用户身份认证的基础,OAuth 2.0 都只是解决这些问题的一种工具。而掌握 OAuth 2.0 这种工具的原理及其使用场景,将会帮助你更高效、更优雅地解决这些问题。

思考题

如果你是一名第三方软件的开发人员,你觉得应该如何提高自己的安全意识呢?

',40),o=[n];function i(u,c){return e(),p("div",null,o)}const s=t(a,[["render",i],["__file","10.html.vue"]]),A=JSON.parse('{"path":"/other/oauth2/10.html","title":"10 | 串讲:OAuth 2.0的工作流程与安全问题","lang":"zh-CN","frontmatter":{"title":"10 | 串讲:OAuth 2.0的工作流程与安全问题","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","publish":true,"description":"今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。 当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。 好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。 OAuth ...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/10.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"10 | 串讲:OAuth 2.0的工作流程与安全问题"}],["meta",{"property":"og:description","content":"今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。 当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。 好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。 OAuth ..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/beb02a5baf3654c5025238552cd26a2a.jpg"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"10 | 串讲:OAuth 2.0的工作流程与安全问题\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/beb02a5baf3654c5025238552cd26a2a.jpg\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"OAuth 2.0 安全问题串讲","slug":"oauth-2-0-安全问题串讲","link":"#oauth-2-0-安全问题串讲","children":[]},{"level":3,"title":"再强调都不为过的安全意识","slug":"再强调都不为过的安全意识","link":"#再强调都不为过的安全意识","children":[]},{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]},{"level":3,"title":"思考题","slug":"思考题","link":"#思考题","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":10.17,"words":3052},"filePathRelative":"other/oauth2/10.md","localizedDate":"2022年11月8日","excerpt":"

今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。

\\n

当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。

\\n

好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。

\\n

OAuth 2.0 工作流程串讲

\\n

\\"img\\"

","autoDesc":true}');export{s as comp,A as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as p,e as h}from"./app-_Oi5YZFn.js";const a={},n=h('

今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。

当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。

好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。

OAuth 2.0 工作流程串讲

img

我们一直在讲 OAuth 2.0 是一种授权协议,这种协议可以让第三方软件代表用户去执行被允许的操作。那么,第三方软件就需要向用户索取授权来获得那个令牌。

我们回想下第 1 讲拜访百度王总的例子。只有拿到前台小姐姐给你的门禁卡,你才能够进入百度大楼。这个过程就相当于前台小姐姐给你做了一次授权,而这个授权的凭证就是门禁卡。对应到我们的系统中,门禁卡便相当于访问令牌。

通过“代表”“授权”这样的关键词,我们可以认识到,OAuth 2.0 是一个授权协议,也是一个安全协议。那么,如果我说它也是一种委托协议,你也不要吃惊。

试想一下,用户在微信平台上有修改昵称、修改头像、修改个人兴趣的权限,当第三方软件请求让自己代表用户来操作这些权限的时候,就是第三方软件请求用户把这些权限委托给自己,用户在批准了委托请求之后,才可以代表用户去执行这些操作。

这时,我们细想一下,委托才是 OAuth 2.0 授权概念的根基,因为没有“委托”之意就不会有“代表”行为的发生。

在整个课程讲述授权的过程中,我频繁举例和强调的就是授权码许可流程。在学习授权码流程的时候,你最困惑的一点恐怕莫过于 “为什么要多此一举,非得通过一个授权码 code 来换取访问令牌 access_token”了吧。这个问题我在讲授权码许可的整体流程时也做过分析了,你现在回想起来应该不会再为此“痛苦不堪” 了吧。

我们再来分析下,第三方软件要获取访问令牌,只能通过两个渠道:

  • 一个渠道是第三方软件的前端页面。但是,如果直接返回到前端页面上,访问令牌是很容易被通过浏览器截获的,所以显然不可取。
  • 另外一个渠道是通过后端传输。第三方软件的后端和授权服务的后端之间通信,这样就可以避免令牌被直接暴露的问题。

再往深了想,第三方软件的后端总不能向授权服务的后端 “硬要” 吧,总要告诉授权服务是要哪个用户的 access_token 吧,所以还需要用户的参与。

用户一旦参与进来,访问的第一个页面是第三方软件,用户要授权,第三方软件就需要把用户引导到授权服务页面。但这个时候,用户就跟第三方软件之间没有任何“通信连接”了。如果授权服务通过后端通信直接将令牌给了第三方软件的后端,那第三方软件该如何通知用户呢,恐怕就不太好实现了。

这种情况下就很巧妙地引入了授权码 code:先把 code 通过重定向返回到第三方软件的页面;第三方软件通过浏览器获取到 code 后,再通过后端通信换取 access_token;待拿到 token 之后,由于此时用户已经在第三方软件的服务上,所以可以很容易地通知到用户。

以上,就是授权码许可的整体工作流程了。我们说,这是 OAuth 2.0 授权体系中最完备的流程,其他的授权许可类型,比如资源拥有者凭据许可、客户端凭据许可、隐式许可,都是以此为基础。因此,只要你能理解授权码许可的流程,也就掌握了整个 OAuth 2.0 中所有许可类型的运转机制,在实际工作场景中用上 OAuth 2.0 将不再是问题。

OAuth 2.0 安全问题串讲

但是,到这里并没有万事大吉,我们只是解决了 OAuth 2.0 的基础使用的问题。要想用好、用对这个协议,成长为这个协议的应用专家,我们还必须关注 OAu

我们在实践 OAuth 2.0 的过程中,还必须按照规范建议来执行,否则便会引发一系列的安全问题。这也往往导致有的同学会发出这样的疑问,OAuth 2.0 不是安全的吗?它不是一直在保护着互联网上成千上万个 Web API 吗,我们不也说它是一种安全协议吗?

首先我们说 OAuth 2.0 是安全协议没问题,但如果使用不当也会引起安全上的问题。比如,我们在第 8 讲中提到了一个很广泛的跨站请求伪造问题。之所以出现这样的安全问题,就是因为我们没有遵循 OAuth 2.0 的使用建议,比如没有使用 state 这样的参数来做请求的校验,或者是没有遵循授权码 code 值只能使用一次,并且还要清除使用过的 code 值跟 token 值之间的绑定关系的建议。

在安全问题上,其实我们一直都没有特别说明一点,那就是在使用 OAuth 2.0 的流程中,我们的 HTTP 通信要使用 HTTPS 协议来保护数据传输的安全性。这是因为 OAuth 2.0 支持的 bearer 令牌类型,也就是任意字符串格式的令牌,并没有提供且没有要求使用信息签名的机制。

你可能会说,JWT 令牌有这样的加密机制啊。但其实,这也正说明了 OAuth 2.0 是一个没有约束普通令牌的规则,所以才有了 JWT 这样对 OAuth 2.0 的额外补充。

实际上,JWT 跟 OAuth 2.0 并没有直接关系,它只是一种结构化的信息存储,可以被用在除了 OAuth 2.0 以外的任何地方。比如,重置密码的时候,会给你的邮箱发送一个链接,这个链接就需要能够标识出用户是谁、不能篡改、有效期 5 分钟,这些特征都跟 JWT 相符合。也就是说,JWT 并不是 OAuth 2.0 协议规范所涵盖的内容。

OAuth 2.0 似乎没有自己的规则约束机制,或者说只有比较弱的约束,但其实不是不约束,而是它就致力于做好授权框架这一件事儿。通过我们前面的学习,也可以验证出它的确很好地胜任了这项工作。

也许正是因为 OAuth 2.0 可以支持类似 OIDC 这样的身份认证协议,导致我们总是“坚持”认为 OAuth 2.0 是一种身份认证协议。当然了,OAuth 2.0 并不是身份认证协议,我在第 9 讲中用“面粉”和“面包”来类比 OAuth 2.0 和 OIDC 的关系。

这里我再解释一下。究竟是什么原因导致了我们对 OAuth 2.0 有这样的 “误解” 呢?我想大概原因是,OAuth 2.0 中确实包含了身份认证的内容,即授权服务需要让用户登录以后才可以进行用户确认授权的操作。

但这样的流程,仅仅是 OAuth 2.0 涉及到了身份认证的行为,还不足以让 OAuth 2.0 成为一个真正的用户身份认证协议。因为 OAuth 2.0 关心的只有两点,颁发令牌和使用令牌,并且令牌对第三方软件是不透明的;同时,受保护资源服务也不关心是哪个用户来请求,只要有合法的令牌 “递” 过来,就会给出正确的响应,把数据返回给第三方软件。

以上,就是与 OAuth 2.0 安全问题息息相关的内容了。讲到这里,希望你可以按照自己的理解,融会贯通 OAuth 2.0 的这些核心知识了。接下来,我再和你分享一个我在实践 OAuth 2.0 过程中感触最深的一个问题吧。

再强调都不为过的安全意识

根据我在开放平台上这些年的工作经验,安全意识是实践 OAuth 2.0 过程中,再怎么强调都不为过的问题。

因为总结起来,要说使用 OAuth 2.0 的过程中如果能有哪个机会让你“栽个大跟头”的话,那这个机会一定是在安全上:OAuth 2.0 本就是致力于保护开放的 Web API,保护用户在平台上的资源,如果因为 OAuth 2.0 使用不当而造成安全问题,确实是一件非常 “丢人” 的事情。

而 OAuth2.0 的流程里面能够为安全做贡献的只有两方,一方是第三方软件,一方是平台方。在安全性这个问题上,第三方软件开发者的安全意识参差不齐。那针对这一点,就需要平台方在其官方文档上重笔描述,并给出常见安全漏洞相应的解决方案。同时,作为平台方的内部开发人员,对安全的问题同样不能忽视,而且要有更高的安全意识和认知。

只有第三方软件开发者和平台方的研发人员共同保有较高的安全意识,才能让“安全的墙”垒得越来越高,让攻击者的成本越来越高。因为安全的本质就是成本问题。

你看,我花了这么大的篇幅来和你讲解 OAuth 2.0 的安全问题,并单独分析了安全意识,是不是足以凸显安全性这个问题的重要程度了。没错儿,这也是你能用好 OAuth 2.0 的一个关键标志。

总结

好了,以上就是我们今天的主要内容了。我希望你能记住以下三点:

  1. OAuth 2.0 是一个授权协议,它通过访问令牌来表示这种授权。第三软件拿到访问令牌之后,就可以使用访问令牌来代表用户去访问用户的数据了。所以,我们说授权的核心就是获取访问令牌和使用访问令牌。

  2. OAuth 2.0 是一个安全协议,但是如果你使用不当,它并不能保证一定是安全的。如果你不按照 OAuth 2.0 规范中的建议来实施,就会有安全风险。比如,你没有遵循授权服务中的授权码只能使用一次、第三方软件的重定向 URL 要精确匹配等建议。

  3. 安全防护的过程一直都是“魔高一尺道高一丈”,相互攀升的过程。因此,在使用 OAuth 2.0 的过程中,第三方软件和平台方都要有足够的安全意识,来把“安全的墙”筑得更高。

    最后我想说的是,无论你使用 OAuth 2.0 目的是保护 API,还是作为用户身份认证的基础,OAuth 2.0 都只是解决这些问题的一种工具。而掌握 OAuth 2.0 这种工具的原理及其使用场景,将会帮助你更高效、更优雅地解决这些问题。

思考题

如果你是一名第三方软件的开发人员,你觉得应该如何提高自己的安全意识呢?

',40),o=[n];function i(u,c){return e(),p("div",null,o)}const s=t(a,[["render",i],["__file","10.html.vue"]]),A=JSON.parse('{"path":"/other/oauth2/10.html","title":"10 | 串讲:OAuth 2.0的工作流程与安全问题","lang":"zh-CN","frontmatter":{"title":"10 | 串讲:OAuth 2.0的工作流程与安全问题","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","publish":true,"description":"今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。 当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。 好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。 OAuth ...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/10.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"10 | 串讲:OAuth 2.0的工作流程与安全问题"}],["meta",{"property":"og:description","content":"今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。 当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。 好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。 OAuth ..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/beb02a5baf3654c5025238552cd26a2a.jpg"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"10 | 串讲:OAuth 2.0的工作流程与安全问题\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/beb02a5baf3654c5025238552cd26a2a.jpg\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"OAuth 2.0 安全问题串讲","slug":"oauth-2-0-安全问题串讲","link":"#oauth-2-0-安全问题串讲","children":[]},{"level":3,"title":"再强调都不为过的安全意识","slug":"再强调都不为过的安全意识","link":"#再强调都不为过的安全意识","children":[]},{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]},{"level":3,"title":"思考题","slug":"思考题","link":"#思考题","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":10.17,"words":3052},"filePathRelative":"other/oauth2/10.md","localizedDate":"2022年11月8日","excerpt":"

今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。

\\n

当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。

\\n

好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。

\\n

OAuth 2.0 工作流程串讲

\\n

\\"img\\"

","autoDesc":true}');export{s as comp,A as data}; diff --git a/assets/11.html-DLQhthQQ.js b/assets/11.html-kw9nHLna.js similarity index 99% rename from assets/11.html-DLQhthQQ.js rename to assets/11.html-kw9nHLna.js index 2f58952fd1..abf5eef8aa 100644 --- a/assets/11.html-DLQhthQQ.js +++ b/assets/11.html-kw9nHLna.js @@ -1,4 +1,4 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as s,b as n,d as t,e as p}from"./app-CVMfKeWw.js";const i={},u={href:"https://time.geekbang.org/column/intro/294",target:"_blank",rel:"noopener noreferrer"},k=p(`

《OAuth 2.0 实战课》上线之后,我也第一时间关注了这门课。在开篇词中,我看到有一些同学留言问道:“如何使用 Spring Security 来实现 OAuth 2.0?”这时,我想到之前自己写过一篇相关的文章,于是就直接在开篇词下留了言。后面我很快收到了不少用户的点赞和肯定,紧接着极客时间编辑也邀请我从自己的角度为专栏写篇加餐。好吧,功不唐捐,于是我就将之前我写的那篇老文章再次迭代、整理为今天的这一讲内容,希望可以帮助你掌握 OAuth 2.0。

如果你熟悉 Spring Security 的话,肯定知道它因为功能多、组件抽象程度高、配置方式多样,导致了强大且复杂的特性。也因此,Spring Security 的学习成本几乎是 Spring 家族中最高的。但不仅于此,在结合实际的复杂业务场景使用 Spring Security 时,我们还要去理解一些组件的工作原理和流程,不然需要自定义和扩展框架的时候就会手足无措。这就让使用 Spring Security 的门槛更高了。

因此,在决定使用 Spring Security 搭建整套安全体系(授权、认证、权限、审计)之前,我们还需要考虑的是:将来我们的业务会多复杂,徒手写一套安全体系来得划算,还是使用 Spring Security 更好?我相信,这也是王老师给出课程配套代码中,并没有使用 Spring Security 来演示 OAuth 2.0 流程的原因之一。

反过来说,如果你的应用已经使用了 Spring Security 来做鉴权、认证和权限管理的话,那么仍然使用 Spring Security 来实现 OAuth 的成本是很低的。而且,在学习了 OAuth 2.0 的流程打下扎实的基础之后,我们再使用 Spring Security 来配置 OAuth 2.0 就不会那么迷茫了。这也是我在工作中使用 Spring Security 来实现 OAuth 2.0 的直观感受。

所以,我就结合自己的实践和积累,带你使用 Spring Security 来一步一步地搭建一套基于 JWT 的 OAuth 2.0 授权体系。这些内容会涉及 OAuth 2.0 的三角色(客户端、授权服务、受保护资源),以及资源拥有者凭据许可、客户端凭据许可和授权码许可这三种常用的授权许可类型(隐式许可类型,不太安全也不太常用)。同时,我还会演示 OAuth 2.0 的权限控制,以及使用 OAuth 2.0 实现 SSO 单点登录体系。

这样一来,今天这一讲涉及到的流程就会比较多,内容也会很长。不过不用担心,我会手把手带你从零开始,完成整个程序的搭建,并给出所有流程的演示。

项目准备工作

实战之前,我们先来搭建项目父依赖和初始化数据库结构,为后面具体的编码做准备。

首先,我们来创建一个父 POM,内含三个模块:

  • springsecurity101-cloud-oauth2-client,用来扮演客户端角色;

  • springsecurity101-cloud-oauth2-server,用来扮演授权服务器角色;

  • springsecurity101-cloud-oauth2-userservice,是用户服务,用来扮演资源提供者角色。

    
    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as s,b as n,d as t,e as p}from"./app-_Oi5YZFn.js";const i={},u={href:"https://time.geekbang.org/column/intro/294",target:"_blank",rel:"noopener noreferrer"},k=p(`

    《OAuth 2.0 实战课》上线之后,我也第一时间关注了这门课。在开篇词中,我看到有一些同学留言问道:“如何使用 Spring Security 来实现 OAuth 2.0?”这时,我想到之前自己写过一篇相关的文章,于是就直接在开篇词下留了言。后面我很快收到了不少用户的点赞和肯定,紧接着极客时间编辑也邀请我从自己的角度为专栏写篇加餐。好吧,功不唐捐,于是我就将之前我写的那篇老文章再次迭代、整理为今天的这一讲内容,希望可以帮助你掌握 OAuth 2.0。

    如果你熟悉 Spring Security 的话,肯定知道它因为功能多、组件抽象程度高、配置方式多样,导致了强大且复杂的特性。也因此,Spring Security 的学习成本几乎是 Spring 家族中最高的。但不仅于此,在结合实际的复杂业务场景使用 Spring Security 时,我们还要去理解一些组件的工作原理和流程,不然需要自定义和扩展框架的时候就会手足无措。这就让使用 Spring Security 的门槛更高了。

    因此,在决定使用 Spring Security 搭建整套安全体系(授权、认证、权限、审计)之前,我们还需要考虑的是:将来我们的业务会多复杂,徒手写一套安全体系来得划算,还是使用 Spring Security 更好?我相信,这也是王老师给出课程配套代码中,并没有使用 Spring Security 来演示 OAuth 2.0 流程的原因之一。

    反过来说,如果你的应用已经使用了 Spring Security 来做鉴权、认证和权限管理的话,那么仍然使用 Spring Security 来实现 OAuth 的成本是很低的。而且,在学习了 OAuth 2.0 的流程打下扎实的基础之后,我们再使用 Spring Security 来配置 OAuth 2.0 就不会那么迷茫了。这也是我在工作中使用 Spring Security 来实现 OAuth 2.0 的直观感受。

    所以,我就结合自己的实践和积累,带你使用 Spring Security 来一步一步地搭建一套基于 JWT 的 OAuth 2.0 授权体系。这些内容会涉及 OAuth 2.0 的三角色(客户端、授权服务、受保护资源),以及资源拥有者凭据许可、客户端凭据许可和授权码许可这三种常用的授权许可类型(隐式许可类型,不太安全也不太常用)。同时,我还会演示 OAuth 2.0 的权限控制,以及使用 OAuth 2.0 实现 SSO 单点登录体系。

    这样一来,今天这一讲涉及到的流程就会比较多,内容也会很长。不过不用担心,我会手把手带你从零开始,完成整个程序的搭建,并给出所有流程的演示。

    项目准备工作

    实战之前,我们先来搭建项目父依赖和初始化数据库结构,为后面具体的编码做准备。

    首先,我们来创建一个父 POM,内含三个模块:

    • springsecurity101-cloud-oauth2-client,用来扮演客户端角色;

    • springsecurity101-cloud-oauth2-server,用来扮演授权服务器角色;

    • springsecurity101-cloud-oauth2-userservice,是用户服务,用来扮演资源提供者角色。

      
       <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns="http://maven.apache.org/POM/4.0.0"
                xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      diff --git a/assets/12.html-By4kPY-J.js b/assets/12.html-DxvMj_JN.js
      similarity index 99%
      rename from assets/12.html-By4kPY-J.js
      rename to assets/12.html-DxvMj_JN.js
      index 7fe0e4c637..927575fba9 100644
      --- a/assets/12.html-By4kPY-J.js
      +++ b/assets/12.html-DxvMj_JN.js
      @@ -1 +1 @@
      -import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as p,c as h,a as e,b as t,d as n,e as a}from"./app-CVMfKeWw.js";const s={},r=e("blockquote",null,[e("p",null,"在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。")],-1),c=e("blockquote",null,[e("p",null,"因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于 OAuth 2.0/JWT 的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监,在微服务和 OAuth 2.0 有非常丰富的实践经验。")],-1),d=e("blockquote",null,[e("p",null,"其中,在携程工作期间,他负责过携程的 API 网关产品的研发工作,包括它和携程的令牌服务的集成;在拍拍贷工作期间,他负责过拍拍贷的令牌服务的研发和运维工作。这两家公司的令牌服务和 OAuth 2.0 类似,但要更简单些。")],-1),u=e("blockquote",null,[e("p",null,"接下来,我们就开始学习杨波老师给我们带来的内容吧。")],-1),b=e("p",null,"你好,我是杨波。",-1),A={href:"https://oauth.net/2/",target:"_blank",rel:"noopener noreferrer"},g={href:"https://jwt.io/",target:"_blank",rel:"noopener noreferrer"},f=e("p",null,"据我目前了解到的情况,虽然有不少企业已经部分或全部转型到微服务架构,但是在授权认证机制方面,它们一般都是定制自研的,比方说携程和拍拍贷的令牌服务。之所以定制自研,主要原因在于标准的 OAuth 2.0 协议相对比较复杂,门槛也比较高。定制自研固然可以暂时解决企业的问题,但是不具备通用性,也可能有很多潜在的安全风险。",-1),O=e("p",null,"那么,到底应该如何将行业标准的 OAuth 2.0/JWT 和微服务集成起来呢,又有没有可落地的参考架构呢?",-1),P={href:"https://www.kaper.com/cloud/micro-services-architecture-with-oauth2-and-jwt-part-1-overview/",target:"_blank",rel:"noopener noreferrer"},W={href:"https://dzone.com/articles/what-is-pkce",target:"_blank",rel:"noopener noreferrer"},T=a('

      为了方便理解,在接下来的讲述中,我会假定有这样一家叫 ACME 的新零售公司,它已经实现了数字化转型,微服务电商平台是支持业务运作的核心基础设施。

      在业务架构方面,ACME 有近千家线下门店,这些门店通过 POS 系统和电商平台对接。公司还有一些物流发货中心,拣选(Order Picking)系统也要和电商平台对接。另外,公司还有很多送货司机,通过 App 和电商平台对接。当然,ACME 还有一些电商网站,做线上营销和销售,这些网站是电商平台的主要流量源。

      虽然支持 ACME 公司业务运作的技术平台很复杂,但是它的核心可以用一个简化的微服务架构图来描述:

      img

      可以看出,这个微服务架构是运行在 Kubernetes 集群中的。当然了,这个架构实际上并不一定需要 Kubernetes 环境,用传统数据中心也可以。另外,它的整体认证授权架构是基于 OAuth 2.0/JWT 实现的。

      接下来,我按这个微服务架构的分层方式,依次和你分析下它的每一层,以及应用认证 / 授权和服务调用的相关流程。这样,你不仅可以理解一个典型的微服务架构该如何分层,还可以弄清楚 OAuth 2.0/JWT 该如何与微服务进行集成。

      微服务分层架构

      ACME 公司的微服务架构,大致可以分为 Nginx 反向代理层、Web 应用层、Gateway 网关层、BEF 层和领域服务层,还包括一个 IDP 服务。总体上讲,这是一种目前主流的微服务架构分层方式,每一层职责单一、清晰。

      接下来,我们具体看看每一层的主要功能。

      Nginx 反向代理层

      首先,Nginx 集群是整个平台的流量入口。Nginx 是 7 层 HTTP 反向代理,主要功能是实现反向路由,也就是将外部流量根据 HOST 主机头或者 PATH,路由到不同的后端,比方说路由到 Web 应用,或者直接到网关 Gateway。在 Kubernetes 体系中,Nginx 是和 Ingress Controller(入口控制器)配合工作的(总称为 Nginx Ingress),Ingress Controller 支持通过 Ingress Rules,配置 Nginx 的路由规则。

      Web 应用层

      这一层主要是一些 Web 应用,html/css/js 等资源就住在这一层。Web 服务层通常采用传统的 Web MVC + 模版引擎方式处理,可以实现服务器端渲染,也可以采用单页 SPA 方式。这一层主要由公司的前端团队负责,通常会使用 Node.js 技术栈来实现,也可以采用 Spring MVC 技术栈实现。具体怎么实现,要看公司的前端团队更擅长哪种技术。当这一层需要后台数据时,可以通过网关调用后台服务获取数据。

      Gateway 网关层

      这一层是微服务调用流量的入口。网关的主要职责是反向路由,也就是将前端请求根据 HOST 主机头、或者 PATH、或者查询参数,路由到后端目标微服务(比如,图中的 IDP/BFF 或者直接到领域服务)。

      另外,网关还承担两个重要的安全职责:

      • 一个是令牌的校验和转换,将前端传递过来的 OAuth 2.0 访问令牌,通过调用 IDP 进行校验,并转换为包含用户和权限信息的 JWT 令牌,再将 JWT 令牌向后台微服务传递。
      • 另外一个是权限校验,网关的路由表可以和 OAuth 2.0 的 Scope 进行关联。这样,网关根据请求令牌中的权限范围 Scope,就可以判断请求是否具有调用后台服务的权限。

      关于安全相关的场景和流程,我会在下一章节做进一步解释。另外,网关还需承担集中式限流、日志监控,以及支持 CORS 等功能。对于网关层的技术选型,当前主流的 API 网关产品,像 Netflix 开源的 Zuul、Spring Cloud Gateway 等,都可以考虑。

      IDP 服务

      IDP 是 Identity Provider 的简称,主要负责 OAuth 2.0 授权协议处理,OAuth 2.0 和 JWT 令牌颁发和管理,以及用户认证等功能。IDP 使用后台的 Login-Service 进行用户认证。对于 IDP 的技术选型,当前主流的 Spring Security OAuth,或者 RedHat 开源的 KeyCloak,都可以考虑。其中,Spring Security OAuth 是一个 OAuth 2.0 的开发框架,适合企业定制。KeyCloak 则是一个开箱即用的 OAuth 2.0/OIDC 产品。

      BFF 层

      BFF 是 Backend for Frontend 的简称,主要实现对后台领域服务的聚合(Aggregation,有点类似数据库的 Join)功能,同时为不同的前端体验(PC/Mobile/ 开放平台等)提供更友好的 API 和数据格式。

      BFF 中可以包含一些业务逻辑,甚至还可以有自己的数据库存储。通常,BFF 要调用两个或两个以上的领域服务,甚至还可能调用其它的 BFF(当然一般并不建议这样调用,因为这样会让调用关系变得错综复杂,无法理解)。

      如果 BFF 需要获取调用用户或者 OAuth 2.0 Scope 相关信息,它可以从传递过来的 JWT 令牌中直接获取。

      BFF 服务可以用 Node.js 开发,也可以用 Java/Spring 等框架开发。

      领域服务层

      领域服务层在整个微服务架构的底层。这些服务包含业务逻辑,通常有自己独立的数据库存储,还可以根据需要调用外部的服务。根据微服务分层原则,领域服务禁止调用其它的领域服务,更不允许反向调用 BFF 服务。这样做是为了保持微服务职责单一(Single Responsibility)和有界上下文(Bounded Context),避免复杂的领域依赖。领域服务是独立的开发、测试和发布单位。在电商领域,常见的领域服务有用户服务、商品服务、订单服务和支付服务等。和 BFF 一样,如果领域服务需要获取调用用户或者 OAuth 2.0 Scope 相关信息,它可以从传递过来的 JWT 令牌中直接获取。可以看到,领域服务和 BFF 服务都是无状态的,它们本身并不存储用户状态,而是通过传递过来的 JWT 数据获取用户信息。所以在整个架构中,微服务都是无状态、可以按需水平扩展的,状态要么存在用户端(浏览器或者手机 App 中),要么存在集中的数据库中。

      OAuth 2.0/JWT 如何与微服务进行集成?

      以上,就是 ACME 公司的整个微服务架构的层次了。这个分层架构,对于大部分的互联网业务系统场景都适用。因此,如果你是一家企业的架构师,需要设计一套微服务架构,完全可以参考它来设计。接下来,我再演示几个典型的应用认证场景,以及相应的服务调用流程,来帮助你理解 OAuth 2.0/JWT 是如何和微服务进行集成的。

      场景 1:第一方 Web 应用 + 资源拥有者凭据模式

      这个场景是用户访问 ACME 公司自己的电商网站,假设这个电商网站是用 Spring MVC 开发的。考虑到这是一个第一方场景(也就是公司自己开发的网站应用),我们可以选 OAuth 2.0 的资源拥有者凭据许可(Resource Owner Password Credentials Grant),也可以选更安全的授权码许可(Authorization Code Grant)。因为这里没有第三方的概念,所以我们就选相对简单的资源拥有者凭据许可。下面是一个认证授权流程样例。注意,这个只是突出了关键步骤,实际生产的话,还有很多需要完善和优化的地方。另外,为描述简单,这里假定一个成功流程。

      img

      在上面的图中,用户对应 OAuth 2.0 中的资源拥有者,ACME IDP 对应 OAuth 2.0 中的授权服务。另外,前面架构图中的后台微服务(包括 BFF 和基础领域服务),对应 OAuth 2.0 中的受保护资源。下面是流程说明:

      1. 用户通过浏览器访问 ACME 公司的电商网站,点击登录链接。
      2. Web 应用返回登录界面(这个登录页可以是网站自己定制开发)。
      3. 用户输入用户名、密码进行认证。
      4. Web 应用将用户名、密码,通过网关转发到 IDP 的令牌获取端点(POST /oauth2/token,grant_type=password)。
      5. IDP 通过 Login Service 对用户进行认证。
      6. IDP 认证通过,返回有效访问令牌(根据需要也可以返回刷新令牌)。
      7. Web 应用接收到访问令牌,创建用户 Session,并将 OAuth 2.0 令牌保存其中,然后返回登录成功到用户端。
      8. 用户浏览器中记录 Session Cookie,登录成功。

      那接下来,我们再来看看认证授权之后的服务调用流程。同样,这里也只是突出了关键步骤,并假定是一个成功流程。

      img

      1. 用户登录后,在网站上点击查看自己的购物历史记录。
      2. Web 应用通过网关调用后台 API(查询用户的购物历史记录),请求 HTTP header 中带上 OAuth 2.0 令牌(来自用户 Session)。
      3. 网关截取 OAuth 2.0 令牌,去 IDP 进行校验。
      4. IDP 校验令牌通过,再通过令牌查询用户和 Scope 信息,构建 JWT 令牌,返回。
      5. 网关获得 JWT 令牌,校验 Scope 是否有权限调用 API,如果有就转发到后台 API 进行调用。
      6. 后台 BFF(或者领域服务)通过传递过来的 JWT 获取用户信息,根据用户 ID 查询购物历史记录,返回。
      7. Web 应用获得用户的购物历史数据,可以根据需要缓存在 Session 中,再返回用户端。
      8. 购物历史数据返回到用户浏览器端。

      注意,这个服务调用流程,也可以应用在其他场景中,比如我们接下来要学习的“第一方移动应用 + 授权码许可模式”和“第三方 Web 应用 + 授权码许可模式”。基本上只要你理解了这个流程原理,就可以根据实际场景灵活套用。

      场景 2:第一方移动应用 + 授权码许可模式

      ',39),S={href:"https://www.kaper.com/cloud/micro-services-architecture-with-oauth2-and-jwt-part-3-idp/",target:"_blank",rel:"noopener noreferrer"},I=a('

      img

      1. 用户访问电商 App,点击登录。
      2. App 生成 PKCE 相关的 code verifier + challenge。
      3. App 以内嵌方式启动手机浏览器,访问 IDP 的统一认证页 (GET /authorize),请求带上 PKCE 的 code challenge 相关参数。
      4. IDP 返回统一认证页。
      5. 用户认证和授权。
      6. IDP 通过 Login Service 对用户进行认证。
      7. IDP 返回授权码到 App 浏览器。
      8. App 截取浏览器带回的授权码,将授权码 +PKCE code verifer,通过网关转发到 IDP 的令牌获取端点(POST /oauth2/token, grant_type=authorization-code)。
      9. IDP 校验 PKCE 和授权码,校验通过则返回有效访问令牌。
      10. App 获取令牌,本地存储,登录成功。

      之后,App 如果需要和后台交互,可直接通过网关调用后台微服务,请求 HTTP header 中带上 OAuth 2.0 访问令牌即可。后续的服务调用流程,和“第一方应用 + 资源拥有者凭据模式”类似。

      场景 3:第三方 Web 应用 + 授权码模式

      第三个场景是某第三方合作厂商开发了一个 Web 网站,要访问 ACME 公司的电商开放平台 API。这是一个第三方 Web 应用场景,通常选用 OAuth 2.0 的授权码许可模式。那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假设是一个成功流程。

      img

      1. 用户访问这个第三方 Web 应用,点击登录链接。
      2. Web 应用后台向 ACME 公司的 IDP 服务发送申请授权码请求(GET /authorize)。
      3. 用户被重定向到 ACME 公司的 IDP 统一登录页面。
      4. 用户进行认证和授权。
      5. IDP 通过 Login Service 对用户进行认证。
      6. 认证和授权通过,IDP 返回授权码。
      7. Web 应用获得授权码,再向 IDP 服务的令牌获取端点发起请求(POST /oauth2/token, grant_type=authorization-code)。
      8. IDP 校验授权码,校验通过则返回有效 OAuth 2.0 令牌(根据需要也可以返回刷新令牌)。
      9. Web 应用创建用户 Session,将 OAuth 2.0 令牌保存在 Session 中,然后返回登录成功到用户端。
      10. 用户浏览器中记录 Session Cookie,登录成功。

      之后,第三方 Web 应用如果需要和 ACME 电商平台交互,可直接通过网关调用微服务,请求 HTTP header 中带上 OAuth 2.0 访问令牌即可。后续的服务调用流程,和前面的“第一方应用 + 资源拥有者凭据模式”类似。

      ==额外说明==

      除了上面的三个主要场景和流程,我还要和你分享 6 点。这 6 点是对上面基本流程的补充,也是企业级的 OAuth 2.0 应用要额外考虑的。

      ==第一点是,IDP 的 API 要支持从 OAuth 2.0 访问令牌到 JWT 令牌的互转。== 今天我们提到的集成架构采用 OAuth 2.0 访问令牌 + JWT 令牌的混合模式,中间需要实现 OAuth 2.0 访问令牌到 JWT 令牌的互转。这个互转 API 并非 OAuth 2.0 的标准,有些 IDP 产品(比方 Spring Security OAuth)可能并不支持,因此需要用户定制扩展。

      ==第二点是,关于单页 SPA 应用场景。== 关于单页 SPA 应用场景,简单做法是采用隐式许可,但是这个模式是 OAuth 2.0 中比较不安全的,所以一般不建议采用。对于纯单页 SPA 应用,业界推荐的做法是:

      • 如果浏览器支持 Web Crypto for PKCE,则可以考虑使用类似“第一方移动应用”场景下的授权码许可 +PKCE 扩展流程;
      • 否则,考虑 SPA+ 传统 Web 混合(hybrid)模式,前端页面可以住在客户浏览器端中,但登录认证还是由后台 Web 站点配合实现,走类似“第一方 Web 应用”场景的资源拥有者凭据模式,或者“第三方 Web 应用”场景下的授权码许可模式。

      ==第三点是,关于 SSO 单点登录场景。== 为了简化描述,上面的流程没有考虑 SSO 单点登录场景。如果要支持 Web SSO,那么各种应用场景都必须通过浏览器 +IDP 登录页集中登录,并且 IDP 要支持 Session,用于维护登录态。如果 IDP 以集群方式部署的话,还要考虑粘性 Sticky Session 或者集中式 Session。

      这样,当用户通过一个 Web 应用登录后,后续如果再用其它 Web 应用登录的话,只要 IDP 上的 Session 还存在,那么这个登录就可以自动完成,相当于单点登录。

      当然,如果要支持 SSO,IDP 的 Session Cookie 要种在 Web 应用的根域上,也就是说不同 Web 应用的根域必须相同,否则会有跨域问题。

      ==第四点是关于 IDP 和网关的部署方式。== 前面的几张架构图中,IDP 虽然躲在网关后面,但实际上 IDP 可以直接通过 Nginx 对外暴露,不经过网关。或者,IDP 的登录授权页面,可以通过 Nginx 直接暴露,API 接口则走网关。

      ==第五点是关于刷新令牌。== 为了简化描述,上面的流程没有详细说明刷新令牌的集成方式。企业根据场景需要,可以启用刷新令牌,来延长用户的登录时间,具体的集成方式需要考虑安全性的需求。

      ==第六点是关于 Web Session。== 为了简化描述,在上面的流程中,Web 应用登录成功后假设启用 Web Session,也就是服务器端 Session。在实际场景中,Web Session 并非唯一选择,也可以采用简单的客户端 Session 方式,也称无状态 Session,也就是在客户端浏览器 Cookie 中保存 OAuth 2.0 访问令牌。

      小结

      好了,以上就是今天的主要内容了。今天,我和你分享了如何将行业标准的 OAuth 2.0/JWT 和微服务集成起来,你需要记住以下四点。

      第一,目前主流的微服务架构大致可以分为 5 层,分别是:Nginx 流量接入层 ->Web 应用层 ->API 网关层 ->BFF 聚合层 -> 领域服务层。这个架构可以住在云原生的 Kubernetes 环境中,也可以住在传统数据中心里头。

      第二,API 网关是微服务调用的入口,承担重要的安全认证和鉴权功能。主要的安全操作包括:一,通过 IDP 校验 OAuth 2.0 访问令牌,并获取带用户和权限信息的 JWT 令牌;二,基于 OAuth 2.0 的 Scope 对 API 调用进行鉴权。

      第三,在微服务架构体系下,通常需要一个集中的 IDP 服务,它相当于一个 Authentication & Authorization as a Service 角色,负责令牌颁发 / 校验 / 管理,还有用户认证。

      第四,在今天这一讲提出的架构中,Web 应用层(网关之前)的安全机制主要基于 OAuth 2.0 访问令牌实现(它是一种透明令牌或者称引用令牌),微服务层(网关之后)的安全机制主要基于 JWT 令牌实现(它是一种不透明的自包含令牌)。网关层在中间实现两种令牌的转换。这是一种 OAuth 2.0 访问令牌 +JWT 令牌的混合模式。

      之所以这样设计,是因为 Web 层靠近用户端,如果采用 JWT 令牌,会暴露用户信息,有一定的安全风险,所以采用 OAuth 2.0 访问令牌,它是一个无意义随机字符串。而在网关之后,安全风险相对低,同时很多服务需要用户信息,所以采用自包含用户信息的 JWT 令牌更合适。当然,如果企业内网没有特别的安全考量,也可以直接传递完全透明的用户信息(例如使用 JSON 格式)。

      思考题

      1. 除了今天我们讲到的 OAuth 2.0 访问令牌 +JWT 令牌的混合模式,实践中也可以全程采用 OAuth 2.0 访问令牌,或者全程采用 JWT 令牌。对比混合模式,如果全程采用 OAuth 2.0 访问令牌,或者全程采用 JWT 令牌,你觉得有哪些利弊呢?
      2. 你可以说说自己对基于传统 Web 应用的认证授权机制的理解吗?并对比今天讲到的现代微服务的认证授权机制,你可以说说它们之间的本质差异和相似点吗?
      ',28);function m(C,_){const i=o("ExternalLinkIcon");return p(),h("div",null,[r,c,d,u,b,e("p",null,[t("从单体到微服务架构的演进,是当前企业数字化转型的一大趋势。"),e("a",A,[t("OAuth 2.0"),n(i)]),t("是当前业界标准的授权协议,它的核心是若干个针对不同场景的令牌颁发和管理流程;而"),e("a",g,[t("JWT"),n(i)]),t("是一种轻量级、自包含的令牌,可用于在微服务间安全地传递用户信息。")]),f,O,e("p",null,[t("针对这个问题,今天我就和你分享一种可落地的参考架构。不过,我要提前说明的是,这个架构的思想源于"),e("a",P,[t("MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT – PART 1 – OVERVIEW"),n(i)]),t("这篇文章。")]),e("p",null,[t("根据原作者 Thijs 的描述,他提出的架构已经在企业落地架构了。如果你还想获得关于原架构的更多细节,建议进一步参考“"),e("a",W,[t("What is PKCE"),n(i)]),t("?”这篇文章。我认为,Thijs 给出的架构确实具有可落地性和参考价值,但是他的架构里面对某些微服务层次的命名,例如 BFF 和 Facade 层,和目前主流的微服务架构不符,还有他的架构应该是手绘,不够清晰,也不容易理解。为此,我专门用今天这一讲,来改进 Thijs 给出的架构,并补充针对不同场景的流程。")]),T,e("p",null,[t("第二个场景是用户通过手机访问 ACME 公司自己的电商 App。这是第一方的原生应用(Native App)场景,通常考虑选用 OAuth 2.0 的用户名密码模式,但是并不安全(参考"),e("a",S,[t("MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT – PART 3 – IDP的 Security Consideration"),n(i)]),t(" 部分),所以业界建议采用授权码模式,而且是要支持PKCE扩展的授权码模式。那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假定是一个成功流程。")]),I])}const D=l(s,[["render",m],["__file","12.html.vue"]]),w=JSON.parse('{"path":"/other/oauth2/12.html","title":"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构","lang":"zh-CN","frontmatter":{"title":"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","publish":true,"description":"在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。 因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于 OAuth 2.0/JWT 的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/12.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构"}],["meta",{"property":"og:description","content":"在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。 因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于 OAuth 2.0/JWT 的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/228199yya6051f1f62f23547a88be4ff.jpg"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/228199yya6051f1f62f23547a88be4ff.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/b658befe1da937fa3685b55522487dbd.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/c88e46dd26deb76d6yy8f42f83066f4a.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/443dab973274d8d13c76b2ef4cd1d393.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/02affbdf32f005af65454f3acc4cd957.jpg\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":2,"title":"微服务分层架构","slug":"微服务分层架构","link":"#微服务分层架构","children":[{"level":3,"title":"Nginx 反向代理层","slug":"nginx-反向代理层","link":"#nginx-反向代理层","children":[]},{"level":3,"title":"Web 应用层","slug":"web-应用层","link":"#web-应用层","children":[]},{"level":3,"title":"Gateway 网关层","slug":"gateway-网关层","link":"#gateway-网关层","children":[]},{"level":3,"title":"IDP 服务","slug":"idp-服务","link":"#idp-服务","children":[]},{"level":3,"title":"BFF 层","slug":"bff-层","link":"#bff-层","children":[]},{"level":3,"title":"领域服务层","slug":"领域服务层","link":"#领域服务层","children":[]}]},{"level":2,"title":"OAuth 2.0/JWT 如何与微服务进行集成?","slug":"oauth-2-0-jwt-如何与微服务进行集成","link":"#oauth-2-0-jwt-如何与微服务进行集成","children":[{"level":3,"title":"场景 1:第一方 Web 应用 + 资源拥有者凭据模式","slug":"场景-1-第一方-web-应用-资源拥有者凭据模式","link":"#场景-1-第一方-web-应用-资源拥有者凭据模式","children":[]},{"level":3,"title":"场景 2:第一方移动应用 + 授权码许可模式","slug":"场景-2-第一方移动应用-授权码许可模式","link":"#场景-2-第一方移动应用-授权码许可模式","children":[]},{"level":3,"title":"场景 3:第三方 Web 应用 + 授权码模式","slug":"场景-3-第三方-web-应用-授权码模式","link":"#场景-3-第三方-web-应用-授权码模式","children":[]}]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]},{"level":2,"title":"思考题","slug":"思考题","link":"#思考题","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":19.48,"words":5844},"filePathRelative":"other/oauth2/12.md","localizedDate":"2022年11月8日","excerpt":"
      \\n

      在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。

      \\n
      \\n
      \\n

      因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于 OAuth 2.0/JWT 的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监,在微服务和 OAuth 2.0 有非常丰富的实践经验。

      \\n
      \\n
      \\n

      其中,在携程工作期间,他负责过携程的 API 网关产品的研发工作,包括它和携程的令牌服务的集成;在拍拍贷工作期间,他负责过拍拍贷的令牌服务的研发和运维工作。这两家公司的令牌服务和 OAuth 2.0 类似,但要更简单些。

      \\n
      ","autoDesc":true}');export{D as comp,w as data}; +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as p,c as h,a as e,b as t,d as n,e as a}from"./app-_Oi5YZFn.js";const s={},r=e("blockquote",null,[e("p",null,"在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。")],-1),c=e("blockquote",null,[e("p",null,"因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于 OAuth 2.0/JWT 的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监,在微服务和 OAuth 2.0 有非常丰富的实践经验。")],-1),d=e("blockquote",null,[e("p",null,"其中,在携程工作期间,他负责过携程的 API 网关产品的研发工作,包括它和携程的令牌服务的集成;在拍拍贷工作期间,他负责过拍拍贷的令牌服务的研发和运维工作。这两家公司的令牌服务和 OAuth 2.0 类似,但要更简单些。")],-1),u=e("blockquote",null,[e("p",null,"接下来,我们就开始学习杨波老师给我们带来的内容吧。")],-1),b=e("p",null,"你好,我是杨波。",-1),A={href:"https://oauth.net/2/",target:"_blank",rel:"noopener noreferrer"},g={href:"https://jwt.io/",target:"_blank",rel:"noopener noreferrer"},f=e("p",null,"据我目前了解到的情况,虽然有不少企业已经部分或全部转型到微服务架构,但是在授权认证机制方面,它们一般都是定制自研的,比方说携程和拍拍贷的令牌服务。之所以定制自研,主要原因在于标准的 OAuth 2.0 协议相对比较复杂,门槛也比较高。定制自研固然可以暂时解决企业的问题,但是不具备通用性,也可能有很多潜在的安全风险。",-1),O=e("p",null,"那么,到底应该如何将行业标准的 OAuth 2.0/JWT 和微服务集成起来呢,又有没有可落地的参考架构呢?",-1),P={href:"https://www.kaper.com/cloud/micro-services-architecture-with-oauth2-and-jwt-part-1-overview/",target:"_blank",rel:"noopener noreferrer"},W={href:"https://dzone.com/articles/what-is-pkce",target:"_blank",rel:"noopener noreferrer"},T=a('

      为了方便理解,在接下来的讲述中,我会假定有这样一家叫 ACME 的新零售公司,它已经实现了数字化转型,微服务电商平台是支持业务运作的核心基础设施。

      在业务架构方面,ACME 有近千家线下门店,这些门店通过 POS 系统和电商平台对接。公司还有一些物流发货中心,拣选(Order Picking)系统也要和电商平台对接。另外,公司还有很多送货司机,通过 App 和电商平台对接。当然,ACME 还有一些电商网站,做线上营销和销售,这些网站是电商平台的主要流量源。

      虽然支持 ACME 公司业务运作的技术平台很复杂,但是它的核心可以用一个简化的微服务架构图来描述:

      img

      可以看出,这个微服务架构是运行在 Kubernetes 集群中的。当然了,这个架构实际上并不一定需要 Kubernetes 环境,用传统数据中心也可以。另外,它的整体认证授权架构是基于 OAuth 2.0/JWT 实现的。

      接下来,我按这个微服务架构的分层方式,依次和你分析下它的每一层,以及应用认证 / 授权和服务调用的相关流程。这样,你不仅可以理解一个典型的微服务架构该如何分层,还可以弄清楚 OAuth 2.0/JWT 该如何与微服务进行集成。

      微服务分层架构

      ACME 公司的微服务架构,大致可以分为 Nginx 反向代理层、Web 应用层、Gateway 网关层、BEF 层和领域服务层,还包括一个 IDP 服务。总体上讲,这是一种目前主流的微服务架构分层方式,每一层职责单一、清晰。

      接下来,我们具体看看每一层的主要功能。

      Nginx 反向代理层

      首先,Nginx 集群是整个平台的流量入口。Nginx 是 7 层 HTTP 反向代理,主要功能是实现反向路由,也就是将外部流量根据 HOST 主机头或者 PATH,路由到不同的后端,比方说路由到 Web 应用,或者直接到网关 Gateway。在 Kubernetes 体系中,Nginx 是和 Ingress Controller(入口控制器)配合工作的(总称为 Nginx Ingress),Ingress Controller 支持通过 Ingress Rules,配置 Nginx 的路由规则。

      Web 应用层

      这一层主要是一些 Web 应用,html/css/js 等资源就住在这一层。Web 服务层通常采用传统的 Web MVC + 模版引擎方式处理,可以实现服务器端渲染,也可以采用单页 SPA 方式。这一层主要由公司的前端团队负责,通常会使用 Node.js 技术栈来实现,也可以采用 Spring MVC 技术栈实现。具体怎么实现,要看公司的前端团队更擅长哪种技术。当这一层需要后台数据时,可以通过网关调用后台服务获取数据。

      Gateway 网关层

      这一层是微服务调用流量的入口。网关的主要职责是反向路由,也就是将前端请求根据 HOST 主机头、或者 PATH、或者查询参数,路由到后端目标微服务(比如,图中的 IDP/BFF 或者直接到领域服务)。

      另外,网关还承担两个重要的安全职责:

      • 一个是令牌的校验和转换,将前端传递过来的 OAuth 2.0 访问令牌,通过调用 IDP 进行校验,并转换为包含用户和权限信息的 JWT 令牌,再将 JWT 令牌向后台微服务传递。
      • 另外一个是权限校验,网关的路由表可以和 OAuth 2.0 的 Scope 进行关联。这样,网关根据请求令牌中的权限范围 Scope,就可以判断请求是否具有调用后台服务的权限。

      关于安全相关的场景和流程,我会在下一章节做进一步解释。另外,网关还需承担集中式限流、日志监控,以及支持 CORS 等功能。对于网关层的技术选型,当前主流的 API 网关产品,像 Netflix 开源的 Zuul、Spring Cloud Gateway 等,都可以考虑。

      IDP 服务

      IDP 是 Identity Provider 的简称,主要负责 OAuth 2.0 授权协议处理,OAuth 2.0 和 JWT 令牌颁发和管理,以及用户认证等功能。IDP 使用后台的 Login-Service 进行用户认证。对于 IDP 的技术选型,当前主流的 Spring Security OAuth,或者 RedHat 开源的 KeyCloak,都可以考虑。其中,Spring Security OAuth 是一个 OAuth 2.0 的开发框架,适合企业定制。KeyCloak 则是一个开箱即用的 OAuth 2.0/OIDC 产品。

      BFF 层

      BFF 是 Backend for Frontend 的简称,主要实现对后台领域服务的聚合(Aggregation,有点类似数据库的 Join)功能,同时为不同的前端体验(PC/Mobile/ 开放平台等)提供更友好的 API 和数据格式。

      BFF 中可以包含一些业务逻辑,甚至还可以有自己的数据库存储。通常,BFF 要调用两个或两个以上的领域服务,甚至还可能调用其它的 BFF(当然一般并不建议这样调用,因为这样会让调用关系变得错综复杂,无法理解)。

      如果 BFF 需要获取调用用户或者 OAuth 2.0 Scope 相关信息,它可以从传递过来的 JWT 令牌中直接获取。

      BFF 服务可以用 Node.js 开发,也可以用 Java/Spring 等框架开发。

      领域服务层

      领域服务层在整个微服务架构的底层。这些服务包含业务逻辑,通常有自己独立的数据库存储,还可以根据需要调用外部的服务。根据微服务分层原则,领域服务禁止调用其它的领域服务,更不允许反向调用 BFF 服务。这样做是为了保持微服务职责单一(Single Responsibility)和有界上下文(Bounded Context),避免复杂的领域依赖。领域服务是独立的开发、测试和发布单位。在电商领域,常见的领域服务有用户服务、商品服务、订单服务和支付服务等。和 BFF 一样,如果领域服务需要获取调用用户或者 OAuth 2.0 Scope 相关信息,它可以从传递过来的 JWT 令牌中直接获取。可以看到,领域服务和 BFF 服务都是无状态的,它们本身并不存储用户状态,而是通过传递过来的 JWT 数据获取用户信息。所以在整个架构中,微服务都是无状态、可以按需水平扩展的,状态要么存在用户端(浏览器或者手机 App 中),要么存在集中的数据库中。

      OAuth 2.0/JWT 如何与微服务进行集成?

      以上,就是 ACME 公司的整个微服务架构的层次了。这个分层架构,对于大部分的互联网业务系统场景都适用。因此,如果你是一家企业的架构师,需要设计一套微服务架构,完全可以参考它来设计。接下来,我再演示几个典型的应用认证场景,以及相应的服务调用流程,来帮助你理解 OAuth 2.0/JWT 是如何和微服务进行集成的。

      场景 1:第一方 Web 应用 + 资源拥有者凭据模式

      这个场景是用户访问 ACME 公司自己的电商网站,假设这个电商网站是用 Spring MVC 开发的。考虑到这是一个第一方场景(也就是公司自己开发的网站应用),我们可以选 OAuth 2.0 的资源拥有者凭据许可(Resource Owner Password Credentials Grant),也可以选更安全的授权码许可(Authorization Code Grant)。因为这里没有第三方的概念,所以我们就选相对简单的资源拥有者凭据许可。下面是一个认证授权流程样例。注意,这个只是突出了关键步骤,实际生产的话,还有很多需要完善和优化的地方。另外,为描述简单,这里假定一个成功流程。

      img

      在上面的图中,用户对应 OAuth 2.0 中的资源拥有者,ACME IDP 对应 OAuth 2.0 中的授权服务。另外,前面架构图中的后台微服务(包括 BFF 和基础领域服务),对应 OAuth 2.0 中的受保护资源。下面是流程说明:

      1. 用户通过浏览器访问 ACME 公司的电商网站,点击登录链接。
      2. Web 应用返回登录界面(这个登录页可以是网站自己定制开发)。
      3. 用户输入用户名、密码进行认证。
      4. Web 应用将用户名、密码,通过网关转发到 IDP 的令牌获取端点(POST /oauth2/token,grant_type=password)。
      5. IDP 通过 Login Service 对用户进行认证。
      6. IDP 认证通过,返回有效访问令牌(根据需要也可以返回刷新令牌)。
      7. Web 应用接收到访问令牌,创建用户 Session,并将 OAuth 2.0 令牌保存其中,然后返回登录成功到用户端。
      8. 用户浏览器中记录 Session Cookie,登录成功。

      那接下来,我们再来看看认证授权之后的服务调用流程。同样,这里也只是突出了关键步骤,并假定是一个成功流程。

      img

      1. 用户登录后,在网站上点击查看自己的购物历史记录。
      2. Web 应用通过网关调用后台 API(查询用户的购物历史记录),请求 HTTP header 中带上 OAuth 2.0 令牌(来自用户 Session)。
      3. 网关截取 OAuth 2.0 令牌,去 IDP 进行校验。
      4. IDP 校验令牌通过,再通过令牌查询用户和 Scope 信息,构建 JWT 令牌,返回。
      5. 网关获得 JWT 令牌,校验 Scope 是否有权限调用 API,如果有就转发到后台 API 进行调用。
      6. 后台 BFF(或者领域服务)通过传递过来的 JWT 获取用户信息,根据用户 ID 查询购物历史记录,返回。
      7. Web 应用获得用户的购物历史数据,可以根据需要缓存在 Session 中,再返回用户端。
      8. 购物历史数据返回到用户浏览器端。

      注意,这个服务调用流程,也可以应用在其他场景中,比如我们接下来要学习的“第一方移动应用 + 授权码许可模式”和“第三方 Web 应用 + 授权码许可模式”。基本上只要你理解了这个流程原理,就可以根据实际场景灵活套用。

      场景 2:第一方移动应用 + 授权码许可模式

      ',39),S={href:"https://www.kaper.com/cloud/micro-services-architecture-with-oauth2-and-jwt-part-3-idp/",target:"_blank",rel:"noopener noreferrer"},I=a('

      img

      1. 用户访问电商 App,点击登录。
      2. App 生成 PKCE 相关的 code verifier + challenge。
      3. App 以内嵌方式启动手机浏览器,访问 IDP 的统一认证页 (GET /authorize),请求带上 PKCE 的 code challenge 相关参数。
      4. IDP 返回统一认证页。
      5. 用户认证和授权。
      6. IDP 通过 Login Service 对用户进行认证。
      7. IDP 返回授权码到 App 浏览器。
      8. App 截取浏览器带回的授权码,将授权码 +PKCE code verifer,通过网关转发到 IDP 的令牌获取端点(POST /oauth2/token, grant_type=authorization-code)。
      9. IDP 校验 PKCE 和授权码,校验通过则返回有效访问令牌。
      10. App 获取令牌,本地存储,登录成功。

      之后,App 如果需要和后台交互,可直接通过网关调用后台微服务,请求 HTTP header 中带上 OAuth 2.0 访问令牌即可。后续的服务调用流程,和“第一方应用 + 资源拥有者凭据模式”类似。

      场景 3:第三方 Web 应用 + 授权码模式

      第三个场景是某第三方合作厂商开发了一个 Web 网站,要访问 ACME 公司的电商开放平台 API。这是一个第三方 Web 应用场景,通常选用 OAuth 2.0 的授权码许可模式。那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假设是一个成功流程。

      img

      1. 用户访问这个第三方 Web 应用,点击登录链接。
      2. Web 应用后台向 ACME 公司的 IDP 服务发送申请授权码请求(GET /authorize)。
      3. 用户被重定向到 ACME 公司的 IDP 统一登录页面。
      4. 用户进行认证和授权。
      5. IDP 通过 Login Service 对用户进行认证。
      6. 认证和授权通过,IDP 返回授权码。
      7. Web 应用获得授权码,再向 IDP 服务的令牌获取端点发起请求(POST /oauth2/token, grant_type=authorization-code)。
      8. IDP 校验授权码,校验通过则返回有效 OAuth 2.0 令牌(根据需要也可以返回刷新令牌)。
      9. Web 应用创建用户 Session,将 OAuth 2.0 令牌保存在 Session 中,然后返回登录成功到用户端。
      10. 用户浏览器中记录 Session Cookie,登录成功。

      之后,第三方 Web 应用如果需要和 ACME 电商平台交互,可直接通过网关调用微服务,请求 HTTP header 中带上 OAuth 2.0 访问令牌即可。后续的服务调用流程,和前面的“第一方应用 + 资源拥有者凭据模式”类似。

      ==额外说明==

      除了上面的三个主要场景和流程,我还要和你分享 6 点。这 6 点是对上面基本流程的补充,也是企业级的 OAuth 2.0 应用要额外考虑的。

      ==第一点是,IDP 的 API 要支持从 OAuth 2.0 访问令牌到 JWT 令牌的互转。== 今天我们提到的集成架构采用 OAuth 2.0 访问令牌 + JWT 令牌的混合模式,中间需要实现 OAuth 2.0 访问令牌到 JWT 令牌的互转。这个互转 API 并非 OAuth 2.0 的标准,有些 IDP 产品(比方 Spring Security OAuth)可能并不支持,因此需要用户定制扩展。

      ==第二点是,关于单页 SPA 应用场景。== 关于单页 SPA 应用场景,简单做法是采用隐式许可,但是这个模式是 OAuth 2.0 中比较不安全的,所以一般不建议采用。对于纯单页 SPA 应用,业界推荐的做法是:

      • 如果浏览器支持 Web Crypto for PKCE,则可以考虑使用类似“第一方移动应用”场景下的授权码许可 +PKCE 扩展流程;
      • 否则,考虑 SPA+ 传统 Web 混合(hybrid)模式,前端页面可以住在客户浏览器端中,但登录认证还是由后台 Web 站点配合实现,走类似“第一方 Web 应用”场景的资源拥有者凭据模式,或者“第三方 Web 应用”场景下的授权码许可模式。

      ==第三点是,关于 SSO 单点登录场景。== 为了简化描述,上面的流程没有考虑 SSO 单点登录场景。如果要支持 Web SSO,那么各种应用场景都必须通过浏览器 +IDP 登录页集中登录,并且 IDP 要支持 Session,用于维护登录态。如果 IDP 以集群方式部署的话,还要考虑粘性 Sticky Session 或者集中式 Session。

      这样,当用户通过一个 Web 应用登录后,后续如果再用其它 Web 应用登录的话,只要 IDP 上的 Session 还存在,那么这个登录就可以自动完成,相当于单点登录。

      当然,如果要支持 SSO,IDP 的 Session Cookie 要种在 Web 应用的根域上,也就是说不同 Web 应用的根域必须相同,否则会有跨域问题。

      ==第四点是关于 IDP 和网关的部署方式。== 前面的几张架构图中,IDP 虽然躲在网关后面,但实际上 IDP 可以直接通过 Nginx 对外暴露,不经过网关。或者,IDP 的登录授权页面,可以通过 Nginx 直接暴露,API 接口则走网关。

      ==第五点是关于刷新令牌。== 为了简化描述,上面的流程没有详细说明刷新令牌的集成方式。企业根据场景需要,可以启用刷新令牌,来延长用户的登录时间,具体的集成方式需要考虑安全性的需求。

      ==第六点是关于 Web Session。== 为了简化描述,在上面的流程中,Web 应用登录成功后假设启用 Web Session,也就是服务器端 Session。在实际场景中,Web Session 并非唯一选择,也可以采用简单的客户端 Session 方式,也称无状态 Session,也就是在客户端浏览器 Cookie 中保存 OAuth 2.0 访问令牌。

      小结

      好了,以上就是今天的主要内容了。今天,我和你分享了如何将行业标准的 OAuth 2.0/JWT 和微服务集成起来,你需要记住以下四点。

      第一,目前主流的微服务架构大致可以分为 5 层,分别是:Nginx 流量接入层 ->Web 应用层 ->API 网关层 ->BFF 聚合层 -> 领域服务层。这个架构可以住在云原生的 Kubernetes 环境中,也可以住在传统数据中心里头。

      第二,API 网关是微服务调用的入口,承担重要的安全认证和鉴权功能。主要的安全操作包括:一,通过 IDP 校验 OAuth 2.0 访问令牌,并获取带用户和权限信息的 JWT 令牌;二,基于 OAuth 2.0 的 Scope 对 API 调用进行鉴权。

      第三,在微服务架构体系下,通常需要一个集中的 IDP 服务,它相当于一个 Authentication & Authorization as a Service 角色,负责令牌颁发 / 校验 / 管理,还有用户认证。

      第四,在今天这一讲提出的架构中,Web 应用层(网关之前)的安全机制主要基于 OAuth 2.0 访问令牌实现(它是一种透明令牌或者称引用令牌),微服务层(网关之后)的安全机制主要基于 JWT 令牌实现(它是一种不透明的自包含令牌)。网关层在中间实现两种令牌的转换。这是一种 OAuth 2.0 访问令牌 +JWT 令牌的混合模式。

      之所以这样设计,是因为 Web 层靠近用户端,如果采用 JWT 令牌,会暴露用户信息,有一定的安全风险,所以采用 OAuth 2.0 访问令牌,它是一个无意义随机字符串。而在网关之后,安全风险相对低,同时很多服务需要用户信息,所以采用自包含用户信息的 JWT 令牌更合适。当然,如果企业内网没有特别的安全考量,也可以直接传递完全透明的用户信息(例如使用 JSON 格式)。

      思考题

      1. 除了今天我们讲到的 OAuth 2.0 访问令牌 +JWT 令牌的混合模式,实践中也可以全程采用 OAuth 2.0 访问令牌,或者全程采用 JWT 令牌。对比混合模式,如果全程采用 OAuth 2.0 访问令牌,或者全程采用 JWT 令牌,你觉得有哪些利弊呢?
      2. 你可以说说自己对基于传统 Web 应用的认证授权机制的理解吗?并对比今天讲到的现代微服务的认证授权机制,你可以说说它们之间的本质差异和相似点吗?
      ',28);function m(C,_){const i=o("ExternalLinkIcon");return p(),h("div",null,[r,c,d,u,b,e("p",null,[t("从单体到微服务架构的演进,是当前企业数字化转型的一大趋势。"),e("a",A,[t("OAuth 2.0"),n(i)]),t("是当前业界标准的授权协议,它的核心是若干个针对不同场景的令牌颁发和管理流程;而"),e("a",g,[t("JWT"),n(i)]),t("是一种轻量级、自包含的令牌,可用于在微服务间安全地传递用户信息。")]),f,O,e("p",null,[t("针对这个问题,今天我就和你分享一种可落地的参考架构。不过,我要提前说明的是,这个架构的思想源于"),e("a",P,[t("MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT – PART 1 – OVERVIEW"),n(i)]),t("这篇文章。")]),e("p",null,[t("根据原作者 Thijs 的描述,他提出的架构已经在企业落地架构了。如果你还想获得关于原架构的更多细节,建议进一步参考“"),e("a",W,[t("What is PKCE"),n(i)]),t("?”这篇文章。我认为,Thijs 给出的架构确实具有可落地性和参考价值,但是他的架构里面对某些微服务层次的命名,例如 BFF 和 Facade 层,和目前主流的微服务架构不符,还有他的架构应该是手绘,不够清晰,也不容易理解。为此,我专门用今天这一讲,来改进 Thijs 给出的架构,并补充针对不同场景的流程。")]),T,e("p",null,[t("第二个场景是用户通过手机访问 ACME 公司自己的电商 App。这是第一方的原生应用(Native App)场景,通常考虑选用 OAuth 2.0 的用户名密码模式,但是并不安全(参考"),e("a",S,[t("MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT – PART 3 – IDP的 Security Consideration"),n(i)]),t(" 部分),所以业界建议采用授权码模式,而且是要支持PKCE扩展的授权码模式。那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假定是一个成功流程。")]),I])}const D=l(s,[["render",m],["__file","12.html.vue"]]),w=JSON.parse('{"path":"/other/oauth2/12.html","title":"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构","lang":"zh-CN","frontmatter":{"title":"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","publish":true,"description":"在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。 因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于 OAuth 2.0/JWT 的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/12.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构"}],["meta",{"property":"og:description","content":"在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。 因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于 OAuth 2.0/JWT 的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/228199yya6051f1f62f23547a88be4ff.jpg"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/228199yya6051f1f62f23547a88be4ff.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/b658befe1da937fa3685b55522487dbd.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/c88e46dd26deb76d6yy8f42f83066f4a.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/443dab973274d8d13c76b2ef4cd1d393.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/02affbdf32f005af65454f3acc4cd957.jpg\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":2,"title":"微服务分层架构","slug":"微服务分层架构","link":"#微服务分层架构","children":[{"level":3,"title":"Nginx 反向代理层","slug":"nginx-反向代理层","link":"#nginx-反向代理层","children":[]},{"level":3,"title":"Web 应用层","slug":"web-应用层","link":"#web-应用层","children":[]},{"level":3,"title":"Gateway 网关层","slug":"gateway-网关层","link":"#gateway-网关层","children":[]},{"level":3,"title":"IDP 服务","slug":"idp-服务","link":"#idp-服务","children":[]},{"level":3,"title":"BFF 层","slug":"bff-层","link":"#bff-层","children":[]},{"level":3,"title":"领域服务层","slug":"领域服务层","link":"#领域服务层","children":[]}]},{"level":2,"title":"OAuth 2.0/JWT 如何与微服务进行集成?","slug":"oauth-2-0-jwt-如何与微服务进行集成","link":"#oauth-2-0-jwt-如何与微服务进行集成","children":[{"level":3,"title":"场景 1:第一方 Web 应用 + 资源拥有者凭据模式","slug":"场景-1-第一方-web-应用-资源拥有者凭据模式","link":"#场景-1-第一方-web-应用-资源拥有者凭据模式","children":[]},{"level":3,"title":"场景 2:第一方移动应用 + 授权码许可模式","slug":"场景-2-第一方移动应用-授权码许可模式","link":"#场景-2-第一方移动应用-授权码许可模式","children":[]},{"level":3,"title":"场景 3:第三方 Web 应用 + 授权码模式","slug":"场景-3-第三方-web-应用-授权码模式","link":"#场景-3-第三方-web-应用-授权码模式","children":[]}]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]},{"level":2,"title":"思考题","slug":"思考题","link":"#思考题","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":19.48,"words":5844},"filePathRelative":"other/oauth2/12.md","localizedDate":"2022年11月8日","excerpt":"
      \\n

      在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。

      \\n
      \\n
      \\n

      因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于 OAuth 2.0/JWT 的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监,在微服务和 OAuth 2.0 有非常丰富的实践经验。

      \\n
      \\n
      \\n

      其中,在携程工作期间,他负责过携程的 API 网关产品的研发工作,包括它和携程的令牌服务的集成;在拍拍贷工作期间,他负责过拍拍贷的令牌服务的研发和运维工作。这两家公司的令牌服务和 OAuth 2.0 类似,但要更简单些。

      \\n
      ","autoDesc":true}');export{D as comp,w as data}; diff --git a/assets/13.html-DbVAkfja.js b/assets/13.html-CBYdZLrV.js similarity index 99% rename from assets/13.html-DbVAkfja.js rename to assets/13.html-CBYdZLrV.js index 117b11e0e9..1f9e94e9b2 100644 --- a/assets/13.html-DbVAkfja.js +++ b/assets/13.html-CBYdZLrV.js @@ -1 +1 @@ -import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as d,c as h,a as t,b as e,d as a,w as c,e as o}from"./app-CVMfKeWw.js";const l={},r=o('

      在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就

      到这里,你会发现“开放平台的官方文档”会是一个关键点。不过呢,当你去各大开放平台上面看这些文

      其中的原因也很简单,那就是开放平台为了让已经具备 OAuth 2.0 知识的研发人员去快速地对接平台上面的业务,把各类对接流程做了分类归档。比如,你会发现微信开放平台上有使用授权码获取授权信息的文档,也有获取令牌的文档,但并没有一份整体的、能够串起来的文档说明。从我的角度来看,这其实也就间接提高了使用门槛,因为如果你不懂 OAuth 2.0,基本是

      那么,今天我就借着这个点,和你说说以京东、微信、支付宝、美团为代表的各大开放平台是如何使用 OAuth 2.0 的。理解了这个问题,你以后再对接一个开放平台、再阅读一份官方对接文档时,就更能明白它们的底层逻辑了。

      在正式介绍各大开放平台的使用细节之前,我们先来看看大厂的开放平台全局体系。据我观察,各个开放平台基本的系统结构和授权系统在中间的交互流程,大同小异,都是通过授权服务来授权,通过网关来鉴权。所以接下来,我就以京东商家开放平台为例,来和你说说开放平台的体系到底是什么样子的。

      开放平台体系是什么样子的?

      我们首先来看一下京东商家开放平台全局体系的结构,如下图所示。

      图1 京东商家开放平台体系结构示意图

      我们可以把这个架构体系分为三部分来看:

      1. 第三方软件,一般是指第三方开发者或者 ISV 通过对接开放平台来实现的应用软件,比如小兔打单软件。
      2. 京东商家开放平台,包含 API 网关服务、OAuth 2.0 授权服务和第三方软件开发者中心服务。其中,API 网关服务和 OAuth 2.0 授权服务,是开放平台的“两条腿”;第三方软件开发者中心服务,是为开发者提供管理第三方软件应用基本信息的服务,比如 app_id、app_secret 等信息。
      3. 京东内部的各个微服务,比如订单服务、商品服务等。这些微服务,就是我们之前提到的受保护资源服务。

      从图中我们还可以看到这个体系整体的调用关系是:第三方软件通过 HTTP 协议请求到开放平台,更具体地说是开放平台的 API 网关服务,然后由 API 网关通过内部的 RPC 调用到各个微服务。接下来,我们再以用户小明使用小兔打单软件为例,来看看这些系统角色之间具体又是怎样交互的?

      图2 开放平台体系交互示意图

      到这里,我们可以发现,在开放平台体系中各个系统角色间的交互可以归结为:

      1. 当用户小明访问小兔软件的时候,小兔会首先向开放平台的 OAuth 2.0 授权服务去请求访问令牌,接着小兔拿着访问令牌去请求 API 网关服务;
      2. 在 API 网关服务中,会做最基本的两种校验,一种是访问令牌的合法性校验,比如访问令牌是否过期的校验,另一种是小兔打单软件的基本信息的合法性校验,比如 app_id 和 app_secret 的校验;
      3. 都校验成功之后,API 网关服务会发起最终的数据请求。
      ',14),g=t("p",null,"我们理解了京东商家开放平台的体系结构后,可以小结下了。依靠开放平台提供的能力,可以说开放平台、用户和开发者实现了三赢:小明因为使用小兔提高了打单效率;小兔的开发者因为小明的订购服务获得了收益;而通过开放出去的 API 让小兔帮助小明能够极快地处理 C 端用户的订单,京东提高了用户的使用体验。",-1),b=o('

      我们已经知道,用户给第三方软件授权之后,授权服务就会生成一个访问令牌,而且这个访问令牌是跟用户关联的。比如,小明给小兔打单软件进行了授权,那么此时访问令牌的粒度就是:小兔打单软件 + 小明。

      我们还知道了,小兔打单软件可以拿着这个访问令牌去代表小明访问小明的数据;如果访问令牌过期了,小兔打单软件还可以继续使用刷新令牌来访问,直到刷新令牌也过期了。

      现在问题来了,如果小明注销了账号,或者修改了自己的密码,那他之前为其它第三方软件进行授权的访问令牌就应该立即失效。否则,在刷新令牌过期之前,第三方软件可以一直拿着之前的访问令牌去请求数据。这显然不合理。

      所以在这种情况下,授权服务就要通过 MQ(消息队列)接收用户的注销和修改密码这两类消息,然后对访问令牌进行清理。

      图3 访问令牌的清理

      其实,这个案例中解决访问令牌安全问题的方式,不仅仅适用于开放平台,还可以为你在企业内构建自己的 OAuth 2.0 授权体系结构时提供借鉴。

      以上就是开放平台整体的结构,以及其中需要重点关注的用户访问令牌的安全性问题了。我们作为第三方软件开发者,在对接到这些开放平台或者浏览它们的网站时,几乎都能看到类似这样的一句话:“所有接口都需要接入 OAuth 授权,经过用户确认授权后才可以调用”,这正是 OAuth 2.0 的根本性作用。

      理解了开放平台的脉络之后,接下来,就让我们通过一组图看一看开放平台是如何使用 OAuth 2.0 授权流程的吧。

      各大开放平台授权流程

      ',9),f={href:"https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html",target:"_blank",rel:"noopener noreferrer"},m={href:"https://opendocs.alipay.com/open/common/105193/",target:"_blank",rel:"noopener noreferrer"},u={href:"https://developer.meituan.com/docs/biz/biz_wmh5api_4bd84411-fbc1-4668-b1e6-8c5cf255b1f4",target:"_blank",rel:"noopener noreferrer"},_=o('

      图4 微信开放平台授权流程图

      图5 支付宝开放平台授权流程图

      图6 美团开放平台授权流程图

      我们可以在这三张授权流程图中看到,都有和授权码 code 相关的文字。这就说明,它们都建议开发者首选授权码流程。所以,你现在更能明白我为啥在这门课里要花这么多篇幅,来和你讲授权码许可相关的内容了吧。

      在这一讲最开始我也提到了,我们作为开发者在对接开放平台的时候,最关心的就是它们提供的官方对接文档了。而这些文档里面,最让人头疼就是那些通信过程中需要传递的参数了。下面我会带着你从我的角度,以京东商家开放平台为例,给你串下这些参数背后的含义,以及关键点。这样你在做具体接入操作的时候,就可以举重若轻了。

      授权码流程中的参数说明

      概括来讲,在京东商家开放平台的授权服务这一侧,提供服务的就是两个端点:负责生成授权码的授权端点以及负责颁发访问令牌的令牌端点。整个授权过程中,虽然看着有很多参数,但你可以围绕这两条线,来对它们做归类。

      接下来,我们继续以小兔打单软件为例,来看一下它在对接京东商家开放平台的时候都用到了哪些参数。

      小明在使用小兔打单软件的时候,首先被小兔通过重定向的方式引导到京东商家开放平台的授权服务上,其实就是引导到了授权服务的授权端点上。这个重定向的过程中用到的参数如下:

      图7 重定向过程用到的参数

      这里需要强调的是,对于 state 参数,现在官方都是“推荐”使用。我们在第 8 讲中说过,OAuth 2.0 官方建议的避免 CSRF 攻击的方式,就是使用 state 参数。所以安全起见,你还是应该使用。

      接着,京东商家开放平台授权服务的授权端点,会向小兔软件做出响应。这个响应的过程用到的基本参数,如下:

      图8 授权端点响应小兔软件用到的参数

      对于授权码 code 的值,一般建议的最长生命周期是 10 分钟。另外,小兔打单软件只能被允许使用一次该授权码的值,如果使用一次之后还用同样的授权码值来请求,授权服务必须拒绝。

      对于这次的 state 值,授权服务每次都是必须要返回给小兔打单软件的。无论小兔打单软件在起初的时候有没有发送该值,都必须返回回去,如果没有就返回空。这样当小兔打单软件日后升级增加该值的时候,京东商家开放平台就不需要改动任何代码逻辑了。

      在拿到授权码 code 的值之后,接下来就是小兔打单软件向京东商家开放平台的授权服务的令牌端点发起请求,申请访问令牌。这个过程中需要传递的基本参数,如下:

      图9 申请访问令牌需要传递的基本参数

      在授权服务接收到小兔打单软件申请访问令牌的请求后,像授权端点一样,令牌端点也需要向小兔打单软件做出响应。这个过程涉及到的基本参数,如下:

      图10 令牌端点响应小兔软件涉及的参数

      对于这里返回的 scope 值,我要强调下,其实就是小兔软件被允许的实际的权限范围,因为小明有可能给小兔软件授予了小于它在开放平台注册时申请的权限范围。比如,小兔打单软件申请了查询历史订单、查询当天订单两个 API 的权限,但小明可能只给小兔授权了查询当天订单 API 的权限。

      总结

      好了,这一讲就要结束了。我们一起学习了开放平台体系的整体结构和授权流程,以及第三方软件开发者关心的对接开放平台的通信流程中需要传递的参数。现在,我希望你能记住以下三点内容。

      1. 当有多个受保护资源服务的时候,基本的鉴权工作,包括访问令牌的验证、第三方软件应用信息的验证都应该抽出一个 API 网关层,并把这些基本的工作放到这个 API 网关层。
      2. 各大开放平台都是推荐使用授权码许可流程,无论是网页版的 Web 应用程序,还是移动应用程序。
      3. 对于第三方软件开发者重点关注的参数,可以从授权服务的授权端点和令牌端点来区分,授权端点重点是授权码请求和响应的处理,令牌端点重点是访问令牌请求和响应的处理。
      ',23);function A(I,P){const n=i("RouteLink"),p=i("ExternalLinkIcon");return d(),h("div",null,[r,t("p",null,[e("这里需要说明的是,在"),a(n,{to:"/other/oauth2/05.html"},{default:c(()=>[e("第 5")]),_:1}),e(" 讲中我们提到,验证访问令牌或者第三方软件应用信息的时候,都是在受保护资源服务中去做的。当有了 API 网关这一层的时候,这些校验工作就会都落到了 API 网关的身上,因为我们不能让很多个受保护资源服务做同样的事情。")]),g,t("p",null,[e("但同时呢,开放也是一把双刃剑。理想状态下,平台、开发者、用户可以实现三赢,但正如我们在"),a(n,{to:"/other/oauth2/08.html"},{default:c(()=>[e("第 8 讲")]),_:1}),e("和"),a(n,{to:"/other/oauth2/10.html"},{default:c(()=>[e("第 10 讲")]),_:1}),e("中提到的,安全的问题绝不容忽视,而用户的信息安全又是重中之重。接下来,我就和你分享一个,开放平台体系是如何解决访问令牌安全问题的案例。")]),b,t("p",null,[e("我们以"),t("a",f,[e("微信"),a(p)]),e("、"),t("a",m,[e("支付宝"),a(p)]),e("、"),t("a",u,[e("美团"),a(p)]),e("为例,看看它们在开放授权上是如何使用 OAuth 2.0 的。我们首先看一下官方的授权流程图:")]),_])}const x=s(l,[["render",A],["__file","13.html.vue"]]),C=JSON.parse('{"path":"/other/oauth2/13.html","title":"13 | 各大开放平台是如何使用OAuth 2.0的?","lang":"zh-CN","frontmatter":{"title":"13 | 各大开放平台是如何使用OAuth 2.0的?","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","publish":true,"description":"在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就 到这里,你会发现“开放平台的...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/13.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"13 | 各大开放平台是如何使用OAuth 2.0的?"}],["meta",{"property":"og:description","content":"在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就 到这里,你会发现“开放平台的..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/5775b5bbc363ba2f94d9f6f8e2d05d56.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"13 | 各大开放平台是如何使用OAuth 2.0的?\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/5775b5bbc363ba2f94d9f6f8e2d05d56.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/30f2b2db8c01f0fc60e2d821cd59f94b.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/9d05fa572bccec8da5c895b49a9946ca.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/abc7611d467d45bf39d8e07e0d0267dc.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/e27a6836ef686e23284f9314cc3a25b7.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/e98e9ed5e607561df173262703ca3dea.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/0e8394fedd9205e38c3yyc6e7bae2303.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/b653bc541a98920001385612f2309361.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/18c8245e61ce14c1f7a227a6e713b37f.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/1ac4ded2b7131b475ac71bf4b39c72b5.jpg\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"开放平台体系是什么样子的?","slug":"开放平台体系是什么样子的","link":"#开放平台体系是什么样子的","children":[]},{"level":3,"title":"各大开放平台授权流程","slug":"各大开放平台授权流程","link":"#各大开放平台授权流程","children":[]},{"level":3,"title":"授权码流程中的参数说明","slug":"授权码流程中的参数说明","link":"#授权码流程中的参数说明","children":[]},{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2}]},"readingTime":{"minutes":11.53,"words":3458},"filePathRelative":"other/oauth2/13.md","localizedDate":"2022年11月8日","excerpt":"

      在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就

      \\n

      到这里,你会发现“开放平台的官方文档”会是一个关键点。不过呢,当你去各大开放平台上面看这些文

      \\n

      其中的原因也很简单,那就是开放平台为了让已经具备 OAuth 2.0 知识的研发人员去快速地对接平台上面的业务,把各类对接流程做了分类归档。比如,你会发现微信开放平台上有使用授权码获取授权信息的文档,也有获取令牌的文档,但并没有一份整体的、能够串起来的文档说明。从我的角度来看,这其实也就间接提高了使用门槛,因为如果你不懂 OAuth 2.0,基本是

      ","autoDesc":true}');export{x as comp,C as data}; +import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as d,c as h,a as t,b as e,d as a,w as c,e as o}from"./app-_Oi5YZFn.js";const l={},r=o('

      在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就

      到这里,你会发现“开放平台的官方文档”会是一个关键点。不过呢,当你去各大开放平台上面看这些文

      其中的原因也很简单,那就是开放平台为了让已经具备 OAuth 2.0 知识的研发人员去快速地对接平台上面的业务,把各类对接流程做了分类归档。比如,你会发现微信开放平台上有使用授权码获取授权信息的文档,也有获取令牌的文档,但并没有一份整体的、能够串起来的文档说明。从我的角度来看,这其实也就间接提高了使用门槛,因为如果你不懂 OAuth 2.0,基本是

      那么,今天我就借着这个点,和你说说以京东、微信、支付宝、美团为代表的各大开放平台是如何使用 OAuth 2.0 的。理解了这个问题,你以后再对接一个开放平台、再阅读一份官方对接文档时,就更能明白它们的底层逻辑了。

      在正式介绍各大开放平台的使用细节之前,我们先来看看大厂的开放平台全局体系。据我观察,各个开放平台基本的系统结构和授权系统在中间的交互流程,大同小异,都是通过授权服务来授权,通过网关来鉴权。所以接下来,我就以京东商家开放平台为例,来和你说说开放平台的体系到底是什么样子的。

      开放平台体系是什么样子的?

      我们首先来看一下京东商家开放平台全局体系的结构,如下图所示。

      图1 京东商家开放平台体系结构示意图

      我们可以把这个架构体系分为三部分来看:

      1. 第三方软件,一般是指第三方开发者或者 ISV 通过对接开放平台来实现的应用软件,比如小兔打单软件。
      2. 京东商家开放平台,包含 API 网关服务、OAuth 2.0 授权服务和第三方软件开发者中心服务。其中,API 网关服务和 OAuth 2.0 授权服务,是开放平台的“两条腿”;第三方软件开发者中心服务,是为开发者提供管理第三方软件应用基本信息的服务,比如 app_id、app_secret 等信息。
      3. 京东内部的各个微服务,比如订单服务、商品服务等。这些微服务,就是我们之前提到的受保护资源服务。

      从图中我们还可以看到这个体系整体的调用关系是:第三方软件通过 HTTP 协议请求到开放平台,更具体地说是开放平台的 API 网关服务,然后由 API 网关通过内部的 RPC 调用到各个微服务。接下来,我们再以用户小明使用小兔打单软件为例,来看看这些系统角色之间具体又是怎样交互的?

      图2 开放平台体系交互示意图

      到这里,我们可以发现,在开放平台体系中各个系统角色间的交互可以归结为:

      1. 当用户小明访问小兔软件的时候,小兔会首先向开放平台的 OAuth 2.0 授权服务去请求访问令牌,接着小兔拿着访问令牌去请求 API 网关服务;
      2. 在 API 网关服务中,会做最基本的两种校验,一种是访问令牌的合法性校验,比如访问令牌是否过期的校验,另一种是小兔打单软件的基本信息的合法性校验,比如 app_id 和 app_secret 的校验;
      3. 都校验成功之后,API 网关服务会发起最终的数据请求。
      ',14),g=t("p",null,"我们理解了京东商家开放平台的体系结构后,可以小结下了。依靠开放平台提供的能力,可以说开放平台、用户和开发者实现了三赢:小明因为使用小兔提高了打单效率;小兔的开发者因为小明的订购服务获得了收益;而通过开放出去的 API 让小兔帮助小明能够极快地处理 C 端用户的订单,京东提高了用户的使用体验。",-1),b=o('

      我们已经知道,用户给第三方软件授权之后,授权服务就会生成一个访问令牌,而且这个访问令牌是跟用户关联的。比如,小明给小兔打单软件进行了授权,那么此时访问令牌的粒度就是:小兔打单软件 + 小明。

      我们还知道了,小兔打单软件可以拿着这个访问令牌去代表小明访问小明的数据;如果访问令牌过期了,小兔打单软件还可以继续使用刷新令牌来访问,直到刷新令牌也过期了。

      现在问题来了,如果小明注销了账号,或者修改了自己的密码,那他之前为其它第三方软件进行授权的访问令牌就应该立即失效。否则,在刷新令牌过期之前,第三方软件可以一直拿着之前的访问令牌去请求数据。这显然不合理。

      所以在这种情况下,授权服务就要通过 MQ(消息队列)接收用户的注销和修改密码这两类消息,然后对访问令牌进行清理。

      图3 访问令牌的清理

      其实,这个案例中解决访问令牌安全问题的方式,不仅仅适用于开放平台,还可以为你在企业内构建自己的 OAuth 2.0 授权体系结构时提供借鉴。

      以上就是开放平台整体的结构,以及其中需要重点关注的用户访问令牌的安全性问题了。我们作为第三方软件开发者,在对接到这些开放平台或者浏览它们的网站时,几乎都能看到类似这样的一句话:“所有接口都需要接入 OAuth 授权,经过用户确认授权后才可以调用”,这正是 OAuth 2.0 的根本性作用。

      理解了开放平台的脉络之后,接下来,就让我们通过一组图看一看开放平台是如何使用 OAuth 2.0 授权流程的吧。

      各大开放平台授权流程

      ',9),f={href:"https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html",target:"_blank",rel:"noopener noreferrer"},m={href:"https://opendocs.alipay.com/open/common/105193/",target:"_blank",rel:"noopener noreferrer"},u={href:"https://developer.meituan.com/docs/biz/biz_wmh5api_4bd84411-fbc1-4668-b1e6-8c5cf255b1f4",target:"_blank",rel:"noopener noreferrer"},_=o('

      图4 微信开放平台授权流程图

      图5 支付宝开放平台授权流程图

      图6 美团开放平台授权流程图

      我们可以在这三张授权流程图中看到,都有和授权码 code 相关的文字。这就说明,它们都建议开发者首选授权码流程。所以,你现在更能明白我为啥在这门课里要花这么多篇幅,来和你讲授权码许可相关的内容了吧。

      在这一讲最开始我也提到了,我们作为开发者在对接开放平台的时候,最关心的就是它们提供的官方对接文档了。而这些文档里面,最让人头疼就是那些通信过程中需要传递的参数了。下面我会带着你从我的角度,以京东商家开放平台为例,给你串下这些参数背后的含义,以及关键点。这样你在做具体接入操作的时候,就可以举重若轻了。

      授权码流程中的参数说明

      概括来讲,在京东商家开放平台的授权服务这一侧,提供服务的就是两个端点:负责生成授权码的授权端点以及负责颁发访问令牌的令牌端点。整个授权过程中,虽然看着有很多参数,但你可以围绕这两条线,来对它们做归类。

      接下来,我们继续以小兔打单软件为例,来看一下它在对接京东商家开放平台的时候都用到了哪些参数。

      小明在使用小兔打单软件的时候,首先被小兔通过重定向的方式引导到京东商家开放平台的授权服务上,其实就是引导到了授权服务的授权端点上。这个重定向的过程中用到的参数如下:

      图7 重定向过程用到的参数

      这里需要强调的是,对于 state 参数,现在官方都是“推荐”使用。我们在第 8 讲中说过,OAuth 2.0 官方建议的避免 CSRF 攻击的方式,就是使用 state 参数。所以安全起见,你还是应该使用。

      接着,京东商家开放平台授权服务的授权端点,会向小兔软件做出响应。这个响应的过程用到的基本参数,如下:

      图8 授权端点响应小兔软件用到的参数

      对于授权码 code 的值,一般建议的最长生命周期是 10 分钟。另外,小兔打单软件只能被允许使用一次该授权码的值,如果使用一次之后还用同样的授权码值来请求,授权服务必须拒绝。

      对于这次的 state 值,授权服务每次都是必须要返回给小兔打单软件的。无论小兔打单软件在起初的时候有没有发送该值,都必须返回回去,如果没有就返回空。这样当小兔打单软件日后升级增加该值的时候,京东商家开放平台就不需要改动任何代码逻辑了。

      在拿到授权码 code 的值之后,接下来就是小兔打单软件向京东商家开放平台的授权服务的令牌端点发起请求,申请访问令牌。这个过程中需要传递的基本参数,如下:

      图9 申请访问令牌需要传递的基本参数

      在授权服务接收到小兔打单软件申请访问令牌的请求后,像授权端点一样,令牌端点也需要向小兔打单软件做出响应。这个过程涉及到的基本参数,如下:

      图10 令牌端点响应小兔软件涉及的参数

      对于这里返回的 scope 值,我要强调下,其实就是小兔软件被允许的实际的权限范围,因为小明有可能给小兔软件授予了小于它在开放平台注册时申请的权限范围。比如,小兔打单软件申请了查询历史订单、查询当天订单两个 API 的权限,但小明可能只给小兔授权了查询当天订单 API 的权限。

      总结

      好了,这一讲就要结束了。我们一起学习了开放平台体系的整体结构和授权流程,以及第三方软件开发者关心的对接开放平台的通信流程中需要传递的参数。现在,我希望你能记住以下三点内容。

      1. 当有多个受保护资源服务的时候,基本的鉴权工作,包括访问令牌的验证、第三方软件应用信息的验证都应该抽出一个 API 网关层,并把这些基本的工作放到这个 API 网关层。
      2. 各大开放平台都是推荐使用授权码许可流程,无论是网页版的 Web 应用程序,还是移动应用程序。
      3. 对于第三方软件开发者重点关注的参数,可以从授权服务的授权端点和令牌端点来区分,授权端点重点是授权码请求和响应的处理,令牌端点重点是访问令牌请求和响应的处理。
      ',23);function A(I,P){const n=i("RouteLink"),p=i("ExternalLinkIcon");return d(),h("div",null,[r,t("p",null,[e("这里需要说明的是,在"),a(n,{to:"/other/oauth2/05.html"},{default:c(()=>[e("第 5")]),_:1}),e(" 讲中我们提到,验证访问令牌或者第三方软件应用信息的时候,都是在受保护资源服务中去做的。当有了 API 网关这一层的时候,这些校验工作就会都落到了 API 网关的身上,因为我们不能让很多个受保护资源服务做同样的事情。")]),g,t("p",null,[e("但同时呢,开放也是一把双刃剑。理想状态下,平台、开发者、用户可以实现三赢,但正如我们在"),a(n,{to:"/other/oauth2/08.html"},{default:c(()=>[e("第 8 讲")]),_:1}),e("和"),a(n,{to:"/other/oauth2/10.html"},{default:c(()=>[e("第 10 讲")]),_:1}),e("中提到的,安全的问题绝不容忽视,而用户的信息安全又是重中之重。接下来,我就和你分享一个,开放平台体系是如何解决访问令牌安全问题的案例。")]),b,t("p",null,[e("我们以"),t("a",f,[e("微信"),a(p)]),e("、"),t("a",m,[e("支付宝"),a(p)]),e("、"),t("a",u,[e("美团"),a(p)]),e("为例,看看它们在开放授权上是如何使用 OAuth 2.0 的。我们首先看一下官方的授权流程图:")]),_])}const x=s(l,[["render",A],["__file","13.html.vue"]]),C=JSON.parse('{"path":"/other/oauth2/13.html","title":"13 | 各大开放平台是如何使用OAuth 2.0的?","lang":"zh-CN","frontmatter":{"title":"13 | 各大开放平台是如何使用OAuth 2.0的?","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","publish":true,"description":"在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就 到这里,你会发现“开放平台的...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/13.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"13 | 各大开放平台是如何使用OAuth 2.0的?"}],["meta",{"property":"og:description","content":"在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就 到这里,你会发现“开放平台的..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/5775b5bbc363ba2f94d9f6f8e2d05d56.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"13 | 各大开放平台是如何使用OAuth 2.0的?\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/5775b5bbc363ba2f94d9f6f8e2d05d56.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/30f2b2db8c01f0fc60e2d821cd59f94b.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/9d05fa572bccec8da5c895b49a9946ca.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/abc7611d467d45bf39d8e07e0d0267dc.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/e27a6836ef686e23284f9314cc3a25b7.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/e98e9ed5e607561df173262703ca3dea.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/0e8394fedd9205e38c3yyc6e7bae2303.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/b653bc541a98920001385612f2309361.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/18c8245e61ce14c1f7a227a6e713b37f.jpg\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/1ac4ded2b7131b475ac71bf4b39c72b5.jpg\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"开放平台体系是什么样子的?","slug":"开放平台体系是什么样子的","link":"#开放平台体系是什么样子的","children":[]},{"level":3,"title":"各大开放平台授权流程","slug":"各大开放平台授权流程","link":"#各大开放平台授权流程","children":[]},{"level":3,"title":"授权码流程中的参数说明","slug":"授权码流程中的参数说明","link":"#授权码流程中的参数说明","children":[]},{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2}]},"readingTime":{"minutes":11.53,"words":3458},"filePathRelative":"other/oauth2/13.md","localizedDate":"2022年11月8日","excerpt":"

      在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就

      \\n

      到这里,你会发现“开放平台的官方文档”会是一个关键点。不过呢,当你去各大开放平台上面看这些文

      \\n

      其中的原因也很简单,那就是开放平台为了让已经具备 OAuth 2.0 知识的研发人员去快速地对接平台上面的业务,把各类对接流程做了分类归档。比如,你会发现微信开放平台上有使用授权码获取授权信息的文档,也有获取令牌的文档,但并没有一份整体的、能够串起来的文档说明。从我的角度来看,这其实也就间接提高了使用门槛,因为如果你不懂 OAuth 2.0,基本是

      ","autoDesc":true}');export{x as comp,C as data}; diff --git a/assets/14.html-DC_xS0f7.js b/assets/14.html-B-d6pKTK.js similarity index 99% rename from assets/14.html-DC_xS0f7.js rename to assets/14.html-B-d6pKTK.js index bdd5245c2a..5087a0c697 100644 --- a/assets/14.html-DC_xS0f7.js +++ b/assets/14.html-B-d6pKTK.js @@ -1 +1 @@ -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c as r,a as t,b as e,d as l,w as h,e as i}from"./app-CVMfKeWw.js";const s={},u=i('

      从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题:

      1. 发明 OAuth 的目的到底是什么?

      2. OAuth 2.0 是身份认证协议吗?

      3. 有了刷新令牌,是不是就可以让访问令牌一直有效了?

      4. 使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?

      5. ID 令牌和访问令牌之间有联系吗?

      6. PKCE 协议到底解决的是什么问题?

      接下来,我们就一一看看这些问题吧。

      发明 OAuth 的目的到底是什么?

      OAuth 协议的设计初衷,就是让最终用户也就是资源拥有者(小明),将他们在受保护资源服务器(京东商家开放平台)上的部分权限(查询当天订单)委托给第三方应用(小兔打单软件),使得第三方应用(小兔)能够代表最终用户(小明)执行操作(查询当天订单)。这便是 OAuth 协议设计的目的。在 OAuth 协议中,通过为每个第三方软件和每个用户的组合分别生成对受保护资源具有受限的访问权限的凭据,也就是访问令牌,来代替之前的用户名和密码。而生成访问令牌之前的登录操作,又是在用户跟平台之间进行的,第三方软件根本无从得知用户的任何信息。这样第三方软件的逻辑处理就大大简化了,它今后的动作就变成了请求访问令牌、使用访问令牌、访问受保护资源,同时在第三方软件调用大量 API 的时候,不再传输用户名和密码,从而减少了网络安全的攻击面。从安全的角度来讲,为每个第三方软件和每个用户的组合来生成一个访问令牌的方式,可以减少对平台更多用户造成的危害。因为这样一来,单个第三方软件被攻破而带来的危害,仅仅会让这一个第三方软件的用户受到影响。那么有的同学就要会问了,这样攻击的对象就会转移到授权服务身上。这个想法没错,但保护一个授权服务肯定要比保护成千上万个、由不同研发人员开发的第三方软件容易得多。

      OAuth 2.0 是身份认证协议吗?

      在这门课中,我其实一直在强调,OAuth 2.0 是一种授权协议,“它一心只专注于干好授权这件事儿”,OAuth 2.0 不是身份认证协议。但实际上,我在刚开始学习 OAuth 2.0 的时候,也曾错误地认为它是身份认证协议。因为我当时觉得,有用户参与其中,比如小明在使用小兔打单软件之前,要向授权服务进行登录操作从而进行身份认证 ,那 OAuth 2.0 就应该是一个身份认证协议啊。但是,小明必须登录之后才能进行授权,是一个额外的需求,登录跟授权体系是独立的。虽然登录操作看似“内嵌”在了 OAuth 2.0 的流程中,但生产环境中登录和授权还是两套独立存在的系统。所以说,像这种“内嵌”的身份认证行为,并不是说 OAuth 2.0 自身承担起了身份认证协议的责任。同时,身份认证会告诉第三方软件当前的用户是谁,但实际上 OAuth 2.0 自始至终都没有向第三方软件透露过关于用户的任何信息。这一点,我们在讲发明 OAuth 协议的目的时也提到过。我们可以再想想小兔打单软件的例子,看是不是这样:小兔打单软件永远也不会知道小明的任何信息,它仅仅是请求访问令牌,使用访问令牌并最终调用查询订单的 API。

      有了刷新令牌,是不是就可以让访问令牌一直有效了?

      要回答这个问题,我们先复习下访问令牌和刷新令牌相关的几个知识点吧。

      第一,OAuth 2.0 的核心是授权,授权的核心是令牌,也就是我们说的访问令牌。

      第二,在第 3 讲中我们提到,为了提高用户的体验,OAuth 2.0 提供了刷新令牌的机制,使得访问令牌过期后,第三方软件在无需用户再次授权的情况下,可以重新请求一个访问令牌。

      第三,在使用上,刷新令牌只能用在授权服务上,而访问令牌只能用在受保护资源服务上。

      有了这些知识做基础,我们可以继续分析“有了刷新令牌,是不是就可以让访问令牌一直有效”这个问题了。

      当访问令牌被 “递给” 受保护资源服务的时候,受保护资源服务需要对访问令牌进行验证,还要对访问令牌关联的权限和第三方软件的请求进行权限匹配校验。当访问令牌过期的时候,我们使用刷新令牌请求到的访问令牌,是授权服务重新生成的,而不是延长了原访问令牌的有效期。

      当前的这个刷新令牌被使用之后,授权服务可以自行决定是颁发一个新的刷新令牌,还是仍然给第三方软件返回上一个刷新令牌。安全起见,我们的建议是返回一个新的刷新令牌。这时,你可能就有一个疑问了:第三方软件已经换了一个访问令牌了,刷新令牌又一直存在,那是不是就可以一直使用刷新令牌来获取访问令牌了呢?

      要解决这个疑问,我们要知道的是,刷新令牌也有有效期。尽管生成了新的刷新令牌,但它的有效期不会改变,有效期的时间戳仍然是上一个刷新令牌的。刷新令牌的有效期到了,就不能再继续用它来申请新的访问令牌了。

      使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?

      OAuth 2.0 的使用从来都不应该脱离 HTTPS。因为访问令牌、应用密钥敏感信息要在网络上传输,都离不开 HTTPS 的保护。但是,HTTPS 也只是保证了访问令牌等重要信息在网络传输上的安全。

      在 OAuth 2.0 的规范中,访问令牌对第三方软件是不透明的,从来都不应该被任何第三方软件解析到。由于 JWT 格式的令牌自包含了用户相关的信息,比如用户标识,因此仅仅对它进行签名还不够。要避免第三方软件有机会获取访问令牌所包含的信息,那我们在与第三方软件交互的环境下使用 JWT 格式的令牌时,还要对它进行加密来保障令牌的安全,而不是仅仅依靠 HTTPS。

      ID 令牌和访问令牌之间有联系吗?

      ',20),c=t("p",null,"我们先来总结下 ID 令牌和访问令牌的作用:",-1),d=t("ul",null,[t("li",null,"ID 令牌,也就是 ID_TOKEN,代表的是用户身份令牌,可以说是一个单独的身份认证结果,永远不会像访问令牌那样作为一个参数,去传递给其它外部服务;"),t("li",null,"访问令牌,也就是 ACCESS_TOKEN,就是一个令牌,是要被第三方软件用来作为凭证,从而代表用户去请求受保护资源服务的。")],-1),A=t("p",null,"你看,这两种令牌是截然不同的。接下来,我们就分析下,它们的区别都体现在哪些方面吧。",-1),O=t("p",null,"第一,ID 令牌是对访问令牌的补充,而不是要替换访问令牌。之所以采用这样双令牌的方式,就是想让早先存在的访问令牌,可以在 OAuth 2.0 中继续保持对第三方软件的不透明性,而让后来新增的 ID 令牌要能够被解析,目的就是方便应用到身份认证协议中。第",-1),m=t("p",null,"二,ID 令牌和访问令牌有不同的生命周期,ID 令牌的生命周期相对来说更短些。因为 ID 令牌的作用就是代表一个单独的身份认证结果,它的使命就是用来标识用户的。而这个标识并不是用户名,用户登录的时候用的是用户名而不是这个 ID 令牌,所以如果用户注销或者退出了登录,ID 令牌的生命周期就随之结束了。",-1),_=t("p",null,"访问令牌可以在用户离开后的很长时间内,继续被第三方软件用来请求受保护资源服务。比如,小明使用了小兔打单软件的批量导出订单功能,如果耗时相对比较长,小明不必一直在场。",-1),T=t("h3",{id:"pkce-协议到底解决的是什么问题",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#pkce-协议到底解决的是什么问题"},[t("span",null,"PKCE 协议到底解决的是什么问题?")])],-1),C=i('

      2012 年 10 月 OAuth 2.0 的正式授权协议框架,也就是官方的 RFC 6749 被正式发布,2015 年 9 月增补了 PKCE 协议,也就是官方的 RFC 7636。从时间上来看,从正式发布 OAuth 2.0 授权协议到增补发布了 PKCE 协议,整整间隔了三年,而这三年恰恰是移动应用蓬勃发展的时期。

      同时,在原生的移动客户端应用保存秘钥又存在特殊的安全问题,使用 OAuth 2.0 授权码许可类型的客户端又容易受到授权码窃听的攻击。

      所以,PKCE 被增补发布的背景是,移动应用大力发展,同时原生客户端使用 OAuth 2.0 面临着安全风险。这样我们就能理解了,发布 PKCE 协议的目的,主要就是缓解针对公开客户端的攻击,提高授权码使用的安全性。

      总结

      1. OAuth 协议被发明的目的,就是用令牌代替用户名和密码。
      2. OAuth 2.0 不能被直接用来“从事”身份认证协议的“工作”。虽然 OAuth2.0 的使用要求是在 HTTPS 的环境下,但这并不能解决 JWT 令牌对第三方软件“不透明”的问题,还需要进行加密。
      3. 有了刷新令牌也不能让访问令牌一直有效下去,因为刷新令牌也有有效期。
      4. ID 令牌是对访问令牌的补充,而不是要替代访问令牌。
      5. PKCE 是 OAuth 2.0 的一个增补协议,主要用来缓解授权码被窃听的安全风险。
      ',5);function P(D,I){const n=p("RouteLink");return o(),r("div",null,[u,t("p",null,[e("在"),l(n,{to:"/other/oauth2/09.html"},{default:h(()=>[e("第 9 讲")]),_:1}),e("中,我们在用 OAuth 2.0 实现一个 OpenID Connect 身份认证协议的时候,讲到了 ID 令牌。在这一讲的后面,有同学还是不太清楚 ID 令牌和访问令牌是啥关系,当时我就在留言区做了回复。现在,我重新整理了思路再和你解释一下,因为认识到 ID 令牌和访问令牌的联系与区别,对我们利用 OAuth 2.0 搭建一个身份认证协议来说太重要了。")]),c,d,A,O,m,_,T,t("p",null,[e("我们在"),l(n,{to:"/other/oauth2/07.html"},{default:h(()=>[e("第 7 讲")]),_:1}),e("中学习 PKCE 协议时,我看到了大家对这个协议的很多留言,有的是自己的思考,有的是问题的进一步讨论。我们要理解 PKCE 协议到底解决了什么问题,就要先看一")]),C])}const S=a(s,[["render",P],["__file","14.html.vue"]]),k=JSON.parse('{"path":"/other/oauth2/14.html","title":"14 | 查漏补缺:OAuth 2.0 常见问题答疑","lang":"zh-CN","frontmatter":{"title":"14 | 查漏补缺:OAuth 2.0 常见问题答疑","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","publish":true,"description":"从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题: 发明 OAuth 的...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/14.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"14 | 查漏补缺:OAuth 2.0 常见问题答疑"}],["meta",{"property":"og:description","content":"从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题: 发明 OAuth 的..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-11-08T08:57:50.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-11-08T08:57:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"14 | 查漏补缺:OAuth 2.0 常见问题答疑\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2022-11-08T08:57:50.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"发明 OAuth 的目的到底是什么?","slug":"发明-oauth-的目的到底是什么","link":"#发明-oauth-的目的到底是什么","children":[]},{"level":3,"title":"OAuth 2.0 是身份认证协议吗?","slug":"oauth-2-0-是身份认证协议吗","link":"#oauth-2-0-是身份认证协议吗","children":[]},{"level":3,"title":"有了刷新令牌,是不是就可以让访问令牌一直有效了?","slug":"有了刷新令牌-是不是就可以让访问令牌一直有效了","link":"#有了刷新令牌-是不是就可以让访问令牌一直有效了","children":[]},{"level":3,"title":"使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?","slug":"使用了-https-是不是就能确保-jwt-格式令牌的数据安全","link":"#使用了-https-是不是就能确保-jwt-格式令牌的数据安全","children":[]},{"level":3,"title":"ID 令牌和访问令牌之间有联系吗?","slug":"id-令牌和访问令牌之间有联系吗","link":"#id-令牌和访问令牌之间有联系吗","children":[]},{"level":3,"title":"PKCE 协议到底解决的是什么问题?","slug":"pkce-协议到底解决的是什么问题","link":"#pkce-协议到底解决的是什么问题","children":[]},{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1667897870000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2}]},"readingTime":{"minutes":10.29,"words":3086},"filePathRelative":"other/oauth2/14.md","localizedDate":"2022年11月8日","excerpt":"

      从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题:

      \\n
        \\n
      1. \\n

        发明 OAuth 的目的到底是什么?

        \\n
      2. \\n
      3. \\n

        OAuth 2.0 是身份认证协议吗?

        \\n
      4. \\n
      5. \\n

        有了刷新令牌,是不是就可以让访问令牌一直有效了?

        \\n
      6. \\n
      7. \\n

        使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?

        \\n
      8. \\n
      9. \\n

        ID 令牌和访问令牌之间有联系吗?

        \\n
      10. \\n
      11. \\n

        PKCE 协议到底解决的是什么问题?

        \\n
      12. \\n
      ","autoDesc":true}');export{S as comp,k as data}; +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c as r,a as t,b as e,d as l,w as h,e as i}from"./app-_Oi5YZFn.js";const s={},u=i('

      从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题:

      1. 发明 OAuth 的目的到底是什么?

      2. OAuth 2.0 是身份认证协议吗?

      3. 有了刷新令牌,是不是就可以让访问令牌一直有效了?

      4. 使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?

      5. ID 令牌和访问令牌之间有联系吗?

      6. PKCE 协议到底解决的是什么问题?

      接下来,我们就一一看看这些问题吧。

      发明 OAuth 的目的到底是什么?

      OAuth 协议的设计初衷,就是让最终用户也就是资源拥有者(小明),将他们在受保护资源服务器(京东商家开放平台)上的部分权限(查询当天订单)委托给第三方应用(小兔打单软件),使得第三方应用(小兔)能够代表最终用户(小明)执行操作(查询当天订单)。这便是 OAuth 协议设计的目的。在 OAuth 协议中,通过为每个第三方软件和每个用户的组合分别生成对受保护资源具有受限的访问权限的凭据,也就是访问令牌,来代替之前的用户名和密码。而生成访问令牌之前的登录操作,又是在用户跟平台之间进行的,第三方软件根本无从得知用户的任何信息。这样第三方软件的逻辑处理就大大简化了,它今后的动作就变成了请求访问令牌、使用访问令牌、访问受保护资源,同时在第三方软件调用大量 API 的时候,不再传输用户名和密码,从而减少了网络安全的攻击面。从安全的角度来讲,为每个第三方软件和每个用户的组合来生成一个访问令牌的方式,可以减少对平台更多用户造成的危害。因为这样一来,单个第三方软件被攻破而带来的危害,仅仅会让这一个第三方软件的用户受到影响。那么有的同学就要会问了,这样攻击的对象就会转移到授权服务身上。这个想法没错,但保护一个授权服务肯定要比保护成千上万个、由不同研发人员开发的第三方软件容易得多。

      OAuth 2.0 是身份认证协议吗?

      在这门课中,我其实一直在强调,OAuth 2.0 是一种授权协议,“它一心只专注于干好授权这件事儿”,OAuth 2.0 不是身份认证协议。但实际上,我在刚开始学习 OAuth 2.0 的时候,也曾错误地认为它是身份认证协议。因为我当时觉得,有用户参与其中,比如小明在使用小兔打单软件之前,要向授权服务进行登录操作从而进行身份认证 ,那 OAuth 2.0 就应该是一个身份认证协议啊。但是,小明必须登录之后才能进行授权,是一个额外的需求,登录跟授权体系是独立的。虽然登录操作看似“内嵌”在了 OAuth 2.0 的流程中,但生产环境中登录和授权还是两套独立存在的系统。所以说,像这种“内嵌”的身份认证行为,并不是说 OAuth 2.0 自身承担起了身份认证协议的责任。同时,身份认证会告诉第三方软件当前的用户是谁,但实际上 OAuth 2.0 自始至终都没有向第三方软件透露过关于用户的任何信息。这一点,我们在讲发明 OAuth 协议的目的时也提到过。我们可以再想想小兔打单软件的例子,看是不是这样:小兔打单软件永远也不会知道小明的任何信息,它仅仅是请求访问令牌,使用访问令牌并最终调用查询订单的 API。

      有了刷新令牌,是不是就可以让访问令牌一直有效了?

      要回答这个问题,我们先复习下访问令牌和刷新令牌相关的几个知识点吧。

      第一,OAuth 2.0 的核心是授权,授权的核心是令牌,也就是我们说的访问令牌。

      第二,在第 3 讲中我们提到,为了提高用户的体验,OAuth 2.0 提供了刷新令牌的机制,使得访问令牌过期后,第三方软件在无需用户再次授权的情况下,可以重新请求一个访问令牌。

      第三,在使用上,刷新令牌只能用在授权服务上,而访问令牌只能用在受保护资源服务上。

      有了这些知识做基础,我们可以继续分析“有了刷新令牌,是不是就可以让访问令牌一直有效”这个问题了。

      当访问令牌被 “递给” 受保护资源服务的时候,受保护资源服务需要对访问令牌进行验证,还要对访问令牌关联的权限和第三方软件的请求进行权限匹配校验。当访问令牌过期的时候,我们使用刷新令牌请求到的访问令牌,是授权服务重新生成的,而不是延长了原访问令牌的有效期。

      当前的这个刷新令牌被使用之后,授权服务可以自行决定是颁发一个新的刷新令牌,还是仍然给第三方软件返回上一个刷新令牌。安全起见,我们的建议是返回一个新的刷新令牌。这时,你可能就有一个疑问了:第三方软件已经换了一个访问令牌了,刷新令牌又一直存在,那是不是就可以一直使用刷新令牌来获取访问令牌了呢?

      要解决这个疑问,我们要知道的是,刷新令牌也有有效期。尽管生成了新的刷新令牌,但它的有效期不会改变,有效期的时间戳仍然是上一个刷新令牌的。刷新令牌的有效期到了,就不能再继续用它来申请新的访问令牌了。

      使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?

      OAuth 2.0 的使用从来都不应该脱离 HTTPS。因为访问令牌、应用密钥敏感信息要在网络上传输,都离不开 HTTPS 的保护。但是,HTTPS 也只是保证了访问令牌等重要信息在网络传输上的安全。

      在 OAuth 2.0 的规范中,访问令牌对第三方软件是不透明的,从来都不应该被任何第三方软件解析到。由于 JWT 格式的令牌自包含了用户相关的信息,比如用户标识,因此仅仅对它进行签名还不够。要避免第三方软件有机会获取访问令牌所包含的信息,那我们在与第三方软件交互的环境下使用 JWT 格式的令牌时,还要对它进行加密来保障令牌的安全,而不是仅仅依靠 HTTPS。

      ID 令牌和访问令牌之间有联系吗?

      ',20),c=t("p",null,"我们先来总结下 ID 令牌和访问令牌的作用:",-1),d=t("ul",null,[t("li",null,"ID 令牌,也就是 ID_TOKEN,代表的是用户身份令牌,可以说是一个单独的身份认证结果,永远不会像访问令牌那样作为一个参数,去传递给其它外部服务;"),t("li",null,"访问令牌,也就是 ACCESS_TOKEN,就是一个令牌,是要被第三方软件用来作为凭证,从而代表用户去请求受保护资源服务的。")],-1),A=t("p",null,"你看,这两种令牌是截然不同的。接下来,我们就分析下,它们的区别都体现在哪些方面吧。",-1),O=t("p",null,"第一,ID 令牌是对访问令牌的补充,而不是要替换访问令牌。之所以采用这样双令牌的方式,就是想让早先存在的访问令牌,可以在 OAuth 2.0 中继续保持对第三方软件的不透明性,而让后来新增的 ID 令牌要能够被解析,目的就是方便应用到身份认证协议中。第",-1),m=t("p",null,"二,ID 令牌和访问令牌有不同的生命周期,ID 令牌的生命周期相对来说更短些。因为 ID 令牌的作用就是代表一个单独的身份认证结果,它的使命就是用来标识用户的。而这个标识并不是用户名,用户登录的时候用的是用户名而不是这个 ID 令牌,所以如果用户注销或者退出了登录,ID 令牌的生命周期就随之结束了。",-1),_=t("p",null,"访问令牌可以在用户离开后的很长时间内,继续被第三方软件用来请求受保护资源服务。比如,小明使用了小兔打单软件的批量导出订单功能,如果耗时相对比较长,小明不必一直在场。",-1),T=t("h3",{id:"pkce-协议到底解决的是什么问题",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#pkce-协议到底解决的是什么问题"},[t("span",null,"PKCE 协议到底解决的是什么问题?")])],-1),C=i('

      2012 年 10 月 OAuth 2.0 的正式授权协议框架,也就是官方的 RFC 6749 被正式发布,2015 年 9 月增补了 PKCE 协议,也就是官方的 RFC 7636。从时间上来看,从正式发布 OAuth 2.0 授权协议到增补发布了 PKCE 协议,整整间隔了三年,而这三年恰恰是移动应用蓬勃发展的时期。

      同时,在原生的移动客户端应用保存秘钥又存在特殊的安全问题,使用 OAuth 2.0 授权码许可类型的客户端又容易受到授权码窃听的攻击。

      所以,PKCE 被增补发布的背景是,移动应用大力发展,同时原生客户端使用 OAuth 2.0 面临着安全风险。这样我们就能理解了,发布 PKCE 协议的目的,主要就是缓解针对公开客户端的攻击,提高授权码使用的安全性。

      总结

      1. OAuth 协议被发明的目的,就是用令牌代替用户名和密码。
      2. OAuth 2.0 不能被直接用来“从事”身份认证协议的“工作”。虽然 OAuth2.0 的使用要求是在 HTTPS 的环境下,但这并不能解决 JWT 令牌对第三方软件“不透明”的问题,还需要进行加密。
      3. 有了刷新令牌也不能让访问令牌一直有效下去,因为刷新令牌也有有效期。
      4. ID 令牌是对访问令牌的补充,而不是要替代访问令牌。
      5. PKCE 是 OAuth 2.0 的一个增补协议,主要用来缓解授权码被窃听的安全风险。
      ',5);function P(D,I){const n=p("RouteLink");return o(),r("div",null,[u,t("p",null,[e("在"),l(n,{to:"/other/oauth2/09.html"},{default:h(()=>[e("第 9 讲")]),_:1}),e("中,我们在用 OAuth 2.0 实现一个 OpenID Connect 身份认证协议的时候,讲到了 ID 令牌。在这一讲的后面,有同学还是不太清楚 ID 令牌和访问令牌是啥关系,当时我就在留言区做了回复。现在,我重新整理了思路再和你解释一下,因为认识到 ID 令牌和访问令牌的联系与区别,对我们利用 OAuth 2.0 搭建一个身份认证协议来说太重要了。")]),c,d,A,O,m,_,T,t("p",null,[e("我们在"),l(n,{to:"/other/oauth2/07.html"},{default:h(()=>[e("第 7 讲")]),_:1}),e("中学习 PKCE 协议时,我看到了大家对这个协议的很多留言,有的是自己的思考,有的是问题的进一步讨论。我们要理解 PKCE 协议到底解决了什么问题,就要先看一")]),C])}const S=a(s,[["render",P],["__file","14.html.vue"]]),k=JSON.parse('{"path":"/other/oauth2/14.html","title":"14 | 查漏补缺:OAuth 2.0 常见问题答疑","lang":"zh-CN","frontmatter":{"title":"14 | 查漏补缺:OAuth 2.0 常见问题答疑","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","publish":true,"description":"从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题: 发明 OAuth 的...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/14.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"14 | 查漏补缺:OAuth 2.0 常见问题答疑"}],["meta",{"property":"og:description","content":"从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题: 发明 OAuth 的..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-11-08T08:57:50.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-11-08T08:57:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"14 | 查漏补缺:OAuth 2.0 常见问题答疑\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2022-11-08T08:57:50.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"发明 OAuth 的目的到底是什么?","slug":"发明-oauth-的目的到底是什么","link":"#发明-oauth-的目的到底是什么","children":[]},{"level":3,"title":"OAuth 2.0 是身份认证协议吗?","slug":"oauth-2-0-是身份认证协议吗","link":"#oauth-2-0-是身份认证协议吗","children":[]},{"level":3,"title":"有了刷新令牌,是不是就可以让访问令牌一直有效了?","slug":"有了刷新令牌-是不是就可以让访问令牌一直有效了","link":"#有了刷新令牌-是不是就可以让访问令牌一直有效了","children":[]},{"level":3,"title":"使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?","slug":"使用了-https-是不是就能确保-jwt-格式令牌的数据安全","link":"#使用了-https-是不是就能确保-jwt-格式令牌的数据安全","children":[]},{"level":3,"title":"ID 令牌和访问令牌之间有联系吗?","slug":"id-令牌和访问令牌之间有联系吗","link":"#id-令牌和访问令牌之间有联系吗","children":[]},{"level":3,"title":"PKCE 协议到底解决的是什么问题?","slug":"pkce-协议到底解决的是什么问题","link":"#pkce-协议到底解决的是什么问题","children":[]},{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1667897870000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2}]},"readingTime":{"minutes":10.29,"words":3086},"filePathRelative":"other/oauth2/14.md","localizedDate":"2022年11月8日","excerpt":"

      从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题:

      \\n
        \\n
      1. \\n

        发明 OAuth 的目的到底是什么?

        \\n
      2. \\n
      3. \\n

        OAuth 2.0 是身份认证协议吗?

        \\n
      4. \\n
      5. \\n

        有了刷新令牌,是不是就可以让访问令牌一直有效了?

        \\n
      6. \\n
      7. \\n

        使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?

        \\n
      8. \\n
      9. \\n

        ID 令牌和访问令牌之间有联系吗?

        \\n
      10. \\n
      11. \\n

        PKCE 协议到底解决的是什么问题?

        \\n
      12. \\n
      ","autoDesc":true}');export{S as comp,k as data}; diff --git a/assets/2.html-CDG7SSJK.js b/assets/2.html-Dp6E9lgb.js similarity index 97% rename from assets/2.html-CDG7SSJK.js rename to assets/2.html-Dp6E9lgb.js index 1ac867a04a..117bef5240 100644 --- a/assets/2.html-CDG7SSJK.js +++ b/assets/2.html-Dp6E9lgb.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as t}from"./app-CVMfKeWw.js";const i={},p=t("h2",{id:"占坑",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#占坑"},[t("span",null,"占坑")])],-1),a=t("p",null,"https://juejin.cn/post/6969389200416178213#heading-11",-1),c=t("p",null,"https://blog.jetbrains.com/clion/2020/03/openjdk-with-clion/",-1),r=[p,a,c];function l(s,h){return o(),n("div",null,r)}const u=e(i,[["render",l],["__file","2.html.vue"]]),_=JSON.parse('{"path":"/cpp/other/2.html","title":"使用Clion搭建jdk源码调试环境","lang":"zh-CN","frontmatter":{"title":"使用Clion搭建jdk源码调试环境","date":"2023-01-13T00:00:00.000Z","description":"占坑 https://juejin.cn/post/6969389200416178213#heading-11 https://blog.jetbrains.com/clion/2020/03/openjdk-with-clion/","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/cpp/other/2.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"使用Clion搭建jdk源码调试环境"}],["meta",{"property":"og:description","content":"占坑 https://juejin.cn/post/6969389200416178213#heading-11 https://blog.jetbrains.com/clion/2020/03/openjdk-with-clion/"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2023-01-29T02:35:09.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-01-13T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2023-01-29T02:35:09.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"使用Clion搭建jdk源码调试环境\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-01-13T00:00:00.000Z\\",\\"dateModified\\":\\"2023-01-29T02:35:09.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"占坑","slug":"占坑","link":"#占坑","children":[]}],"git":{"createdTime":1674959709000,"updatedTime":1674959709000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.09,"words":27},"filePathRelative":"cpp/other/2.md","localizedDate":"2023年1月13日","excerpt":"

      占坑

      \\n

      https://juejin.cn/post/6969389200416178213#heading-11

      \\n

      https://blog.jetbrains.com/clion/2020/03/openjdk-with-clion/

      \\n","autoDesc":true}');export{u as comp,_ as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as t}from"./app-_Oi5YZFn.js";const i={},p=t("h2",{id:"占坑",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#占坑"},[t("span",null,"占坑")])],-1),a=t("p",null,"https://juejin.cn/post/6969389200416178213#heading-11",-1),c=t("p",null,"https://blog.jetbrains.com/clion/2020/03/openjdk-with-clion/",-1),r=[p,a,c];function l(s,h){return o(),n("div",null,r)}const u=e(i,[["render",l],["__file","2.html.vue"]]),_=JSON.parse('{"path":"/cpp/other/2.html","title":"使用Clion搭建jdk源码调试环境","lang":"zh-CN","frontmatter":{"title":"使用Clion搭建jdk源码调试环境","date":"2023-01-13T00:00:00.000Z","description":"占坑 https://juejin.cn/post/6969389200416178213#heading-11 https://blog.jetbrains.com/clion/2020/03/openjdk-with-clion/","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/cpp/other/2.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"使用Clion搭建jdk源码调试环境"}],["meta",{"property":"og:description","content":"占坑 https://juejin.cn/post/6969389200416178213#heading-11 https://blog.jetbrains.com/clion/2020/03/openjdk-with-clion/"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2023-01-29T02:35:09.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-01-13T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2023-01-29T02:35:09.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"使用Clion搭建jdk源码调试环境\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-01-13T00:00:00.000Z\\",\\"dateModified\\":\\"2023-01-29T02:35:09.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"占坑","slug":"占坑","link":"#占坑","children":[]}],"git":{"createdTime":1674959709000,"updatedTime":1674959709000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.09,"words":27},"filePathRelative":"cpp/other/2.md","localizedDate":"2023年1月13日","excerpt":"

      占坑

      \\n

      https://juejin.cn/post/6969389200416178213#heading-11

      \\n

      https://blog.jetbrains.com/clion/2020/03/openjdk-with-clion/

      \\n","autoDesc":true}');export{u as comp,_ as data}; diff --git a/assets/2022-04-12.html-BnI2oaJK.js b/assets/2022-04-12.html-omw7Q29H.js similarity index 99% rename from assets/2022-04-12.html-BnI2oaJK.js rename to assets/2022-04-12.html-omw7Q29H.js index bab84d3b06..bc8b7b08f3 100644 --- a/assets/2022-04-12.html-BnI2oaJK.js +++ b/assets/2022-04-12.html-omw7Q29H.js @@ -1,4 +1,4 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-CVMfKeWw.js";const n={},c=t(`

      背景:

      我有一个库A,这个库同时被两个服务使用(serviceA、serviceB),某天,因serviceA业务需要,必须更改数据库结构,导致serviceB无法使用,但是serviceB又不能停机,所以就考虑把数据库克隆一份给serviceB使用。在克隆使用的是mysqldump直接导出,然后再新建另一个库B,在库B导入sql,结果就悲剧了。

      1、库A导出

      # 在服务器把写了个定时备份脚本,这个databaseA是未更新前备份的库
      +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-_Oi5YZFn.js";const n={},c=t(`

      背景:

      我有一个库A,这个库同时被两个服务使用(serviceA、serviceB),某天,因serviceA业务需要,必须更改数据库结构,导致serviceB无法使用,但是serviceB又不能停机,所以就考虑把数据库克隆一份给serviceB使用。在克隆使用的是mysqldump直接导出,然后再新建另一个库B,在库B导入sql,结果就悲剧了。

      1、库A导出

      # 在服务器把写了个定时备份脚本,这个databaseA是未更新前备份的库
       mysqldump -h localhost -uroot -p123456 databaseA>databaseA.sql
       

      2、使用source导入

      # 1. 先创建databaseB(此步省略)
       
      diff --git a/assets/404.html-2xAmqrLX.js b/assets/404.html-CFV1KPyG.js
      similarity index 94%
      rename from assets/404.html-2xAmqrLX.js
      rename to assets/404.html-CFV1KPyG.js
      index 167d593c24..985c79f02d 100644
      --- a/assets/404.html-2xAmqrLX.js
      +++ b/assets/404.html-CFV1KPyG.js
      @@ -1 +1 @@
      -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o,a as n}from"./app-CVMfKeWw.js";const r={},a=n("p",null,"404 Not Found",-1),c=[a];function p(i,s){return e(),o("div",null,c)}const d=t(r,[["render",p],["__file","404.html.vue"]]),h=JSON.parse('{"path":"/404.html","title":"","lang":"zh-CN","frontmatter":{"layout":"NotFound","description":"404 Not Found","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/404.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:description","content":"404 Not Found"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"\\",\\"description\\":\\"404 Not Found\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0.01,"words":3},"filePathRelative":null,"excerpt":"

      404 Not Found

      \\n","autoDesc":true}');export{d as comp,h as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o,a as n}from"./app-_Oi5YZFn.js";const r={},a=n("p",null,"404 Not Found",-1),c=[a];function p(i,s){return e(),o("div",null,c)}const d=t(r,[["render",p],["__file","404.html.vue"]]),h=JSON.parse('{"path":"/404.html","title":"","lang":"zh-CN","frontmatter":{"layout":"NotFound","description":"404 Not Found","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/404.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:description","content":"404 Not Found"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"\\",\\"description\\":\\"404 Not Found\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0.01,"words":3},"filePathRelative":null,"excerpt":"

      404 Not Found

      \\n","autoDesc":true}');export{d as comp,h as data}; diff --git a/assets/AOPLog.html-jSrFg30s.js b/assets/AOPLog.html-WkAcygqg.js similarity index 99% rename from assets/AOPLog.html-jSrFg30s.js rename to assets/AOPLog.html-WkAcygqg.js index 1fa8e6504c..acbed525c5 100644 --- a/assets/AOPLog.html-jSrFg30s.js +++ b/assets/AOPLog.html-WkAcygqg.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

      1. 博客背景

      最近业务提了一个需求,让记录每个用户的每个操作请求到数据库,保证每个操作都可追溯,这个需求很典型,实现起来也不难,一个自定义注解就搞定了。

      2. 实现

      实现思路比较简单,采用AOP,先自定义一个注解,在需要记录的地方就使用注解

      2.1 自定义注解SysLog
      @Target(ElementType.METHOD)
      +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

      1. 博客背景

      最近业务提了一个需求,让记录每个用户的每个操作请求到数据库,保证每个操作都可追溯,这个需求很典型,实现起来也不难,一个自定义注解就搞定了。

      2. 实现

      实现思路比较简单,采用AOP,先自定义一个注解,在需要记录的地方就使用注解

      2.1 自定义注解SysLog
      @Target(ElementType.METHOD)
       @Retention(RetentionPolicy.RUNTIME)
       @Documented
       public @interface SysLog {
      diff --git a/assets/Annotation.html-Bu-Jo73Z.js b/assets/Annotation.html-CN1Pgot9.js
      similarity index 99%
      rename from assets/Annotation.html-Bu-Jo73Z.js
      rename to assets/Annotation.html-CN1Pgot9.js
      index 64db74f3a4..d194b328ad 100644
      --- a/assets/Annotation.html-Bu-Jo73Z.js
      +++ b/assets/Annotation.html-CN1Pgot9.js
      @@ -1,4 +1,4 @@
      -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as r,c as l,a as n,b as e,d as t,w as c,e as s}from"./app-CVMfKeWw.js";const d={},u=s(`

      1、依赖注入

       1. @Autowired
      +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as r,c as l,a as n,b as e,d as t,w as c,e as s}from"./app-_Oi5YZFn.js";const d={},u=s(`

      1、依赖注入

       1. @Autowired
            是Spring中的注解,按照类型注入,此注解可以用于字段属性上、setter方法上、构造函数上,用在字段上则Spring底层会使用反射对字段进行赋值,用成员变量的在setter方法上,则会调用setter方法进行注入。从Spring4.3开始,如果只有一个有参的构造方法,则可以省略构造方法上的@Autowired
        2. @Autowired + @Qualifier 
           按照bean的名字注入
      diff --git a/assets/Arthas.html-D1k7ux4i.js b/assets/Arthas.html-BDHUU6lY.js
      similarity index 99%
      rename from assets/Arthas.html-D1k7ux4i.js
      rename to assets/Arthas.html-BDHUU6lY.js
      index a2a9a9ff19..5014696e19 100644
      --- a/assets/Arthas.html-D1k7ux4i.js
      +++ b/assets/Arthas.html-BDHUU6lY.js
      @@ -1,4 +1,4 @@
      -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as s,e}from"./app-CVMfKeWw.js";const t={},p=e(`

      背景:有一个导出PDF的功能,在本地运行正常,在线上异常,抛出的异常无法直接定位到问题。

      1、使用arthas来跟踪

      1. 进入arths,attach进入项目
      $ as.sh
      +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as s,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

      背景:有一个导出PDF的功能,在本地运行正常,在线上异常,抛出的异常无法直接定位到问题。

      1、使用arthas来跟踪

      1. 进入arths,attach进入项目
      $ as.sh
       Arthas script version: 3.5.5
       [INFO] JAVA_HOME: /usr/local/jdk1.8.0_72
       [INFO] Process 57135 already using port 3658
      diff --git a/assets/Authorization.html-BqA7v7f3.js b/assets/Authorization.html-DYO-IVFw.js
      similarity index 98%
      rename from assets/Authorization.html-BqA7v7f3.js
      rename to assets/Authorization.html-DYO-IVFw.js
      index 788fe19058..48d31504b0 100644
      --- a/assets/Authorization.html-BqA7v7f3.js
      +++ b/assets/Authorization.html-DYO-IVFw.js
      @@ -1 +1 @@
      -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as i,e as r}from"./app-CVMfKeWw.js";const o={},n=r('

      1、权限说明

      认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401
      授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403

      2、Security中负责权限校验的类结构图

      Security中权限类

      如上图,可以看到最底层有两个类,分别是FilterSecurityInterceptorMethodSecurityInterceptor,这两个类都是AbstractSecurityInterceptor的子类。
      其中FilterSecurityInterceptor还实现了Filter接口,它是一个SecurityFilter,是众多SecurityFilterChain过滤器中的一个,它处理认证问题,当用户访问未认证接口 会被此类拦截,抛出异常,返回401。

      MethodSecurityInterceptor是当程序即将调用Controller中方法之前调用,对应的它处理Controller层被使用了@PreAuthorize注解的方法,它用来校验当前用户是否有注解中 包含的权限,当前用户不包含对应权限时,会抛出异常返回403。

      ',6),a=[n];function c(p,h){return e(),i("div",null,a)}const u=t(o,[["render",c],["__file","Authorization.html.vue"]]),d=JSON.parse('{"path":"/java/framework/security/Authorization.html","title":"权限校验原理","lang":"zh-CN","frontmatter":{"title":"权限校验原理","date":"2022-12-20T00:00:00.000Z","category":["security"],"description":"1、权限说明 认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401 授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403 2、Secu...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/Authorization.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"权限校验原理"}],["meta",{"property":"og:description","content":"1、权限说明 认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401 授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403 2、Secu..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221220222603.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-12-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"权限校验原理\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221220222603.png\\"],\\"datePublished\\":\\"2022-12-20T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、权限说明","slug":"_1、权限说明","link":"#_1、权限说明","children":[]},{"level":2,"title":"2、Security中负责权限校验的类结构图","slug":"_2、security中负责权限校验的类结构图","link":"#_2、security中负责权限校验的类结构图","children":[]}],"git":{"createdTime":1671546901000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":3},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":1.07,"words":322},"filePathRelative":"java/framework/security/Authorization.md","localizedDate":"2022年12月20日","excerpt":"

      1、权限说明

      \\n

      认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401
      \\n授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403

      \\n

      2、Security中负责权限校验的类结构图

      \\n

      \\"Security中权限类\\"

      ","autoDesc":true}');export{u as comp,d as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as i,e as r}from"./app-_Oi5YZFn.js";const o={},n=r('

      1、权限说明

      认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401
      授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403

      2、Security中负责权限校验的类结构图

      Security中权限类

      如上图,可以看到最底层有两个类,分别是FilterSecurityInterceptorMethodSecurityInterceptor,这两个类都是AbstractSecurityInterceptor的子类。
      其中FilterSecurityInterceptor还实现了Filter接口,它是一个SecurityFilter,是众多SecurityFilterChain过滤器中的一个,它处理认证问题,当用户访问未认证接口 会被此类拦截,抛出异常,返回401。

      MethodSecurityInterceptor是当程序即将调用Controller中方法之前调用,对应的它处理Controller层被使用了@PreAuthorize注解的方法,它用来校验当前用户是否有注解中 包含的权限,当前用户不包含对应权限时,会抛出异常返回403。

      ',6),a=[n];function c(p,h){return e(),i("div",null,a)}const u=t(o,[["render",c],["__file","Authorization.html.vue"]]),d=JSON.parse('{"path":"/java/framework/security/Authorization.html","title":"权限校验原理","lang":"zh-CN","frontmatter":{"title":"权限校验原理","date":"2022-12-20T00:00:00.000Z","category":["security"],"description":"1、权限说明 认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401 授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403 2、Secu...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/Authorization.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"权限校验原理"}],["meta",{"property":"og:description","content":"1、权限说明 认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401 授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403 2、Secu..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221220222603.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-12-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"权限校验原理\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221220222603.png\\"],\\"datePublished\\":\\"2022-12-20T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、权限说明","slug":"_1、权限说明","link":"#_1、权限说明","children":[]},{"level":2,"title":"2、Security中负责权限校验的类结构图","slug":"_2、security中负责权限校验的类结构图","link":"#_2、security中负责权限校验的类结构图","children":[]}],"git":{"createdTime":1671546901000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":3},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":1.07,"words":322},"filePathRelative":"java/framework/security/Authorization.md","localizedDate":"2022年12月20日","excerpt":"

      1、权限说明

      \\n

      认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401
      \\n授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403

      \\n

      2、Security中负责权限校验的类结构图

      \\n

      \\"Security中权限类\\"

      ","autoDesc":true}');export{u as comp,d as data}; diff --git a/assets/BTree.html-CuSxbXvJ.js b/assets/BTree.html-DJQoEL8B.js similarity index 97% rename from assets/BTree.html-CuSxbXvJ.js rename to assets/BTree.html-DJQoEL8B.js index b2dde764e0..d406cf2bf5 100644 --- a/assets/BTree.html-CuSxbXvJ.js +++ b/assets/BTree.html-DJQoEL8B.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as o,a as e}from"./app-CVMfKeWw.js";const a={},r=e("h2",{id:"二叉树进化图",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#二叉树进化图"},[e("span",null,"二叉树进化图")])],-1),i=e("p",null,[e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/16181908373514.jpg",alt:"树结构大道"})],-1),c=[r,i];function p(s,l){return n(),o("div",null,c)}const d=t(a,[["render",p],["__file","BTree.html.vue"]]),g=JSON.parse('{"path":"/other/essay/BTree.html","title":"二叉树","lang":"zh-CN","frontmatter":{"title":"二叉树","date":"2021-02-20T00:00:00.000Z","sticky":8,"tags":["数据结构","二叉树"],"description":"二叉树进化图 树结构大道","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/essay/BTree.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"二叉树"}],["meta",{"property":"og:description","content":"二叉树进化图 树结构大道"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/16181908373514.jpg"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"数据结构"}],["meta",{"property":"article:tag","content":"二叉树"}],["meta",{"property":"article:published_time","content":"2021-02-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"二叉树\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/16181908373514.jpg\\"],\\"datePublished\\":\\"2021-02-20T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"二叉树进化图","slug":"二叉树进化图","link":"#二叉树进化图","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.11,"words":32},"filePathRelative":"other/essay/BTree.md","localizedDate":"2021年2月20日","excerpt":"

      二叉树进化图

      \\n

      \\"树结构大道\\"

      \\n","autoDesc":true}');export{d as comp,g as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as o,a as e}from"./app-_Oi5YZFn.js";const a={},r=e("h2",{id:"二叉树进化图",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#二叉树进化图"},[e("span",null,"二叉树进化图")])],-1),i=e("p",null,[e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/16181908373514.jpg",alt:"树结构大道"})],-1),c=[r,i];function p(s,l){return n(),o("div",null,c)}const d=t(a,[["render",p],["__file","BTree.html.vue"]]),g=JSON.parse('{"path":"/other/essay/BTree.html","title":"二叉树","lang":"zh-CN","frontmatter":{"title":"二叉树","date":"2021-02-20T00:00:00.000Z","sticky":8,"tags":["数据结构","二叉树"],"description":"二叉树进化图 树结构大道","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/essay/BTree.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"二叉树"}],["meta",{"property":"og:description","content":"二叉树进化图 树结构大道"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/16181908373514.jpg"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"数据结构"}],["meta",{"property":"article:tag","content":"二叉树"}],["meta",{"property":"article:published_time","content":"2021-02-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"二叉树\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/16181908373514.jpg\\"],\\"datePublished\\":\\"2021-02-20T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"二叉树进化图","slug":"二叉树进化图","link":"#二叉树进化图","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.11,"words":32},"filePathRelative":"other/essay/BTree.md","localizedDate":"2021年2月20日","excerpt":"

      二叉树进化图

      \\n

      \\"树结构大道\\"

      \\n","autoDesc":true}');export{d as comp,g as data}; diff --git a/assets/BatchDeleteGitHubRepo.html-WNdAC_un.js b/assets/BatchDeleteGitHubRepo.html-DtqMXGl3.js similarity index 98% rename from assets/BatchDeleteGitHubRepo.html-WNdAC_un.js rename to assets/BatchDeleteGitHubRepo.html-DtqMXGl3.js index 2c4cb9d337..1e2f1ae6a0 100644 --- a/assets/BatchDeleteGitHubRepo.html-WNdAC_un.js +++ b/assets/BatchDeleteGitHubRepo.html-DtqMXGl3.js @@ -1,4 +1,4 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as n,e as t}from"./app-CVMfKeWw.js";const s={},i=t(`

      背景

      github上fork了很多仓库,但是平时又没看,所以索性删除,一个个删除又很慢,所以搞个脚本批量删除

      方法

      1. 到github配置一个token,https://github.com/settings/tokens
      +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as n,e as t}from"./app-_Oi5YZFn.js";const s={},i=t(`

      背景

      github上fork了很多仓库,但是平时又没看,所以索性删除,一个个删除又很慢,所以搞个脚本批量删除

      方法

      1. 到github配置一个token,https://github.com/settings/tokens
       2. 准备一个文件放置待删除的仓库,每行一个仓库名
       3. 用脚本批量删除
       

      脚本:

      # 将 DELETE_KOKEN 和 GithubName 都替换为自己的
      diff --git a/assets/BeanPostProcessor.html-2fGhuRBC.js b/assets/BeanPostProcessor.html-CLtCpKMO.js
      similarity index 99%
      rename from assets/BeanPostProcessor.html-2fGhuRBC.js
      rename to assets/BeanPostProcessor.html-CLtCpKMO.js
      index 0861e94f1e..0bf413730d 100644
      --- a/assets/BeanPostProcessor.html-2fGhuRBC.js
      +++ b/assets/BeanPostProcessor.html-CLtCpKMO.js
      @@ -1,4 +1,4 @@
      -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

      1. BeanPostProcessor介绍

      打开源码里面有两个方法,分别是postProcessBeforeInitialization和postProcessAfterInitialization。

      
      +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

      1. BeanPostProcessor介绍

      打开源码里面有两个方法,分别是postProcessBeforeInitialization和postProcessAfterInitialization。

      
       public interface BeanPostProcessor {
       	@Nullable
       	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      diff --git a/assets/BuildWebProject.html-D1F1nzup.js b/assets/BuildWebProject.html-D4yumRMO.js
      similarity index 99%
      rename from assets/BuildWebProject.html-D1F1nzup.js
      rename to assets/BuildWebProject.html-D4yumRMO.js
      index 4d30f7b069..06539da289 100644
      --- a/assets/BuildWebProject.html-D1F1nzup.js
      +++ b/assets/BuildWebProject.html-D4yumRMO.js
      @@ -1,4 +1,4 @@
      -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as n,b as s,d as t,e}from"./app-CVMfKeWw.js";const i={},u={class:"hint-container caution"},r=n("p",{class:"hint-container-title"},"温馨提示",-1),k={href:"https://github.com/ChenSino/ChenSino.git",target:"_blank",rel:"noopener noreferrer"},d=e('

      1、后端篇

      1.1 初始化springboot项目

      git reset --hard 20e22c237e51fb9c7f01bdfd589a90f47fa73c34

      1.1.1 使用maven聚合模块以及parent依赖的方式初始化好了项目

      问题1
      分模块后,如何读取到其他模块中的bean,比如全局异常处理放在了common模块,在业务模块依赖了common,如何让common中的全局异常拦截生效?
      首先要明白无法common模块的component在core-biz不生效的原因是在biz模块默认扫描的component的包范围是启动类所在的包,也就是com.chensino.core,而全局异常类所在的包是com.chensino.common.security.exception,根本没有被扫描到。

      1

      ',6),m=n("br",null,null,-1),v={href:"https://ddns.chensina.cn:29000/afatpig/ebooks/Springboot.pdf",target:"_blank",rel:"noopener noreferrer"},g=e(`
      1. 把扫描范围搞大一点
      package com.chensino.core;
      +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as n,b as s,d as t,e}from"./app-_Oi5YZFn.js";const i={},u={class:"hint-container caution"},r=n("p",{class:"hint-container-title"},"温馨提示",-1),k={href:"https://github.com/ChenSino/ChenSino.git",target:"_blank",rel:"noopener noreferrer"},d=e('

      1、后端篇

      1.1 初始化springboot项目

      git reset --hard 20e22c237e51fb9c7f01bdfd589a90f47fa73c34

      1.1.1 使用maven聚合模块以及parent依赖的方式初始化好了项目

      问题1
      分模块后,如何读取到其他模块中的bean,比如全局异常处理放在了common模块,在业务模块依赖了common,如何让common中的全局异常拦截生效?
      首先要明白无法common模块的component在core-biz不生效的原因是在biz模块默认扫描的component的包范围是启动类所在的包,也就是com.chensino.core,而全局异常类所在的包是com.chensino.common.security.exception,根本没有被扫描到。

      1

      ',6),m=n("br",null,null,-1),v={href:"https://ddns.chensina.cn:29000/afatpig/ebooks/Springboot.pdf",target:"_blank",rel:"noopener noreferrer"},g=e(`
      1. 把扫描范围搞大一点
      package com.chensino.core;
       
       import org.springframework.boot.SpringApplication;
       import org.springframework.boot.autoconfigure.SpringBootApplication;
      diff --git a/assets/CDN.html-rIf3-Wbw.js b/assets/CDN.html-Cd7ildNJ.js
      similarity index 99%
      rename from assets/CDN.html-rIf3-Wbw.js
      rename to assets/CDN.html-Cd7ildNJ.js
      index c2546fc3a5..981f312091 100644
      --- a/assets/CDN.html-rIf3-Wbw.js
      +++ b/assets/CDN.html-Cd7ildNJ.js
      @@ -1,4 +1,4 @@
      -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o,c as i,a as e,b as n,d as c,e as t}from"./app-CVMfKeWw.js";const d={},r=t(`

      1、什么是CDN?

      CDN(Content Delivery Network,内容分发网络)是构建在现有互联网基础之上的一层智能虚拟网络,通过在网络各处部署节点服务器,实现将源站内容分发至所有CDN节点,使用户可以就近获得所需的内容。CDN服务缩短了用户查看内容的访问延迟,提高了用户访问网站的响应速度与网站的可用性,解决了网络带宽小、用户访问量大、网点分布不均等问题。
      +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o,c as i,a as e,b as n,d as c,e as t}from"./app-_Oi5YZFn.js";const d={},r=t(`

      1、什么是CDN?

      CDN(Content Delivery Network,内容分发网络)是构建在现有互联网基础之上的一层智能虚拟网络,通过在网络各处部署节点服务器,实现将源站内容分发至所有CDN节点,使用户可以就近获得所需的内容。CDN服务缩短了用户查看内容的访问延迟,提高了用户访问网站的响应速度与网站的可用性,解决了网络带宽小、用户访问量大、网点分布不均等问题。
       

      2、工作原理

      `,3),l={href:"https://support.huaweicloud.com/productdesc-cdn/cdn_01_0109.html",target:"_blank",rel:"noopener noreferrer"},h=t('

      当用户访问使用CDN服务的网站时,本地DNS服务器通过CNAME方式将最终域名请求重定向到CDN服务。CDN通过一组预先定义好的策略(如内容类型、地理区域、网络负载状况等),将当时能够最快响应用户的CDN节点IP地址提供给用户,使用户可以以最快的速度获得网站内容。使用CDN后的HTTP请求处理流程如下。

      img

      HTTP请求流程说明:

      1. 用户在浏览器输入要访问的网站域名www.example.com,向本地DNS发起域名解析请求。
      2. 本地DNS检查缓存中是否有www.example.com的IP地址记录。如果有,则直接返回给终端用户;如果没有,则向网站授权DNS查询。
      3. 网站DNS服务器解析发现域名已经解析到了CNAME:www.example.com.c.cdnhwc1.com。
      4. 请求被指向CDN服务。
      5. CDN对域名进行智能解析,将响应速度最快的CDN节点IP地址返回给本地DNS。
      6. 用户获取响应速度最快的CDN节点IP地址。
      7. 浏览器在得到最佳节点的IP地址以后,向CDN节点发出访问请求。
        • 如果该IP地址对应的节点已缓存该资源,节点将数据直接返回给用户,如图中步骤7和8,请求结束。
        • 如果该IP地址对应的节点未缓存该资源,节点回源请求资源。获取资源后,结合用户自定义配置的缓存策略,将资源缓存至节点,如图中的北京节点,并返回给用户,请求结束。

      3、CDN应用场景

      ',5),m={href:"https://support.huaweicloud.com/productdesc-cdn/cdn_01_0067.html",target:"_blank",rel:"noopener noreferrer"},N=t(`
      3.1 网站加速

      适用于有加速需求的网站,包括门户网站、电商平台、资讯APP、UGC应用(User Generated Content,用户原创内容)等。CDN网络能够对加速域名下的静态内容提供良好的加速服务。支持自定义缓存规则,用户可以根据数据需求设置缓存过期时间,缓存格式包括但不限于zip、exe、wmv、gif、png、bmp、wma、rar、jpeg、jpg等。

      img

      3.2 文件下载加速

      适用于使用http/https文件下载业务的网站、下载工具、游戏客户端、APP商店等。现在越来越多的新业务需要通过网络对客户端软件进行实时更新,包括APP更新,手游更新等,传统的下载类业务也需要支持更多的文件数量和更大的文件,如果所有的请求都通过源站服务器来处理,服务器和网络会成为很大的瓶颈,导致下载体验变差。使用CDN下载加速可以将下载量大的内容分发到各地的CDN节点,有效减轻源站的压力,同时保证了客户端高速下载的需求。

      img

      3.3 点播加速

      适用于提供音视频点播服务的客户。例如:在线教育类网站、在线视频分享网站、互联网电视点播平台、音乐视频点播APP等。传统的点播服务会加大服务器的负载,并消耗巨大的带宽资源,同时又无法保证终端用户访问时需要的高速体验,CDN点播加速可以提供快速、稳定和安全的点播加速服务,通过分布在各个区域的CDN节点,将音视频内容扩展到距离用户较近的地方,随时随地为用户提供高品质的访问体验。

      点击放大

      3.4 全站加速

      适用于各行业动静态内容混合,含较多动态资源请求(如asp、jsp、php等格式的文件)的网站。全站加速融合了动态和静态加速,用户请求资源时,静态内容从边缘节点就近获取,动态内容通过动态加速技术智能选择较优路由回源获取。CDN全站加速有效提升动态页面的加载速度,避开网络拥堵路由,提高访问成功率,实现网站整体加速与实时优化。

      4、CDN加速原理

      当用户访问使用CDN服务的网站时,本地DNS服务器通过CNAME方式将最终域名请求重定向到CDN服务。CDN通过一组预先定义好的策略(如内容类型、地理区域、网络负载状况等),将当时能够最快响应用户的CDN节点IP地址提供给用户,使用户可以以最快的速度获得网站内容。
       
      4.1 CDN节点无缓存场景

      CDN节点无缓存场景

      HTTP请求流程说明:

      1、用户在浏览器输入要访问的网站域名,向本地DNS发起域名解析请求。

      2、域名解析的请求被发往网站授权DNS服务器。

      3、网站DNS服务器解析发现域名已经CNAME到了www.example.com.c.cdnhwc1.com。

      4、请求被指向CDN服务。

      5、CDN对域名进行智能解析,将响应速度最快的CDN节点IP地址返回给本地DNS。

      6、用户获取响应速度最快的CDN节点IP地址。

      7、浏览器在得到速度最快节点的IP地址以后,向CDN节点发出访问请求。

      8、CDN节点回源站拉取用户所需资源。(==无缓存时,第一次请求要回源地址获取资源,因此第一次请求会感觉仍然慢==)

      9、将回源拉取的资源缓存至节点。

      10、将用户所需资源返回给用户。

      4.2 CDN节点有缓存场景

      CDN节点有缓存场景

      HTTP请求流程说明:

      1、用户在浏览器输入要访问的网站域名,向本地DNS发起域名解析请求。

      2、域名解析的请求被发往网站授权DNS服务器。

      3、网站DNS服务器解析发现域名已经CNAME到了www.example.com.c.cdnhwc1.com。

      4、请求被指向CDN服务。

      5、CDN对域名进行智能解析,将响应速度最快的CDN节点IP地址返回给本地DNS。

      6、用户获取响应速度最快的CDN节点IP地址。

      7、浏览器在得到速度最快节点的IP地址以后,向CDN节点发出访问请求。

      8、CDN节点将用户所需资源返回给用户。

      5、如何使用华为云CDN?

      `,38),C={href:"https://support.huaweicloud.com/cdn/index.html",target:"_blank",rel:"noopener noreferrer"},D=t(`

      举例说明:我要开通加速的地址是h.sonoscape.com,是公司下的一个子域名,则开通cdn的步骤如下:

      1. 在华为云花钱开通CDN服务;

      2. 添加加速域名,就是把你要加速的域名到华为控制台配置以下,配置好后,它会根据你的域名生成一个CNAME,类似h.sonoscape.com.7bf9428.c.cdnhwc1.com这样;

      3. 配置CNAME解析,要看你的域名是哪里来的,这个h.sonoscape.com是从公司申请的,因此要找公司IT部门去配置一个CNAME,把h.sonoscape.com下配置一个cname,h.sonoscape.com.7bf9428.c.cdnhwc1.com,配置好一般两小时生效;

      4. 验证cname是否生效

        nslookup -qt=cname h.sonoscape.com
         

        如果回显CNAME,则表示CNAME配置已经生效,如下图:

        点击放大

      6、问题

      1. 为何第一次请求感觉CDN没用?

        正常,由于首次访问时,CDN未对源站的相关资源进行缓存,需要回源拉取资源。您可以在首次访问前,进行缓存预热,将访问频率高的资源预热到CDN。
        diff --git a/assets/CPU.html-D5a-tI0p.js b/assets/CPU.html-BmK1dlqs.js
        similarity index 97%
        rename from assets/CPU.html-D5a-tI0p.js
        rename to assets/CPU.html-BmK1dlqs.js
        index d3319ccb82..01dd8cdfba 100644
        --- a/assets/CPU.html-D5a-tI0p.js
        +++ b/assets/CPU.html-BmK1dlqs.js
        @@ -1 +1 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as c,c as r,a as t,b as a,d,e as o}from"./app-CVMfKeWw.js";const s={},i=t("h2",{id:"intel-cpu型号",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#intel-cpu型号"},[t("span",null,"intel cpu型号")])],-1),p={href:"https://www.intel.cn/content/www/cn/zh/processors/processor-numbers.html",target:"_blank",rel:"noopener noreferrer"},u=o('

        cpu型号命名

        20230809094135

        20230809094205

        20230809094220

        cpu 后缀

        ',5),h=t("table",{class:"table image-rendition-feature",disprows:"20"},[t("thead",null,[t("tr",null,[t("th",null,[t("b",null,"外形/功能类型/细分市场")]),t("th",null,[t("b",null,"后缀")]),t("th",null,[t("b",null,"优化/设计")])])]),t("tbody",null,[t("tr",{class:"data","data-category-id":""},[t("td",{rowspan:"5"},"台式机"),t("td",null,"K"),t("td",null,"高性能,未锁频")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"Φ"),t("td",null,"需要独立显卡")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"S"),t("td",null,"特别版")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"T"),t("td",null,"功耗优化生活方式")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"X/XE"),t("td",null,"最高性能,未锁频")]),t("tr",{class:"data","data-category-id":""},[t("td",{rowspan:"7"},"移动设备(笔记本电脑 2、2 合 1 电脑)"),t("td",null,"HX"),t("td",null,"最高性能,所有 SKU 未锁频")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"HK"),t("td",null,"高性能,未锁频")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"H"),t("td",null,"高性能")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"P"),t("td",null,"提供轻薄型设备所需的性能")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"U"),t("td",null,"能效更高")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"Y"),t("td",null,"功耗极低")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"G1-G7"),t("td",null,"显卡级别(采用较新集成显卡技术的处理器)")]),t("tr",{class:"data","data-category-id":""},[t("td",{rowspan:"5"},"嵌入式"),t("td",null,"E"),t("td",null,"嵌入式")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"UE"),t("td",null,"能效更高")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"HE"),t("td",null,"高性能")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"UL"),t("td",null,"高能效,采用 LGA 封装")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"HL"),t("td",null,"高性能,采用 LGA 封装")])])],-1);function g(m,y){const e=l("ExternalLinkIcon");return c(),r("div",null,[i,t("p",null,[t("a",p,[a("官方说明"),d(e)])]),u,h,a(" git ")])}const _=n(s,[["render",g],["__file","CPU.html.vue"]]),w=JSON.parse('{"path":"/other/hardware/CPU.html","title":"cpu介绍","lang":"zh-CN","frontmatter":{"title":"cpu介绍","date":"2023-08-09T00:00:00.000Z","isOriginal":true,"description":"intel cpu型号 官方说明 cpu型号命名 20230809094135 20230809094205 20230809094220 cpu 后缀 git","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/hardware/CPU.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"cpu介绍"}],["meta",{"property":"og:description","content":"intel cpu型号 官方说明 cpu型号命名 20230809094135 20230809094205 20230809094220 cpu 后缀 git"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20230809094135.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-08-09T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"cpu介绍\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20230809094135.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230809094205.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230809094220.png\\"],\\"datePublished\\":\\"2023-08-09T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"intel cpu型号","slug":"intel-cpu型号","link":"#intel-cpu型号","children":[{"level":3,"title":"cpu型号命名","slug":"cpu型号命名","link":"#cpu型号命名","children":[]},{"level":3,"title":"cpu 后缀","slug":"cpu-后缀","link":"#cpu-后缀","children":[]}]}],"git":{"createdTime":1694571353000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":4}]},"readingTime":{"minutes":1.46,"words":438},"filePathRelative":"other/hardware/CPU.md","localizedDate":"2023年8月9日","excerpt":"

        intel cpu型号

        \\n

        官方说明

        \\n

        cpu型号命名

        \\n

        \\"20230809094135\\"

        \\n

        \\"20230809094205\\"

        ","autoDesc":true}');export{_ as comp,w as data}; +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as c,c as r,a as t,b as a,d,e as o}from"./app-_Oi5YZFn.js";const s={},i=t("h2",{id:"intel-cpu型号",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#intel-cpu型号"},[t("span",null,"intel cpu型号")])],-1),p={href:"https://www.intel.cn/content/www/cn/zh/processors/processor-numbers.html",target:"_blank",rel:"noopener noreferrer"},u=o('

        cpu型号命名

        20230809094135

        20230809094205

        20230809094220

        cpu 后缀

        ',5),h=t("table",{class:"table image-rendition-feature",disprows:"20"},[t("thead",null,[t("tr",null,[t("th",null,[t("b",null,"外形/功能类型/细分市场")]),t("th",null,[t("b",null,"后缀")]),t("th",null,[t("b",null,"优化/设计")])])]),t("tbody",null,[t("tr",{class:"data","data-category-id":""},[t("td",{rowspan:"5"},"台式机"),t("td",null,"K"),t("td",null,"高性能,未锁频")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"Φ"),t("td",null,"需要独立显卡")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"S"),t("td",null,"特别版")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"T"),t("td",null,"功耗优化生活方式")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"X/XE"),t("td",null,"最高性能,未锁频")]),t("tr",{class:"data","data-category-id":""},[t("td",{rowspan:"7"},"移动设备(笔记本电脑 2、2 合 1 电脑)"),t("td",null,"HX"),t("td",null,"最高性能,所有 SKU 未锁频")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"HK"),t("td",null,"高性能,未锁频")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"H"),t("td",null,"高性能")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"P"),t("td",null,"提供轻薄型设备所需的性能")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"U"),t("td",null,"能效更高")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"Y"),t("td",null,"功耗极低")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"G1-G7"),t("td",null,"显卡级别(采用较新集成显卡技术的处理器)")]),t("tr",{class:"data","data-category-id":""},[t("td",{rowspan:"5"},"嵌入式"),t("td",null,"E"),t("td",null,"嵌入式")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"UE"),t("td",null,"能效更高")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"HE"),t("td",null,"高性能")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"UL"),t("td",null,"高能效,采用 LGA 封装")]),t("tr",{class:"data","data-category-id":""},[t("td",null,"HL"),t("td",null,"高性能,采用 LGA 封装")])])],-1);function g(m,y){const e=l("ExternalLinkIcon");return c(),r("div",null,[i,t("p",null,[t("a",p,[a("官方说明"),d(e)])]),u,h,a(" git ")])}const _=n(s,[["render",g],["__file","CPU.html.vue"]]),w=JSON.parse('{"path":"/other/hardware/CPU.html","title":"cpu介绍","lang":"zh-CN","frontmatter":{"title":"cpu介绍","date":"2023-08-09T00:00:00.000Z","isOriginal":true,"description":"intel cpu型号 官方说明 cpu型号命名 20230809094135 20230809094205 20230809094220 cpu 后缀 git","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/hardware/CPU.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"cpu介绍"}],["meta",{"property":"og:description","content":"intel cpu型号 官方说明 cpu型号命名 20230809094135 20230809094205 20230809094220 cpu 后缀 git"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20230809094135.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-08-09T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"cpu介绍\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20230809094135.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230809094205.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230809094220.png\\"],\\"datePublished\\":\\"2023-08-09T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"intel cpu型号","slug":"intel-cpu型号","link":"#intel-cpu型号","children":[{"level":3,"title":"cpu型号命名","slug":"cpu型号命名","link":"#cpu型号命名","children":[]},{"level":3,"title":"cpu 后缀","slug":"cpu-后缀","link":"#cpu-后缀","children":[]}]}],"git":{"createdTime":1694571353000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":4}]},"readingTime":{"minutes":1.46,"words":438},"filePathRelative":"other/hardware/CPU.md","localizedDate":"2023年8月9日","excerpt":"

        intel cpu型号

        \\n

        官方说明

        \\n

        cpu型号命名

        \\n

        \\"20230809094135\\"

        \\n

        \\"20230809094205\\"

        ","autoDesc":true}');export{_ as comp,w as data}; diff --git a/assets/CPUOverLoad.html-Cl4MUidN.js b/assets/CPUOverLoad.html-B_JZj2Bd.js similarity index 99% rename from assets/CPUOverLoad.html-Cl4MUidN.js rename to assets/CPUOverLoad.html-B_JZj2Bd.js index 7764f5aa63..8bc0ec71d8 100644 --- a/assets/CPUOverLoad.html-Cl4MUidN.js +++ b/assets/CPUOverLoad.html-B_JZj2Bd.js @@ -1,4 +1,4 @@ -import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as n,e}from"./app-CVMfKeWw.js";const o={},t=e(`

        问题

        某天突然收到预警邮件,服务器CPU超过阈值,并且一直持续居高不下

        分析原因

        1. 使用htop查看资源消耗,按照CPU使用率降序排列,发现都是mysqld进程占用CPU很高
        2. 进入mysql命令行使用show processlist;查看当前正在执行的命令,经过多次执行show processlist发现有几条固定的sql一直在执行,并且每次传递的参数害不一样
        mysql> show processlist;
        +import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as n,e}from"./app-_Oi5YZFn.js";const o={},t=e(`

        问题

        某天突然收到预警邮件,服务器CPU超过阈值,并且一直持续居高不下

        分析原因

        1. 使用htop查看资源消耗,按照CPU使用率降序排列,发现都是mysqld进程占用CPU很高
        2. 进入mysql命令行使用show processlist;查看当前正在执行的命令,经过多次执行show processlist发现有几条固定的sql一直在执行,并且每次传递的参数害不一样
        mysql> show processlist;
         +--------+------+-------------------+--------------------------+---------+------+--------------+-----------------------------------------------------------------------------------------------+
         | Id     | User | Host              | db                       | Command | Time | State        | Info                                                                                          |
         +--------+------+-------------------+--------------------------+---------+------+--------------+-----------------------------------------------------------------------------------------------+
        diff --git a/assets/CentOS.html-C59_Q0zj.js b/assets/CentOS.html-CSukTNYu.js
        similarity index 96%
        rename from assets/CentOS.html-C59_Q0zj.js
        rename to assets/CentOS.html-CSukTNYu.js
        index 7ffd729fe8..6fff7fb61c 100644
        --- a/assets/CentOS.html-C59_Q0zj.js
        +++ b/assets/CentOS.html-CSukTNYu.js
        @@ -1,3 +1,3 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o,c as i,a as e,b as r,d as l,e as t}from"./app-CVMfKeWw.js";const c={},d=t(`

        1、安装JDK11

        sudo yum -y install java-11-openjdk java-11-openjdk-devel
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o,c as i,a as e,b as r,d as l,e as t}from"./app-_Oi5YZFn.js";const c={},d=t(`

        1、安装JDK11

        sudo yum -y install java-11-openjdk java-11-openjdk-devel
         

        2、多版本JDK切换

        `,3),p={href:"https://computingforgeeks.com/how-to-install-java-11-openjdk-11-on-rhel-8/",target:"_blank",rel:"noopener noreferrer"},h=t(`
        sudo alternatives --config javac
         
        `,1);function m(u,k){const a=s("ExternalLinkIcon");return o(),i("div",null,[d,e("p",null,[e("a",p,[r("参考博客"),l(a)])]),h])}const g=n(c,[["render",m],["__file","CentOS.html.vue"]]),b=JSON.parse('{"path":"/other/linux/CentOS.html","title":"RedHat系","lang":"zh-CN","frontmatter":{"title":"RedHat系","date":"2022-08-10T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"1、安装JDK11 2、多版本JDK切换 参考博客","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/linux/CentOS.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"RedHat系"}],["meta",{"property":"og:description","content":"1、安装JDK11 2、多版本JDK切换 参考博客"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-10-08T07:30:29.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-08-10T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-10-08T07:30:29.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"RedHat系\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-10T00:00:00.000Z\\",\\"dateModified\\":\\"2024-10-08T07:30:29.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、安装JDK11","slug":"_1、安装jdk11","link":"#_1、安装jdk11","children":[]},{"level":2,"title":"2、多版本JDK切换","slug":"_2、多版本jdk切换","link":"#_2、多版本jdk切换","children":[]}],"git":{"createdTime":1660113379000,"updatedTime":1728372629000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":3},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.18,"words":55},"filePathRelative":"other/linux/CentOS.md","localizedDate":"2022年8月10日","excerpt":"

        1、安装JDK11

        \\n
        sudo yum -y install java-11-openjdk java-11-openjdk-devel\\n
        ","autoDesc":true}');export{g as comp,b as data}; diff --git a/assets/ChromeDevTools.html-BoMI5RzK.js b/assets/ChromeDevTools.html-jvUjLE8a.js similarity index 97% rename from assets/ChromeDevTools.html-BoMI5RzK.js rename to assets/ChromeDevTools.html-jvUjLE8a.js index 9d3bf6005c..8b45313f5b 100644 --- a/assets/ChromeDevTools.html-BoMI5RzK.js +++ b/assets/ChromeDevTools.html-jvUjLE8a.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as r,a as e}from"./app-CVMfKeWw.js";const a={},c=e("p",null,"学会ChromeDevtool,高效率定位问题",-1),m=e("p",null,"https://developer.chrome.com/docs/devtools/evaluate-performance/",-1),n=[c,m];function p(s,l){return o(),r("div",null,n)}const d=t(a,[["render",p],["__file","ChromeDevTools.html.vue"]]),v=JSON.parse('{"path":"/other/essay/ChromeDevTools.html","title":"ChromeDevTools学习","lang":"zh-CN","frontmatter":{"title":"ChromeDevTools学习","date":"2022-06-20T00:00:00.000Z","author":"陈老师","tag":["工具使用"],"description":"学会ChromeDevtool,高效率定位问题 https://developer.chrome.com/docs/devtools/evaluate-performance/","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/essay/ChromeDevTools.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"ChromeDevTools学习"}],["meta",{"property":"og:description","content":"学会ChromeDevtool,高效率定位问题 https://developer.chrome.com/docs/devtools/evaluate-performance/"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"陈老师"}],["meta",{"property":"article:tag","content":"工具使用"}],["meta",{"property":"article:published_time","content":"2022-06-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"ChromeDevTools学习\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-06-20T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"陈老师\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.1,"words":31},"filePathRelative":"other/essay/ChromeDevTools.md","localizedDate":"2022年6月20日","excerpt":"

        学会ChromeDevtool,高效率定位问题

        \\n\\n

        https://developer.chrome.com/docs/devtools/evaluate-performance/

        \\n","autoDesc":true}');export{d as comp,v as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as r,a as e}from"./app-_Oi5YZFn.js";const a={},c=e("p",null,"学会ChromeDevtool,高效率定位问题",-1),m=e("p",null,"https://developer.chrome.com/docs/devtools/evaluate-performance/",-1),n=[c,m];function p(s,l){return o(),r("div",null,n)}const d=t(a,[["render",p],["__file","ChromeDevTools.html.vue"]]),v=JSON.parse('{"path":"/other/essay/ChromeDevTools.html","title":"ChromeDevTools学习","lang":"zh-CN","frontmatter":{"title":"ChromeDevTools学习","date":"2022-06-20T00:00:00.000Z","author":"陈老师","tag":["工具使用"],"description":"学会ChromeDevtool,高效率定位问题 https://developer.chrome.com/docs/devtools/evaluate-performance/","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/essay/ChromeDevTools.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"ChromeDevTools学习"}],["meta",{"property":"og:description","content":"学会ChromeDevtool,高效率定位问题 https://developer.chrome.com/docs/devtools/evaluate-performance/"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"陈老师"}],["meta",{"property":"article:tag","content":"工具使用"}],["meta",{"property":"article:published_time","content":"2022-06-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"ChromeDevTools学习\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-06-20T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"陈老师\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.1,"words":31},"filePathRelative":"other/essay/ChromeDevTools.md","localizedDate":"2022年6月20日","excerpt":"

        学会ChromeDevtool,高效率定位问题

        \\n\\n

        https://developer.chrome.com/docs/devtools/evaluate-performance/

        \\n","autoDesc":true}');export{d as comp,v as data}; diff --git a/assets/CircularDependency.html-Cy-x1Wkb.js b/assets/CircularDependency.html-DRv79HhX.js similarity index 99% rename from assets/CircularDependency.html-Cy-x1Wkb.js rename to assets/CircularDependency.html-DRv79HhX.js index 9e1b9a49fe..6fb3c99475 100644 --- a/assets/CircularDependency.html-Cy-x1Wkb.js +++ b/assets/CircularDependency.html-DRv79HhX.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},p=e(`

        1、循环依赖的产生

        A依赖B,B也依赖于A

        public class A{
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

        1、循环依赖的产生

        A依赖B,B也依赖于A

        public class A{
             private A a;
         
             public void setA(A a){
        diff --git a/assets/ClassLoader.html-9dp_fs5y.js b/assets/ClassLoader.html-fNJjFm9m.js
        similarity index 99%
        rename from assets/ClassLoader.html-9dp_fs5y.js
        rename to assets/ClassLoader.html-fNJjFm9m.js
        index a2ba4e69d2..adfa12c9f4 100644
        --- a/assets/ClassLoader.html-9dp_fs5y.js
        +++ b/assets/ClassLoader.html-fNJjFm9m.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as p}from"./app-CVMfKeWw.js";const t={},e=p(`

        1、自定义类加载器

        自定义类加载器比较简单,只需要继承ClassLoader重写findClass方法即可,因为原ClassLoader中并未实现findClass方法,在方法体中直接抛出了ClassNotFoundException

        
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as p}from"./app-_Oi5YZFn.js";const t={},e=p(`

        1、自定义类加载器

        自定义类加载器比较简单,只需要继承ClassLoader重写findClass方法即可,因为原ClassLoader中并未实现findClass方法,在方法体中直接抛出了ClassNotFoundException

        
         import java.io.ByteArrayOutputStream;
         import java.io.File;
         import java.io.FileInputStream;
        diff --git a/assets/CloudService.html-DjGDK1Z4.js b/assets/CloudService.html-DKHWbJgc.js
        similarity index 99%
        rename from assets/CloudService.html-DjGDK1Z4.js
        rename to assets/CloudService.html-DKHWbJgc.js
        index 5b31639894..2086d21447 100644
        --- a/assets/CloudService.html-DjGDK1Z4.js
        +++ b/assets/CloudService.html-DKHWbJgc.js
        @@ -1,4 +1,4 @@
        -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as n,b as a,d as t,e}from"./app-CVMfKeWw.js";const i={},u=e('

        1、我的云服务使用场景

        1. OBS用来存储图片,而CDN用来加速图片的访问速度;

        2. ECS服务器上有个导出pdf服务需要访问obs的图片;

        2、遇到的问题

        1. 一开始没有上cdn,用户反应慢,后来配置了cdn加速obs中的图片,这样第一次打开图片会把图片资源拉到就近的服务器,问题就是第一次打开依然慢;
        2. 导出PDF很慢,经常出现504Timeout

        3、问题分析

        先上cdn原理图,对照原理图来进行分析

        img

        3.1 使用cdn 第一次访问慢原因分析

        image-20220620164944526

        以上是我的服务访问线路:

        1. 客户端通过加速域名obs.sonoscapecloud.com去请求图片,以下是我 在云服务管理台做的配置,使用obs.sonoscapecloud.com加速桶域名ccs.obs.ap-southeast-1.myhuaweicloud.com

          image-20220620163303675

        2. cdn通过cname去查找是否缓存过此文件,若没有缓存,则回源到原来的obs存储服务器,先把图片从obs服务器拉到就近的cdn服务器,比如我在武汉访问图片,可能就把图片拉到了华中区的服务器,缓存到华中区后,再把图片返回到客户端。


          第二步因为要回源拉取,所以第一次打开会慢点,第二次就直接就近到华中区去获取,就会快一些。这里有个地方要注意,虽然使用cdn后第一次拉取会稍慢,但是比不使用cdn还是快很多的,原因就是从obs源服务器把文件拉取到就近的cdn服务器应该是走的华为内网,所以速度相对来说还是要快一些,具体还要分析从源到cdn是否走了内网。

        3.2 解决第一次访问慢的问题

        防止第一次访问图片,数据回源,只需要在新增文件时,在新增接口添加缓存预热一下即可。具体的代码参考华为云提供的demo即可。

        ',13),r={href:"https://support.huaweicloud.com/usermanual-cdn/cdn_01_0113.html",target:"_blank",rel:"noopener noreferrer"},k={href:"https://support.huaweicloud.com/api-cdn/CreatePreheatingTasks.html",target:"_blank",rel:"noopener noreferrer"},m=e(`
        private void preHeatingFile(String fileName) {
        +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as n,b as a,d as t,e}from"./app-_Oi5YZFn.js";const i={},u=e('

        1、我的云服务使用场景

        1. OBS用来存储图片,而CDN用来加速图片的访问速度;

        2. ECS服务器上有个导出pdf服务需要访问obs的图片;

        2、遇到的问题

        1. 一开始没有上cdn,用户反应慢,后来配置了cdn加速obs中的图片,这样第一次打开图片会把图片资源拉到就近的服务器,问题就是第一次打开依然慢;
        2. 导出PDF很慢,经常出现504Timeout

        3、问题分析

        先上cdn原理图,对照原理图来进行分析

        img

        3.1 使用cdn 第一次访问慢原因分析

        image-20220620164944526

        以上是我的服务访问线路:

        1. 客户端通过加速域名obs.sonoscapecloud.com去请求图片,以下是我 在云服务管理台做的配置,使用obs.sonoscapecloud.com加速桶域名ccs.obs.ap-southeast-1.myhuaweicloud.com

          image-20220620163303675

        2. cdn通过cname去查找是否缓存过此文件,若没有缓存,则回源到原来的obs存储服务器,先把图片从obs服务器拉到就近的cdn服务器,比如我在武汉访问图片,可能就把图片拉到了华中区的服务器,缓存到华中区后,再把图片返回到客户端。


          第二步因为要回源拉取,所以第一次打开会慢点,第二次就直接就近到华中区去获取,就会快一些。这里有个地方要注意,虽然使用cdn后第一次拉取会稍慢,但是比不使用cdn还是快很多的,原因就是从obs源服务器把文件拉取到就近的cdn服务器应该是走的华为内网,所以速度相对来说还是要快一些,具体还要分析从源到cdn是否走了内网。

        3.2 解决第一次访问慢的问题

        防止第一次访问图片,数据回源,只需要在新增文件时,在新增接口添加缓存预热一下即可。具体的代码参考华为云提供的demo即可。

        ',13),r={href:"https://support.huaweicloud.com/usermanual-cdn/cdn_01_0113.html",target:"_blank",rel:"noopener noreferrer"},k={href:"https://support.huaweicloud.com/api-cdn/CreatePreheatingTasks.html",target:"_blank",rel:"noopener noreferrer"},m=e(`
        private void preHeatingFile(String fileName) {
         		ICredential auth = new GlobalCredentials()
         				.withAk(ossProperties.getAccessKey())
         				.withSk( ossProperties.getSecretKey());
        diff --git a/assets/CloudServiceTraining.html-C8NinniP.js b/assets/CloudServiceTraining.html-CsW1-SEa.js
        similarity index 99%
        rename from assets/CloudServiceTraining.html-C8NinniP.js
        rename to assets/CloudServiceTraining.html-CsW1-SEa.js
        index 7f3d9fb6b4..2b827b8aad 100644
        --- a/assets/CloudServiceTraining.html-C8NinniP.js
        +++ b/assets/CloudServiceTraining.html-CsW1-SEa.js
        @@ -1,4 +1,4 @@
        -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o,c as l,a as n,b as s,d as e,e as t}from"./app-CVMfKeWw.js";const i={},r=n("p",null,"[TOC]",-1),d=n("blockquote",null,[n("p",null,"问题:"),n("ol",null,[n("li",null,"一个域名能解析出多少个IP?"),n("li",null,"请求一个地址,加www和不加是否有区别?")])],-1),u=n("hr",null,null,-1),m=n("h1",{id:"一、dns",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#一、dns"},[n("span",null,"一、DNS")])],-1),b={href:"https://www.ruanyifeng.com/blog/2016/06/dns.html",target:"_blank",rel:"noopener noreferrer"},k=t('

        1.1 什么是DNS

        DNS (Domain Name System 的缩写)的作用非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。 举例来说,如果你要访问域名math.stackexchange.com,首先要通过DNS查出它的IP地址是151.101.129.69。

        1.1 A(Address)记录

        A (Address) 记录是用来指定主机名(或域名)对应的IP地址记录。用户可以将该域名下的网站服务器指向到自己的web server上。 同时也可以设置您域名的二级域名。一般情况下我们收的域名都指的是A记录,比如sonoscape.com,dccm.sonoscape.com,meet.sonoscape.com 都是A记录

        1.2 CNAME(Canonical Name)

        1.2.1 定义

        CName记录是Canonical Name的简称,通常称别名指向,CNAME记录可用于将一个域名别名为另一个规范名称的域名系统(DNS)资源记录。

        以下摘自国内某dns服务商文档

        如果需要将域名指向另一个域名,再由另一个域名提供 IP 地址,就需要添加 CNAME 记录,最常用到 CNAME 的场景包括做 CDN、做企业邮箱。

        1.2.2 参考博客

        ',9),v={href:"https://www.zhihu.com/question/22916306",target:"_blank",rel:"noopener noreferrer"},h={href:"https://blog.csdn.net/weixin_29898627/article/details/112337714",target:"_blank",rel:"noopener noreferrer"},g=t(`

        注意
        CNAME只是一个相对的叫法,CNAME相对A记录叫CNAME,但是它本身也是一条A记录

        1.2.2 场景

        • CDN加速
        • 为特定网络服务(例如电子邮件或 FTP)提供单独的主机名,并将该主机名指向根域
        • 许多托管服务在服务提供商的域(例如 company.hostname.com)上为每个客户提供一个子域,并使用 CNAME 指向客户的域(www.company.com)。
        • 在多个国家注册同一个域并将国家版本指向主“.com”域
        • 从同一组织拥有的多个网站指向一个主网站 -
        • 用于 SSL 证书申请时的域名验证,例如 _dnsauth.yryz.net CNAME mnwwgx3uijnhsvkyjezf6nlpkn4xotzrkjpto6tfgbbuu22g.dcv.httpsauto.com.

        1.3 NS(Name Server)记录

        NS(Name Server)记录是域名服务器记录,用来指定该域名由哪个DNS服务器来进行解析。比如指定sonoscape.com的子域名具体由哪个服务器进行解析(参考后续dig指令)

        ns记录查询使用指令:

        $ dig ns com
        +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o,c as l,a as n,b as s,d as e,e as t}from"./app-_Oi5YZFn.js";const i={},r=n("p",null,"[TOC]",-1),d=n("blockquote",null,[n("p",null,"问题:"),n("ol",null,[n("li",null,"一个域名能解析出多少个IP?"),n("li",null,"请求一个地址,加www和不加是否有区别?")])],-1),u=n("hr",null,null,-1),m=n("h1",{id:"一、dns",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#一、dns"},[n("span",null,"一、DNS")])],-1),b={href:"https://www.ruanyifeng.com/blog/2016/06/dns.html",target:"_blank",rel:"noopener noreferrer"},k=t('

        1.1 什么是DNS

        DNS (Domain Name System 的缩写)的作用非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。 举例来说,如果你要访问域名math.stackexchange.com,首先要通过DNS查出它的IP地址是151.101.129.69。

        1.1 A(Address)记录

        A (Address) 记录是用来指定主机名(或域名)对应的IP地址记录。用户可以将该域名下的网站服务器指向到自己的web server上。 同时也可以设置您域名的二级域名。一般情况下我们收的域名都指的是A记录,比如sonoscape.com,dccm.sonoscape.com,meet.sonoscape.com 都是A记录

        1.2 CNAME(Canonical Name)

        1.2.1 定义

        CName记录是Canonical Name的简称,通常称别名指向,CNAME记录可用于将一个域名别名为另一个规范名称的域名系统(DNS)资源记录。

        以下摘自国内某dns服务商文档

        如果需要将域名指向另一个域名,再由另一个域名提供 IP 地址,就需要添加 CNAME 记录,最常用到 CNAME 的场景包括做 CDN、做企业邮箱。

        1.2.2 参考博客

        ',9),v={href:"https://www.zhihu.com/question/22916306",target:"_blank",rel:"noopener noreferrer"},h={href:"https://blog.csdn.net/weixin_29898627/article/details/112337714",target:"_blank",rel:"noopener noreferrer"},g=t(`

        注意
        CNAME只是一个相对的叫法,CNAME相对A记录叫CNAME,但是它本身也是一条A记录

        1.2.2 场景

        • CDN加速
        • 为特定网络服务(例如电子邮件或 FTP)提供单独的主机名,并将该主机名指向根域
        • 许多托管服务在服务提供商的域(例如 company.hostname.com)上为每个客户提供一个子域,并使用 CNAME 指向客户的域(www.company.com)。
        • 在多个国家注册同一个域并将国家版本指向主“.com”域
        • 从同一组织拥有的多个网站指向一个主网站 -
        • 用于 SSL 证书申请时的域名验证,例如 _dnsauth.yryz.net CNAME mnwwgx3uijnhsvkyjezf6nlpkn4xotzrkjpto6tfgbbuu22g.dcv.httpsauto.com.

        1.3 NS(Name Server)记录

        NS(Name Server)记录是域名服务器记录,用来指定该域名由哪个DNS服务器来进行解析。比如指定sonoscape.com的子域名具体由哪个服务器进行解析(参考后续dig指令)

        ns记录查询使用指令:

        $ dig ns com
         $ dig ns sonoscape.com
         

        示例:

        $ dig ns sonoscape.com.
         
        diff --git a/assets/Collection.html-CeAHa-2T.js b/assets/Collection.html-D-nzOS-a.js
        similarity index 97%
        rename from assets/Collection.html-CeAHa-2T.js
        rename to assets/Collection.html-D-nzOS-a.js
        index edfd4ce945..859ba4a3e1 100644
        --- a/assets/Collection.html-CeAHa-2T.js
        +++ b/assets/Collection.html-D-nzOS-a.js
        @@ -1 +1 @@
        -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as a,a as e}from"./app-CVMfKeWw.js";const n={},i=e("p",null,"分析集合数据结构,HashMap、ArrayList、LinkedList扩容原理等",-1),c=e("h2",{id:"_1、collection",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、collection"},[e("span",null,"1、Collection")])],-1),l=[i,c];function r(s,p){return o(),a("div",null,l)}const d=t(n,[["render",r],["__file","Collection.html.vue"]]),_=JSON.parse('{"path":"/java/advance/Collection.html","title":"java集合","lang":"zh-CN","frontmatter":{"title":"java集合","date":"2019-03-28T00:00:00.000Z","publish":true,"keys":null,"category":["集合"],"tag":["集合"],"description":"分析集合数据结构,HashMap、ArrayList、LinkedList扩容原理等 1、Collection","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/advance/Collection.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"java集合"}],["meta",{"property":"og:description","content":"分析集合数据结构,HashMap、ArrayList、LinkedList扩容原理等 1、Collection"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-09-02T00:39:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"集合"}],["meta",{"property":"article:published_time","content":"2019-03-28T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-09-02T00:39:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"java集合\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2019-03-28T00:00:00.000Z\\",\\"dateModified\\":\\"2024-09-02T00:39:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、Collection","slug":"_1、collection","link":"#_1、collection","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1725237552000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.12,"words":36},"filePathRelative":"java/advance/Collection.md","localizedDate":"2019年3月28日","excerpt":"

        分析集合数据结构,HashMap、ArrayList、LinkedList扩容原理等

        \\n \\n

        1、Collection

        \\n","autoDesc":true}');export{d as comp,_ as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as a,a as e}from"./app-_Oi5YZFn.js";const n={},i=e("p",null,"分析集合数据结构,HashMap、ArrayList、LinkedList扩容原理等",-1),c=e("h2",{id:"_1、collection",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、collection"},[e("span",null,"1、Collection")])],-1),l=[i,c];function r(s,p){return o(),a("div",null,l)}const d=t(n,[["render",r],["__file","Collection.html.vue"]]),_=JSON.parse('{"path":"/java/advance/Collection.html","title":"java集合","lang":"zh-CN","frontmatter":{"title":"java集合","date":"2019-03-28T00:00:00.000Z","publish":true,"keys":null,"category":["集合"],"tag":["集合"],"description":"分析集合数据结构,HashMap、ArrayList、LinkedList扩容原理等 1、Collection","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/advance/Collection.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"java集合"}],["meta",{"property":"og:description","content":"分析集合数据结构,HashMap、ArrayList、LinkedList扩容原理等 1、Collection"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-09-02T00:39:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"集合"}],["meta",{"property":"article:published_time","content":"2019-03-28T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-09-02T00:39:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"java集合\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2019-03-28T00:00:00.000Z\\",\\"dateModified\\":\\"2024-09-02T00:39:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、Collection","slug":"_1、collection","link":"#_1、collection","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1725237552000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.12,"words":36},"filePathRelative":"java/advance/Collection.md","localizedDate":"2019年3月28日","excerpt":"

        分析集合数据结构,HashMap、ArrayList、LinkedList扩容原理等

        \\n \\n

        1、Collection

        \\n","autoDesc":true}');export{d as comp,_ as data}; diff --git a/assets/CollectionInject.html-Ds4tphSm.js b/assets/CollectionInject.html-Wk5anYr4.js similarity index 99% rename from assets/CollectionInject.html-Ds4tphSm.js rename to assets/CollectionInject.html-Wk5anYr4.js index cf69b12d60..b69332d692 100644 --- a/assets/CollectionInject.html-Ds4tphSm.js +++ b/assets/CollectionInject.html-Wk5anYr4.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},o=e(`

        1、测试

        加入有以下代码,~MyProcessor~是一个接口,没有提供任何实现,然后启动容器会发现执行Bean1的构造方法时并不会空指针,容器会自动提供一个Collection的实现类~LinkedHashMap$LinkedValues~,那么容器如何注入我自己的MyProcessor呢?

        @Component
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},o=e(`

        1、测试

        加入有以下代码,~MyProcessor~是一个接口,没有提供任何实现,然后启动容器会发现执行Bean1的构造方法时并不会空指针,容器会自动提供一个Collection的实现类~LinkedHashMap$LinkedValues~,那么容器如何注入我自己的MyProcessor呢?

        @Component
         public class Bean1 {
         
             public Bean1(Collection<MyProcessor> processors){
        diff --git a/assets/CommonUsedCMD.html-WpgS454f.js b/assets/CommonUsedCMD.html-CHwLkMH3.js
        similarity index 99%
        rename from assets/CommonUsedCMD.html-WpgS454f.js
        rename to assets/CommonUsedCMD.html-CHwLkMH3.js
        index 44f60578c9..3a84b6720a 100644
        --- a/assets/CommonUsedCMD.html-WpgS454f.js
        +++ b/assets/CommonUsedCMD.html-CHwLkMH3.js
        @@ -1,4 +1,4 @@
        -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as n,e as s}from"./app-CVMfKeWw.js";const t={},l=s(`

        1、查找多个文件中是否包含字符串

        grep -r targetString targetDirectory
        +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as n,e as s}from"./app-_Oi5YZFn.js";const t={},l=s(`

        1、查找多个文件中是否包含字符串

        grep -r targetString targetDirectory
         # -r 表示递归查询
         # targetString  表示目标字符串
         # targetDirectory 表示目录
        diff --git a/assets/CompileJdk11.html-DQ_9XJev.js b/assets/CompileJdk11.html-k8rk73iW.js
        similarity index 99%
        rename from assets/CompileJdk11.html-DQ_9XJev.js
        rename to assets/CompileJdk11.html-k8rk73iW.js
        index 44b9e8eda1..6f1fa8d39e 100644
        --- a/assets/CompileJdk11.html-DQ_9XJev.js
        +++ b/assets/CompileJdk11.html-k8rk73iW.js
        @@ -1,4 +1,4 @@
        -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as i,c as p,a as n,b as s,d as e,e as c}from"./app-CVMfKeWw.js";const l={},r=n("h2",{id:"_1-下载源码",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1-下载源码"},[n("span",null,"1 下载源码")])],-1),u={href:"https://github.com/openjdk?",target:"_blank",rel:"noopener noreferrer"},d=c(`

        20230113095229

        git clone https://github.com/openjdk/jdk11u.git
        +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as i,c as p,a as n,b as s,d as e,e as c}from"./app-_Oi5YZFn.js";const l={},r=n("h2",{id:"_1-下载源码",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1-下载源码"},[n("span",null,"1 下载源码")])],-1),u={href:"https://github.com/openjdk?",target:"_blank",rel:"noopener noreferrer"},d=c(`

        20230113095229

        git clone https://github.com/openjdk/jdk11u.git
         

        2 编译

        按照readme的文档进行编译

        20230113095821

        2.1 configure

        首先执行进入源码目录,执行bash configure此命令会检查编译需要的环境,如果报错,根据错误提示安装必要的编译工具。

        注意事项:

        1. 安装必要的环境,包括gcc、autoconf、boot JDK等
        2. 编译一个jdk是需要依赖一个现有的jdk,另外对版本有要求,比如你编译的版本是N,则需要你电脑上有一个版本至少为N-1的版本,这里我编译jdk11时,我电脑事先安装了jdk11,理论上至少需要一个jdk10+
        3. gcc版本不能太老,也不能太新,这里一定要看你当前版本对应的文档,每个jdk版本对gcc版本要求也不一样,新版本jdk肯定能兼容更新的gcc,我在编译jdk11时,因为我的manjro是滚动更新,gcc版本是12,结果太新了导致编译报错。
        bash configure
         #……省略前边若干日志
         #……
        diff --git a/assets/Concurrent.html-ClwuWlCD.js b/assets/Concurrent.html-DQRS-IpH.js
        similarity index 99%
        rename from assets/Concurrent.html-ClwuWlCD.js
        rename to assets/Concurrent.html-DQRS-IpH.js
        index 62dc2c4cdc..42899f8967 100644
        --- a/assets/Concurrent.html-ClwuWlCD.js
        +++ b/assets/Concurrent.html-DQRS-IpH.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},o=t(`

        1、背景

        因没做过大项目,并发经验匮乏,所以很多时候考虑不到并发问题,今天做接口的压力测试,无意间发现一个并发的问题,就是判断数据库是否存在某个记录时,如果没做并发处理,就会出现并发的问题,比如以下代码,给某个医院添加科室,当医院已经有这个科室则不允许重复添加重名的科室

        public void addHospitalDept(SysHospitalDept sysHospitalDept) {
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},o=t(`

        1、背景

        因没做过大项目,并发经验匮乏,所以很多时候考虑不到并发问题,今天做接口的压力测试,无意间发现一个并发的问题,就是判断数据库是否存在某个记录时,如果没做并发处理,就会出现并发的问题,比如以下代码,给某个医院添加科室,当医院已经有这个科室则不允许重复添加重名的科室

        public void addHospitalDept(SysHospitalDept sysHospitalDept) {
                 //根据医院id,科室名,校验科室唯一性
                 SysHospitalDept hospitalDept = this.queryByHospitalIdAndDeptName(sysHospitalDept.getHospitalId(), sysHospitalDept.getDeptName());
                 if (Objects.nonNull(hospitalDept)) {
        diff --git a/assets/ConstantPool.html-BlmpO7p_.js b/assets/ConstantPool.html-C5zSxd7l.js
        similarity index 99%
        rename from assets/ConstantPool.html-BlmpO7p_.js
        rename to assets/ConstantPool.html-C5zSxd7l.js
        index f262b0ecad..94502300db 100644
        --- a/assets/ConstantPool.html-BlmpO7p_.js
        +++ b/assets/ConstantPool.html-C5zSxd7l.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-CVMfKeWw.js";const t={},p=e(`

        1. Integer常量池默认的范围

        范围:[-128,127],Integer内部有个缓存池,最小值-128是固定的,最大的值127是可以调整的,看源码知道, 最大值是和integerCacheHighPropValue有关,这个值是可以通过~java.lang.Integer.IntegerCache.high~ 属性指定,实际测试~~~System.setProperty("java.lang.Integer.IntegerCache.high","300")~~~不生效, 因为他是jvm参数应该在启动时设置vm参数,-Djava.lang.Integer.IntegerCache.high=300 使用-XX:AutoBoxCacheMax=300也可以。

        private static class IntegerCache {
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

        1. Integer常量池默认的范围

        范围:[-128,127],Integer内部有个缓存池,最小值-128是固定的,最大的值127是可以调整的,看源码知道, 最大值是和integerCacheHighPropValue有关,这个值是可以通过~java.lang.Integer.IntegerCache.high~ 属性指定,实际测试~~~System.setProperty("java.lang.Integer.IntegerCache.high","300")~~~不生效, 因为他是jvm参数应该在启动时设置vm参数,-Djava.lang.Integer.IntegerCache.high=300 使用-XX:AutoBoxCacheMax=300也可以。

        private static class IntegerCache {
                 static final int low = -128;
                 static final int high;
                 static final Integer cache[];
        diff --git a/assets/Cookie.html-ffctXEz0.js b/assets/Cookie.html-BobniOCU.js
        similarity index 99%
        rename from assets/Cookie.html-ffctXEz0.js
        rename to assets/Cookie.html-BobniOCU.js
        index b713e5fbde..124fb75992 100644
        --- a/assets/Cookie.html-ffctXEz0.js
        +++ b/assets/Cookie.html-BobniOCU.js
        @@ -1 +1 @@
        -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c,e}from"./app-CVMfKeWw.js";const t={},n=e('

        Cookie的作用域domain

        一级域名:aaa.com 二级域名:bbb.aaa.com 三级域名:ccc.bbb.aaa.com

        如上例子: aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.bbb.aaa.com 的父域名; 反过来bbb.aaa.com 和 ccc.bbb.aaa.com 是 aaa.com 的子域名;ccc.bbb.aaa.com 是 bbb.aaa.com 的子域名

        划重点

        在当前域名下,只能设置当前域以及父域的cookie,不能设置子域下的cookie。例如我在浏览器访问后端服务的域名为bbb.aaa.com时,我在后端就只能把cookie的 域设置为当前域(缺省状态就是当前)或者设置为其父域名aaa.com,而不能设置为其子域名ccc.bbb.aaa.com,设置子域名前端SetCookie或有警告

        重点

        cookie挂载在某个域下,只有在此域名下或者此域名的子域下才能获取cookie。也就是说例如我当前在浏览器访问的域名为bbb.aaa.com,我只能看到当前域名下的cookie以及父域名aaa.com下的 cookie,而看不到子域名ccc.bbb.aaa.com下的cookie

        Cookie的path

        path和域差不多,默认情况下的path是/也就是域下所有路径都可以看到cookie,如果设置了path为/somepth则浏览器只有访问/somepath或者/somepath/***等这些地址才能看到

        实例

        域名可以看到当前域以及父域名下的cookie,看不到其子域名下的cookie

        20221027151137

        20221027151245

        设置了path,要同时满足url中有指定path才能看到

        20221027151447

        20221027151508

        浏览器请求时会自动携带其所有能看到的cookie发送到后端

        20221027153648

        20221027153801

        20221027153905

        20221027153954

        java中Session和Cookie交互

        以上图中可以看到,每次请求在请求头都会携带一个名字为JSESSIONID的COOKIE这个cookie的值是一个sessionId,也就是当前客户端和服务器交互的一个凭证, 客户端吧sessionid给了服务端,服务端就能找到对应session,有了session后,可以从session中获取到对应信息,比如用户信息。 20221027161215

        ',21),i=[n];function p(s,b){return o(),c("div",null,i)}const m=a(t,[["render",p],["__file","Cookie.html.vue"]]),r=JSON.parse('{"path":"/other/web/Cookie.html","title":"Cookie","lang":"zh-CN","frontmatter":{"title":"Cookie","date":"2022-10-25T16:57:01.000Z","author":"qianxun","tag":["必会"],"description":"Cookie的作用域domain 一级域名:aaa.com 二级域名:bbb.aaa.com 三级域名:ccc.bbb.aaa.com 如上例子: aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.bbb.aaa.com 的父域名; 反过来bbb.aaa.com 和 ccc.b...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/web/Cookie.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Cookie"}],["meta",{"property":"og:description","content":"Cookie的作用域domain 一级域名:aaa.com 二级域名:bbb.aaa.com 三级域名:ccc.bbb.aaa.com 如上例子: aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.bbb.aaa.com 的父域名; 反过来bbb.aaa.com 和 ccc.b..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221027151137.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"qianxun"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:published_time","content":"2022-10-25T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Cookie\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027151137.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027151245.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027151447.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027151508.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027153648.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027153801.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027153905.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027153954.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027161215.png\\"],\\"datePublished\\":\\"2022-10-25T16:57:01.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"qianxun\\"}]}"]]},"headers":[{"level":2,"title":"Cookie的作用域domain","slug":"cookie的作用域domain","link":"#cookie的作用域domain","children":[]},{"level":2,"title":"Cookie的path","slug":"cookie的path","link":"#cookie的path","children":[]},{"level":2,"title":"实例","slug":"实例","link":"#实例","children":[{"level":3,"title":"域名可以看到当前域以及父域名下的cookie,看不到其子域名下的cookie","slug":"域名可以看到当前域以及父域名下的cookie-看不到其子域名下的cookie","link":"#域名可以看到当前域以及父域名下的cookie-看不到其子域名下的cookie","children":[]},{"level":3,"title":"设置了path,要同时满足url中有指定path才能看到","slug":"设置了path-要同时满足url中有指定path才能看到","link":"#设置了path-要同时满足url中有指定path才能看到","children":[]}]},{"level":2,"title":"浏览器请求时会自动携带其所有能看到的cookie发送到后端","slug":"浏览器请求时会自动携带其所有能看到的cookie发送到后端","link":"#浏览器请求时会自动携带其所有能看到的cookie发送到后端","children":[]},{"level":2,"title":"java中Session和Cookie交互","slug":"java中session和cookie交互","link":"#java中session和cookie交互","children":[]}],"git":{"createdTime":1666854933000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":3},{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":1.81,"words":542},"filePathRelative":"other/web/Cookie.md","localizedDate":"2022年10月25日","excerpt":"

        Cookie的作用域domain

        \\n

        一级域名:aaa.com\\n二级域名:bbb.aaa.com\\n三级域名:ccc.bbb.aaa.com

        \\n

        如上例子:\\naaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.bbb.aaa.com 的父域名;\\n反过来bbb.aaa.com 和 ccc.bbb.aaa.com 是 aaa.com 的子域名;ccc.bbb.aaa.com 是 bbb.aaa.com 的子域名

        \\n
        \\n

        划重点

        \\n

        在当前域名下,只能设置当前域以及父域的cookie,不能设置子域下的cookie。例如我在浏览器访问后端服务的域名为bbb.aaa.com时,我在后端就只能把cookie的\\n域设置为当前域(缺省状态就是当前)或者设置为其父域名aaa.com,而不能设置为其子域名ccc.bbb.aaa.com,设置子域名前端SetCookie或有警告

        \\n
        ","autoDesc":true}');export{m as comp,r as data}; +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c,e}from"./app-_Oi5YZFn.js";const t={},n=e('

        Cookie的作用域domain

        一级域名:aaa.com 二级域名:bbb.aaa.com 三级域名:ccc.bbb.aaa.com

        如上例子: aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.bbb.aaa.com 的父域名; 反过来bbb.aaa.com 和 ccc.bbb.aaa.com 是 aaa.com 的子域名;ccc.bbb.aaa.com 是 bbb.aaa.com 的子域名

        划重点

        在当前域名下,只能设置当前域以及父域的cookie,不能设置子域下的cookie。例如我在浏览器访问后端服务的域名为bbb.aaa.com时,我在后端就只能把cookie的 域设置为当前域(缺省状态就是当前)或者设置为其父域名aaa.com,而不能设置为其子域名ccc.bbb.aaa.com,设置子域名前端SetCookie或有警告

        重点

        cookie挂载在某个域下,只有在此域名下或者此域名的子域下才能获取cookie。也就是说例如我当前在浏览器访问的域名为bbb.aaa.com,我只能看到当前域名下的cookie以及父域名aaa.com下的 cookie,而看不到子域名ccc.bbb.aaa.com下的cookie

        Cookie的path

        path和域差不多,默认情况下的path是/也就是域下所有路径都可以看到cookie,如果设置了path为/somepth则浏览器只有访问/somepath或者/somepath/***等这些地址才能看到

        实例

        域名可以看到当前域以及父域名下的cookie,看不到其子域名下的cookie

        20221027151137

        20221027151245

        设置了path,要同时满足url中有指定path才能看到

        20221027151447

        20221027151508

        浏览器请求时会自动携带其所有能看到的cookie发送到后端

        20221027153648

        20221027153801

        20221027153905

        20221027153954

        java中Session和Cookie交互

        以上图中可以看到,每次请求在请求头都会携带一个名字为JSESSIONID的COOKIE这个cookie的值是一个sessionId,也就是当前客户端和服务器交互的一个凭证, 客户端吧sessionid给了服务端,服务端就能找到对应session,有了session后,可以从session中获取到对应信息,比如用户信息。 20221027161215

        ',21),i=[n];function p(s,b){return o(),c("div",null,i)}const m=a(t,[["render",p],["__file","Cookie.html.vue"]]),r=JSON.parse('{"path":"/other/web/Cookie.html","title":"Cookie","lang":"zh-CN","frontmatter":{"title":"Cookie","date":"2022-10-25T16:57:01.000Z","author":"qianxun","tag":["必会"],"description":"Cookie的作用域domain 一级域名:aaa.com 二级域名:bbb.aaa.com 三级域名:ccc.bbb.aaa.com 如上例子: aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.bbb.aaa.com 的父域名; 反过来bbb.aaa.com 和 ccc.b...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/web/Cookie.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Cookie"}],["meta",{"property":"og:description","content":"Cookie的作用域domain 一级域名:aaa.com 二级域名:bbb.aaa.com 三级域名:ccc.bbb.aaa.com 如上例子: aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.bbb.aaa.com 的父域名; 反过来bbb.aaa.com 和 ccc.b..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221027151137.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"qianxun"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:published_time","content":"2022-10-25T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Cookie\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027151137.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027151245.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027151447.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027151508.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027153648.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027153801.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027153905.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027153954.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221027161215.png\\"],\\"datePublished\\":\\"2022-10-25T16:57:01.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"qianxun\\"}]}"]]},"headers":[{"level":2,"title":"Cookie的作用域domain","slug":"cookie的作用域domain","link":"#cookie的作用域domain","children":[]},{"level":2,"title":"Cookie的path","slug":"cookie的path","link":"#cookie的path","children":[]},{"level":2,"title":"实例","slug":"实例","link":"#实例","children":[{"level":3,"title":"域名可以看到当前域以及父域名下的cookie,看不到其子域名下的cookie","slug":"域名可以看到当前域以及父域名下的cookie-看不到其子域名下的cookie","link":"#域名可以看到当前域以及父域名下的cookie-看不到其子域名下的cookie","children":[]},{"level":3,"title":"设置了path,要同时满足url中有指定path才能看到","slug":"设置了path-要同时满足url中有指定path才能看到","link":"#设置了path-要同时满足url中有指定path才能看到","children":[]}]},{"level":2,"title":"浏览器请求时会自动携带其所有能看到的cookie发送到后端","slug":"浏览器请求时会自动携带其所有能看到的cookie发送到后端","link":"#浏览器请求时会自动携带其所有能看到的cookie发送到后端","children":[]},{"level":2,"title":"java中Session和Cookie交互","slug":"java中session和cookie交互","link":"#java中session和cookie交互","children":[]}],"git":{"createdTime":1666854933000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":3},{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":1.81,"words":542},"filePathRelative":"other/web/Cookie.md","localizedDate":"2022年10月25日","excerpt":"

        Cookie的作用域domain

        \\n

        一级域名:aaa.com\\n二级域名:bbb.aaa.com\\n三级域名:ccc.bbb.aaa.com

        \\n

        如上例子:\\naaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.bbb.aaa.com 的父域名;\\n反过来bbb.aaa.com 和 ccc.bbb.aaa.com 是 aaa.com 的子域名;ccc.bbb.aaa.com 是 bbb.aaa.com 的子域名

        \\n
        \\n

        划重点

        \\n

        在当前域名下,只能设置当前域以及父域的cookie,不能设置子域下的cookie。例如我在浏览器访问后端服务的域名为bbb.aaa.com时,我在后端就只能把cookie的\\n域设置为当前域(缺省状态就是当前)或者设置为其父域名aaa.com,而不能设置为其子域名ccc.bbb.aaa.com,设置子域名前端SetCookie或有警告

        \\n
        ","autoDesc":true}');export{m as comp,r as data}; diff --git a/assets/Curl.html-DlrJSikw.js b/assets/Curl.html-BO77gaot.js similarity index 99% rename from assets/Curl.html-DlrJSikw.js rename to assets/Curl.html-BO77gaot.js index 9579c0fb35..441d483ac2 100644 --- a/assets/Curl.html-DlrJSikw.js +++ b/assets/Curl.html-BO77gaot.js @@ -1,4 +1,4 @@ -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as l,c as r,a as e,b as s,d as t,e as o}from"./app-CVMfKeWw.js";const c={},d=e("h2",{id:"_1、使用curl分析接口请求耗时",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、使用curl分析接口请求耗时"},[e("span",null,"1、使用CURL分析接口请求耗时")])],-1),u={href:"https://cizixs.com/2017/04/11/use-curl-to-analyze-request/",target:"_blank",rel:"noopener noreferrer"},p={href:"https://blog.josephscott.org/2011/10/14/timing-details-with-curl/",target:"_blank",rel:"noopener noreferrer"},m=o(`

        在服务器上发送请求一般用四字命令curl,本博客记录一下如何用curl测试接口耗时

        1.1 构造curl命令

        浏览器提供了快速构建各种curl请求的方式,直接复制,有需要再编辑即可 构造curl

        1.2、分析耗时

        使用curl的-w选项,其手册如下:

        -w, --write-out <format>
        +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as l,c as r,a as e,b as s,d as t,e as o}from"./app-_Oi5YZFn.js";const c={},d=e("h2",{id:"_1、使用curl分析接口请求耗时",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、使用curl分析接口请求耗时"},[e("span",null,"1、使用CURL分析接口请求耗时")])],-1),u={href:"https://cizixs.com/2017/04/11/use-curl-to-analyze-request/",target:"_blank",rel:"noopener noreferrer"},p={href:"https://blog.josephscott.org/2011/10/14/timing-details-with-curl/",target:"_blank",rel:"noopener noreferrer"},m=o(`

        在服务器上发送请求一般用四字命令curl,本博客记录一下如何用curl测试接口耗时

        1.1 构造curl命令

        浏览器提供了快速构建各种curl请求的方式,直接复制,有需要再编辑即可 构造curl

        1.2、分析耗时

        使用curl的-w选项,其手册如下:

        -w, --write-out <format>
                       Defines  what  to  display  on  stdout after a completed and successful operation. The format is a string that may contain plain text mixed with any number of variables. The string can be
                       specified as "string", to get read from a particular file you specify it "@filename" and to tell curl to read the format from stdin you write "@-".
         
        diff --git a/assets/CustomAuthenticationProvider.html-D7qaxkkr.js b/assets/CustomAuthenticationProvider.html-C05ZFsDP.js
        similarity index 99%
        rename from assets/CustomAuthenticationProvider.html-D7qaxkkr.js
        rename to assets/CustomAuthenticationProvider.html-C05ZFsDP.js
        index 22ee40f587..4ba8188007 100644
        --- a/assets/CustomAuthenticationProvider.html-D7qaxkkr.js
        +++ b/assets/CustomAuthenticationProvider.html-C05ZFsDP.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

        1、需求

        前后分离项目使用不同登录方式进行登录
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

        1、需求

        前后分离项目使用不同登录方式进行登录
             1. 使用帐号/密码登录
             2. 使用手机号/验证码登录
         

        2、实现方法

        Security是一个扩展性很强的框架,预留了各种端点进行扩展,多种方式登录需要扩展AuthenticationProvider,进行自定义实现。默认情况 Security使用的是DAOAuthenticationProvider,就是从数据库中读取用户名/密码进行校验。

        2.1 自定义AuthenticationProvider

        思考

        自定义了AuthenticationProvider后为什么连AuthenticationToken也要自定义?
        diff --git a/assets/CustomLRU.html-CX9zw1r2.js b/assets/CustomLRU.html-DHBi8rnr.js
        similarity index 99%
        rename from assets/CustomLRU.html-CX9zw1r2.js
        rename to assets/CustomLRU.html-DHBi8rnr.js
        index 8240d87d2a..79a694c068 100644
        --- a/assets/CustomLRU.html-CX9zw1r2.js
        +++ b/assets/CustomLRU.html-DHBi8rnr.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

        LRU介绍

        lru(latest recently used)最近最少使用,在缓存中可以使用LRU算法移除最近最少使用的

        自定义lru算法

        在java中LinkedHashMap已经实现了LRU算法,在使用时只需要继承此类,然后重写removeEldestEntry方法即可

        public class MyLRU<K, V> extends LinkedHashMap<K, V> {
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

        LRU介绍

        lru(latest recently used)最近最少使用,在缓存中可以使用LRU算法移除最近最少使用的

        自定义lru算法

        在java中LinkedHashMap已经实现了LRU算法,在使用时只需要继承此类,然后重写removeEldestEntry方法即可

        public class MyLRU<K, V> extends LinkedHashMap<K, V> {
         
             private int cacheCount;
         
        diff --git a/assets/CustomLoginPage.html-DheOzz7K.js b/assets/CustomLoginPage.html-R1ammlY9.js
        similarity index 99%
        rename from assets/CustomLoginPage.html-DheOzz7K.js
        rename to assets/CustomLoginPage.html-R1ammlY9.js
        index 5539f087fb..f77ec25c6d 100644
        --- a/assets/CustomLoginPage.html-DheOzz7K.js
        +++ b/assets/CustomLoginPage.html-R1ammlY9.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-CVMfKeWw.js";const p={},o=t(`

        注意

        本篇博客是用来配置前后不分离的项目,正常情况下现在的项目都是前后分离了,因此本篇内容 并没有太多学习价值,但是网上大多数教程都特别喜欢讲这一部分内容,就我目前了解到的内容, 在搭建oauth2授权服务器可能会用到,因为授权服务器需要一个登录页面,这个页面可以单独放到后端,仅仅做个登录没有必要开一个前端项目.

        1、修改自定义的登陆页面以及登陆请求校验

        官方文档

        https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html

        1.1 Secutity配置

        在Security配置文件配置loginPage指定登陆的页面,loginProcessingUrl指定用户名密码认证处理地址,同时一定要放行这两个页面,否则会一直被拦截导致重定向到登陆页面。 另外,还要准备自定义页面,以及自定义处理接口。

        注意

        一般情况,无需自定义登陆处理逻辑,只需要修改登陆页面,在登陆页面把action保留原来的login即可

        protected void configure(HttpSecurity http) {
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-_Oi5YZFn.js";const p={},o=t(`

        注意

        本篇博客是用来配置前后不分离的项目,正常情况下现在的项目都是前后分离了,因此本篇内容 并没有太多学习价值,但是网上大多数教程都特别喜欢讲这一部分内容,就我目前了解到的内容, 在搭建oauth2授权服务器可能会用到,因为授权服务器需要一个登录页面,这个页面可以单独放到后端,仅仅做个登录没有必要开一个前端项目.

        1、修改自定义的登陆页面以及登陆请求校验

        官方文档

        https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html

        1.1 Secutity配置

        在Security配置文件配置loginPage指定登陆的页面,loginProcessingUrl指定用户名密码认证处理地址,同时一定要放行这两个页面,否则会一直被拦截导致重定向到登陆页面。 另外,还要准备自定义页面,以及自定义处理接口。

        注意

        一般情况,无需自定义登陆处理逻辑,只需要修改登陆页面,在登陆页面把action保留原来的login即可

        protected void configure(HttpSecurity http) {
             http.formLogin().loginPage("/token/login").loginProcessingUrl("/token/custom")
                     .successHandler(tenantSavedRequestAwareAuthenticationSuccessHandler())
                     .failureHandler(authenticationFailureHandler()).and().logout()
        diff --git a/assets/CustomTokenAuthentication.html-BAwukKcd.js b/assets/CustomTokenAuthentication.html-DcEKKC-z.js
        similarity index 99%
        rename from assets/CustomTokenAuthentication.html-BAwukKcd.js
        rename to assets/CustomTokenAuthentication.html-DcEKKC-z.js
        index a98fce08e8..a3cacce3c3 100644
        --- a/assets/CustomTokenAuthentication.html-BAwukKcd.js
        +++ b/assets/CustomTokenAuthentication.html-DcEKKC-z.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

        注意

        网上很难精准找到一个前后端分离项目自定义Token认证的教程,找了很久终于找到,特此记录

        1、Security配置注意事项

        1. 我在很多教程中都看到他们有讲解自定义登录页面,但是我想说的是,都21世纪了,早都前后分离了,
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

        注意

        网上很难精准找到一个前后端分离项目自定义Token认证的教程,找了很久终于找到,特此记录

        1、Security配置注意事项

        1. 我在很多教程中都看到他们有讲解自定义登录页面,但是我想说的是,都21世纪了,早都前后分离了,
            所以在Security配置中不要配置formLogin了,前后分离项目会直接在Controller自定义登录逻辑,
            一旦配置这个万一Security会自动生成表单登录那几个过滤器。这里重点强调一下,前后分离项目和前
            后不分离在Security中是大大的不一样!!大家学习的时候关键词检索要记得加上前后分离!!
        diff --git a/assets/DelegatingFilterProxy.html-BfP7rcad.js b/assets/DelegatingFilterProxy.html-CK8Yiklp.js
        similarity index 99%
        rename from assets/DelegatingFilterProxy.html-BfP7rcad.js
        rename to assets/DelegatingFilterProxy.html-CK8Yiklp.js
        index 3d4c87536d..c19ec740b2 100644
        --- a/assets/DelegatingFilterProxy.html-BfP7rcad.js
        +++ b/assets/DelegatingFilterProxy.html-CK8Yiklp.js
        @@ -1 +1 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as i,e as r}from"./app-CVMfKeWw.js";const n={},l=r('

        作用

        在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。

        具体解析:

        1. ContextLoaderListener:
        • ContextLoaderListener 是 Spring 中常见的监听器,负责在应用启动时加载 Spring 的 ApplicationContext,从而初始化应用中的所有 Spring Bean。这通常是在应用启动的较晚阶段完成的。
        • ContextLoaderListener 会监听 Web 应用的启动事件,并在合适的时机加载和管理 Spring 上下文。
        1. Servlet 过滤器的加载顺序:
        • 在典型的 Servlet 容器(如 Tomcat)中,过滤器的实例化和注册会在应用启动的早期阶段进行,即在应用的主业务逻辑启动之前。
        • 这些过滤器的初始化是 Servlet 容器的一部分,不依赖于 Spring 的 ApplicationContext。换句话说,过滤器的生命周期由 Servlet 容器本身管理,而不受 Spring 管理的影响。
        1. 问题所在:
        • 因为过滤器(Filter)的实例需要在 Spring 上下文加载之前注册,而 Spring 的 Bean 管理是由 ContextLoaderListener 在较后阶段完成的,这就导致了过滤器实例在创建时无法直接依赖于 Spring 的 Bean 或上下文。如果直接在 web.xml 中定义过滤器,过滤器实例会在 Spring 上下文加载完成之前被创建。

        解决方案——DelegatingFilterProxy:

        • DelegatingFilterProxy 可以解决这个问题。它允许 Servlet 容器在初始化过滤器时,并不直接创建过滤器的实际实例,而是通过代理的方式,将过滤器的执行逻辑委托给 Spring 上下文中的某个 Bean。这样,过滤器的真正逻辑就可以依赖于 Spring 容器管理的 Bean。
        • 具体来说,DelegatingFilterProxy 本身是一个标准的 Servlet 过滤器,在容器启动时被注册。但是它在处理过滤时,会去查找 Spring 上下文中已经定义好的过滤器 Bean(例如 Spring Security 的 FilterChainProxy),并将请求委托给这个 Spring 管理的过滤器 Bean。

        总结:

        • 问题:在传统的 Servlet 应用中,过滤器的初始化顺序早于 Spring 上下文的加载,这导致无法将 Spring 的依赖注入直接应用于过滤器。
        • 解决:通过使用 DelegatingFilterProxy,可以将过滤器的创建延迟到 Spring 上下文加载完成之后,由 Spring 容器管理过滤器实例的生命周期,使得过滤器可以正常依赖 Spring Bean。 因此,DelegatingFilterProxy 是一个桥梁,它确保即使 Servlet 容器在 Spring 上下文加载之前注册过滤器,过滤器的实际执行逻辑依然能够使用 Spring 管理的 Bean 和资源。
        ',13),a=[l];function o(p,g){return t(),i("div",null,a)}const S=e(n,[["render",o],["__file","DelegatingFilterProxy.html.vue"]]),d=JSON.parse('{"path":"/java/framework/security/DelegatingFilterProxy.html","title":"DelegatingFilterProxy介绍","lang":"zh-CN","frontmatter":{"title":"DelegatingFilterProxy介绍","date":"2024-09-20T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"作用 在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。 具体解析: Conte...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/DelegatingFilterProxy.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"DelegatingFilterProxy介绍"}],["meta",{"property":"og:description","content":"作用 在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。 具体解析: Conte..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-09-29T09:49:47.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-09-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-09-29T09:49:47.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"DelegatingFilterProxy介绍\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-09-20T00:00:00.000Z\\",\\"dateModified\\":\\"2024-09-29T09:49:47.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":3,"title":"作用","slug":"作用","link":"#作用","children":[]}],"git":{"createdTime":1727603387000,"updatedTime":1727603387000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":2.4,"words":721},"filePathRelative":"java/framework/security/DelegatingFilterProxy.md","localizedDate":"2024年9月20日","excerpt":"

        作用

        \\n

        在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。

        \\n

        具体解析:

        \\n
          \\n
        1. ContextLoaderListener:
        2. \\n
        \\n
          \\n
        • ContextLoaderListener 是 Spring 中常见的监听器,负责在应用启动时加载 Spring 的 ApplicationContext,从而初始化应用中的所有 Spring Bean。这通常是在应用启动的较晚阶段完成的。
        • \\n
        • ContextLoaderListener 会监听 Web 应用的启动事件,并在合适的时机加载和管理 Spring 上下文。
        • \\n
        ","autoDesc":true}');export{S as comp,d as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as i,e as r}from"./app-_Oi5YZFn.js";const n={},l=r('

        作用

        在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。

        具体解析:

        1. ContextLoaderListener:
        • ContextLoaderListener 是 Spring 中常见的监听器,负责在应用启动时加载 Spring 的 ApplicationContext,从而初始化应用中的所有 Spring Bean。这通常是在应用启动的较晚阶段完成的。
        • ContextLoaderListener 会监听 Web 应用的启动事件,并在合适的时机加载和管理 Spring 上下文。
        1. Servlet 过滤器的加载顺序:
        • 在典型的 Servlet 容器(如 Tomcat)中,过滤器的实例化和注册会在应用启动的早期阶段进行,即在应用的主业务逻辑启动之前。
        • 这些过滤器的初始化是 Servlet 容器的一部分,不依赖于 Spring 的 ApplicationContext。换句话说,过滤器的生命周期由 Servlet 容器本身管理,而不受 Spring 管理的影响。
        1. 问题所在:
        • 因为过滤器(Filter)的实例需要在 Spring 上下文加载之前注册,而 Spring 的 Bean 管理是由 ContextLoaderListener 在较后阶段完成的,这就导致了过滤器实例在创建时无法直接依赖于 Spring 的 Bean 或上下文。如果直接在 web.xml 中定义过滤器,过滤器实例会在 Spring 上下文加载完成之前被创建。

        解决方案——DelegatingFilterProxy:

        • DelegatingFilterProxy 可以解决这个问题。它允许 Servlet 容器在初始化过滤器时,并不直接创建过滤器的实际实例,而是通过代理的方式,将过滤器的执行逻辑委托给 Spring 上下文中的某个 Bean。这样,过滤器的真正逻辑就可以依赖于 Spring 容器管理的 Bean。
        • 具体来说,DelegatingFilterProxy 本身是一个标准的 Servlet 过滤器,在容器启动时被注册。但是它在处理过滤时,会去查找 Spring 上下文中已经定义好的过滤器 Bean(例如 Spring Security 的 FilterChainProxy),并将请求委托给这个 Spring 管理的过滤器 Bean。

        总结:

        • 问题:在传统的 Servlet 应用中,过滤器的初始化顺序早于 Spring 上下文的加载,这导致无法将 Spring 的依赖注入直接应用于过滤器。
        • 解决:通过使用 DelegatingFilterProxy,可以将过滤器的创建延迟到 Spring 上下文加载完成之后,由 Spring 容器管理过滤器实例的生命周期,使得过滤器可以正常依赖 Spring Bean。 因此,DelegatingFilterProxy 是一个桥梁,它确保即使 Servlet 容器在 Spring 上下文加载之前注册过滤器,过滤器的实际执行逻辑依然能够使用 Spring 管理的 Bean 和资源。
        ',13),a=[l];function o(p,g){return t(),i("div",null,a)}const S=e(n,[["render",o],["__file","DelegatingFilterProxy.html.vue"]]),d=JSON.parse('{"path":"/java/framework/security/DelegatingFilterProxy.html","title":"DelegatingFilterProxy介绍","lang":"zh-CN","frontmatter":{"title":"DelegatingFilterProxy介绍","date":"2024-09-20T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"作用 在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。 具体解析: Conte...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/DelegatingFilterProxy.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"DelegatingFilterProxy介绍"}],["meta",{"property":"og:description","content":"作用 在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。 具体解析: Conte..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-09-29T09:49:47.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-09-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-09-29T09:49:47.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"DelegatingFilterProxy介绍\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-09-20T00:00:00.000Z\\",\\"dateModified\\":\\"2024-09-29T09:49:47.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":3,"title":"作用","slug":"作用","link":"#作用","children":[]}],"git":{"createdTime":1727603387000,"updatedTime":1727603387000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":2.4,"words":721},"filePathRelative":"java/framework/security/DelegatingFilterProxy.md","localizedDate":"2024年9月20日","excerpt":"

        作用

        \\n

        在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。

        \\n

        具体解析:

        \\n
          \\n
        1. ContextLoaderListener:
        2. \\n
        \\n
          \\n
        • ContextLoaderListener 是 Spring 中常见的监听器,负责在应用启动时加载 Spring 的 ApplicationContext,从而初始化应用中的所有 Spring Bean。这通常是在应用启动的较晚阶段完成的。
        • \\n
        • ContextLoaderListener 会监听 Web 应用的启动事件,并在合适的时机加载和管理 Spring 上下文。
        • \\n
        ","autoDesc":true}');export{S as comp,d as data}; diff --git a/assets/DeployGithubPage.html-2HbSyCiP.js b/assets/DeployGithubPage.html-COQNI509.js similarity index 99% rename from assets/DeployGithubPage.html-2HbSyCiP.js rename to assets/DeployGithubPage.html-COQNI509.js index c8fdfaec97..bd81f15259 100644 --- a/assets/DeployGithubPage.html-2HbSyCiP.js +++ b/assets/DeployGithubPage.html-COQNI509.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-CVMfKeWw.js";const t={},i=e(`

        1、 GitHubPage介绍

        1.1 ok

        1.2 搭建个人githubpage

        个人page和项目page的区别就是个人page只有一个,所谓的个人Page说白了也是一个特殊的项目Page,无非就是它的仓库名字比较特殊,必须为<username>.github.io,比如java框架\`spring-cloud.github.io\`、\`facebook.github.io\`,注意个人page的仓库名一定要加上 \`.github.io\`才算个人Page,不加的话就是一个普通项目了。
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-_Oi5YZFn.js";const t={},i=e(`

        1、 GitHubPage介绍

        1.1 ok

        1.2 搭建个人githubpage

        个人page和项目page的区别就是个人page只有一个,所谓的个人Page说白了也是一个特殊的项目Page,无非就是它的仓库名字比较特殊,必须为<username>.github.io,比如java框架\`spring-cloud.github.io\`、\`facebook.github.io\`,注意个人page的仓库名一定要加上 \`.github.io\`才算个人Page,不加的话就是一个普通项目了。
         个人page有啥特殊之处呢?
         在访问页面时可以直接使用https://<username>.github.io,不用加仓库名,普通的项目page,访问时需要加仓库名,比如https://<username>.github.io/<reponame>
         
        diff --git a/assets/DesignPatternInSpring.html-Cz-ZqYXH.js b/assets/DesignPatternInSpring.html-B1tCH6O1.js
        similarity index 99%
        rename from assets/DesignPatternInSpring.html-Cz-ZqYXH.js
        rename to assets/DesignPatternInSpring.html-B1tCH6O1.js
        index 41542402a7..16ea669cbc 100644
        --- a/assets/DesignPatternInSpring.html-Cz-ZqYXH.js
        +++ b/assets/DesignPatternInSpring.html-B1tCH6O1.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

        1、工厂模式

        BeanFactory是典型的工厂方法模式,其有多个实现,不同的实现有不同的getBean方法,默认实现是DefaultListalbeBeanFactory,我们也可以定义自己的工厂实现BeanFactory接口,重写里面的getBean方法

        2、单例模式

        在spring中使用singleton修饰的bean都是单例模式,org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton,在spring实例化出真正的对象后,会把这个对象加到容器中

        修正,以上说singleton=单例模式,是错误的,singleton指的是容器中该对象的bean只有一个,和单例模式不是一回事,单例模式有:Mybatis的连接工厂、redis的连接工厂等

        
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

        1、工厂模式

        BeanFactory是典型的工厂方法模式,其有多个实现,不同的实现有不同的getBean方法,默认实现是DefaultListalbeBeanFactory,我们也可以定义自己的工厂实现BeanFactory接口,重写里面的getBean方法

        2、单例模式

        在spring中使用singleton修饰的bean都是单例模式,org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton,在spring实例化出真正的对象后,会把这个对象加到容器中

        修正,以上说singleton=单例模式,是错误的,singleton指的是容器中该对象的bean只有一个,和单例模式不是一回事,单例模式有:Mybatis的连接工厂、redis的连接工厂等

        
         /**
         * Add the given singleton object to the singleton cache of this factory.
         * <p>To be called for eager registration of singletons.
        diff --git a/assets/DistributeLock.html-PlGmUQ21.js b/assets/DistributeLock.html-C1ihBiBe.js
        similarity index 99%
        rename from assets/DistributeLock.html-PlGmUQ21.js
        rename to assets/DistributeLock.html-C1ihBiBe.js
        index 36105439a2..49d57369a4 100644
        --- a/assets/DistributeLock.html-PlGmUQ21.js
        +++ b/assets/DistributeLock.html-C1ihBiBe.js
        @@ -1,4 +1,4 @@
        -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o,c as l,a as e,b as a,d as n,e as c}from"./app-CVMfKeWw.js";const r={},p=c(`

        1、写在前面

        以前一直没搞清楚分布式锁和分布式事务,对其概念以及使用场景很模糊,今天查查资料,好好总结一下分布式事务和分布式锁。另外提前说一句,使用redis来解决分布式、高并发问题存在一些困难,Redis 分布式锁只能作为一种缓解并发的手段,如果要完全解决并发问题,仍需要数据库的防并发手段。

        2、锁和分布式锁解决了什么问题?

        单机锁:解决单进程中多线程同时操作共有数据(比如java堆数据)带来的安全问题,强调的是单机服务中线程安全问题,这种场景很常见,比如单机多线程售票的例子。这种锁直接通过java的本地锁实现即可,可以使用java自带的synchroized和Lock

        分布式锁:解决集群服务中,多个相同服务操作同一个资源数据的安全问题。比如淘宝双十一抢购,为了支持高并发,下订单的服务肯定是集群模式而非单机,比如有一个商品促销,数量共有1000个,下订单的集群服务是10个,如果抢购开始,这10个集群服务应该是共同拥有这1000个商品,应当避免在并发情况下销售的总量超过1000的情况,这种就是典型的分布式锁需要处理的问题。

        3、分布式锁特性

        • 互斥性: 同一时刻只能有一个服务(进程)持有锁
        • 可重入性: 同一服务节点上的同一个线程如果获取了锁之后能够再次获取锁
        • 锁超时:和J.U.C中的锁一样支持锁超时,防止死锁
        • 高性能和高可用: 加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效
        • 具备阻塞和非阻塞性:能够及时从阻塞状态中被唤醒

        4、分布式锁的实现

        4.1 redis实现

        实现思路:利用redis处理网络请求是单线程(其他模块是多线程),并且其操作具有原子性。

        4.1.1 单机redis实现(采用逐步升级的方式来进行分析)
        1. 采用redis 的SETNX(SET IF NOT EXIST),key和value我们可以随意指定,若key不存在此操作成功返回1,如果key已存在则set失败返回0。正常情况第一个抢占锁的服务设置肯定是返回1的,如果此时其他服务(JVM进程)再次来set相同的key就返回0,代表抢锁失败。

          SETNX lock_source_key lock_value # 加锁
          +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o,c as l,a as e,b as a,d as n,e as c}from"./app-_Oi5YZFn.js";const r={},p=c(`

          1、写在前面

          以前一直没搞清楚分布式锁和分布式事务,对其概念以及使用场景很模糊,今天查查资料,好好总结一下分布式事务和分布式锁。另外提前说一句,使用redis来解决分布式、高并发问题存在一些困难,Redis 分布式锁只能作为一种缓解并发的手段,如果要完全解决并发问题,仍需要数据库的防并发手段。

          2、锁和分布式锁解决了什么问题?

          单机锁:解决单进程中多线程同时操作共有数据(比如java堆数据)带来的安全问题,强调的是单机服务中线程安全问题,这种场景很常见,比如单机多线程售票的例子。这种锁直接通过java的本地锁实现即可,可以使用java自带的synchroized和Lock

          分布式锁:解决集群服务中,多个相同服务操作同一个资源数据的安全问题。比如淘宝双十一抢购,为了支持高并发,下订单的服务肯定是集群模式而非单机,比如有一个商品促销,数量共有1000个,下订单的集群服务是10个,如果抢购开始,这10个集群服务应该是共同拥有这1000个商品,应当避免在并发情况下销售的总量超过1000的情况,这种就是典型的分布式锁需要处理的问题。

          3、分布式锁特性

          • 互斥性: 同一时刻只能有一个服务(进程)持有锁
          • 可重入性: 同一服务节点上的同一个线程如果获取了锁之后能够再次获取锁
          • 锁超时:和J.U.C中的锁一样支持锁超时,防止死锁
          • 高性能和高可用: 加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效
          • 具备阻塞和非阻塞性:能够及时从阻塞状态中被唤醒

          4、分布式锁的实现

          4.1 redis实现

          实现思路:利用redis处理网络请求是单线程(其他模块是多线程),并且其操作具有原子性。

          4.1.1 单机redis实现(采用逐步升级的方式来进行分析)
          1. 采用redis 的SETNX(SET IF NOT EXIST),key和value我们可以随意指定,若key不存在此操作成功返回1,如果key已存在则set失败返回0。正常情况第一个抢占锁的服务设置肯定是返回1的,如果此时其他服务(JVM进程)再次来set相同的key就返回0,代表抢锁失败。

            SETNX lock_source_key lock_value # 加锁
             do something #获取锁以后的业务处理代码
             DEL lock_source_key #业务处理完以后释放锁
             

          如果仅仅是设置key,是存在问题的,比如第一个服务抢到了锁,但是服务挂掉、或者带宽堵塞、GC等种种原因,导致其迟迟不能释放锁,那么其他服务就一直没有机会抢到锁,这样肯定是不合理的。

          1. 在1的基础上升级一下,给key加一个过期时间,如果出现意外情况,key到期以后可以自动释放

            SETNX lock_source_key lock_value # 加锁
            diff --git a/assets/Docker.html-BaiyWfkU.js b/assets/Docker.html-DzlBwcZ1.js
            similarity index 97%
            rename from assets/Docker.html-BaiyWfkU.js
            rename to assets/Docker.html-DzlBwcZ1.js
            index 5d60381d23..4b4d358352 100644
            --- a/assets/Docker.html-BaiyWfkU.js
            +++ b/assets/Docker.html-DzlBwcZ1.js
            @@ -1 +1 @@
            -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as r,a as e}from"./app-CVMfKeWw.js";const n={},c=e("h2",{id:"_1-命令速查",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-命令速查"},[e("span",null,"1 命令速查")])],-1),a=[c];function i(p,l){return o(),r("div",null,a)}const s=t(n,[["render",i],["__file","Docker.html.vue"]]),d=JSON.parse('{"path":"/other/docker/Docker.html","title":"Docker常用命令","lang":"zh-CN","frontmatter":{"title":"Docker常用命令","date":"2022-10-10T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["docker"],"description":"1 命令速查","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/docker/Docker.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Docker常用命令"}],["meta",{"property":"og:description","content":"1 命令速查"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-11-28T06:54:04.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-10-10T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-11-28T06:54:04.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Docker常用命令\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-10-10T00:00:00.000Z\\",\\"dateModified\\":\\"2022-11-28T06:54:04.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1 命令速查","slug":"_1-命令速查","link":"#_1-命令速查","children":[]}],"git":{"createdTime":1669618444000,"updatedTime":1669618444000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.07,"words":22},"filePathRelative":"other/docker/Docker.md","localizedDate":"2022年10月10日","excerpt":"

            1 命令速查

            \\n","autoDesc":true}');export{s as comp,d as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as r,a as e}from"./app-_Oi5YZFn.js";const n={},c=e("h2",{id:"_1-命令速查",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-命令速查"},[e("span",null,"1 命令速查")])],-1),a=[c];function i(p,l){return o(),r("div",null,a)}const s=t(n,[["render",i],["__file","Docker.html.vue"]]),d=JSON.parse('{"path":"/other/docker/Docker.html","title":"Docker常用命令","lang":"zh-CN","frontmatter":{"title":"Docker常用命令","date":"2022-10-10T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["docker"],"description":"1 命令速查","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/docker/Docker.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Docker常用命令"}],["meta",{"property":"og:description","content":"1 命令速查"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-11-28T06:54:04.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-10-10T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-11-28T06:54:04.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Docker常用命令\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-10-10T00:00:00.000Z\\",\\"dateModified\\":\\"2022-11-28T06:54:04.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1 命令速查","slug":"_1-命令速查","link":"#_1-命令速查","children":[]}],"git":{"createdTime":1669618444000,"updatedTime":1669618444000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.07,"words":22},"filePathRelative":"other/docker/Docker.md","localizedDate":"2022年10月10日","excerpt":"

            1 命令速查

            \\n","autoDesc":true}');export{s as comp,d as data}; diff --git a/assets/Future.html-C-IGHrDE.js b/assets/Future.html-CI2mkHT2.js similarity index 98% rename from assets/Future.html-C-IGHrDE.js rename to assets/Future.html-CI2mkHT2.js index 744130521b..fffb42b5a6 100644 --- a/assets/Future.html-C-IGHrDE.js +++ b/assets/Future.html-CI2mkHT2.js @@ -1 +1 @@ -import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e,b as t}from"./app-CVMfKeWw.js";const a={},c=e("h2",{id:"_1、future的作用",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、future的作用"},[e("span",null,"1、Future的作用")])],-1),u=e("p",null,[t("Future可以用来获取一个异步执行的结果,可以使用"),e("code",null,"isDone"),t("方法检查异步任务是否完成,或者使用"),e("code",null,"get"),t("阻塞住调用线程,直到计算完成返回结果,你也可以使用"),e("code",null,"cancel"),t("方法停止任务的执行。")],-1),i=[c,u];function l(p,d){return o(),n("div",null,i)}const h=r(a,[["render",l],["__file","Future.html.vue"]]),_=JSON.parse('{"path":"/java/advance/Future.html","title":"多线程中的Future","lang":"zh-CN","frontmatter":{"title":"多线程中的Future","date":"2022-04-06T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["线程池","多线程"],"tag":["多线程","线程池"],"description":"1、Future的作用 Future可以用来获取一个异步执行的结果,可以使用isDone方法检查异步任务是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/advance/Future.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"多线程中的Future"}],["meta",{"property":"og:description","content":"1、Future的作用 Future可以用来获取一个异步执行的结果,可以使用isDone方法检查异步任务是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:tag","content":"多线程"}],["meta",{"property":"article:tag","content":"线程池"}],["meta",{"property":"article:published_time","content":"2022-04-06T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"多线程中的Future\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-04-06T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、Future的作用","slug":"_1、future的作用","link":"#_1、future的作用","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.36,"words":107},"filePathRelative":"java/advance/Future.md","localizedDate":"2022年4月6日","excerpt":"\\n

            1、Future的作用

            \\n

            Future可以用来获取一个异步执行的结果,可以使用isDone方法检查异步任务是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。

            \\n","autoDesc":true}');export{h as comp,_ as data}; +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e,b as t}from"./app-_Oi5YZFn.js";const a={},c=e("h2",{id:"_1、future的作用",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、future的作用"},[e("span",null,"1、Future的作用")])],-1),u=e("p",null,[t("Future可以用来获取一个异步执行的结果,可以使用"),e("code",null,"isDone"),t("方法检查异步任务是否完成,或者使用"),e("code",null,"get"),t("阻塞住调用线程,直到计算完成返回结果,你也可以使用"),e("code",null,"cancel"),t("方法停止任务的执行。")],-1),i=[c,u];function l(p,d){return o(),n("div",null,i)}const h=r(a,[["render",l],["__file","Future.html.vue"]]),_=JSON.parse('{"path":"/java/advance/Future.html","title":"多线程中的Future","lang":"zh-CN","frontmatter":{"title":"多线程中的Future","date":"2022-04-06T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["线程池","多线程"],"tag":["多线程","线程池"],"description":"1、Future的作用 Future可以用来获取一个异步执行的结果,可以使用isDone方法检查异步任务是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/advance/Future.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"多线程中的Future"}],["meta",{"property":"og:description","content":"1、Future的作用 Future可以用来获取一个异步执行的结果,可以使用isDone方法检查异步任务是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:tag","content":"多线程"}],["meta",{"property":"article:tag","content":"线程池"}],["meta",{"property":"article:published_time","content":"2022-04-06T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"多线程中的Future\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-04-06T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、Future的作用","slug":"_1、future的作用","link":"#_1、future的作用","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.36,"words":107},"filePathRelative":"java/advance/Future.md","localizedDate":"2022年4月6日","excerpt":"\\n

            1、Future的作用

            \\n

            Future可以用来获取一个异步执行的结果,可以使用isDone方法检查异步任务是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。

            \\n","autoDesc":true}');export{h as comp,_ as data}; diff --git a/assets/GitCommands.html-_C8ZQnH_.js b/assets/GitCommands.html-BB60xrRT.js similarity index 99% rename from assets/GitCommands.html-_C8ZQnH_.js rename to assets/GitCommands.html-BB60xrRT.js index 36aaeee6db..fc4fe560da 100644 --- a/assets/GitCommands.html-_C8ZQnH_.js +++ b/assets/GitCommands.html-BB60xrRT.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},i=e(`

            一、善用手册

            $ git --help
            +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},i=e(`

            一、善用手册

            $ git --help
             用法:git [--version] [--help] [-C <路径>] [-c <名称>=<取值>]
                        [--exec-path[=<路径>]] [--html-path] [--man-path] [--info-path]
                        [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
            diff --git a/assets/Http.html-C3TqYNG0.js b/assets/Http.html-Cx1NIixk.js
            similarity index 99%
            rename from assets/Http.html-C3TqYNG0.js
            rename to assets/Http.html-Cx1NIixk.js
            index 94a75e4d15..9e616363cd 100644
            --- a/assets/Http.html-C3TqYNG0.js
            +++ b/assets/Http.html-Cx1NIixk.js
            @@ -1,4 +1,4 @@
            -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as e,e as s}from"./app-CVMfKeWw.js";const t={},o=s(`

            必看手册

            https://developer.mozilla.org/zh-CN/docs/Web/HTTP

            一、状态码

            1.1 3xx

            1.1.1 304

            HTTP 304 Not Modified 说明无需再次传输请求的内容,也就是说可以使用缓存的内容。这通常是在一些安全的方法(safe),例如GET 或HEAD 或在请求中附带了头部信息: If-None-Match 或If-Modified-Since。

            如果是 200 OK ,响应会带有头部 Cache-Control, Content-Location, Date, ETag, Expires,和 Vary.

            温馨提示

            很多浏览器的 开发者工具 会发出额外的请求,以达到 304 的目的,这样可以把资源以本地缓存的形式展现给开发者。一般缓存静态文件,如果用户在服务器上修改了静态文件,则请求时服务器会读取其修改时间, 这样就知道此文件是已修改过的,需要重新响应给浏览器修改后的内容。

            比如以下请求,浏览器会自动携带If-Modified-Since请求头,然后拿这个时间和服务器上文件修改时间对比,如果服务器上的时间比这个新,就会返回200,否则返回304。 20221101150553

            另外浏览器有个Disable cache选择项,勾选此项代表不允许浏览器自动携带If-Modified-Sinc请求头,也就无法使用本地缓存了。当使用Ctrl+F5刷新页面时也是同样的道理,不携带If-Modified-Sinc请求头。 20221101150938

            二、http请求

            2.1 302重定向

            请求被重定向后,是无法给客户端响应ResbonseBody,但是可以有Response Header,测试代码如下,请求http://localhost:8888/hello/cookie,在请求处理逻辑里面 添加一个cookie,然后重定向到百度。可以看到如下代码我有设置返回值,但是其实毫无意义,在return之前就被redirect到了百度。同时,在Response Headers中可以看到有 Cookie: test=aaaaaa,并且打开localhost能看到在下面有对应的cookie

            @Controller
            +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as e,e as s}from"./app-_Oi5YZFn.js";const t={},o=s(`

            必看手册

            https://developer.mozilla.org/zh-CN/docs/Web/HTTP

            一、状态码

            1.1 3xx

            1.1.1 304

            HTTP 304 Not Modified 说明无需再次传输请求的内容,也就是说可以使用缓存的内容。这通常是在一些安全的方法(safe),例如GET 或HEAD 或在请求中附带了头部信息: If-None-Match 或If-Modified-Since。

            如果是 200 OK ,响应会带有头部 Cache-Control, Content-Location, Date, ETag, Expires,和 Vary.

            温馨提示

            很多浏览器的 开发者工具 会发出额外的请求,以达到 304 的目的,这样可以把资源以本地缓存的形式展现给开发者。一般缓存静态文件,如果用户在服务器上修改了静态文件,则请求时服务器会读取其修改时间, 这样就知道此文件是已修改过的,需要重新响应给浏览器修改后的内容。

            比如以下请求,浏览器会自动携带If-Modified-Since请求头,然后拿这个时间和服务器上文件修改时间对比,如果服务器上的时间比这个新,就会返回200,否则返回304。 20221101150553

            另外浏览器有个Disable cache选择项,勾选此项代表不允许浏览器自动携带If-Modified-Sinc请求头,也就无法使用本地缓存了。当使用Ctrl+F5刷新页面时也是同样的道理,不携带If-Modified-Sinc请求头。 20221101150938

            二、http请求

            2.1 302重定向

            请求被重定向后,是无法给客户端响应ResbonseBody,但是可以有Response Header,测试代码如下,请求http://localhost:8888/hello/cookie,在请求处理逻辑里面 添加一个cookie,然后重定向到百度。可以看到如下代码我有设置返回值,但是其实毫无意义,在return之前就被redirect到了百度。同时,在Response Headers中可以看到有 Cookie: test=aaaaaa,并且打开localhost能看到在下面有对应的cookie

            @Controller
             @RequestMapping("hello")
             public class HttptestApplication {
             
            diff --git a/assets/Http2.html-yFvJ3oRy.js b/assets/Http2.html-B3cAbYAj.js
            similarity index 97%
            rename from assets/Http2.html-yFvJ3oRy.js
            rename to assets/Http2.html-B3cAbYAj.js
            index dd4b8503f7..f1b2fba627 100644
            --- a/assets/Http2.html-yFvJ3oRy.js
            +++ b/assets/Http2.html-B3cAbYAj.js
            @@ -1 +1 @@
            -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as t}from"./app-CVMfKeWw.js";const p={},r=t("h2",{id:"_1、http2-0优势",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#_1、http2-0优势"},[t("span",null,"1、http2.0优势")])],-1),i=t("h2",{id:"_2、springboot开启http2-0",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#_2、springboot开启http2-0"},[t("span",null,"2、springboot开启http2.0")])],-1),a=[r,i];function h(s,c){return o(),n("div",null,a)}const d=e(p,[["render",h],["__file","Http2.html.vue"]]),g=JSON.parse('{"path":"/java/framework/springboot/Http2.html","title":"springboot开启http2.0","lang":"zh-CN","frontmatter":{"title":"springboot开启http2.0","date":"2023-01-29T00:00:00.000Z","description":"1、http2.0优势 2、springboot开启http2.0","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/springboot/Http2.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"springboot开启http2.0"}],["meta",{"property":"og:description","content":"1、http2.0优势 2、springboot开启http2.0"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-01-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"springboot开启http2.0\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-01-29T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、http2.0优势","slug":"_1、http2-0优势","link":"#_1、http2-0优势","children":[]},{"level":2,"title":"2、springboot开启http2.0","slug":"_2、springboot开启http2-0","link":"#_2、springboot开启http2-0","children":[]}],"git":{"createdTime":1674980614000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.06,"words":18},"filePathRelative":"java/framework/springboot/Http2.md","localizedDate":"2023年1月29日","excerpt":"

            1、http2.0优势

            \\n

            2、springboot开启http2.0

            \\n","autoDesc":true}');export{d as comp,g as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as t}from"./app-_Oi5YZFn.js";const p={},r=t("h2",{id:"_1、http2-0优势",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#_1、http2-0优势"},[t("span",null,"1、http2.0优势")])],-1),i=t("h2",{id:"_2、springboot开启http2-0",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#_2、springboot开启http2-0"},[t("span",null,"2、springboot开启http2.0")])],-1),a=[r,i];function h(s,c){return o(),n("div",null,a)}const d=e(p,[["render",h],["__file","Http2.html.vue"]]),g=JSON.parse('{"path":"/java/framework/springboot/Http2.html","title":"springboot开启http2.0","lang":"zh-CN","frontmatter":{"title":"springboot开启http2.0","date":"2023-01-29T00:00:00.000Z","description":"1、http2.0优势 2、springboot开启http2.0","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/springboot/Http2.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"springboot开启http2.0"}],["meta",{"property":"og:description","content":"1、http2.0优势 2、springboot开启http2.0"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-01-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"springboot开启http2.0\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-01-29T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、http2.0优势","slug":"_1、http2-0优势","link":"#_1、http2-0优势","children":[]},{"level":2,"title":"2、springboot开启http2.0","slug":"_2、springboot开启http2-0","link":"#_2、springboot开启http2-0","children":[]}],"git":{"createdTime":1674980614000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.06,"words":18},"filePathRelative":"java/framework/springboot/Http2.md","localizedDate":"2023年1月29日","excerpt":"

            1、http2.0优势

            \\n

            2、springboot开启http2.0

            \\n","autoDesc":true}');export{d as comp,g as data}; diff --git "a/assets/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html-DiDYCl7r.js" "b/assets/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html-B2_U3umv.js" similarity index 99% rename from "assets/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html-DiDYCl7r.js" rename to "assets/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html-B2_U3umv.js" index 336d7d009a..3a9afd8981 100644 --- "a/assets/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html-DiDYCl7r.js" +++ "b/assets/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html-B2_U3umv.js" @@ -1,2 +1,2 @@ -import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as d,o as s,c as r,a as t,b as e,d as a,e as n}from"./app-CVMfKeWw.js";const c={},h=n('

            1. 需求

            服务端开发语言:Java

            终端:Android、iOS、Web、小程序

            核心需求:发送文字、图片、文件、语音、视频、消息缓存、消息存储、消息未读、已读、撤回,离线消息、历史消息、单聊、群聊,多端同步,以及其他一些需求。

            用户管理:添加好友、查看好友列表、删除好友、查看好友信息、创建群聊、加入群聊、查看群成员信息、退出群聊、修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注以及其他用户相关的需求等。

            权限管理: 针对群聊不同用户有不同的权限,比如群主、管理员、普通成员,普通成员可以发送消息、撤回消息、删除消息、修改消息、查看消息等,管理员可以管理群成员,修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注等。

            2. 原有方案评估

            2.1 webrtc即时通信

            优点:

            • 实时性强:WebRTC 的设计初衷就是为实时通信提供低延迟的解决方案,适合需要快速响应的应用场景。
            • P2P通信:WebRTC 支持点对点(P2P)通信,这可以减少延迟和服务器压力,适用于小规模的直接通信场景。
            • 跨平台支持:WebRTC 在现代浏览器中原生支持,适用于多平台的应用开发。 缺点
            • 复杂性:WebRTC 需要处理信令(比如通过 WebSocket 或其他方式),而且连接的建立和维护(如 NAT 穿透、ICE 候选等)较为复杂。
            • 消息持久化和历史记录:IM 通常需要消息持久化和历史记录功能,而 WebRTC 本身不具备这些功能,需要额外的服务器支持。
            • 扩展性问题:WebRTC 的 P2P 特性使其更适合于小规模的通信,如果用户量大或者需要支持大规模群聊,P2P 方式的扩展性会受到限制。
            • 服务器压力:虽然 P2P 减少了服务器的带宽负担,但信令服务器和消息的中转(在一些情况下)仍然需要服务器支持,尤其是当点对点连接不可用时。

            总结
            WebRTC 可以用于即时通信,但它的优缺点使其更适合特定的场景。例如,如果你需要实现一个注重实时性的小规模视频通话或音频通话的应用, WebRTC 是一个很好的选择。但如果要构建一个大规模的、需要强大消息持久化和历史记录支持的 IM 应用,那么传统的基于 WebSocket更加适合。

            远程会诊的通信技术使用的是WebRTC技术SFU架构,聊天功能模块是基于WebSocket的通信,WebRTC的优势是音视频通信,其即时聊天的多方建立连接需要依赖于信令服务器。 原有方案业务代码和会诊业务耦合度高,im通信不是其核心功能,很多功能需要从头实现,改造成本较大。

            3. IM通信的难点

            20240829105122

            3.1 消息传递可靠性问题

            离线消息需要被可靠地存储在服务器端,可能涉及大规模的数据存储需求。如何在不影响系统性能的情况下高效存储和检索离线消息? 用户从离线到在线的状态切换时,如何处理未同步的离线消息与新接收的实时消息,并确保两者的有序合并?

            3.2 消息重复问题

            20240829115908 由于网络的不可靠性,如果在如上图的环节5(ack)丢包,则会出现服务端认为消息发送成功,但是客户端认为消息发送失败的问题。当客户端重试后,如果不进行处理, 则会在数据库出现2条相同的消息。

            3.3 消息时序问题

            IM类系统中,都需要考虑消息时序问题,如果后发送的消息先显示,可能严重扰乱聊天消息所要表达的意义。

            消息时序是分布式系统架构设计中非常难的问题,一个分布式的IM系统必须要解决这个问题。

            IM系统中主要有两类消息 (1)单聊消息,两个人之间的聊天。需要确保发送方和接收方消息时序展示一致。 (2)群聊消息,一群人在一起聊天。需要确保所有接收方消息顺序一致。

            一、为什么会出现时序问题 1、时间不一致。 IM系统存在大量的客户端、IM服务器集群、长连接接入层集群、短连接接入层集群、数据库集群,这些应用分布在不同的机器上,时间很可能 不一致,时区也可能不一致。

            2、网络传输 网络传输延迟不同。同一用户后发送的消息可能早与先发送的消息到达服务器;不同用户的发送的消息到达服务器的延时差异可能更大。如下 图,msg1先发送,msg2后发送。由于网络原因,可能msg2先到达消息服务器

            3、服务集群时差 由于IM服务器分布式部署,不同的消息可能路由到不同的逻辑层处理。路由到不同logic的时延不同(尤其是跨机房),且不同logic之间存 在微量时差。

            4、消息处理速度不一致 服务器收到消息后,不同logic,不同线程对消息的处理速度可能不同,导致投递消息的时序出现错乱。

            3.4 音视频QoS问题

            网络抖动、视频延迟、丢包问题... 当同时有多路音视频通话时,如何节流,如何保证通话质量,一般云服务器的出站(上行)带宽价格是非常高昂的。

            跨网络如何保证质量? M应用需要在不同的网络环境下(如Wi-Fi、4G/5G、宽带等)保持一致的服务质量,这需要解决不同网络类型的QoS兼容问题。

            3.5 消息推送问题

            可靠的推送机制:在移动设备上,确保应用能够在后台接收消息,并及时推送通知给用户,即使应用未启动或网络不佳。 推送服务选择:根据不同平台(如Android、iOS)选择合适的推送服务,如Firebase Cloud Messaging(FCM)、APNs等,并确保推送消息的延迟和成功率。 推送策略优化:在推送大量消息时,设计合理的推送策略,避免过度消耗设备电量或造成用户干扰,同时提供灵活的推送设置供用户选择。

            3.6 技术栈问题

            音视频通话都是基于C语言或者C++开发的,超出了java技术范围。 20240830103446

            4. 技术选型目标

            20240812175557

            1)业务目标:满足需求分析篇章中的各类需求场景;
            2)技术目标:支持扩容,前期最大要能支持万级别用户同时在线聊天;
            3)架构目标:高性能、高可用、可监控、可预警、可伸缩,支持扩展。
            4)价格目标:免费开源或一次买断源码,再做二次开发

            4. 候选方案

            4.1 uni-im

            4.1.1 简介

            ',39),p={href:"https://doc.dcloud.net.cn/uniCloud/uni-im.html",target:"_blank",rel:"noopener noreferrer"},g=n('

            uni-im是云端一体的、全平台的、免费的、开源即时通讯系统。

            • 基于uni-app,App、小程序、web全端兼容
            • 基于uniCloud,前后端都使用js开发
            • 基于uni-push2,专业稳定的全端推送系统
            • 基于uni-id,完善的账户体系
            • 支持服务端为非uniCloud(比如:应用服务端的开发语言是php、java、go、.net、python、c#等)或 不基于uni-id-pages 开发的项目接入

            4.1.2 优势

            • 比较详细的文档支持
            • 前端工作量大大减少
            • 全端可用
            • App端支持nvue,更好的长列表性能。list组件性能优势详情参考
            • 中心化响应式数据管理,切换会话无需重新加载数据,更流畅的体验
            • App端聚合多个手机厂商推送通道,app不在线也可以收到消息

            4.1.3 劣势

            • 服务收费,且价格不便宜
            • 无法私有化部署

            CDN流量、出网流量、存储费用比较贵

            4.1.4 费用

            按量计费:

            • 调用10000次云函数0.0133元
            • 调用10000次数据库查询仅0.015元

            私聊一次: 1次云函数请求、2次数据库读操作、2次数据库写操作、1次uni-push2推送操作,即 (1 * 0.0133 + 2 * 0.015 + 2 * 0.05 + 1 * 0.0283)/10000 ≈ 0.000017元

            500人的群聊:1次云函数请求、4次数据库读操作、2次数据库写操作、1次uni-push2推送操作,即 (1 * 0.0133 + 4 * 0.015 + 2 * 0.05 + 1 * 0.0283)/10000 ≈ 0.000020元

            资源分类 资源细项 售价(元)
            云函数 资源使用量(GBs) 0.000110592
            调用次数(万次) 0.0133
            出网流量(GB) 0.8
            云数据库 容量(GB/天) 0.07
            读操作使用量(万RU) 0.015
            写操作使用量(万RU) 0.05
            云存储 容量(GB/天) 0.0043
            下载操作次数(万次) 0.01
            上传操作次数(万次) 0.01
            CDN 流量(GB) 0.18
            前端网站托管 容量(GB/天) 0.0043
            流量(GB) 0.18

            套餐计费:

            资源分类 资源细项 免费版 基础版 标准版 专业版 企业版 旗舰版
            云函数 资源使用量(GBs/月) 1000 1万 20万 40万 150万 400万
            调用次数(万次/月) 1.5 15 300 600 2400 6000
            出网流量(GB/月) 1 1 20 40 160 500
            云数据库 容量(GB) 2 2 3 5 10 10
            读操作使用量(万RU/天) 0.05 5 25 50 150 500
            写操作使用量(万WU/天) 0.03 3 15 30 100 300
            集合数量 100 100 100 100 100 100
            索引数量 400 400 400 400 400 400
            云存储 容量(GB) 5 8 10 50 100 500
            下载操作次数(万次/月) 0.2 10 200 750 1500 3750
            上传操作次数(万次/月) 0.1 5 100 300 600 1500
            CDN流量(GB/月) 1 2 10 50 150 500
            前端网页托管 容量(GB) 5 8 10 50 100 500
            CDN流量(GB/月) 1 2 10 50 150 500
            售价(元/月) 免费 5 24 82 316 688

            名词解释:

            资源分类 资源细项 说明 数据更新延迟时间
            云函数 资源使用量(GBs) 资源使用量GBs = 函数配置内存GB × 运行计费时长s。 例如,配置为256MB的函数,单次运行了1760ms,计费时长为1760ms,则单次运行的资源使用量为(256 / 1024) × (1760 / 1000) = 0.44GBs 20分钟
            调用次数 - 20分钟
            出网流量(GB) 在云函数中访问外网时产生的出网流量,包含请求三方服务器发送的数据和返回给客户端的数据。 20分钟
            云数据库 容量(GB) - 1小时
            读操作使用量(RU) 读操作使用量(Read Unit)= ceil(查询数据量KB / 4),即从数据表中读取一条4 KB数据(向上取整)计作1RU,例如读取7.6 KB的数据计作2RU。 20分钟
            写操作使用量(WU) 写操作使用量(Write Unit)= ceil(写入数据量KB / 1),即向数据表中写入一条1 KB数据(向上取整)计作1WU,例如写入1.8 KB的数据计作2WU。 20分钟
            云存储 容量(GB) - 6小时
            下载操作次数 通过CDN加速访问的次数,回源次数暂不收费。 6小时
            上传操作次数 - 6小时
            CDN 流量(GB) 通过CDN加速产生的流量,回源流量暂不收费。 6小时
            前端网站托管 容量(GB) - 6小时
            CDN 流量(GB) 通过CDN加速产生的流量,回源流量暂不收费。 6小时

            4.2 J-IM

            ',18),o={href:"https://gitee.com/xchao/j-im",target:"_blank",rel:"noopener noreferrer"},x=n('

            4.2.1 简介

            J-IM 是用JAVA语言开发的轻量、高性能、单机支持几十万至百万在线用户IM,主要目标降低即时通讯门槛,快速打造低成本接入在线IM系统,通过极简洁的消息格式就可以实现多端不同协议间的消息发送如内置(Http、Websocket、Tcp自定义IM协议)等,并提供通过http协议的api接口进行消息发送无需关心接收端属于什么协议,一个消息格式搞定一切!

            4.2.2 特性

            1、高性能(单机可支持几十万至百万人同时在线) 2、轻量、可扩展性极强 3、支持集群多机部署 4、支持SSL/TLS加密传输 5、消息格式极其简洁(JSON) 6、一端口支持可插拔多种协议(Socket自定义IM协议、Websocket、Http),各协议可分别独立部署。 7、内置消息持久化(离线、历史、漫游),保证消息可靠性,高性能存储 8、各种丰富的API接口。 9、零成本部署,一键启动。

            4.2.3 劣势

            1. 项目不活跃长期不更新
            2. 没有完善的文档支持,官网打不开

            4.3 cim

            ',7),y={href:"https://gitee.com/farsunset/cim",target:"_blank",rel:"noopener noreferrer"},_=n('

            4.3.1 简介

            CIM是一套完善的消息推送框架,可应用于信令推送,即时聊天,移动设备指令推送等领域。开发者可沉浸于业务开发,不用关心消息通道长连接、消息编解码协议等繁杂处理。

            CIM采用业内主流开源技术构建,易于扩展和使用,并完美支持集群部署支持海量链接,目前支持websocket,android,ios,桌面应用,系统应用等多端接入持,可应用于移动应用,物联网,智能家居,嵌入式开发,桌面应用,WEB应用即时消服务。

            4.3.2 优势

            1. 维护时间比较长,10年了 ,目前还在活跃提交代码
            2. 提供多个客户端SDK(android/.net/fluttter/ios/swift/uniapp/web)
            3. 有web/vue/android的demo,有后台管理
            4. 文档详细

            4.3.3 劣势

            1. 按照平台SDK收费,uni-app(h5,android,ios)

            20240813112230

            4.4 V-IM

            ',9),b={href:"https://gitee.com/alyouge/V-IM",target:"_blank",rel:"noopener noreferrer"},m=n(`

            4.4.1 简介

            开源与企业版功能点对比 20240813112349

            企业版优势

            多终端支持:PC(windows、linux、mac、web) 手机(安卓、IOS、H5、小程序); 上传支持两种方案(直接存服务器和minio); 私有云代码仓库永久更新,无加密部分,不依赖第三方。 一对一技术支持。 bug修复优先级最高。 支持付费定制化需求。 功能更新频率高。 聊天记录存储在mongoDB; 支持国产化部署,服务端已对接到snowy开源项目(分支版本)。

            4.4.2 企业版收费情况

            2980元,交付源码
            +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as d,o as s,c as r,a as t,b as e,d as a,e as n}from"./app-_Oi5YZFn.js";const c={},h=n('

            1. 需求

            服务端开发语言:Java

            终端:Android、iOS、Web、小程序

            核心需求:发送文字、图片、文件、语音、视频、消息缓存、消息存储、消息未读、已读、撤回,离线消息、历史消息、单聊、群聊,多端同步,以及其他一些需求。

            用户管理:添加好友、查看好友列表、删除好友、查看好友信息、创建群聊、加入群聊、查看群成员信息、退出群聊、修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注以及其他用户相关的需求等。

            权限管理: 针对群聊不同用户有不同的权限,比如群主、管理员、普通成员,普通成员可以发送消息、撤回消息、删除消息、修改消息、查看消息等,管理员可以管理群成员,修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注等。

            2. 原有方案评估

            2.1 webrtc即时通信

            优点:

            • 实时性强:WebRTC 的设计初衷就是为实时通信提供低延迟的解决方案,适合需要快速响应的应用场景。
            • P2P通信:WebRTC 支持点对点(P2P)通信,这可以减少延迟和服务器压力,适用于小规模的直接通信场景。
            • 跨平台支持:WebRTC 在现代浏览器中原生支持,适用于多平台的应用开发。 缺点
            • 复杂性:WebRTC 需要处理信令(比如通过 WebSocket 或其他方式),而且连接的建立和维护(如 NAT 穿透、ICE 候选等)较为复杂。
            • 消息持久化和历史记录:IM 通常需要消息持久化和历史记录功能,而 WebRTC 本身不具备这些功能,需要额外的服务器支持。
            • 扩展性问题:WebRTC 的 P2P 特性使其更适合于小规模的通信,如果用户量大或者需要支持大规模群聊,P2P 方式的扩展性会受到限制。
            • 服务器压力:虽然 P2P 减少了服务器的带宽负担,但信令服务器和消息的中转(在一些情况下)仍然需要服务器支持,尤其是当点对点连接不可用时。

            总结
            WebRTC 可以用于即时通信,但它的优缺点使其更适合特定的场景。例如,如果你需要实现一个注重实时性的小规模视频通话或音频通话的应用, WebRTC 是一个很好的选择。但如果要构建一个大规模的、需要强大消息持久化和历史记录支持的 IM 应用,那么传统的基于 WebSocket更加适合。

            远程会诊的通信技术使用的是WebRTC技术SFU架构,聊天功能模块是基于WebSocket的通信,WebRTC的优势是音视频通信,其即时聊天的多方建立连接需要依赖于信令服务器。 原有方案业务代码和会诊业务耦合度高,im通信不是其核心功能,很多功能需要从头实现,改造成本较大。

            3. IM通信的难点

            20240829105122

            3.1 消息传递可靠性问题

            离线消息需要被可靠地存储在服务器端,可能涉及大规模的数据存储需求。如何在不影响系统性能的情况下高效存储和检索离线消息? 用户从离线到在线的状态切换时,如何处理未同步的离线消息与新接收的实时消息,并确保两者的有序合并?

            3.2 消息重复问题

            20240829115908 由于网络的不可靠性,如果在如上图的环节5(ack)丢包,则会出现服务端认为消息发送成功,但是客户端认为消息发送失败的问题。当客户端重试后,如果不进行处理, 则会在数据库出现2条相同的消息。

            3.3 消息时序问题

            IM类系统中,都需要考虑消息时序问题,如果后发送的消息先显示,可能严重扰乱聊天消息所要表达的意义。

            消息时序是分布式系统架构设计中非常难的问题,一个分布式的IM系统必须要解决这个问题。

            IM系统中主要有两类消息 (1)单聊消息,两个人之间的聊天。需要确保发送方和接收方消息时序展示一致。 (2)群聊消息,一群人在一起聊天。需要确保所有接收方消息顺序一致。

            一、为什么会出现时序问题 1、时间不一致。 IM系统存在大量的客户端、IM服务器集群、长连接接入层集群、短连接接入层集群、数据库集群,这些应用分布在不同的机器上,时间很可能 不一致,时区也可能不一致。

            2、网络传输 网络传输延迟不同。同一用户后发送的消息可能早与先发送的消息到达服务器;不同用户的发送的消息到达服务器的延时差异可能更大。如下 图,msg1先发送,msg2后发送。由于网络原因,可能msg2先到达消息服务器

            3、服务集群时差 由于IM服务器分布式部署,不同的消息可能路由到不同的逻辑层处理。路由到不同logic的时延不同(尤其是跨机房),且不同logic之间存 在微量时差。

            4、消息处理速度不一致 服务器收到消息后,不同logic,不同线程对消息的处理速度可能不同,导致投递消息的时序出现错乱。

            3.4 音视频QoS问题

            网络抖动、视频延迟、丢包问题... 当同时有多路音视频通话时,如何节流,如何保证通话质量,一般云服务器的出站(上行)带宽价格是非常高昂的。

            跨网络如何保证质量? M应用需要在不同的网络环境下(如Wi-Fi、4G/5G、宽带等)保持一致的服务质量,这需要解决不同网络类型的QoS兼容问题。

            3.5 消息推送问题

            可靠的推送机制:在移动设备上,确保应用能够在后台接收消息,并及时推送通知给用户,即使应用未启动或网络不佳。 推送服务选择:根据不同平台(如Android、iOS)选择合适的推送服务,如Firebase Cloud Messaging(FCM)、APNs等,并确保推送消息的延迟和成功率。 推送策略优化:在推送大量消息时,设计合理的推送策略,避免过度消耗设备电量或造成用户干扰,同时提供灵活的推送设置供用户选择。

            3.6 技术栈问题

            音视频通话都是基于C语言或者C++开发的,超出了java技术范围。 20240830103446

            4. 技术选型目标

            20240812175557

            1)业务目标:满足需求分析篇章中的各类需求场景;
            2)技术目标:支持扩容,前期最大要能支持万级别用户同时在线聊天;
            3)架构目标:高性能、高可用、可监控、可预警、可伸缩,支持扩展。
            4)价格目标:免费开源或一次买断源码,再做二次开发

            4. 候选方案

            4.1 uni-im

            4.1.1 简介

            ',39),p={href:"https://doc.dcloud.net.cn/uniCloud/uni-im.html",target:"_blank",rel:"noopener noreferrer"},g=n('

            uni-im是云端一体的、全平台的、免费的、开源即时通讯系统。

            • 基于uni-app,App、小程序、web全端兼容
            • 基于uniCloud,前后端都使用js开发
            • 基于uni-push2,专业稳定的全端推送系统
            • 基于uni-id,完善的账户体系
            • 支持服务端为非uniCloud(比如:应用服务端的开发语言是php、java、go、.net、python、c#等)或 不基于uni-id-pages 开发的项目接入

            4.1.2 优势

            • 比较详细的文档支持
            • 前端工作量大大减少
            • 全端可用
            • App端支持nvue,更好的长列表性能。list组件性能优势详情参考
            • 中心化响应式数据管理,切换会话无需重新加载数据,更流畅的体验
            • App端聚合多个手机厂商推送通道,app不在线也可以收到消息

            4.1.3 劣势

            • 服务收费,且价格不便宜
            • 无法私有化部署

            CDN流量、出网流量、存储费用比较贵

            4.1.4 费用

            按量计费:

            • 调用10000次云函数0.0133元
            • 调用10000次数据库查询仅0.015元

            私聊一次: 1次云函数请求、2次数据库读操作、2次数据库写操作、1次uni-push2推送操作,即 (1 * 0.0133 + 2 * 0.015 + 2 * 0.05 + 1 * 0.0283)/10000 ≈ 0.000017元

            500人的群聊:1次云函数请求、4次数据库读操作、2次数据库写操作、1次uni-push2推送操作,即 (1 * 0.0133 + 4 * 0.015 + 2 * 0.05 + 1 * 0.0283)/10000 ≈ 0.000020元

            资源分类 资源细项 售价(元)
            云函数 资源使用量(GBs) 0.000110592
            调用次数(万次) 0.0133
            出网流量(GB) 0.8
            云数据库 容量(GB/天) 0.07
            读操作使用量(万RU) 0.015
            写操作使用量(万RU) 0.05
            云存储 容量(GB/天) 0.0043
            下载操作次数(万次) 0.01
            上传操作次数(万次) 0.01
            CDN 流量(GB) 0.18
            前端网站托管 容量(GB/天) 0.0043
            流量(GB) 0.18

            套餐计费:

            资源分类 资源细项 免费版 基础版 标准版 专业版 企业版 旗舰版
            云函数 资源使用量(GBs/月) 1000 1万 20万 40万 150万 400万
            调用次数(万次/月) 1.5 15 300 600 2400 6000
            出网流量(GB/月) 1 1 20 40 160 500
            云数据库 容量(GB) 2 2 3 5 10 10
            读操作使用量(万RU/天) 0.05 5 25 50 150 500
            写操作使用量(万WU/天) 0.03 3 15 30 100 300
            集合数量 100 100 100 100 100 100
            索引数量 400 400 400 400 400 400
            云存储 容量(GB) 5 8 10 50 100 500
            下载操作次数(万次/月) 0.2 10 200 750 1500 3750
            上传操作次数(万次/月) 0.1 5 100 300 600 1500
            CDN流量(GB/月) 1 2 10 50 150 500
            前端网页托管 容量(GB) 5 8 10 50 100 500
            CDN流量(GB/月) 1 2 10 50 150 500
            售价(元/月) 免费 5 24 82 316 688

            名词解释:

            资源分类 资源细项 说明 数据更新延迟时间
            云函数 资源使用量(GBs) 资源使用量GBs = 函数配置内存GB × 运行计费时长s。 例如,配置为256MB的函数,单次运行了1760ms,计费时长为1760ms,则单次运行的资源使用量为(256 / 1024) × (1760 / 1000) = 0.44GBs 20分钟
            调用次数 - 20分钟
            出网流量(GB) 在云函数中访问外网时产生的出网流量,包含请求三方服务器发送的数据和返回给客户端的数据。 20分钟
            云数据库 容量(GB) - 1小时
            读操作使用量(RU) 读操作使用量(Read Unit)= ceil(查询数据量KB / 4),即从数据表中读取一条4 KB数据(向上取整)计作1RU,例如读取7.6 KB的数据计作2RU。 20分钟
            写操作使用量(WU) 写操作使用量(Write Unit)= ceil(写入数据量KB / 1),即向数据表中写入一条1 KB数据(向上取整)计作1WU,例如写入1.8 KB的数据计作2WU。 20分钟
            云存储 容量(GB) - 6小时
            下载操作次数 通过CDN加速访问的次数,回源次数暂不收费。 6小时
            上传操作次数 - 6小时
            CDN 流量(GB) 通过CDN加速产生的流量,回源流量暂不收费。 6小时
            前端网站托管 容量(GB) - 6小时
            CDN 流量(GB) 通过CDN加速产生的流量,回源流量暂不收费。 6小时

            4.2 J-IM

            ',18),o={href:"https://gitee.com/xchao/j-im",target:"_blank",rel:"noopener noreferrer"},x=n('

            4.2.1 简介

            J-IM 是用JAVA语言开发的轻量、高性能、单机支持几十万至百万在线用户IM,主要目标降低即时通讯门槛,快速打造低成本接入在线IM系统,通过极简洁的消息格式就可以实现多端不同协议间的消息发送如内置(Http、Websocket、Tcp自定义IM协议)等,并提供通过http协议的api接口进行消息发送无需关心接收端属于什么协议,一个消息格式搞定一切!

            4.2.2 特性

            1、高性能(单机可支持几十万至百万人同时在线) 2、轻量、可扩展性极强 3、支持集群多机部署 4、支持SSL/TLS加密传输 5、消息格式极其简洁(JSON) 6、一端口支持可插拔多种协议(Socket自定义IM协议、Websocket、Http),各协议可分别独立部署。 7、内置消息持久化(离线、历史、漫游),保证消息可靠性,高性能存储 8、各种丰富的API接口。 9、零成本部署,一键启动。

            4.2.3 劣势

            1. 项目不活跃长期不更新
            2. 没有完善的文档支持,官网打不开

            4.3 cim

            ',7),y={href:"https://gitee.com/farsunset/cim",target:"_blank",rel:"noopener noreferrer"},_=n('

            4.3.1 简介

            CIM是一套完善的消息推送框架,可应用于信令推送,即时聊天,移动设备指令推送等领域。开发者可沉浸于业务开发,不用关心消息通道长连接、消息编解码协议等繁杂处理。

            CIM采用业内主流开源技术构建,易于扩展和使用,并完美支持集群部署支持海量链接,目前支持websocket,android,ios,桌面应用,系统应用等多端接入持,可应用于移动应用,物联网,智能家居,嵌入式开发,桌面应用,WEB应用即时消服务。

            4.3.2 优势

            1. 维护时间比较长,10年了 ,目前还在活跃提交代码
            2. 提供多个客户端SDK(android/.net/fluttter/ios/swift/uniapp/web)
            3. 有web/vue/android的demo,有后台管理
            4. 文档详细

            4.3.3 劣势

            1. 按照平台SDK收费,uni-app(h5,android,ios)

            20240813112230

            4.4 V-IM

            ',9),b={href:"https://gitee.com/alyouge/V-IM",target:"_blank",rel:"noopener noreferrer"},m=n(`

            4.4.1 简介

            开源与企业版功能点对比 20240813112349

            企业版优势

            多终端支持:PC(windows、linux、mac、web) 手机(安卓、IOS、H5、小程序); 上传支持两种方案(直接存服务器和minio); 私有云代码仓库永久更新,无加密部分,不依赖第三方。 一对一技术支持。 bug修复优先级最高。 支持付费定制化需求。 功能更新频率高。 聊天记录存储在mongoDB; 支持国产化部署,服务端已对接到snowy开源项目(分支版本)。

            4.4.2 企业版收费情况

            2980元,交付源码
             

            4.4.3 劣势

            • 文档不够详细
            • git不活跃

            4.5 MobileIMSDK

            `,9),u={href:"https://gitee.com/jackjiang/MobileIMSDK",target:"_blank",rel:"noopener noreferrer"},f=n('

            2024083010352720240830103538

            4.5.1 简介

            历经10年、久经考验; 超轻量级、高度提炼,lib包50KB以内; 精心封装,一套API同时支持UDP、TCP、WebSocket三种协议(可能是全网唯一开源的); 客户端支持iOS、Android、标准Java、H5(暂未开源)、微信小程序(暂未开源)、Uniap(暂未开源); 服务端基于Netty,性能卓越、易于扩展 new; 可与姊妹工程 MobileIMSDK-Web 无缝互通实现网页端聊天或推送等; 可应用于跨设备、跨网络的聊天APP、企业OA、消息推送等各种场景。

            20240813114025

            4.5.2 优势

            1. 轻量级,可支持多端多协议,可扩展性极强
            2. 有自己的论坛,社区活跃,有开发者提供支持,作者响应及时
            3. 文档详细,收费版提供详细的代码注释
            4. 覆盖所有平台,所有平台客户端都提供源码,若需要定制可参考他的源码

            2024083009021120240830090249

            4.5.3 劣势

            1. 仅支持单机部署(单机支持10万人同时在线)
            2. 按照终端收费,安卓/ios/web端分开收费

            4.5.4 费用

            20240821173051

            4.6 野火IM/im-server

            ',12),M={href:"https://gitee.com/wfchat/im-server",target:"_blank",rel:"noopener noreferrer"},I=n('

            4.6.1 简介

            野火IM是一套通用的即时通讯和实时音视频组件,能够更加容易地赋予客户IM和RTC能力,使客户可以快速的在自有产品上添加聊天和通话功能或者直接使用野火提供的应用。使用野火可以替代云通讯产品或减少自研即时通讯和实时音视频的工作量,降低客户研发成本和难度。

            4.6.2 提供产品

            1. 即时通讯服务(IM Server)
            2. 应用服务
            3. 推送服务
            4. Android 客户端
            5. iOS 客户端
            6. PC 客户端
            7. Web 客户端
            8. 小程序 Demo
            9. uni-app Demo
            10. 机器人服务
            11. 开发平台
            12. 频道(公众号)管理系统
            13. IM 管理后台系统

            4.6.3 优势

            1. 文档齐全
            2. 支持多端

            4.6.4 劣势

            1. 价格偏贵且后续升级需要付费(原价10% )
            2. 购买SDK要绑定IP/域名,无法部署到不同机器
            3. 只通过github提供技术支持,响应会很慢
            4. 即使付费购买,产品的功能库也是闭源的,不提供源码,只提供demo源码

            4.6.4 体验

            20240830090803

            4.6.5 收费

            20240830091017

            20240830091029

            20240830091040

            5. 总结

            大厂的im比如腾讯im,都是卖服务卖套餐收费高昂,包括uniapp也是,所以不考虑。 其他都是提供SDK,基于SDK进行业务开发,SDK提供的功能差不多, 其中MobileIMSDK的资料最详细, 支持所有平台的部署,可单机10万人同时在线,10万人对我们业务来说绰绰有余了。另外MobileIMSDK有自己的社区,他的的文档是 最详细的,从社区数据来看作者比较活跃,能及时响应。 综合从费用、开发成本、文档支持、社区支持几个维度来看,MobileIMSDK比较适合。

            ',16);function k(v,B){const l=d("ExternalLinkIcon");return s(),r("div",null,[h,t("p",null,[e("地址:"),t("a",p,[e("https://doc.dcloud.net.cn/uniCloud/uni-im.html"),a(l)])]),g,t("p",null,[e("GIT地址:"),t("a",o,[e("https://gitee.com/xchao/j-im"),a(l)])]),x,t("p",null,[e("GIT地址: "),t("a",y,[e("https://gitee.com/farsunset/cim"),a(l)])]),_,t("p",null,[e("GIT地址:"),t("a",b,[e("https://gitee.com/alyouge/V-IM"),a(l)])]),m,t("p",null,[e("GIT地址:"),t("a",u,[e("https://gitee.com/jackjiang/MobileIMSDK"),a(l)])]),f,t("p",null,[e("GIT地址:"),t("a",M,[e("https://gitee.com/wfchat/im-server"),a(l)])]),I])}const w=i(c,[["render",k],["__file","IM即时通信技术选型.html.vue"]]),D=JSON.parse('{"path":"/other/essay/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B.html","title":"IM即时通信选型","lang":"zh-CN","frontmatter":{"title":"IM即时通信选型","date":"2024-08-12T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"1. 需求 服务端开发语言:Java 终端:Android、iOS、Web、小程序 核心需求:发送文字、图片、文件、语音、视频、消息缓存、消息存储、消息未读、已读、撤回,离线消息、历史消息、单聊、群聊,多端同步,以及其他一些需求。 用户管理:添加好友、查看好友列表、删除好友、查看好友信息、创建群聊、加入群聊、查看群成员信息、退出群聊、修改群昵称、拉人进...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/essay/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"IM即时通信选型"}],["meta",{"property":"og:description","content":"1. 需求 服务端开发语言:Java 终端:Android、iOS、Web、小程序 核心需求:发送文字、图片、文件、语音、视频、消息缓存、消息存储、消息未读、已读、撤回,离线消息、历史消息、单聊、群聊,多端同步,以及其他一些需求。 用户管理:添加好友、查看好友列表、删除好友、查看好友信息、创建群聊、加入群聊、查看群成员信息、退出群聊、修改群昵称、拉人进..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20240829105122.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-08-12T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"IM即时通信选型\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20240829105122.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240829115908.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240830103446.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240812175557.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240813112230.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240813112349.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240830103527.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240830103538.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240813114025.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240830090211.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240830090249.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240821173051.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240830090803.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240830091017.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240830091029.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240830091040.png\\"],\\"datePublished\\":\\"2024-08-12T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":2,"title":"1. 需求","slug":"_1-需求","link":"#_1-需求","children":[]},{"level":2,"title":"2. 原有方案评估","slug":"_2-原有方案评估","link":"#_2-原有方案评估","children":[{"level":3,"title":"2.1 webrtc即时通信","slug":"_2-1-webrtc即时通信","link":"#_2-1-webrtc即时通信","children":[]}]},{"level":2,"title":"3. IM通信的难点","slug":"_3-im通信的难点","link":"#_3-im通信的难点","children":[{"level":3,"title":"3.1 消息传递可靠性问题","slug":"_3-1-消息传递可靠性问题","link":"#_3-1-消息传递可靠性问题","children":[]},{"level":3,"title":"3.2 消息重复问题","slug":"_3-2-消息重复问题","link":"#_3-2-消息重复问题","children":[]},{"level":3,"title":"3.3 消息时序问题","slug":"_3-3-消息时序问题","link":"#_3-3-消息时序问题","children":[]},{"level":3,"title":"3.4 音视频QoS问题","slug":"_3-4-音视频qos问题","link":"#_3-4-音视频qos问题","children":[]},{"level":3,"title":"3.5 消息推送问题","slug":"_3-5-消息推送问题","link":"#_3-5-消息推送问题","children":[]},{"level":3,"title":"3.6 技术栈问题","slug":"_3-6-技术栈问题","link":"#_3-6-技术栈问题","children":[]}]},{"level":2,"title":"4. 技术选型目标","slug":"_4-技术选型目标","link":"#_4-技术选型目标","children":[]},{"level":2,"title":"4. 候选方案","slug":"_4-候选方案","link":"#_4-候选方案","children":[{"level":3,"title":"4.1 uni-im","slug":"_4-1-uni-im","link":"#_4-1-uni-im","children":[]},{"level":3,"title":"4.1.1 简介","slug":"_4-1-1-简介","link":"#_4-1-1-简介","children":[]},{"level":3,"title":"4.1.2 优势","slug":"_4-1-2-优势","link":"#_4-1-2-优势","children":[]},{"level":3,"title":"4.1.3 劣势","slug":"_4-1-3-劣势","link":"#_4-1-3-劣势","children":[]},{"level":3,"title":"4.1.4 费用","slug":"_4-1-4-费用","link":"#_4-1-4-费用","children":[]},{"level":3,"title":"4.2 J-IM","slug":"_4-2-j-im","link":"#_4-2-j-im","children":[]},{"level":3,"title":"4.3 cim","slug":"_4-3-cim","link":"#_4-3-cim","children":[]},{"level":3,"title":"4.4 V-IM","slug":"_4-4-v-im","link":"#_4-4-v-im","children":[]},{"level":3,"title":"4.5 MobileIMSDK","slug":"_4-5-mobileimsdk","link":"#_4-5-mobileimsdk","children":[]},{"level":3,"title":"4.6 野火IM/im-server","slug":"_4-6-野火im-im-server","link":"#_4-6-野火im-im-server","children":[]}]},{"level":2,"title":"5. 总结","slug":"_5-总结","link":"#_5-总结","children":[]}],"git":{"createdTime":1723616084000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":5}]},"readingTime":{"minutes":19.44,"words":5832},"filePathRelative":"other/essay/IM即时通信技术选型.md","localizedDate":"2024年8月12日","excerpt":"

            1. 需求

            \\n

            服务端开发语言:Java

            \\n

            终端:Android、iOS、Web、小程序

            \\n

            核心需求:发送文字、图片、文件、语音、视频、消息缓存、消息存储、消息未读、已读、撤回,离线消息、历史消息、单聊、群聊,多端同步,以及其他一些需求。

            \\n

            用户管理:添加好友、查看好友列表、删除好友、查看好友信息、创建群聊、加入群聊、查看群成员信息、退出群聊、修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注以及其他用户相关的需求等。

            \\n

            权限管理: 针对群聊不同用户有不同的权限,比如群主、管理员、普通成员,普通成员可以发送消息、撤回消息、删除消息、修改消息、查看消息等,管理员可以管理群成员,修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注等。

            ","autoDesc":true}');export{w as comp,D as data}; diff --git a/assets/IO-Model.html-BPdWwFrU.js b/assets/IO-Model.html-O-6ElLZv.js similarity index 99% rename from assets/IO-Model.html-BPdWwFrU.js rename to assets/IO-Model.html-O-6ElLZv.js index b515a5e5bf..b1d7c31aa1 100644 --- a/assets/IO-Model.html-BPdWwFrU.js +++ b/assets/IO-Model.html-O-6ElLZv.js @@ -1,4 +1,4 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c,a as s,b as n,d as t,e as l}from"./app-CVMfKeWw.js";const i={},u={href:"https://zhuanlan.zhihu.com/p/115912936",target:"_blank",rel:"noopener noreferrer"},r={href:"https://www.zhihu.com/people/duan-pan-ykjym",target:"_blank",rel:"noopener noreferrer"},d=l(`

            1、从TCP发送数据的流程说起

            所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。
            +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c,a as s,b as n,d as t,e as l}from"./app-_Oi5YZFn.js";const i={},u={href:"https://zhuanlan.zhihu.com/p/115912936",target:"_blank",rel:"noopener noreferrer"},r={href:"https://www.zhihu.com/people/duan-pan-ykjym",target:"_blank",rel:"noopener noreferrer"},d=l(`

            1、从TCP发送数据的流程说起

            所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。
             
             需要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在”干活”,而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。
             

            要深入的理解各种IO模型,那么必须先了解下产生各种IO的原因是什么,要知道这其中的本质问题那么我们就必须要知道一条消息是如何从一个人发送到另外一个人的;

            以两个应用程序通讯为例,我们来了解一下当“A”向"B" 发送一条消息,简单来说会经过如下流程:

            第一步:应用A把消息发送到 TCP发送缓冲区。

            第二步: TCP发送缓冲区再把消息发送出去,经过网络传递后,消息会发送到B服务器的TCP接收缓冲区。

            第三步:B再从TCP接收缓冲区去读取属于自己的数据。

            20221227100621

            根据上图我们基本上了解消息发送要经过 应用A、应用A对应服务器的TCP发送缓冲区、经过网络传输后消息发送到了应用B对应服务器TCP接收缓冲区、然后最终B应用读取到消息。

            如果理解了上面的消息发送流程,那么我们下面开始进入文章的主题;

            2、阻塞IO |非阻塞IO

            们把视角切换到上面图中的第三步, 也就是应用B从TCP缓冲区中读取数据。

            20221227100706

            思考一个问题:

            因为应用之间发送消息是间断性的,也就是说在上图中TCP缓冲区还没有接收到属于应用B该读取的消息时,那么此时应用B向TCP缓冲区发起读取申请,TCP接收缓冲区是应该马上告诉应用B 现在没有你的数据,还是说让应用B在这里等着,直到有数据再把数据交给应用B。

            把这个问题应用到第一个步骤也是一样,应用A在向TCP发送缓冲区发送数据时,如果TCP发送缓冲区已经满了,那么是告诉应用A现在没空间了,还是让应用A等待着,等TCP发送缓冲区有空间了再把应用A的数据访拷贝到发送缓冲区。

            2.1 什么是阻塞IO

            如果上面的问题你已经思考过了,那么其实你已经明白了什么是阻塞IO了,所谓阻塞IO就是当应用B发起读取数据申请时,在内核数据没有准备好之前,应用B会一直处于等待数据状态,直到内核把数据准备好了交给应用B才结束。

            术语描述:在应用调用recvfrom读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的称为阻塞IO;

            流程:

            1、应用进程向内核发起recfrom读取数据。

            2、准备数据报(应用进程阻塞)。

            3、将数据从内核负责到应用空间。

            4、复制完成后,返回成功提示。

            20221227100729

            2.1.1 阻塞IO存在的问题

            如果IO阻塞了,用户线程会变成阻塞态,此时会让出CPU的使用权限,即使让出CPU使用权,也需要在寄存器中记录用户线程当前的状态(也就是上下文), 因为要保证,当用户线程恢复执行时能够正常运行(这里就是常说的线程上下文切换),那么问题就产生了。

            线程是很”贵”的资源,主要表现在:

            1. 线程的创建和销毁成本很高,在Linux这样的操作系统中,线程本质上就是一个进程。创建和销毁都是重量级的系统函数
            2. 线程本身占用较大内存,像Java的线程栈,一般至少分配512K~1M的空间,如果系统中的线程数过千,恐怕整个JVM的内存都会被吃掉一半
            3. 线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间,这时候带来的表现往往是系统load偏高、CPU sy使用率特别高(超过20%以上),导致系统几乎陷入不可用的状态
            4. 容易造成锯齿状的系统负载。因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大

            2.1.1 阻塞IO模型demo

            
            diff --git a/assets/IO-model1.html-DbFAWkDL.js b/assets/IO-model1.html-Bko-tL29.js
            similarity index 99%
            rename from assets/IO-model1.html-DbFAWkDL.js
            rename to assets/IO-model1.html-Bko-tL29.js
            index a132b77528..e83a41c836 100644
            --- a/assets/IO-model1.html-DbFAWkDL.js
            +++ b/assets/IO-model1.html-Bko-tL29.js
            @@ -1,4 +1,4 @@
            -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as c,c as o,a as s,b as n,d as l,e as a}from"./app-CVMfKeWw.js";const i={},u=a('

            1 BIO

            当给BIO的accept和read方法加上超时机制后,可以在代码层面解决阻塞问题,但这不是真正的非阻塞,通常我们说的非阻塞是指的操作系统层面的非阻塞,就是当accept通过jni调用native方法后,最终系统不会一直被阻塞。真正的非阻塞是操作系统增加非阻塞功能后,java同步添加java.nio出现以后才有的,因此通过java实现非阻塞,需要调用nio中的类。

            2 NIO

            3 IO多路复用

            4 异步IO

            5 事件驱动的io

            6 reactor线程模型

            ',7),k={href:"https://ddns.chensina.cn:29000/afatpig/blog/nio.pdf",target:"_blank",rel:"noopener noreferrer"},r=a(`

            注意

            注意reactor线程模型并不是5种io模型之一,它是一种经典的事件驱动的线程模型,它是基于IO多路复用模型衍生出来的:

            Reactor线程模式 = Reactor(I/O多路复用)+ 线程池

            Reactor负责监听和分配事件,线程池负责处理事件

            根据Reactor的数量和线程池的数量,又可以将Reactor分为三种模型

            • 单Reactor单线程模型 (固定大小为1的线程池)
            • 单Reactor多线程模型
            • 多Reactor多线程模型 (一般是主从2个Reactor)

            reactor模型中有三种角色,分别是:

            Acceptor:处理客户端新连接,并分派请求到处理器链中
            Reactor:负责监听和分配事件,将I/O事件分派给对应的Handler
            Handler: 事件处理,如编码、解码等

            6.1 单reactor单线程

            应用:redis4.0

            20230209155247

            源码示例:

            //------------------------reactor------------------
            +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as c,c as o,a as s,b as n,d as l,e as a}from"./app-_Oi5YZFn.js";const i={},u=a('

            1 BIO

            当给BIO的accept和read方法加上超时机制后,可以在代码层面解决阻塞问题,但这不是真正的非阻塞,通常我们说的非阻塞是指的操作系统层面的非阻塞,就是当accept通过jni调用native方法后,最终系统不会一直被阻塞。真正的非阻塞是操作系统增加非阻塞功能后,java同步添加java.nio出现以后才有的,因此通过java实现非阻塞,需要调用nio中的类。

            2 NIO

            3 IO多路复用

            4 异步IO

            5 事件驱动的io

            6 reactor线程模型

            ',7),k={href:"https://ddns.chensina.cn:29000/afatpig/blog/nio.pdf",target:"_blank",rel:"noopener noreferrer"},r=a(`

            注意

            注意reactor线程模型并不是5种io模型之一,它是一种经典的事件驱动的线程模型,它是基于IO多路复用模型衍生出来的:

            Reactor线程模式 = Reactor(I/O多路复用)+ 线程池

            Reactor负责监听和分配事件,线程池负责处理事件

            根据Reactor的数量和线程池的数量,又可以将Reactor分为三种模型

            • 单Reactor单线程模型 (固定大小为1的线程池)
            • 单Reactor多线程模型
            • 多Reactor多线程模型 (一般是主从2个Reactor)

            reactor模型中有三种角色,分别是:

            Acceptor:处理客户端新连接,并分派请求到处理器链中
            Reactor:负责监听和分配事件,将I/O事件分派给对应的Handler
            Handler: 事件处理,如编码、解码等

            6.1 单reactor单线程

            应用:redis4.0

            20230209155247

            源码示例:

            //------------------------reactor------------------
             import java.io.IOException;
             import java.net.InetSocketAddress;
             import java.nio.channels.SelectionKey;
            diff --git a/assets/Idea.html-N3gMCtcp.js b/assets/Idea.html-DFw9lvM7.js
            similarity index 98%
            rename from assets/Idea.html-N3gMCtcp.js
            rename to assets/Idea.html-DFw9lvM7.js
            index 05dfd76903..586aff9ea3 100644
            --- a/assets/Idea.html-N3gMCtcp.js
            +++ b/assets/Idea.html-DFw9lvM7.js
            @@ -1,3 +1,3 @@
            -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as d,c as s,a as e,b as a,d as l,e as o}from"./app-CVMfKeWw.js";const p={},r=e("h2",{id:"_1、多线程debug遇到的问题",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、多线程debug遇到的问题"},[e("span",null,"1、多线程debug遇到的问题")])],-1),h={href:"https://www.jetbrains.com/help/idea/using-breakpoints.html#breakpoint-properties",target:"_blank",rel:"noopener noreferrer"},c=o(`
            • All: all threads are suspended when any of the threads hits the breakpoint.

            • Thread: only the thread which hits the breakpoint is suspended.

            1

            今天用idea调试mybatis-plus多数据源切换时,遇到一个有趣的问题,我有两个线程,分别进入了两个方法,因为多数据源切换使用的ThreadLocal,我想调试为何数据源切换会失败的问题。在调试过程中,我在两个方法分别获取ThreadLocalMap,调试过程如下:

            1. 我先执行了线程1,然后断点停留,查看第一个方法中threadLocalMap的结果,符合预期
            2. 然后执行线程2,查看第二个方法中threadLocalMap的结果,也符合预期
            3. 此时我又用Ctrl+鼠标左键去查看线程1中的threadLocalMap,发现变成和线程2中一样了 2

            经过第三步测试,我蒙蔽了,ThreadLocal不是线程之间不会互相干扰吗?怎么线程2修改了线程1的结果?
            这里说一下,debug中,Ctrl+鼠标左键点击字段确实能快捷查看字段值,但是其实它的本质还是查看当前线程的xxx字段,只要你没切换线程,直接用鼠标点到另一个线程中的字段,它其实只是用了你用快捷键点的那个字段名,实际还是差的当前线程的这个字段 3

            如果想看另一个线程的同名字段,需要先切换线程
            4

            另外如果你把两个线程中的字段名改成不一样,你会发现在线程2中用快捷键点线程1的字段,是取不到值的

            2、Idea指定jdk启动

            Idea升级后无法启动,查看日志报错明显的是jdk版本不兼容,需要用最新的jdk17,解决方法有多个。

            方法1:设置全局jdk为17,这个方法可以启动idea,但是不太好,毕竟其他软件可能不能使用最新的jdk

            方法2:单独给idea配置jdk,我记得以前使用eclipse有个虚拟机参数-vm可以指定,但是在idea中并不好使, 所以就去看了一下idea的启动脚本idea.sh,看一下启动脚本,就知idea启动时到底使用的是那个jdk,根据idea读取 的配置文件去配置就好了,可以设置JRE环境变量、可以指定JDK_HOME环境变量,我这里使用的是配置文件的方式,配置 idea.jdk

            20221202112129

             cat ~/.config/JetBrains/IntelliJIdea2022.3/idea.jdk                                      
            +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as d,c as s,a as e,b as a,d as l,e as o}from"./app-_Oi5YZFn.js";const p={},r=e("h2",{id:"_1、多线程debug遇到的问题",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、多线程debug遇到的问题"},[e("span",null,"1、多线程debug遇到的问题")])],-1),h={href:"https://www.jetbrains.com/help/idea/using-breakpoints.html#breakpoint-properties",target:"_blank",rel:"noopener noreferrer"},c=o(`
            • All: all threads are suspended when any of the threads hits the breakpoint.

            • Thread: only the thread which hits the breakpoint is suspended.

            1

            今天用idea调试mybatis-plus多数据源切换时,遇到一个有趣的问题,我有两个线程,分别进入了两个方法,因为多数据源切换使用的ThreadLocal,我想调试为何数据源切换会失败的问题。在调试过程中,我在两个方法分别获取ThreadLocalMap,调试过程如下:

            1. 我先执行了线程1,然后断点停留,查看第一个方法中threadLocalMap的结果,符合预期
            2. 然后执行线程2,查看第二个方法中threadLocalMap的结果,也符合预期
            3. 此时我又用Ctrl+鼠标左键去查看线程1中的threadLocalMap,发现变成和线程2中一样了 2

            经过第三步测试,我蒙蔽了,ThreadLocal不是线程之间不会互相干扰吗?怎么线程2修改了线程1的结果?
            这里说一下,debug中,Ctrl+鼠标左键点击字段确实能快捷查看字段值,但是其实它的本质还是查看当前线程的xxx字段,只要你没切换线程,直接用鼠标点到另一个线程中的字段,它其实只是用了你用快捷键点的那个字段名,实际还是差的当前线程的这个字段 3

            如果想看另一个线程的同名字段,需要先切换线程
            4

            另外如果你把两个线程中的字段名改成不一样,你会发现在线程2中用快捷键点线程1的字段,是取不到值的

            2、Idea指定jdk启动

            Idea升级后无法启动,查看日志报错明显的是jdk版本不兼容,需要用最新的jdk17,解决方法有多个。

            方法1:设置全局jdk为17,这个方法可以启动idea,但是不太好,毕竟其他软件可能不能使用最新的jdk

            方法2:单独给idea配置jdk,我记得以前使用eclipse有个虚拟机参数-vm可以指定,但是在idea中并不好使, 所以就去看了一下idea的启动脚本idea.sh,看一下启动脚本,就知idea启动时到底使用的是那个jdk,根据idea读取 的配置文件去配置就好了,可以设置JRE环境变量、可以指定JDK_HOME环境变量,我这里使用的是配置文件的方式,配置 idea.jdk

            20221202112129

             cat ~/.config/JetBrains/IntelliJIdea2022.3/idea.jdk                                      
             /usr/lib/jvm/java-17-openjdk
             

            3、idea中git操作

            3.1 对比任意非连续的两次提交文件差异

            选中两次提交,右键Compare Versions,

            image-20221229215718576

            会出现一个change log的视图,这就是两次提价的差异文件

            image-20221229215820791

            4、debug无法进入jdk源码

            idea2022版本默认,调试时跳过底层源码,具体参考下图,把需要加入debug的源码去掉勾选 20230201173830

            `,21);function g(m,u){const t=i("ExternalLinkIcon");return d(),s("div",null,[r,e("p",null,[a("多线程调试需要把Thread选上,至于Thread和All的区别请查看"),e("a",h,[a("官方文档"),l(t)])]),c])}const _=n(p,[["render",g],["__file","Idea.html.vue"]]),f=JSON.parse('{"path":"/other/tools/Idea.html","title":"Idea","lang":"zh-CN","frontmatter":{"title":"Idea","date":"2022-07-29T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"1、多线程debug遇到的问题 多线程调试需要把Thread选上,至于Thread和All的区别请查看官方文档 All: all threads are suspended when any of the threads hits the breakpoint. Thread: only the thread which hits the breakp...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/tools/Idea.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Idea"}],["meta",{"property":"og:description","content":"1、多线程debug遇到的问题 多线程调试需要把Thread选上,至于Thread和All的区别请查看官方文档 All: all threads are suspended when any of the threads hits the breakpoint. Thread: only the thread which hits the breakp..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20220729150247.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-07-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Idea\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20220729150247.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20220729145847.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20220729151140.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20220729151320.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221202112129.png\\",\\"http://afatpig.oss-cn-chengdu.aliyuncs.com/blog/image-20221229215718576.png\\",\\"http://afatpig.oss-cn-chengdu.aliyuncs.com/blog/image-20221229215820791.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230201173830.png\\"],\\"datePublished\\":\\"2022-07-29T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、多线程debug遇到的问题","slug":"_1、多线程debug遇到的问题","link":"#_1、多线程debug遇到的问题","children":[]},{"level":2,"title":"2、Idea指定jdk启动","slug":"_2、idea指定jdk启动","link":"#_2、idea指定jdk启动","children":[]},{"level":2,"title":"3、idea中git操作","slug":"_3、idea中git操作","link":"#_3、idea中git操作","children":[{"level":3,"title":"3.1 对比任意非连续的两次提交文件差异","slug":"_3-1-对比任意非连续的两次提交文件差异","link":"#_3-1-对比任意非连续的两次提交文件差异","children":[]}]},{"level":2,"title":"4、debug无法进入jdk源码","slug":"_4、debug无法进入jdk源码","link":"#_4、debug无法进入jdk源码","children":[]}],"git":{"createdTime":1669951441000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":4},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":2.72,"words":817},"filePathRelative":"other/tools/Idea.md","localizedDate":"2022年7月29日","excerpt":"

            1、多线程debug遇到的问题

            \\n

            多线程调试需要把Thread选上,至于Thread和All的区别请查看官方文档

            \\n
              \\n
            • \\n

              All: all threads are suspended when any of the threads hits the breakpoint.

              \\n
            • \\n
            • \\n

              Thread: only the thread which hits the breakpoint is suspended.

              \\n
            • \\n
            ","autoDesc":true}');export{_ as comp,f as data}; diff --git a/assets/ImplementSameInterface.html-8aOTv4So.js b/assets/ImplementSameInterface.html-iv1dGOUt.js similarity index 99% rename from assets/ImplementSameInterface.html-8aOTv4So.js rename to assets/ImplementSameInterface.html-iv1dGOUt.js index af358f875a..a3ec422199 100644 --- a/assets/ImplementSameInterface.html-8aOTv4So.js +++ b/assets/ImplementSameInterface.html-iv1dGOUt.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},p=e(`

            1、背景

            今天看Securfity的源码,其中org.springframework.security.config.annotation.web.builders.HttpSecurity类的UML看着很奇怪,如下图所示,命名其父类和父接口都实现过SecurityBuilder,为什么自己要再次实现呢?

            20230105160622

            2、探索

            我一开始注意力被泛型吸引了,想着是不是因为用了不同的泛型类的原因,为此我还专门去复习了一下泛型的东西。后来确定和泛型没关系,然后百度了一下,找到了以下网友的博客,为此我还专门写demo验证了他的博客内容,发现确实如此。 20230105161007

            2.1 demo验证

            //接口
            +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

            1、背景

            今天看Securfity的源码,其中org.springframework.security.config.annotation.web.builders.HttpSecurity类的UML看着很奇怪,如下图所示,命名其父类和父接口都实现过SecurityBuilder,为什么自己要再次实现呢?

            20230105160622

            2、探索

            我一开始注意力被泛型吸引了,想着是不是因为用了不同的泛型类的原因,为此我还专门去复习了一下泛型的东西。后来确定和泛型没关系,然后百度了一下,找到了以下网友的博客,为此我还专门写demo验证了他的博客内容,发现确实如此。 20230105161007

            2.1 demo验证

            //接口
             public interface Animal {
                 public void sayHi();
             }
            diff --git a/assets/InstallMysqlWithDocker.html-cGZHFWnE.js b/assets/InstallMysqlWithDocker.html-Dlzu3km3.js
            similarity index 99%
            rename from assets/InstallMysqlWithDocker.html-cGZHFWnE.js
            rename to assets/InstallMysqlWithDocker.html-Dlzu3km3.js
            index df0f64065d..3b10e77616 100644
            --- a/assets/InstallMysqlWithDocker.html-cGZHFWnE.js
            +++ b/assets/InstallMysqlWithDocker.html-Dlzu3km3.js
            @@ -1,4 +1,4 @@
            -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as e,e as n}from"./app-CVMfKeWw.js";const t={},l=n(`

            1. 不要用太新的版本

            安装一定要选对版本,刚开始我使用的8.0.30镜像,一直无法启动,日志报错大概意思是我映射的目录有问题,应该是新版本mysql的安装文件有变动, 我也懒得去深究,直接换了一个版本就好了了。

            2 安装

            # 1. 下载镜像
            +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as e,e as n}from"./app-_Oi5YZFn.js";const t={},l=n(`

            1. 不要用太新的版本

            安装一定要选对版本,刚开始我使用的8.0.30镜像,一直无法启动,日志报错大概意思是我映射的目录有问题,应该是新版本mysql的安装文件有变动, 我也懒得去深究,直接换了一个版本就好了了。

            2 安装

            # 1. 下载镜像
             docker pull mysql:8.0.23
             # 2. 启动
             docker run -p 3306:3306 --name mysql \\
            diff --git a/assets/IntegerConstantPool.html-BgqTmJIN.js b/assets/IntegerConstantPool.html-B-ijklmd.js
            similarity index 99%
            rename from assets/IntegerConstantPool.html-BgqTmJIN.js
            rename to assets/IntegerConstantPool.html-B-ijklmd.js
            index 7137a9c8ee..709e2c16ea 100644
            --- a/assets/IntegerConstantPool.html-BgqTmJIN.js
            +++ b/assets/IntegerConstantPool.html-B-ijklmd.js
            @@ -1,4 +1,4 @@
            -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

            1. Integer常量池默认的范围

            范围:[-128,127],Integer内部有个缓存池,最小值-128是固定的,最大的值127是可以调整的,看源码知道,最大值是和integerCacheHighPropValue有关,这个值是可以通过~java.lang.Integer.IntegerCache.high~属性指定,实际测试~~~System.setProperty("java.lang.Integer.IntegerCache.high","300")~~~不生效,使用-XX:AutoBoxCacheMax=300可以。

            private static class IntegerCache {
            +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

            1. Integer常量池默认的范围

            范围:[-128,127],Integer内部有个缓存池,最小值-128是固定的,最大的值127是可以调整的,看源码知道,最大值是和integerCacheHighPropValue有关,这个值是可以通过~java.lang.Integer.IntegerCache.high~属性指定,实际测试~~~System.setProperty("java.lang.Integer.IntegerCache.high","300")~~~不生效,使用-XX:AutoBoxCacheMax=300可以。

            private static class IntegerCache {
                     static final int low = -128;
                     static final int high;
                     static final Integer cache[];
            diff --git a/assets/JdkVersion.html-BvKsTRiv.js b/assets/JdkVersion.html-CS9P09_u.js
            similarity index 99%
            rename from assets/JdkVersion.html-BvKsTRiv.js
            rename to assets/JdkVersion.html-CS9P09_u.js
            index c8262daa45..8cb261290a 100644
            --- a/assets/JdkVersion.html-BvKsTRiv.js
            +++ b/assets/JdkVersion.html-CS9P09_u.js
            @@ -1 +1 @@
            -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as t,e as n}from"./app-CVMfKeWw.js";const i={},l=n('

            开发者注意

            Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。

            1、Jdk11对比jdk1.8

            1.1 Java9

            1. 模块系统 模块是一个包的容器,Java 9 最大的变化之一是引入模块系统。(Jigsaw 项目) 作用:解决大型项目模块化开发的需求,更适合客户端软件,在打包时可以自定义依赖模块,根据所依赖的模块打包出一个包含jdk环境的软件,
              这样的好处是打包比较小,用户下载快,并且无需自己安装jdk环境。个人认为这项新特性对于web应用并不是那么重要,web应用无需考虑包的大小。

            2. 支持 HTTP/2 标准 HTTP/2 标准是 HTTP 协议的最新版本,新的 HTTPClient API 支持 Websocket 和 HTTP2 流以及服务器推送特性。

            3. 提供创建不可变集合的静态工厂方法 List、Set、Map 接口中,提供新的静态工厂方法直接创建不可变的集合实例。 作用:创建不可变集合更方便,一行代码就搞定,节省了开销。

            4. 私有接口方法 在接口中也允许编写 private 修饰的私有方法了。 作用:增强了接口的功能,提高了可扩展性。

            5. 轻量级的 JSON API 内置了一个轻量级的 JSON API。

            6. 引入响应式流 API Java 9 引入了新的响应式流 API。

            1.2 Java10

            1. 增加var类型推断

            1.3 Java11(LTS)

            1. HttpClient在java9引入,但是在java11进行优化升级

            ',8),o=[l];function r(p,d){return a(),t("div",null,o)}const h=e(i,[["render",r],["__file","JdkVersion.html.vue"]]),v=JSON.parse('{"path":"/java/other/JdkVersion.html","title":"Jdk版本","lang":"zh-CN","frontmatter":{"title":"Jdk版本","date":"2022-08-17T00:00:00.000Z","author":"chenkun","description":"开发者注意 Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。 1、Jdk11对比jdk1.8 1.1 Java9 模块系统 模块是一个包的容器,Java 9 最大的变化之一是引入模块系统。(Jigsaw 项目) 作用:解决大型项...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/JdkVersion.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Jdk版本"}],["meta",{"property":"og:description","content":"开发者注意 Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。 1、Jdk11对比jdk1.8 1.1 Java9 模块系统 模块是一个包的容器,Java 9 最大的变化之一是引入模块系统。(Jigsaw 项目) 作用:解决大型项..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-08-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Jdk版本\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-17T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、Jdk11对比jdk1.8","slug":"_1、jdk11对比jdk1-8","link":"#_1、jdk11对比jdk1-8","children":[{"level":3,"title":"1.1 Java9","slug":"_1-1-java9","link":"#_1-1-java9","children":[]},{"level":3,"title":"1.2 Java10","slug":"_1-2-java10","link":"#_1-2-java10","children":[]},{"level":3,"title":"1.3 Java11(LTS)","slug":"_1-3-java11-lts","link":"#_1-3-java11-lts","children":[]}]}],"git":{"createdTime":1660706572000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":1.54,"words":463},"filePathRelative":"java/other/JdkVersion.md","localizedDate":"2022年8月17日","excerpt":"
            \\n

            开发者注意

            \\n

            Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。

            \\n
            \\n

            1、Jdk11对比jdk1.8

            \\n

            1.1 Java9

            \\n
              \\n
            1. \\n

              模块系统\\n模块是一个包的容器,Java 9 最大的变化之一是引入模块系统。(Jigsaw 项目)\\n作用:解决大型项目模块化开发的需求,更适合客户端软件,在打包时可以自定义依赖模块,根据所依赖的模块打包出一个包含jdk环境的软件,
              \\n这样的好处是打包比较小,用户下载快,并且无需自己安装jdk环境。个人认为这项新特性对于web应用并不是那么重要,web应用无需考虑包的大小。

              \\n
            2. \\n
            3. \\n

              支持 HTTP/2 标准\\nHTTP/2 标准是 HTTP 协议的最新版本,新的 HTTPClient API 支持 Websocket 和 HTTP2 流以及服务器推送特性。

              \\n
            4. \\n
            5. \\n

              提供创建不可变集合的静态工厂方法\\nList、Set、Map 接口中,提供新的静态工厂方法直接创建不可变的集合实例。\\n作用:创建不可变集合更方便,一行代码就搞定,节省了开销。

              \\n
            6. \\n
            7. \\n

              私有接口方法\\n在接口中也允许编写 private 修饰的私有方法了。\\n作用:增强了接口的功能,提高了可扩展性。

              \\n
            8. \\n
            9. \\n

              轻量级的 JSON API\\n内置了一个轻量级的 JSON API。

              \\n
            10. \\n
            11. \\n

              引入响应式流 API\\nJava 9 引入了新的响应式流 API。

              \\n
            12. \\n
            ","autoDesc":true}');export{h as comp,v as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as t,e as n}from"./app-_Oi5YZFn.js";const i={},l=n('

            开发者注意

            Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。

            1、Jdk11对比jdk1.8

            1.1 Java9

            1. 模块系统 模块是一个包的容器,Java 9 最大的变化之一是引入模块系统。(Jigsaw 项目) 作用:解决大型项目模块化开发的需求,更适合客户端软件,在打包时可以自定义依赖模块,根据所依赖的模块打包出一个包含jdk环境的软件,
              这样的好处是打包比较小,用户下载快,并且无需自己安装jdk环境。个人认为这项新特性对于web应用并不是那么重要,web应用无需考虑包的大小。

            2. 支持 HTTP/2 标准 HTTP/2 标准是 HTTP 协议的最新版本,新的 HTTPClient API 支持 Websocket 和 HTTP2 流以及服务器推送特性。

            3. 提供创建不可变集合的静态工厂方法 List、Set、Map 接口中,提供新的静态工厂方法直接创建不可变的集合实例。 作用:创建不可变集合更方便,一行代码就搞定,节省了开销。

            4. 私有接口方法 在接口中也允许编写 private 修饰的私有方法了。 作用:增强了接口的功能,提高了可扩展性。

            5. 轻量级的 JSON API 内置了一个轻量级的 JSON API。

            6. 引入响应式流 API Java 9 引入了新的响应式流 API。

            1.2 Java10

            1. 增加var类型推断

            1.3 Java11(LTS)

            1. HttpClient在java9引入,但是在java11进行优化升级

            ',8),o=[l];function r(p,d){return a(),t("div",null,o)}const h=e(i,[["render",r],["__file","JdkVersion.html.vue"]]),v=JSON.parse('{"path":"/java/other/JdkVersion.html","title":"Jdk版本","lang":"zh-CN","frontmatter":{"title":"Jdk版本","date":"2022-08-17T00:00:00.000Z","author":"chenkun","description":"开发者注意 Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。 1、Jdk11对比jdk1.8 1.1 Java9 模块系统 模块是一个包的容器,Java 9 最大的变化之一是引入模块系统。(Jigsaw 项目) 作用:解决大型项...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/JdkVersion.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Jdk版本"}],["meta",{"property":"og:description","content":"开发者注意 Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。 1、Jdk11对比jdk1.8 1.1 Java9 模块系统 模块是一个包的容器,Java 9 最大的变化之一是引入模块系统。(Jigsaw 项目) 作用:解决大型项..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-08-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Jdk版本\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-17T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、Jdk11对比jdk1.8","slug":"_1、jdk11对比jdk1-8","link":"#_1、jdk11对比jdk1-8","children":[{"level":3,"title":"1.1 Java9","slug":"_1-1-java9","link":"#_1-1-java9","children":[]},{"level":3,"title":"1.2 Java10","slug":"_1-2-java10","link":"#_1-2-java10","children":[]},{"level":3,"title":"1.3 Java11(LTS)","slug":"_1-3-java11-lts","link":"#_1-3-java11-lts","children":[]}]}],"git":{"createdTime":1660706572000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":1.54,"words":463},"filePathRelative":"java/other/JdkVersion.md","localizedDate":"2022年8月17日","excerpt":"
            \\n

            开发者注意

            \\n

            Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。

            \\n
            \\n

            1、Jdk11对比jdk1.8

            \\n

            1.1 Java9

            \\n
              \\n
            1. \\n

              模块系统\\n模块是一个包的容器,Java 9 最大的变化之一是引入模块系统。(Jigsaw 项目)\\n作用:解决大型项目模块化开发的需求,更适合客户端软件,在打包时可以自定义依赖模块,根据所依赖的模块打包出一个包含jdk环境的软件,
              \\n这样的好处是打包比较小,用户下载快,并且无需自己安装jdk环境。个人认为这项新特性对于web应用并不是那么重要,web应用无需考虑包的大小。

              \\n
            2. \\n
            3. \\n

              支持 HTTP/2 标准\\nHTTP/2 标准是 HTTP 协议的最新版本,新的 HTTPClient API 支持 Websocket 和 HTTP2 流以及服务器推送特性。

              \\n
            4. \\n
            5. \\n

              提供创建不可变集合的静态工厂方法\\nList、Set、Map 接口中,提供新的静态工厂方法直接创建不可变的集合实例。\\n作用:创建不可变集合更方便,一行代码就搞定,节省了开销。

              \\n
            6. \\n
            7. \\n

              私有接口方法\\n在接口中也允许编写 private 修饰的私有方法了。\\n作用:增强了接口的功能,提高了可扩展性。

              \\n
            8. \\n
            9. \\n

              轻量级的 JSON API\\n内置了一个轻量级的 JSON API。

              \\n
            10. \\n
            11. \\n

              引入响应式流 API\\nJava 9 引入了新的响应式流 API。

              \\n
            12. \\n
            ","autoDesc":true}');export{h as comp,v as data}; diff --git "a/assets/Jellyfin\346\220\255\345\273\272.html-CLPGwkSq.js" "b/assets/Jellyfin\346\220\255\345\273\272.html-B4JCtBiz.js" similarity index 97% rename from "assets/Jellyfin\346\220\255\345\273\272.html-CLPGwkSq.js" rename to "assets/Jellyfin\346\220\255\345\273\272.html-B4JCtBiz.js" index 0b77a913ab..7fd5ea73ef 100644 --- "a/assets/Jellyfin\346\220\255\345\273\272.html-CLPGwkSq.js" +++ "b/assets/Jellyfin\346\220\255\345\273\272.html-B4JCtBiz.js" @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as o,a as e}from"./app-CVMfKeWw.js";const i={},r=e("h2",{id:"家庭影院",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#家庭影院"},[e("span",null,"家庭影院")])],-1),a=[r];function l(c,s){return n(),o("div",null,a)}const h=t(i,[["render",l],["__file","Jellyfin搭建.html.vue"]]),d=JSON.parse('{"path":"/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html","title":"jellyfin搭建","lang":"zh-CN","frontmatter":{"title":"jellyfin搭建","date":"2024-03-22T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"家庭影院","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"jellyfin搭建"}],["meta",{"property":"og:description","content":"家庭影院"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T06:08:50.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-03-22T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T06:08:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"jellyfin搭建\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-03-22T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T06:08:50.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":2,"title":"家庭影院","slug":"家庭影院","link":"#家庭影院","children":[]}],"git":{"createdTime":1711087730000,"updatedTime":1711087730000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.06,"words":18},"filePathRelative":"myserver/Jellyfin搭建.md","localizedDate":"2024年3月22日","excerpt":"

            家庭影院

            \\n","autoDesc":true}');export{h as comp,d as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as o,a as e}from"./app-_Oi5YZFn.js";const i={},r=e("h2",{id:"家庭影院",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#家庭影院"},[e("span",null,"家庭影院")])],-1),a=[r];function l(c,s){return n(),o("div",null,a)}const h=t(i,[["render",l],["__file","Jellyfin搭建.html.vue"]]),d=JSON.parse('{"path":"/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html","title":"jellyfin搭建","lang":"zh-CN","frontmatter":{"title":"jellyfin搭建","date":"2024-03-22T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"家庭影院","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"jellyfin搭建"}],["meta",{"property":"og:description","content":"家庭影院"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T06:08:50.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-03-22T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T06:08:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"jellyfin搭建\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-03-22T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T06:08:50.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":2,"title":"家庭影院","slug":"家庭影院","link":"#家庭影院","children":[]}],"git":{"createdTime":1711087730000,"updatedTime":1711087730000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.06,"words":18},"filePathRelative":"myserver/Jellyfin搭建.md","localizedDate":"2024年3月22日","excerpt":"

            家庭影院

            \\n","autoDesc":true}');export{h as comp,d as data}; diff --git a/assets/Jenkins.html-D5aTJlwz.js b/assets/Jenkins.html-e7mi9H_K.js similarity index 99% rename from assets/Jenkins.html-D5aTJlwz.js rename to assets/Jenkins.html-e7mi9H_K.js index 8d6fbfe4cb..e75e14e75f 100644 --- a/assets/Jenkins.html-D5aTJlwz.js +++ b/assets/Jenkins.html-e7mi9H_K.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

            1、jenkins插件更新报错

            1.1 报错如下,ssl证书问题

            image-20220415144539319

            Jenkins(2020年及以后版本,2.260以上)安装后,插件下载时失败,网上找了各种解决方法,修改jenkins插件的下载源地址:

            找到菜单Manage Jenkins → Manage Plugins → Advanced → Update Site,

            把URL改为 https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

            或把默认地址 https://updates.jenkins.io/update-center.json 的https改为http再重启。

            我使用的是最新版本(2022-04-15),使用以上方式无效。

            1.2 解决办法

            1. 新建一个java源文件InstallCert.java,内容如下。
            2. 编译javac InstallCert.java
            3. 执行java InstallCert <hostname>出现提示后按1,回车。会生成jssecacerts 文件。hostname是mirrors.tuna.tsinghua.edu.cn,记得在jenkins中把插件仓库地址设置为https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
            4. 将第3步生成的jssecacerts 拷贝到$JAVA_HOME/jre/lib/security
            5. 重启jenkins
            6. 重新更新插件
            /*
            +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

            1、jenkins插件更新报错

            1.1 报错如下,ssl证书问题

            image-20220415144539319

            Jenkins(2020年及以后版本,2.260以上)安装后,插件下载时失败,网上找了各种解决方法,修改jenkins插件的下载源地址:

            找到菜单Manage Jenkins → Manage Plugins → Advanced → Update Site,

            把URL改为 https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

            或把默认地址 https://updates.jenkins.io/update-center.json 的https改为http再重启。

            我使用的是最新版本(2022-04-15),使用以上方式无效。

            1.2 解决办法

            1. 新建一个java源文件InstallCert.java,内容如下。
            2. 编译javac InstallCert.java
            3. 执行java InstallCert <hostname>出现提示后按1,回车。会生成jssecacerts 文件。hostname是mirrors.tuna.tsinghua.edu.cn,记得在jenkins中把插件仓库地址设置为https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
            4. 将第3步生成的jssecacerts 拷贝到$JAVA_HOME/jre/lib/security
            5. 重启jenkins
            6. 重新更新插件
            /*
              * Copyright 2006 Sun Microsystems, Inc.  All Rights Reserved.
              *
              * Redistribution and use in source and binary forms, with or without
            diff --git a/assets/Jwt.html-BI9W3_p_.js b/assets/Jwt.html-CP1cMj9m.js
            similarity index 99%
            rename from assets/Jwt.html-BI9W3_p_.js
            rename to assets/Jwt.html-CP1cMj9m.js
            index f6d7945f07..62ab60f3a4 100644
            --- a/assets/Jwt.html-BI9W3_p_.js
            +++ b/assets/Jwt.html-CP1cMj9m.js
            @@ -1,4 +1,4 @@
            -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

            1、jwt在服务端如何校验的?

            之前一直用jwt但是仅仅了解他的基本原理没有去思考一个问题——服务端是如何校验jwt的?

            了解过jwt原理的同学都知道jwt是可以自校验的,token里面有header,payload,我有个想法就是如果用户随便生成一个token,那后端是如何知道这个token不能用?或者我把开发环境的token拿到生产环境使用 是否可行?

            jwt用户认证流程如下,因jwt的token是无状态的,所以每次请求都要经过过滤器进行校验,第一次登陆后把生成的token缓存到redis,当校验时如果找到对应token则继续,解析head中userid信息,根据userid查用户角色权限等信息,然后再设置到security的context中,具体代码如下

            Jwt认证

            @Slf4j
            +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

            1、jwt在服务端如何校验的?

            之前一直用jwt但是仅仅了解他的基本原理没有去思考一个问题——服务端是如何校验jwt的?

            了解过jwt原理的同学都知道jwt是可以自校验的,token里面有header,payload,我有个想法就是如果用户随便生成一个token,那后端是如何知道这个token不能用?或者我把开发环境的token拿到生产环境使用 是否可行?

            jwt用户认证流程如下,因jwt的token是无状态的,所以每次请求都要经过过滤器进行校验,第一次登陆后把生成的token缓存到redis,当校验时如果找到对应token则继续,解析head中userid信息,根据userid查用户角色权限等信息,然后再设置到security的context中,具体代码如下

            Jwt认证

            @Slf4j
             @Component
             public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
             
            diff --git a/assets/Manjaro.html-CTuhb7rc.js b/assets/Manjaro.html-Dy2e0Hrp.js
            similarity index 99%
            rename from assets/Manjaro.html-CTuhb7rc.js
            rename to assets/Manjaro.html-Dy2e0Hrp.js
            index d926b66aa4..b920ceb92a 100644
            --- a/assets/Manjaro.html-CTuhb7rc.js
            +++ b/assets/Manjaro.html-Dy2e0Hrp.js
            @@ -1,4 +1,4 @@
            -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as l,c as o,a,b as n,d as r,e}from"./app-CVMfKeWw.js";const c={},p=e(`

            1、降级软件包

            安装downgrade程序 sudo pacman -S downgrade 降级 sudo DOWNGRADE_FROM_ALA=1 downgrade xxx包 注意DOWNGRADE_FROM_ALA=1一定要按照我上边这样写,不能单独export DOWNGRADE_FROM_ALA=1 设置忽略升级的包 第二步会让你选择更新的时候是否要忽略更新,选择y的话,它会在/etc/pacman.conf添加一个忽略,如果不想湖绿,把下面的IgnorePkg注释即可

            image-20220322171440300

            2、开机报错failed to start rotate log files

            2.1 分析问题

            1. logrotate是什么 按照老套路分析,先百度了一下logrotate是什么,参考,说白了就是个日志切割,和java里面的差不多。就是防止单文件日志过大,按照一定的规则切割成多个日志,或者删除,比如设置超过一个月直接删除,或者超过10M直接删除等等。
            2. 查看logrotate是什么时候启动,以及启动后的状态。首先我们知道它是一个systemctl启动的service服务。那就到/lib/systemd/system下看一下ll |grep rotate
             ll|grep rota
            +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as l,c as o,a,b as n,d as r,e}from"./app-_Oi5YZFn.js";const c={},p=e(`

            1、降级软件包

            安装downgrade程序 sudo pacman -S downgrade 降级 sudo DOWNGRADE_FROM_ALA=1 downgrade xxx包 注意DOWNGRADE_FROM_ALA=1一定要按照我上边这样写,不能单独export DOWNGRADE_FROM_ALA=1 设置忽略升级的包 第二步会让你选择更新的时候是否要忽略更新,选择y的话,它会在/etc/pacman.conf添加一个忽略,如果不想湖绿,把下面的IgnorePkg注释即可

            image-20220322171440300

            2、开机报错failed to start rotate log files

            2.1 分析问题

            1. logrotate是什么 按照老套路分析,先百度了一下logrotate是什么,参考,说白了就是个日志切割,和java里面的差不多。就是防止单文件日志过大,按照一定的规则切割成多个日志,或者删除,比如设置超过一个月直接删除,或者超过10M直接删除等等。
            2. 查看logrotate是什么时候启动,以及启动后的状态。首先我们知道它是一个systemctl启动的service服务。那就到/lib/systemd/system下看一下ll |grep rotate
             ll|grep rota
             -rw-r--r-- 1 root root  870  18  2021 logrotate.service
             -rw-r--r-- 1 root root  191  18  2021 logrotate.timer
             
            `,7),d={href:"https://blog.csdn.net/chen462488588/article/details/118737574?spm=1001.2014.3001.5501",target:"_blank",rel:"noopener noreferrer"},u=e(`
            $ cat logrotate.service     
            diff --git a/assets/MemoryModel.html-Bzwww5iG.js b/assets/MemoryModel.html-CRsJwAOr.js
            similarity index 99%
            rename from assets/MemoryModel.html-Bzwww5iG.js
            rename to assets/MemoryModel.html-CRsJwAOr.js
            index d3438105f4..ba5e83a686 100644
            --- a/assets/MemoryModel.html-Bzwww5iG.js
            +++ b/assets/MemoryModel.html-CRsJwAOr.js
            @@ -1,4 +1,4 @@
            -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

            环境jdk8

            1、元空间

            jdk8中用元空间取代了原来的方法区,元空间是没有上限,只要系统有可用内存那么jvm就能一直申请,那么元空间里放的是啥?比如类元信息,就是类加载器加载的类,就放在元空间。

            以下进行测试,测试之前,需要了解一个知识点,两个class相同的前提是同一个类加载器加载的同一个类,这样得到的才是同一个Class.

            import java.io.IOException;
            +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

            环境jdk8

            1、元空间

            jdk8中用元空间取代了原来的方法区,元空间是没有上限,只要系统有可用内存那么jvm就能一直申请,那么元空间里放的是啥?比如类元信息,就是类加载器加载的类,就放在元空间。

            以下进行测试,测试之前,需要了解一个知识点,两个class相同的前提是同一个类加载器加载的同一个类,这样得到的才是同一个Class.

            import java.io.IOException;
             import java.util.ArrayList;
             import java.util.List;
             
            diff --git a/assets/MountDisk.html-AWo0fohf.js b/assets/MountDisk.html-BqspOYo5.js
            similarity index 99%
            rename from assets/MountDisk.html-AWo0fohf.js
            rename to assets/MountDisk.html-BqspOYo5.js
            index 26c89dd136..9159f0e8ad 100644
            --- a/assets/MountDisk.html-AWo0fohf.js
            +++ b/assets/MountDisk.html-BqspOYo5.js
            @@ -1,4 +1,4 @@
            -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as s,c as d,a as e,b as l,d as a,e as r}from"./app-CVMfKeWw.js";const o={},c=e("h2",{id:"参考",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#参考"},[e("span",null,"参考")])],-1),v={href:"https://support.huaweicloud.com/qs-evs/evs_01_0033.html",target:"_blank",rel:"noopener noreferrer"},p=r(`

            挂载

            划分分区并挂载磁盘

            本操作以该场景为例,当云服务器挂载了一块新的数据盘时,使用fdisk分区工具将该数据盘设为主分区,分区形式默认设置为MBR,文件系统设为ext4格式,挂载在“/mnt/sdc”下,并设置开机启动自动挂载。

            1. fdisk -l 回显类似如下信息:
            [root@ecs-test-0001 ~]# fdisk -l
            +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as s,c as d,a as e,b as l,d as a,e as r}from"./app-_Oi5YZFn.js";const o={},c=e("h2",{id:"参考",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#参考"},[e("span",null,"参考")])],-1),v={href:"https://support.huaweicloud.com/qs-evs/evs_01_0033.html",target:"_blank",rel:"noopener noreferrer"},p=r(`

            挂载

            划分分区并挂载磁盘

            本操作以该场景为例,当云服务器挂载了一块新的数据盘时,使用fdisk分区工具将该数据盘设为主分区,分区形式默认设置为MBR,文件系统设为ext4格式,挂载在“/mnt/sdc”下,并设置开机启动自动挂载。

            1. fdisk -l 回显类似如下信息:
            [root@ecs-test-0001 ~]# fdisk -l
             
             Disk /dev/vda: 42.9 GB, 42949672960 bytes, 83886080 sectors
             Units = sectors of 1 * 512 = 512 bytes
            diff --git a/assets/MultiNetworkCard.html-ZHqfhuQy.js b/assets/MultiNetworkCard.html-BdZVkWRH.js
            similarity index 99%
            rename from assets/MultiNetworkCard.html-ZHqfhuQy.js
            rename to assets/MultiNetworkCard.html-BdZVkWRH.js
            index 0f3b318a12..fa3a9a0969 100644
            --- a/assets/MultiNetworkCard.html-ZHqfhuQy.js
            +++ b/assets/MultiNetworkCard.html-BdZVkWRH.js
            @@ -1,4 +1,4 @@
            -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as a,e as s}from"./app-CVMfKeWw.js";const t={},i=s(`

            1.1 kde桌面双网卡内外网设置

            环境如下:

            $ screenfetch   
            +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as a,e as s}from"./app-_Oi5YZFn.js";const t={},i=s(`

            1.1 kde桌面双网卡内外网设置

            环境如下:

            $ screenfetch   
             
              ██████████████████  ████████     chenkun@chenkun-pc
              ██████████████████  ████████     OS: Manjaro 21.3.7 Ruah
            diff --git a/assets/MybatisPlusDataSource.html-2C9lgUhC.js b/assets/MybatisPlusDataSource.html-J7-G54ls.js
            similarity index 99%
            rename from assets/MybatisPlusDataSource.html-2C9lgUhC.js
            rename to assets/MybatisPlusDataSource.html-J7-G54ls.js
            index a8fc927b46..19499de593 100644
            --- a/assets/MybatisPlusDataSource.html-2C9lgUhC.js
            +++ b/assets/MybatisPlusDataSource.html-J7-G54ls.js
            @@ -1,4 +1,4 @@
            -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as e,c,a as s,b as n,d as o,e as l}from"./app-CVMfKeWw.js";const i={},u=l(`

            1、问题的背景

            有两个库,ccsx_data、ccsx_weibao,默认库是ccsx_data,我在代码中使用了>mybatis-pulus的@DS()注解,想切换到ccsx_weibao这个库,但是切换一直失败,代码如下:

            	@DS("ccsx_weibao")
            +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as e,c,a as s,b as n,d as o,e as l}from"./app-_Oi5YZFn.js";const i={},u=l(`

            1、问题的背景

            有两个库,ccsx_data、ccsx_weibao,默认库是ccsx_data,我在代码中使用了>mybatis-pulus的@DS()注解,想切换到ccsx_weibao这个库,但是切换一直失败,代码如下:

            	@DS("ccsx_weibao")
             	public IPage<InstallRecordVO> queryByPage(Page page, InstallRecordSearchVO installRecordSearchVO, DataScope dataScope) {
             		Wrapper<InstallRecordSearchVO> wrapper = QueryWrapperUtil.getWrapper(installRecordSearchVO);
             		IPage<InstallRecordVO> installRecordVOIPage = installRecordMapper.queryPageByDataScope(page, wrapper, dataScope);
            diff --git a/assets/MysqlCollate.html-D6Cxtc4N.js b/assets/MysqlCollate.html-BXuIpRKi.js
            similarity index 99%
            rename from assets/MysqlCollate.html-D6Cxtc4N.js
            rename to assets/MysqlCollate.html-BXuIpRKi.js
            index a3d5b398ab..d3e79f3935 100644
            --- a/assets/MysqlCollate.html-D6Cxtc4N.js
            +++ b/assets/MysqlCollate.html-BXuIpRKi.js
            @@ -1,4 +1,4 @@
            -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as p,c as o,a as n,b as s,d as l,e as c}from"./app-CVMfKeWw.js";const i={},r={href:"https://cloud.tencent.com/developer/article/1366841",target:"_blank",rel:"noopener noreferrer"},d=c(`

            在mysql中执行show create table &lt tablename>指令,可以看到一张表的建表语句,example如下:

            CREATE TABLE \`table1\` (
            +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as p,c as o,a as n,b as s,d as l,e as c}from"./app-_Oi5YZFn.js";const i={},r={href:"https://cloud.tencent.com/developer/article/1366841",target:"_blank",rel:"noopener noreferrer"},d=c(`

            在mysql中执行show create table &lt tablename>指令,可以看到一张表的建表语句,example如下:

            CREATE TABLE \`table1\` (
                 \`id\` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
                 \`field1\` text COLLATE utf8_unicode_ci NOT NULL COMMENT '字段1',
                 \`field2\` varchar(128) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '字段2',
            diff --git a/assets/MysqlMasterSlave.html-CfozUk_D.js b/assets/MysqlMasterSlave.html-CPoF9taw.js
            similarity index 99%
            rename from assets/MysqlMasterSlave.html-CfozUk_D.js
            rename to assets/MysqlMasterSlave.html-CPoF9taw.js
            index d8953a57fb..bf90f0c7f5 100644
            --- a/assets/MysqlMasterSlave.html-CfozUk_D.js
            +++ b/assets/MysqlMasterSlave.html-CPoF9taw.js
            @@ -1,4 +1,4 @@
            -import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c as i,a as s,b as a,d as e,e as t}from"./app-CVMfKeWw.js";const c={},r=t(`

            1、mysql主从复制

            1.1 搭建主从复制目的?

            为了实现读写分离,解决数据库性能问题,读写分离中,“读”的数据是从哪里来呢?其实他是从“写”库copy过来的

            1.2 使用docker搭建基于mysql8的主从复制

            1. 创建容器

              docker run --name mysql_master -p 3001:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:latest
              +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c as i,a as s,b as a,d as e,e as t}from"./app-_Oi5YZFn.js";const c={},r=t(`

              1、mysql主从复制

              1.1 搭建主从复制目的?

              为了实现读写分离,解决数据库性能问题,读写分离中,“读”的数据是从哪里来呢?其实他是从“写”库copy过来的

              1.2 使用docker搭建基于mysql8的主从复制

              1. 创建容器

                docker run --name mysql_master -p 3001:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:latest
                 

                这一步创建容器的目的是查看那以及获取mysql配置文件,然后把它的配置文件copy到宿主机,这样方便直接在宿主机修改mysql配置,如果不事先把mysql配置文件获取出来,直接用docker的-v去挂载的话会有问题,无法达到把docker容器配置映射到宿主机的目的(可能是我的方式不对)

              2. 把容器内mysql配置copy到宿主机,配置文件在/etc/mysql,直接把整个目录copy到宿主机

                docker cp 容器ID@:/etc/mysql /home/user/master #master 库的配置,路径可以自由在指定
                 docker cp 容器ID@:/etc/mysql /home/user/slave  #slave库配置
                 
              3. 分别修改master和slave的配置文件my.cnf,在mysqld下增加以下内容

                master配置

                [mysqld]
                diff --git a/assets/MysqlNote.html-C-U71JLc.js b/assets/MysqlNote.html-Cy1AGfO1.js
                similarity index 99%
                rename from assets/MysqlNote.html-C-U71JLc.js
                rename to assets/MysqlNote.html-Cy1AGfO1.js
                index b6e4339922..34460dc337 100644
                --- a/assets/MysqlNote.html-C-U71JLc.js
                +++ b/assets/MysqlNote.html-Cy1AGfO1.js
                @@ -1,4 +1,4 @@
                -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

                1、批量插入速度慢

                项目使用的MyBatis-plus批量插入效率很低,遂百度一下原因

                在jdbc的链接上加上rewriteBatchedStatements=true参数,可以解决此问题。

                默认情况下rewriteBatchedStatements=false,jdbc批量插入会判断rewriteBatchedStatements,当为true才会执行批量语句,以下从源码(以下jdbc驱动源码版本为8.0.20)角度分析:

                1. com.mysql.cj.jdbc.StatementImpl#executeBatch
                  @Override
                +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

                1、批量插入速度慢

                项目使用的MyBatis-plus批量插入效率很低,遂百度一下原因

                在jdbc的链接上加上rewriteBatchedStatements=true参数,可以解决此问题。

                默认情况下rewriteBatchedStatements=false,jdbc批量插入会判断rewriteBatchedStatements,当为true才会执行批量语句,以下从源码(以下jdbc驱动源码版本为8.0.20)角度分析:

                1. com.mysql.cj.jdbc.StatementImpl#executeBatch
                  @Override
                     public int[] executeBatch() throws SQLException {
                         //注意此处打开executeBatchInternal()源码要进入ClientPreparedStatement的executeBatchInternal方法,别进入StatementImpl的executeBatchInternal方法
                         return Util.truncateAndConvertToInt(executeBatchInternal()源码要进入());
                diff --git a/assets/MysqlRemoteConnect.html-DGYiJFHo.js b/assets/MysqlRemoteConnect.html-B6Rz_Dfl.js
                similarity index 98%
                rename from assets/MysqlRemoteConnect.html-DGYiJFHo.js
                rename to assets/MysqlRemoteConnect.html-B6Rz_Dfl.js
                index b2b89d8ca6..91ee5eb372 100644
                --- a/assets/MysqlRemoteConnect.html-DGYiJFHo.js
                +++ b/assets/MysqlRemoteConnect.html-B6Rz_Dfl.js
                @@ -1,3 +1,3 @@
                -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-CVMfKeWw.js";const s={},o=a(`

                默认情况下,mysql只允许本地登录,如果要开启远程连接,则需要修改/etc/mysql/my.conf文件。Mariadb可以去/etc/my.cnf看看引用的目录配置

                1、修改/etc/mysql/my.conf

                找到bind-address = 127.0.0.1这一行 改为bind-address = 0.0.0.0即可

                二、为需要远程登录的用户赋予权限

                1、新建用户远程连接mysql数据库 grant all on . to admin@'%' identified by '123456' with grant option; flush privileges; 允许任何ip地址(%表示允许任何ip地址)的电脑用admin帐户和密码(123456)来访问这个mysql server。 注意admin账户不一定要存在。

                2、支持root用户允许远程连接mysql数据库 grant all privileges on . to 'root'@'%' identified by '123456' with grant option; flush privileges;

                三、查看系统用户

                use mysql;
                +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-_Oi5YZFn.js";const s={},o=a(`

                默认情况下,mysql只允许本地登录,如果要开启远程连接,则需要修改/etc/mysql/my.conf文件。Mariadb可以去/etc/my.cnf看看引用的目录配置

                1、修改/etc/mysql/my.conf

                找到bind-address = 127.0.0.1这一行 改为bind-address = 0.0.0.0即可

                二、为需要远程登录的用户赋予权限

                1、新建用户远程连接mysql数据库 grant all on . to admin@'%' identified by '123456' with grant option; flush privileges; 允许任何ip地址(%表示允许任何ip地址)的电脑用admin帐户和密码(123456)来访问这个mysql server。 注意admin账户不一定要存在。

                2、支持root用户允许远程连接mysql数据库 grant all privileges on . to 'root'@'%' identified by '123456' with grant option; flush privileges;

                三、查看系统用户

                use mysql;
                 select user,host from user;
                 

                四、放开服务器对应端口

                `,9),i=[o];function r(c,m){return t(),n("div",null,i)}const p=e(s,[["render",r],["__file","MysqlRemoteConnect.html.vue"]]),h=JSON.parse(`{"path":"/other/database/MysqlRemoteConnect.html","title":"Mysql开启远程连接权限","lang":"zh-CN","frontmatter":{"title":"Mysql开启远程连接权限","date":"2022-05-10T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"默认情况下,mysql只允许本地登录,如果要开启远程连接,则需要修改/etc/mysql/my.conf文件。Mariadb可以去/etc/my.cnf看看引用的目录配置 1、修改/etc/mysql/my.conf 找到bind-address = 127.0.0.1这一行 改为bind-address = 0.0.0.0即可 二、为需要远程登录的用...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/database/MysqlRemoteConnect.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Mysql开启远程连接权限"}],["meta",{"property":"og:description","content":"默认情况下,mysql只允许本地登录,如果要开启远程连接,则需要修改/etc/mysql/my.conf文件。Mariadb可以去/etc/my.cnf看看引用的目录配置 1、修改/etc/mysql/my.conf 找到bind-address = 127.0.0.1这一行 改为bind-address = 0.0.0.0即可 二、为需要远程登录的用..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-10-08T07:30:29.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-05-10T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-10-08T07:30:29.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Mysql开启远程连接权限\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-05-10T00:00:00.000Z\\",\\"dateModified\\":\\"2024-10-08T07:30:29.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1728372629000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.76,"words":227},"filePathRelative":"other/database/MysqlRemoteConnect.md","localizedDate":"2022年5月10日","excerpt":"\\n
                \\n

                默认情况下,mysql只允许本地登录,如果要开启远程连接,则需要修改/etc/mysql/my.conf文件。Mariadb可以去/etc/my.cnf看看引用的目录配置

                \\n
                \\n

                1、修改/etc/mysql/my.conf

                \\n

                找到bind-address = 127.0.0.1这一行\\n改为bind-address = 0.0.0.0即可

                \\n

                二、为需要远程登录的用户赋予权限

                \\n

                1、新建用户远程连接mysql数据库\\ngrant all on . to admin@'%' identified by '123456' with grant option;\\nflush privileges;\\n允许任何ip地址(%表示允许任何ip地址)的电脑用admin帐户和密码(123456)来访问这个mysql server。\\n注意admin账户不一定要存在。

                ","autoDesc":true}`);export{p as comp,h as data}; diff --git a/assets/Nacos.html-D6kbUFLh.js b/assets/Nacos.html-DC2XGbQ_.js similarity index 98% rename from assets/Nacos.html-D6kbUFLh.js rename to assets/Nacos.html-DC2XGbQ_.js index de738fe3f0..9f212aaa8e 100644 --- a/assets/Nacos.html-D6kbUFLh.js +++ b/assets/Nacos.html-DC2XGbQ_.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o,e as p}from"./app-CVMfKeWw.js";const a={},n=p('

                1、配置管理

                命名空间——>Group——>Data Id——>配置项

                命名空间:

                默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。

                如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM代表bom系统。

                image-20220602103505994

                Group

                group是应用程序(服务)粒度的隔离,我在dev环境有两个微服务,中控微服务使用CCS分组下的配置,bom微服务使用bom分组下的配置

                DataId

                配置集,就是一个配置文件,类似springboot中的application.yml

                ',9),r=[n];function i(c,s){return e(),o("div",null,r)}const m=t(a,[["render",i],["__file","Nacos.html.vue"]]),u=JSON.parse('{"path":"/other/distributeservice/Nacos.html","title":"Nacos学习","lang":"zh-CN","frontmatter":{"title":"Nacos学习","date":"2022-03-17T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"1、配置管理 命名空间——>Group——>Data Id——>配置项 命名空间: 默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。 如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/distributeservice/Nacos.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Nacos学习"}],["meta",{"property":"og:description","content":"1、配置管理 命名空间——>Group——>Data Id——>配置项 命名空间: 默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。 如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/image-20220602103505994.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-03-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Nacos学习\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220602103505994.png\\"],\\"datePublished\\":\\"2022-03-17T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、配置管理","slug":"_1、配置管理","link":"#_1、配置管理","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":3},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.7,"words":209},"filePathRelative":"other/distributeservice/Nacos.md","localizedDate":"2022年3月17日","excerpt":"

                1、配置管理

                \\n

                命名空间——>Group——>Data Id——>配置项

                \\n

                命名空间:

                \\n
                \\n

                默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。

                \\n

                如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM代表bom系统。

                \\n
                \\n

                \\"image-20220602103505994\\"

                ","autoDesc":true}');export{m as comp,u as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o,e as p}from"./app-_Oi5YZFn.js";const a={},n=p('

                1、配置管理

                命名空间——>Group——>Data Id——>配置项

                命名空间:

                默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。

                如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM代表bom系统。

                image-20220602103505994

                Group

                group是应用程序(服务)粒度的隔离,我在dev环境有两个微服务,中控微服务使用CCS分组下的配置,bom微服务使用bom分组下的配置

                DataId

                配置集,就是一个配置文件,类似springboot中的application.yml

                ',9),r=[n];function i(c,s){return e(),o("div",null,r)}const m=t(a,[["render",i],["__file","Nacos.html.vue"]]),u=JSON.parse('{"path":"/other/distributeservice/Nacos.html","title":"Nacos学习","lang":"zh-CN","frontmatter":{"title":"Nacos学习","date":"2022-03-17T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"1、配置管理 命名空间——>Group——>Data Id——>配置项 命名空间: 默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。 如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/distributeservice/Nacos.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Nacos学习"}],["meta",{"property":"og:description","content":"1、配置管理 命名空间——>Group——>Data Id——>配置项 命名空间: 默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。 如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/image-20220602103505994.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-03-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Nacos学习\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220602103505994.png\\"],\\"datePublished\\":\\"2022-03-17T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、配置管理","slug":"_1、配置管理","link":"#_1、配置管理","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":3},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.7,"words":209},"filePathRelative":"other/distributeservice/Nacos.md","localizedDate":"2022年3月17日","excerpt":"

                1、配置管理

                \\n

                命名空间——>Group——>Data Id——>配置项

                \\n

                命名空间:

                \\n
                \\n

                默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。

                \\n

                如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM代表bom系统。

                \\n
                \\n

                \\"image-20220602103505994\\"

                ","autoDesc":true}');export{m as comp,u as data}; diff --git a/assets/NativeMethod.html-BICH8rB4.js b/assets/NativeMethod.html-CW2rOE3c.js similarity index 99% rename from assets/NativeMethod.html-BICH8rB4.js rename to assets/NativeMethod.html-CW2rOE3c.js index fe08820fd7..69bf7ad992 100644 --- a/assets/NativeMethod.html-BICH8rB4.js +++ b/assets/NativeMethod.html-CW2rOE3c.js @@ -1,4 +1,4 @@ -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as e,e as s}from"./app-CVMfKeWw.js";const i={},t=s(`

                使用c++实现一个native方法供java调用

                环境

                实验环境linux、jdk11、gcc
                +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as e,e as s}from"./app-_Oi5YZFn.js";const i={},t=s(`

                使用c++实现一个native方法供java调用

                环境

                实验环境linux、jdk11、gcc
                 

                1、native方法

                2、自定义实现native方法

                用c++实现一个动态链接库,使用java调用链接库中的方法
                 

                2.1 定义java源文件

                public class TestMain
                 {
                diff --git a/assets/NewObject.html-_zRwVPgF.js b/assets/NewObject.html-DNLlDT5H.js
                similarity index 99%
                rename from assets/NewObject.html-_zRwVPgF.js
                rename to assets/NewObject.html-DNLlDT5H.js
                index d40f88fcde..5193885561 100644
                --- a/assets/NewObject.html-_zRwVPgF.js
                +++ b/assets/NewObject.html-DNLlDT5H.js
                @@ -1,4 +1,4 @@
                -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-CVMfKeWw.js";const t={},p=e(`

                1、在类中本地变量引用自身类,会引发的问题

                public class BaseFormBean {
                +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

                1、在类中本地变量引用自身类,会引发的问题

                public class BaseFormBean {
                     private BaseFormBean baseBean = new BaseFormBean();
                     {
                          String bar = "非静态代码块中字段";
                diff --git a/assets/OAUTH_LOGIN.html-Dt5KbJsg.js b/assets/OAUTH_LOGIN.html-DZ9PB-Wb.js
                similarity index 99%
                rename from assets/OAUTH_LOGIN.html-Dt5KbJsg.js
                rename to assets/OAUTH_LOGIN.html-DZ9PB-Wb.js
                index 9536b11ee8..70f648a9b1 100644
                --- a/assets/OAUTH_LOGIN.html-Dt5KbJsg.js
                +++ b/assets/OAUTH_LOGIN.html-DZ9PB-Wb.js
                @@ -1,4 +1,4 @@
                -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

                本博客介绍前后端分离项目的完整接入oauth的流程,本博客使用github来示范,因为github注册oauth应用无须审核,微信审核特别麻烦,并且个人用户无法注册

                1 oauth2

                1.1 典型应用场景

                    1. 社交帐号登录应用,比如使用微信、微博登录其它应用
                +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

                本博客介绍前后端分离项目的完整接入oauth的流程,本博客使用github来示范,因为github注册oauth应用无须审核,微信审核特别麻烦,并且个人用户无法注册

                1 oauth2

                1.1 典型应用场景

                    1. 社交帐号登录应用,比如使用微信、微博登录其它应用
                     2. 从第三方获取用户资料比如:手机号、邮箱、头像等
                     3. 从第三方获取业务数据,比如:通过自己的系统想从京东获取订单
                 

                1.2 完整时序图

                假设有一个软件叫开立权限管理系统,该系统接入了github登录,则使用github登录的完整的oauth流程如下:

                sequenceDiagram
                diff --git a/assets/OAuth2Authentication.html-BPOz3SI3.js b/assets/OAuth2Authentication.html-ChmdjSKd.js
                similarity index 99%
                rename from assets/OAuth2Authentication.html-BPOz3SI3.js
                rename to assets/OAuth2Authentication.html-ChmdjSKd.js
                index 7c16be902a..d32611df71 100644
                --- a/assets/OAuth2Authentication.html-BPOz3SI3.js
                +++ b/assets/OAuth2Authentication.html-ChmdjSKd.js
                @@ -1 +1 @@
                -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as n,e as a}from"./app-CVMfKeWw.js";const i={},o=a('

                1、时序图

                时序图

                2、流程解析

                本流程是以使用Ruoyi对接Pig授权中心为例,进行讲解,其他网站的的oauth的原理都和这个一样,所以只要把这个流程搞懂了即可,接下来就按照真实的流程进行逐步解析。

                2.1 第1步

                用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页
                20230104105625

                2.2 第2步

                点击SSO登录会访问如下这样子的一个url,申请授权,(127.0.0.1:3000就是授权服务器) https://127.0.0.1:3000/oauth/authorize?client_id=ruyi&response_type=code&scope=server&redirect_uri=http://127.0.0.1:1024/sso&TENANT-ID=1,当授权服务收到这个请求时会发现用户还未登录授权服务器,会重定向到授权服务器的登录页面http://127.0.0.1:3000/token/login,大概就是下面这样

                20230104105039

                2.3 第3步

                用户输入帐号密码提交后pig授权中心会校验ruoyi客户端是否合法,就是验证你这个客户端是否在pigx中已经注册,验证通过再验证你输入的用户名密码是否正确,验证通过后再生成一个授权页面,就是和平时我们微信授权给第三方小程序弹出一个让你确认授权的那个界面差不多,参考下一步

                2.4 第4步

                用户点击授权, 20230104105954

                2.5 第5步

                pig授权中心生成授权码

                2.6 第6步

                pig产生授权码后,会带着这个授权码重定向到注册客户端时填的那个地址,这里就是http://127.0.0.1:1024/sso?code=U1wLD7 这个地址是在数据库注册好的,它是ruoyi前端的一个页面,并不是后端接口

                20230104110557

                20230104110449

                2.7 第7步

                ruoyi前端构造一个请求,并且携带code请求ruoyi后端接口, 2023010411090920230104110921

                2.8 第8步

                ruoyi后端拿到code,发送post请求到http://127.0.0.1:3000/oauth/token获取token,注意这是个oauth的默认端点,不是用户写的,在TokenEndPoint类中 20230104111117

                2.9 第9步

                pig授权中心生成token,这里生成token的逻辑可以自定义实现,具体的请参考pig的源码TokenService类 20230104111420

                2.10 第10步

                pig授权中心生成的token返回到ruoyi后端,ruoyi后端拿到token 20230104111504

                2.11 第11步

                有了token后面的就不再赘述,可以拿token访问资源了,这里其实仅仅用了oauth2的特性来做了一个sso单点登录,并没有用到oauth2的核心来进行资源服务器的访问,oauth2的核心其实是用来无需帐号密码即可访问资源服务器的资源。

                ',29),p=[o];function s(h,c){return e(),n("div",null,p)}const d=t(i,[["render",s],["__file","OAuth2Authentication.html.vue"]]),g=JSON.parse('{"path":"/java/framework/security/OAuth2Authentication.html","title":"Ruoyi使用oauth对接pig","lang":"zh-CN","frontmatter":{"title":"Ruoyi使用oauth对接pig","date":"2023-01-03T00:00:00.000Z","author":"chenkun","keys":null,"category":["Security","OAuth"],"description":"1、时序图 时序图 2、流程解析 本流程是以使用Ruoyi对接Pig授权中心为例,进行讲解,其他网站的的oauth的原理都和这个一样,所以只要把这个流程搞懂了即可,接下来就按照真实的流程进行逐步解析。 2.1 第1步 用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页 20230104105625 2.2 第2步 点击SSO登录会访...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/OAuth2Authentication.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Ruoyi使用oauth对接pig"}],["meta",{"property":"og:description","content":"1、时序图 时序图 2、流程解析 本流程是以使用Ruoyi对接Pig授权中心为例,进行讲解,其他网站的的oauth的原理都和这个一样,所以只要把这个流程搞懂了即可,接下来就按照真实的流程进行逐步解析。 2.1 第1步 用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页 20230104105625 2.2 第2步 点击SSO登录会访..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/oauth.drawio.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2023-01-03T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Ruoyi使用oauth对接pig\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/oauth.drawio.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104105625.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104105039.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104105954.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104110557.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104110449.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104110909.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104110921.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104111117.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104111420.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104111504.png\\"],\\"datePublished\\":\\"2023-01-03T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、时序图","slug":"_1、时序图","link":"#_1、时序图","children":[]},{"level":2,"title":"2、流程解析","slug":"_2、流程解析","link":"#_2、流程解析","children":[{"level":3,"title":"2.1 第1步","slug":"_2-1-第1步","link":"#_2-1-第1步","children":[]},{"level":3,"title":"2.2 第2步","slug":"_2-2-第2步","link":"#_2-2-第2步","children":[]},{"level":3,"title":"2.3 第3步","slug":"_2-3-第3步","link":"#_2-3-第3步","children":[]},{"level":3,"title":"2.4 第4步","slug":"_2-4-第4步","link":"#_2-4-第4步","children":[]},{"level":3,"title":"2.5 第5步","slug":"_2-5-第5步","link":"#_2-5-第5步","children":[]},{"level":3,"title":"2.6 第6步","slug":"_2-6-第6步","link":"#_2-6-第6步","children":[]},{"level":3,"title":"2.7 第7步","slug":"_2-7-第7步","link":"#_2-7-第7步","children":[]},{"level":3,"title":"2.8 第8步","slug":"_2-8-第8步","link":"#_2-8-第8步","children":[]},{"level":3,"title":"2.9 第9步","slug":"_2-9-第9步","link":"#_2-9-第9步","children":[]},{"level":3,"title":"2.10 第10步","slug":"_2-10-第10步","link":"#_2-10-第10步","children":[]},{"level":3,"title":"2.11 第11步","slug":"_2-11-第11步","link":"#_2-11-第11步","children":[]}]}],"git":{"createdTime":1672740702000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":3},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":3}]},"readingTime":{"minutes":2.4,"words":720},"filePathRelative":"java/framework/security/OAuth2Authentication.md","localizedDate":"2023年1月3日","excerpt":"

                1、时序图

                \\n

                \\"时序图\\"

                \\n

                2、流程解析

                \\n
                \\n

                本流程是以使用Ruoyi对接Pig授权中心为例,进行讲解,其他网站的的oauth的原理都和这个一样,所以只要把这个流程搞懂了即可,接下来就按照真实的流程进行逐步解析。

                \\n
                \\n

                2.1 第1步

                \\n

                用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页
                \\n\\"20230104105625\\"

                ","autoDesc":true}');export{d as comp,g as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as n,e as a}from"./app-_Oi5YZFn.js";const i={},o=a('

                1、时序图

                时序图

                2、流程解析

                本流程是以使用Ruoyi对接Pig授权中心为例,进行讲解,其他网站的的oauth的原理都和这个一样,所以只要把这个流程搞懂了即可,接下来就按照真实的流程进行逐步解析。

                2.1 第1步

                用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页
                20230104105625

                2.2 第2步

                点击SSO登录会访问如下这样子的一个url,申请授权,(127.0.0.1:3000就是授权服务器) https://127.0.0.1:3000/oauth/authorize?client_id=ruyi&response_type=code&scope=server&redirect_uri=http://127.0.0.1:1024/sso&TENANT-ID=1,当授权服务收到这个请求时会发现用户还未登录授权服务器,会重定向到授权服务器的登录页面http://127.0.0.1:3000/token/login,大概就是下面这样

                20230104105039

                2.3 第3步

                用户输入帐号密码提交后pig授权中心会校验ruoyi客户端是否合法,就是验证你这个客户端是否在pigx中已经注册,验证通过再验证你输入的用户名密码是否正确,验证通过后再生成一个授权页面,就是和平时我们微信授权给第三方小程序弹出一个让你确认授权的那个界面差不多,参考下一步

                2.4 第4步

                用户点击授权, 20230104105954

                2.5 第5步

                pig授权中心生成授权码

                2.6 第6步

                pig产生授权码后,会带着这个授权码重定向到注册客户端时填的那个地址,这里就是http://127.0.0.1:1024/sso?code=U1wLD7 这个地址是在数据库注册好的,它是ruoyi前端的一个页面,并不是后端接口

                20230104110557

                20230104110449

                2.7 第7步

                ruoyi前端构造一个请求,并且携带code请求ruoyi后端接口, 2023010411090920230104110921

                2.8 第8步

                ruoyi后端拿到code,发送post请求到http://127.0.0.1:3000/oauth/token获取token,注意这是个oauth的默认端点,不是用户写的,在TokenEndPoint类中 20230104111117

                2.9 第9步

                pig授权中心生成token,这里生成token的逻辑可以自定义实现,具体的请参考pig的源码TokenService类 20230104111420

                2.10 第10步

                pig授权中心生成的token返回到ruoyi后端,ruoyi后端拿到token 20230104111504

                2.11 第11步

                有了token后面的就不再赘述,可以拿token访问资源了,这里其实仅仅用了oauth2的特性来做了一个sso单点登录,并没有用到oauth2的核心来进行资源服务器的访问,oauth2的核心其实是用来无需帐号密码即可访问资源服务器的资源。

                ',29),p=[o];function s(h,c){return e(),n("div",null,p)}const d=t(i,[["render",s],["__file","OAuth2Authentication.html.vue"]]),g=JSON.parse('{"path":"/java/framework/security/OAuth2Authentication.html","title":"Ruoyi使用oauth对接pig","lang":"zh-CN","frontmatter":{"title":"Ruoyi使用oauth对接pig","date":"2023-01-03T00:00:00.000Z","author":"chenkun","keys":null,"category":["Security","OAuth"],"description":"1、时序图 时序图 2、流程解析 本流程是以使用Ruoyi对接Pig授权中心为例,进行讲解,其他网站的的oauth的原理都和这个一样,所以只要把这个流程搞懂了即可,接下来就按照真实的流程进行逐步解析。 2.1 第1步 用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页 20230104105625 2.2 第2步 点击SSO登录会访...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/OAuth2Authentication.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Ruoyi使用oauth对接pig"}],["meta",{"property":"og:description","content":"1、时序图 时序图 2、流程解析 本流程是以使用Ruoyi对接Pig授权中心为例,进行讲解,其他网站的的oauth的原理都和这个一样,所以只要把这个流程搞懂了即可,接下来就按照真实的流程进行逐步解析。 2.1 第1步 用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页 20230104105625 2.2 第2步 点击SSO登录会访..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/oauth.drawio.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2023-01-03T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Ruoyi使用oauth对接pig\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/oauth.drawio.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104105625.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104105039.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104105954.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104110557.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104110449.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104110909.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104110921.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104111117.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104111420.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104111504.png\\"],\\"datePublished\\":\\"2023-01-03T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、时序图","slug":"_1、时序图","link":"#_1、时序图","children":[]},{"level":2,"title":"2、流程解析","slug":"_2、流程解析","link":"#_2、流程解析","children":[{"level":3,"title":"2.1 第1步","slug":"_2-1-第1步","link":"#_2-1-第1步","children":[]},{"level":3,"title":"2.2 第2步","slug":"_2-2-第2步","link":"#_2-2-第2步","children":[]},{"level":3,"title":"2.3 第3步","slug":"_2-3-第3步","link":"#_2-3-第3步","children":[]},{"level":3,"title":"2.4 第4步","slug":"_2-4-第4步","link":"#_2-4-第4步","children":[]},{"level":3,"title":"2.5 第5步","slug":"_2-5-第5步","link":"#_2-5-第5步","children":[]},{"level":3,"title":"2.6 第6步","slug":"_2-6-第6步","link":"#_2-6-第6步","children":[]},{"level":3,"title":"2.7 第7步","slug":"_2-7-第7步","link":"#_2-7-第7步","children":[]},{"level":3,"title":"2.8 第8步","slug":"_2-8-第8步","link":"#_2-8-第8步","children":[]},{"level":3,"title":"2.9 第9步","slug":"_2-9-第9步","link":"#_2-9-第9步","children":[]},{"level":3,"title":"2.10 第10步","slug":"_2-10-第10步","link":"#_2-10-第10步","children":[]},{"level":3,"title":"2.11 第11步","slug":"_2-11-第11步","link":"#_2-11-第11步","children":[]}]}],"git":{"createdTime":1672740702000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":3},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":3}]},"readingTime":{"minutes":2.4,"words":720},"filePathRelative":"java/framework/security/OAuth2Authentication.md","localizedDate":"2023年1月3日","excerpt":"

                1、时序图

                \\n

                \\"时序图\\"

                \\n

                2、流程解析

                \\n
                \\n

                本流程是以使用Ruoyi对接Pig授权中心为例,进行讲解,其他网站的的oauth的原理都和这个一样,所以只要把这个流程搞懂了即可,接下来就按照真实的流程进行逐步解析。

                \\n
                \\n

                2.1 第1步

                \\n

                用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页
                \\n\\"20230104105625\\"

                ","autoDesc":true}');export{d as comp,g as data}; diff --git a/assets/ObjectReference.html-BttKKJld.js b/assets/ObjectReference.html-CG8KX2PY.js similarity index 99% rename from assets/ObjectReference.html-BttKKJld.js rename to assets/ObjectReference.html-CG8KX2PY.js index 63fbb7ded6..4fd159ebbc 100644 --- a/assets/ObjectReference.html-BttKKJld.js +++ b/assets/ObjectReference.html-CG8KX2PY.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},p=e(`

                1、强引用

                StrongReference,java中默认的引用类型都是强引用,比如\`Objectg obj = new Object()\`,这个obj就是强引用。强引用的特性是只要引用存在,被引用的对象就不会被垃圾回收器回收,这个比较简单,不做测试。
                +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

                1、强引用

                StrongReference,java中默认的引用类型都是强引用,比如\`Objectg obj = new Object()\`,这个obj就是强引用。强引用的特性是只要引用存在,被引用的对象就不会被垃圾回收器回收,这个比较简单,不做测试。
                 

                2、软引用

                SoftReference,软引用代码比较简单,直接调用其构造函数即可构造一个软引用对象。软引用的特性是只要虚拟机内存够用,则这个对象会 一直存在,不会被垃圾回收器回收,当内存不够时会触发垃圾回收,当回收后内存够用则不会回收软引用的对象,当执行了一次回收后内存依然不够才会考虑回收软引用的对象。

                    /**
                      * 堆内存分配30m,默认情况老年代和新生代2:1,eden和survivor是8:1,因此老年代是
                      * 20m,新生代10m,其中eden是8m,suvivor是1m,因此新生代总共8+1=9m,还有1m是浪* 费的
                diff --git a/assets/OncePerRequestFilter.html-BEOxLqvE.js b/assets/OncePerRequestFilter.html-DIikqGft.js
                similarity index 99%
                rename from assets/OncePerRequestFilter.html-BEOxLqvE.js
                rename to assets/OncePerRequestFilter.html-DIikqGft.js
                index e2129981e8..f4f34a14a9 100644
                --- a/assets/OncePerRequestFilter.html-BEOxLqvE.js
                +++ b/assets/OncePerRequestFilter.html-DIikqGft.js
                @@ -1,4 +1,4 @@
                -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},p=e(`

                1、OnecePerRequestFilter初识

                第一次接触这个类,在SpringSecurity中,大概百度了一下,知道此类是限制一次请求只
                +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

                1、OnecePerRequestFilter初识

                第一次接触这个类,在SpringSecurity中,大概百度了一下,知道此类是限制一次请求只
                 走一次过滤器,但是我不明白为啥要做这个限制,或者说难道还有一次请求会走两次过滤器?
                 

                1.1 源码doc

                学习一个框架最好的文档肯定是看官方doc,以下是官方doc对此类的注释,简单来说就是确保 一个请求在一个过滤器只执行一次doFilter,因在在不同版本的Servlet容器中是存在多次执 行doFilter的可能的,比如一个request forward到另一个request,在servlet2.0和3.0 表现可能都不一样,在Tomcat和weblogic容器中可能表现也不一样,为了统一此行为,所以 Spring官方提供了此类。

                Filter base class that aims to guarantee a single execution per request dispatch, on any servlet container. It provides a doFilterInternal method with HttpServletRequest and HttpServletResponse arguments.
                 As of Servlet 3.0, a filter may be invoked as part of a REQUEST or ASYNC dispatches that occur in separate threads. A filter can be configured in web.xml whether it should be involved in async dispatches. However, in some cases servlet containers assume different default configuration. Therefore, subclasses can override the method shouldNotFilterAsyncDispatch() to declare statically if they should indeed be invoked, once, during both types of dispatches in order to provide thread initialization, logging, security, and so on. This mechanism complements and does not replace the need to configure a filter in web.xml with dispatcher types.
                diff --git a/assets/OncePerRequestFilter.html-C_TVU5f6.js b/assets/OncePerRequestFilter.html-DvJ1C4ir.js
                similarity index 99%
                rename from assets/OncePerRequestFilter.html-C_TVU5f6.js
                rename to assets/OncePerRequestFilter.html-DvJ1C4ir.js
                index 4762d6001c..8cc0627056 100644
                --- a/assets/OncePerRequestFilter.html-C_TVU5f6.js
                +++ b/assets/OncePerRequestFilter.html-DvJ1C4ir.js
                @@ -1 +1 @@
                -import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as s,a as e,b as t,d as n}from"./app-CVMfKeWw.js";const a={},c=e("h2",{id:"_1、onceperrequestfilter",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、onceperrequestfilter"},[e("span",null,"1、OncePerRequestFilter")])],-1),p=e("p",null,[e("code",null,"org.springframework.web.filter.OncePerRequestFilter"),t('是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次? 其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤。因为Springweb无法保证每个用户使用的Servlet容器是一样的,因此,在有些场景如果我们要确保同一个请求只能被过滤器处理一次,那就需要spring自己来实现了,因此有了这个类。其注释下写的很清楚,就是为了要陈any servlet container都确保只执行一次。')],-1),d=e("p",null,[e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/20221103172449.png",alt:"20221103172449"})],-1),h={href:"https://www.baeldung.com/spring-onceperrequestfilter",target:"_blank",rel:"noopener noreferrer"},g=e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/20221103173249.png",alt:"20221103173249"},null,-1),m=e("h2",{id:"_2、参考",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2、参考"},[e("span",null,"2、参考")])],-1),u={href:"https://blog.csdn.net/weixin_43944305/article/details/119923969",target:"_blank",rel:"noopener noreferrer"},_=e("br",null,null,-1),f={href:"https://blog.csdn.net/zl1zl2zl3/article/details/79270664",target:"_blank",rel:"noopener noreferrer"};function b(q,w){const r=o("ExternalLinkIcon");return l(),s("div",null,[c,p,d,e("p",null,[t("最开始被误导是因为看了"),e("a",h,[t("这篇"),n(r)]),g,t(" 看完这篇,我就做实验想复现他说的这个,因为我用的springboot,内嵌的tomcat是9.x版本,对应的servlet是4.0,这个版本servlet不存在它说的问题,所以一直无法复现,导致我理解出现了偏差。后来看了别人博客,才知道怎么回事。")]),m,e("p",null,[e("a",u,[t("Spring的OncePerRequestFilter过滤器"),n(r)]),_,e("a",f,[t("Spring的OncePerRequestFilter的作用"),n(r)])])])}const P=i(a,[["render",b],["__file","OncePerRequestFilter.html.vue"]]),O=JSON.parse('{"path":"/java/framework/spring/OncePerRequestFilter.html","title":"OncePerRequestFilter","lang":"zh-CN","frontmatter":{"title":"OncePerRequestFilter","date":"2018-05-29T00:00:00.000Z","author":"chensino","description":"1、OncePerRequestFilter org.springframework.web.filter.OncePerRequestFilter是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次? 其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在s...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/spring/OncePerRequestFilter.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"OncePerRequestFilter"}],["meta",{"property":"og:description","content":"1、OncePerRequestFilter org.springframework.web.filter.OncePerRequestFilter是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次? 其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在s..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221103172449.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"OncePerRequestFilter\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221103172449.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221103173249.png\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":2,"title":"1、OncePerRequestFilter","slug":"_1、onceperrequestfilter","link":"#_1、onceperrequestfilter","children":[]},{"level":2,"title":"2、参考","slug":"_2、参考","link":"#_2、参考","children":[]}],"git":{"createdTime":1667467771000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":3},{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":1.26,"words":378},"filePathRelative":"java/framework/spring/OncePerRequestFilter.md","localizedDate":"2018年5月29日","excerpt":"

                1、OncePerRequestFilter

                \\n

                org.springframework.web.filter.OncePerRequestFilter是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次?\\n其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file=\\"/index.jsp\\"%>的情况。到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤。因为Springweb无法保证每个用户使用的Servlet容器是一样的,因此,在有些场景如果我们要确保同一个请求只能被过滤器处理一次,那就需要spring自己来实现了,因此有了这个类。其注释下写的很清楚,就是为了要陈any servlet container都确保只执行一次。

                ","autoDesc":true}');export{P as comp,O as data}; +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as s,a as e,b as t,d as n}from"./app-_Oi5YZFn.js";const a={},c=e("h2",{id:"_1、onceperrequestfilter",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、onceperrequestfilter"},[e("span",null,"1、OncePerRequestFilter")])],-1),p=e("p",null,[e("code",null,"org.springframework.web.filter.OncePerRequestFilter"),t('是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次? 其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤。因为Springweb无法保证每个用户使用的Servlet容器是一样的,因此,在有些场景如果我们要确保同一个请求只能被过滤器处理一次,那就需要spring自己来实现了,因此有了这个类。其注释下写的很清楚,就是为了要陈any servlet container都确保只执行一次。')],-1),d=e("p",null,[e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/20221103172449.png",alt:"20221103172449"})],-1),h={href:"https://www.baeldung.com/spring-onceperrequestfilter",target:"_blank",rel:"noopener noreferrer"},g=e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/20221103173249.png",alt:"20221103173249"},null,-1),m=e("h2",{id:"_2、参考",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2、参考"},[e("span",null,"2、参考")])],-1),u={href:"https://blog.csdn.net/weixin_43944305/article/details/119923969",target:"_blank",rel:"noopener noreferrer"},_=e("br",null,null,-1),f={href:"https://blog.csdn.net/zl1zl2zl3/article/details/79270664",target:"_blank",rel:"noopener noreferrer"};function b(q,w){const r=o("ExternalLinkIcon");return l(),s("div",null,[c,p,d,e("p",null,[t("最开始被误导是因为看了"),e("a",h,[t("这篇"),n(r)]),g,t(" 看完这篇,我就做实验想复现他说的这个,因为我用的springboot,内嵌的tomcat是9.x版本,对应的servlet是4.0,这个版本servlet不存在它说的问题,所以一直无法复现,导致我理解出现了偏差。后来看了别人博客,才知道怎么回事。")]),m,e("p",null,[e("a",u,[t("Spring的OncePerRequestFilter过滤器"),n(r)]),_,e("a",f,[t("Spring的OncePerRequestFilter的作用"),n(r)])])])}const P=i(a,[["render",b],["__file","OncePerRequestFilter.html.vue"]]),O=JSON.parse('{"path":"/java/framework/spring/OncePerRequestFilter.html","title":"OncePerRequestFilter","lang":"zh-CN","frontmatter":{"title":"OncePerRequestFilter","date":"2018-05-29T00:00:00.000Z","author":"chensino","description":"1、OncePerRequestFilter org.springframework.web.filter.OncePerRequestFilter是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次? 其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在s...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/spring/OncePerRequestFilter.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"OncePerRequestFilter"}],["meta",{"property":"og:description","content":"1、OncePerRequestFilter org.springframework.web.filter.OncePerRequestFilter是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次? 其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在s..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221103172449.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"OncePerRequestFilter\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221103172449.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221103173249.png\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":2,"title":"1、OncePerRequestFilter","slug":"_1、onceperrequestfilter","link":"#_1、onceperrequestfilter","children":[]},{"level":2,"title":"2、参考","slug":"_2、参考","link":"#_2、参考","children":[]}],"git":{"createdTime":1667467771000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":3},{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":1.26,"words":378},"filePathRelative":"java/framework/spring/OncePerRequestFilter.md","localizedDate":"2018年5月29日","excerpt":"

                1、OncePerRequestFilter

                \\n

                org.springframework.web.filter.OncePerRequestFilter是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次?\\n其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file=\\"/index.jsp\\"%>的情况。到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤。因为Springweb无法保证每个用户使用的Servlet容器是一样的,因此,在有些场景如果我们要确保同一个请求只能被过滤器处理一次,那就需要spring自己来实现了,因此有了这个类。其注释下写的很清楚,就是为了要陈any servlet container都确保只执行一次。

                ","autoDesc":true}');export{P as comp,O as data}; diff --git a/assets/ParentDelegationClassLoader.html-lWjNd7F6.js b/assets/ParentDelegationClassLoader.html-BE03CURY.js similarity index 99% rename from assets/ParentDelegationClassLoader.html-lWjNd7F6.js rename to assets/ParentDelegationClassLoader.html-BE03CURY.js index debe20e60b..56fabc82d9 100644 --- a/assets/ParentDelegationClassLoader.html-lWjNd7F6.js +++ b/assets/ParentDelegationClassLoader.html-BE03CURY.js @@ -1,4 +1,4 @@ -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o,c as l,a,b as s,d as t,e}from"./app-CVMfKeWw.js";const i={},r=e(`

                为什么说spi打破了双亲委派机制?

                1、什么是双亲委派?

                image-20220330170731913

                注:此处直接摘抄周志明老师的《深入理解java虚拟机》

                站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现[1] ,是虚拟机自身的一部分;另外一种就是其他所有 的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。 ​站在Java开发人员的角度来看,类加载器就应当划分得更细致一些。自JDK 1.2以来,Java一直保 持着三层类加载器、双亲委派的类加载架构,尽管这套架构在Java模块化系统出现后有了一些调整变 动,但依然未改变其主体结构,我们将在7.5节中专门讨论模块化系统下的类加载器。 本节内容将针对JDK 8及之前版本的Java来介绍什么是三层类加载器,以及什么是双亲委派模型。 对于这个时期的Java应用,绝大多数Java程序都会使用到以下3个系统提供的类加载器来进行加载。 ·启动类加载器(Bootstrap Class Loader):前面已经介绍过,这个类加载器负责加载存放在 JAVA_HOM/lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够 识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类 库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时, 如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可,代码清单7-9展示的就是 java.lang.ClassLoader.getClassLoader()方法的代码片段,其中的注释和代码实现都明确地说明了以null值 来代表引导类加载器的约定规则。

                ·扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.Launcher$ExtClassLoader 中以Java代码的形式实现的。它负责加载<JAVA_HOME>\\lib\\ext目录中,或者被java.ext.dirs系统变量所 指定的路径中所有的类库。根据“扩展类加载器”这个名称,就可以推断出这是一种Java系统类库的扩 展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能,在JDK 9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。由于扩展类加载器是由Java代码实现 的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。 ·应用程序类加载器(Application Class Loader):这个类加载器由 sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem- ClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径 (ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有 自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

                ​JDK 9之前的Java应用都是由这三种类加载器互相配合来完成加载的,如果用户认为有必要,还可 以加入自定义的类加载器来进行拓展,典型的如增加除了磁盘位置之外的Class文件来源,或者通过类 加载器实现类的隔离、重载等功能。这些类加载器之间的协作关系“通常”会如图7-2所示。 图7-2中展示的各种类加载器之间的层次关系被称为类加载器的“双亲委派模型(Parents Delegation M odel)”。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载 器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用 组合(Composition)关系来复用父加载器的代码。 读者可能注意到前面描述这种类加载器协作关系时,笔者专门用双引号强调这是“通常”的协作关 系。类加载器的双亲委派模型在JDK 1.2时期被引入,并被广泛应用于此后几乎所有的Java程序中,但 它并不是一个具有强制性约束力的模型,而是Java设计者们推荐给开发者的一种类加载器实现的最佳 实践。 ​双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加 载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的 加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请 求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。 使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java中的类随着它的类 加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一 个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类 在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个 类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的 ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应 用程序将会变得一片混乱。如果读者有兴趣的话,可以尝试去写一个与rt.jar类库中已有类重名的Java 类,将会发现它可以正常编译,但永远无法被加载运行[2]。 ​双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现却异常简单,用以实现双亲委 派的代码只有短短十余行,全部集中在java.lang.ClassLoader的loadClass()方法之中。

                2、什么是SPI

                2.1 定义

                SPI(Service provide interface),直译过来是服务提供接口,在这里指的是厂商负责定义一个接口但不负责提供实现类,定义完接口后厂商直接使用这个接口的方法,但是如果不给此接口提供实现肯定运行要报错的,所以谁要想用厂商这个接口,谁负责实现。最典型的是jdbc,java可以连接各种数据库,比如mysql、oracle、h2……若是让各个数据库厂商都去实现自己的数据库连接方式,那么非常不利于统一管理,所以sun公司为了避免这种各自为战的乱象,他们就规定了一个规范,这就是jdbc了,在java.sql包下,sun指定一个接口叫做Driver,各大厂商负责实现这个Driver就可以了,只要你实现按要求这个接口的方法,那么你就可以直接连接到你的数据库。此处不得不说一句“一流的公司卖标准,二流公司卖实物,三流公司卖服务”

                2.2 使用场景

                1. jdbc4(jdbc4是随着jdk1.6发布的,此版本才开始支持SPI)
                2. springboot的自动话配置也是同样的原理
                3. 阿里的dubbo
                4. 其他

                2.3 自己写一个SPI模拟jdbc的spi

                2.3.1 定义规范(sun公司定义的jdbc规范在java.sql包)

                <?xml version="1.0" encoding="UTF-8"?>
                +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o,c as l,a,b as s,d as t,e}from"./app-_Oi5YZFn.js";const i={},r=e(`

                为什么说spi打破了双亲委派机制?

                1、什么是双亲委派?

                image-20220330170731913

                注:此处直接摘抄周志明老师的《深入理解java虚拟机》

                站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现[1] ,是虚拟机自身的一部分;另外一种就是其他所有 的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。 ​站在Java开发人员的角度来看,类加载器就应当划分得更细致一些。自JDK 1.2以来,Java一直保 持着三层类加载器、双亲委派的类加载架构,尽管这套架构在Java模块化系统出现后有了一些调整变 动,但依然未改变其主体结构,我们将在7.5节中专门讨论模块化系统下的类加载器。 本节内容将针对JDK 8及之前版本的Java来介绍什么是三层类加载器,以及什么是双亲委派模型。 对于这个时期的Java应用,绝大多数Java程序都会使用到以下3个系统提供的类加载器来进行加载。 ·启动类加载器(Bootstrap Class Loader):前面已经介绍过,这个类加载器负责加载存放在 JAVA_HOM/lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够 识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类 库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时, 如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可,代码清单7-9展示的就是 java.lang.ClassLoader.getClassLoader()方法的代码片段,其中的注释和代码实现都明确地说明了以null值 来代表引导类加载器的约定规则。

                ·扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.Launcher$ExtClassLoader 中以Java代码的形式实现的。它负责加载<JAVA_HOME>\\lib\\ext目录中,或者被java.ext.dirs系统变量所 指定的路径中所有的类库。根据“扩展类加载器”这个名称,就可以推断出这是一种Java系统类库的扩 展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能,在JDK 9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。由于扩展类加载器是由Java代码实现 的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。 ·应用程序类加载器(Application Class Loader):这个类加载器由 sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem- ClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径 (ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有 自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

                ​JDK 9之前的Java应用都是由这三种类加载器互相配合来完成加载的,如果用户认为有必要,还可 以加入自定义的类加载器来进行拓展,典型的如增加除了磁盘位置之外的Class文件来源,或者通过类 加载器实现类的隔离、重载等功能。这些类加载器之间的协作关系“通常”会如图7-2所示。 图7-2中展示的各种类加载器之间的层次关系被称为类加载器的“双亲委派模型(Parents Delegation M odel)”。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载 器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用 组合(Composition)关系来复用父加载器的代码。 读者可能注意到前面描述这种类加载器协作关系时,笔者专门用双引号强调这是“通常”的协作关 系。类加载器的双亲委派模型在JDK 1.2时期被引入,并被广泛应用于此后几乎所有的Java程序中,但 它并不是一个具有强制性约束力的模型,而是Java设计者们推荐给开发者的一种类加载器实现的最佳 实践。 ​双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加 载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的 加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请 求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。 使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java中的类随着它的类 加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一 个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类 在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个 类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的 ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应 用程序将会变得一片混乱。如果读者有兴趣的话,可以尝试去写一个与rt.jar类库中已有类重名的Java 类,将会发现它可以正常编译,但永远无法被加载运行[2]。 ​双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现却异常简单,用以实现双亲委 派的代码只有短短十余行,全部集中在java.lang.ClassLoader的loadClass()方法之中。

                2、什么是SPI

                2.1 定义

                SPI(Service provide interface),直译过来是服务提供接口,在这里指的是厂商负责定义一个接口但不负责提供实现类,定义完接口后厂商直接使用这个接口的方法,但是如果不给此接口提供实现肯定运行要报错的,所以谁要想用厂商这个接口,谁负责实现。最典型的是jdbc,java可以连接各种数据库,比如mysql、oracle、h2……若是让各个数据库厂商都去实现自己的数据库连接方式,那么非常不利于统一管理,所以sun公司为了避免这种各自为战的乱象,他们就规定了一个规范,这就是jdbc了,在java.sql包下,sun指定一个接口叫做Driver,各大厂商负责实现这个Driver就可以了,只要你实现按要求这个接口的方法,那么你就可以直接连接到你的数据库。此处不得不说一句“一流的公司卖标准,二流公司卖实物,三流公司卖服务”

                2.2 使用场景

                1. jdbc4(jdbc4是随着jdk1.6发布的,此版本才开始支持SPI)
                2. springboot的自动话配置也是同样的原理
                3. 阿里的dubbo
                4. 其他

                2.3 自己写一个SPI模拟jdbc的spi

                2.3.1 定义规范(sun公司定义的jdbc规范在java.sql包)

                <?xml version="1.0" encoding="UTF-8"?>
                 <project xmlns="http://maven.apache.org/POM/4.0.0"
                          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
                diff --git a/assets/PreAuthorize.html-B_BWAtGw.js b/assets/PreAuthorize.html-DFU2o_nN.js
                similarity index 97%
                rename from assets/PreAuthorize.html-B_BWAtGw.js
                rename to assets/PreAuthorize.html-DFU2o_nN.js
                index df4b59a842..cb2e2a6484 100644
                --- a/assets/PreAuthorize.html-B_BWAtGw.js
                +++ b/assets/PreAuthorize.html-DFU2o_nN.js
                @@ -1 +1 @@
                -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as r,e as o}from"./app-CVMfKeWw.js";const i={},a=o('

                1、使用方式

                比较简单,省略.....

                2、@PreAuthorize注解是何时生效的?

                2.1

                ',4),n=[a];function h(c,p){return t(),r("div",null,n)}const d=e(i,[["render",h],["__file","PreAuthorize.html.vue"]]),m=JSON.parse('{"path":"/java/framework/security/PreAuthorize.html","title":"PreAuthorize注解","lang":"zh-CN","frontmatter":{"title":"PreAuthorize注解","date":"2023-05-22T00:00:00.000Z","isOriginal":true,"description":"1、使用方式 比较简单,省略..... 2、@PreAuthorize注解是何时生效的? 2.1","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/PreAuthorize.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"PreAuthorize注解"}],["meta",{"property":"og:description","content":"1、使用方式 比较简单,省略..... 2、@PreAuthorize注解是何时生效的? 2.1"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-05-22T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"PreAuthorize注解\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-05-22T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、使用方式","slug":"_1、使用方式","link":"#_1、使用方式","children":[]},{"level":2,"title":"2、@PreAuthorize注解是何时生效的?","slug":"_2、-preauthorize注解是何时生效的","link":"#_2、-preauthorize注解是何时生效的","children":[{"level":3,"title":"2.1","slug":"_2-1","link":"#_2-1","children":[]}]}],"git":{"createdTime":1685430003000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.11,"words":33},"filePathRelative":"java/framework/security/PreAuthorize.md","localizedDate":"2023年5月22日","excerpt":"

                1、使用方式

                \\n

                比较简单,省略.....

                \\n

                2、@PreAuthorize注解是何时生效的?

                \\n

                2.1

                \\n","autoDesc":true}');export{d as comp,m as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as r,e as o}from"./app-_Oi5YZFn.js";const i={},a=o('

                1、使用方式

                比较简单,省略.....

                2、@PreAuthorize注解是何时生效的?

                2.1

                ',4),n=[a];function h(c,p){return t(),r("div",null,n)}const d=e(i,[["render",h],["__file","PreAuthorize.html.vue"]]),m=JSON.parse('{"path":"/java/framework/security/PreAuthorize.html","title":"PreAuthorize注解","lang":"zh-CN","frontmatter":{"title":"PreAuthorize注解","date":"2023-05-22T00:00:00.000Z","isOriginal":true,"description":"1、使用方式 比较简单,省略..... 2、@PreAuthorize注解是何时生效的? 2.1","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/PreAuthorize.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"PreAuthorize注解"}],["meta",{"property":"og:description","content":"1、使用方式 比较简单,省略..... 2、@PreAuthorize注解是何时生效的? 2.1"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-05-22T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"PreAuthorize注解\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-05-22T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、使用方式","slug":"_1、使用方式","link":"#_1、使用方式","children":[]},{"level":2,"title":"2、@PreAuthorize注解是何时生效的?","slug":"_2、-preauthorize注解是何时生效的","link":"#_2、-preauthorize注解是何时生效的","children":[{"level":3,"title":"2.1","slug":"_2-1","link":"#_2-1","children":[]}]}],"git":{"createdTime":1685430003000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.11,"words":33},"filePathRelative":"java/framework/security/PreAuthorize.md","localizedDate":"2023年5月22日","excerpt":"

                1、使用方式

                \\n

                比较简单,省略.....

                \\n

                2、@PreAuthorize注解是何时生效的?

                \\n

                2.1

                \\n","autoDesc":true}');export{d as comp,m as data}; diff --git a/assets/ProxyInJava.html-C0ibZtz7.js b/assets/ProxyInJava.html-Craly_CB.js similarity index 99% rename from assets/ProxyInJava.html-C0ibZtz7.js rename to assets/ProxyInJava.html-Craly_CB.js index 4f333bbbc7..4fb71a9596 100644 --- a/assets/ProxyInJava.html-C0ibZtz7.js +++ b/assets/ProxyInJava.html-Craly_CB.js @@ -1,4 +1,4 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as n,b as s,d as t,e as p}from"./app-CVMfKeWw.js";const i={},u=p(`

                是兄弟就来看我的博客

                1、代理的分类

                graph LR;
                +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as n,b as s,d as t,e as p}from"./app-_Oi5YZFn.js";const i={},u=p(`

                是兄弟就来看我的博客

                1、代理的分类

                graph LR;
                 A[JAVA中的代理]-->B[动态代理]-->C[jdk动态代理]
                 B[动态代理]-->D[cglib动态代理]
                 A[JAVA中的代理]--> E[静态代理]-->F[AspectJ]
                diff --git a/assets/Recurse.html-6jai2Fmm.js b/assets/Recurse.html-CzvVNt5P.js
                similarity index 99%
                rename from assets/Recurse.html-6jai2Fmm.js
                rename to assets/Recurse.html-CzvVNt5P.js
                index 03d7b9ca21..769e184f2d 100644
                --- a/assets/Recurse.html-6jai2Fmm.js
                +++ b/assets/Recurse.html-CzvVNt5P.js
                @@ -1,4 +1,4 @@
                -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},p=e(`

                注意!!!

                只有在mysql8.0之后才有递归,5.7及之前是不支持的

                1、递归下钻

                MySQL中的递归查询通常用于处理树形结构数据,如组织架构、文件目录、级联地址、多级菜单等。

                2、示例

                WITH RECURSIVE cte (id, parent_id, level) AS (
                +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

                注意!!!

                只有在mysql8.0之后才有递归,5.7及之前是不支持的

                1、递归下钻

                MySQL中的递归查询通常用于处理树形结构数据,如组织架构、文件目录、级联地址、多级菜单等。

                2、示例

                WITH RECURSIVE cte (id, parent_id, level) AS (
                   -- 初始查询
                   SELECT id, parent_id, 0 FROM your_table WHERE id = <your starting id>
                 
                diff --git a/assets/RefreshToken.html-CFwkbthk.js b/assets/RefreshToken.html-B7uT6L3n.js
                similarity index 99%
                rename from assets/RefreshToken.html-CFwkbthk.js
                rename to assets/RefreshToken.html-B7uT6L3n.js
                index 9dd05848df..7b75979942 100644
                --- a/assets/RefreshToken.html-CFwkbthk.js
                +++ b/assets/RefreshToken.html-B7uT6L3n.js
                @@ -1 +1 @@
                -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as o}from"./app-CVMfKeWw.js";const r={},s=o('

                1、 refresh_token介绍

                ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_token和refresh_token,并在access_token过期时使用refresh_token申请新的access_token。refresh_token的安全性非常重要,授权服务器会根据设定的有效期对refresh_token进行管理,以确保对用户的信息和资源进行保护。同时,在使用refresh_token时,也需要进行国家或行业标准的数据保护和安全性认证,以避免出现不必要的风险和隐私泄漏。

                2、token过期处理

                2.1 方式一,重新登录

                让用户重新登录,获得新的Token,但是这种方式体验很差,通常Token过期时间都比较短,每次都要重新登录操作。

                2.2 方式二,续签(刷新)token

                续签token,避免用户在操作的过程中被强制下线,续签也有多种方式

                1. 在每个请求响应后进行拦截,如果发现请求失败(Token过期导致的)时,刷新Token再刷新请求接口。这种方式的优点是无需Token过期时间字段且无需判断时间,缺点在于多消耗一次请求。
                2. 在每个请求发起前进行拦截,根据expires_in判断token是否过期,如果过期则会刷新后再继续请求接口。这个方法的优点是请求前拦截处理,能节省请求次数,缺点是后端需要提供Token过期时间字段,并且需要结合计算机本地时间判断,如果计算机时间被篡改,拦截就会失败。
                3. 添加一个过滤器,每个请求进来都重置token的过期时间,这种方式对服务器资源消耗增加了
                4. 在OAuth2.0协议中,可以使用refresh_token实现自动续期token。这个方案需要你的应用程序使用OAuth2.0的认证机制,并且获取到了refresh_token。
                5. 使用JSON Web Token:对于使用JSON Web Token (JWT)的应用程序,可以在JWT中设置token的过期时间,预先考虑过期时间,合理设置有效期,通常情况下,token的有效期建议短一些,这样更安全。在即将到期时,在后端进行更新或延长有效期。
                ',8),a=[s];function h(c,k){return t(),n("div",null,a)}const l=e(r,[["render",h],["__file","RefreshToken.html.vue"]]),p=JSON.parse('{"path":"/other/web/RefreshToken.html","title":"refresh_token","lang":"zh-CN","frontmatter":{"title":"refresh_token","date":"2023-05-08T00:00:00.000Z","isOriginal":true,"description":"1、 refresh_token介绍 ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_tok...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/web/RefreshToken.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"refresh_token"}],["meta",{"property":"og:description","content":"1、 refresh_token介绍 ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_tok..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-05-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"refresh_token\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-05-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、 refresh_token介绍","slug":"_1、-refresh-token介绍","link":"#_1、-refresh-token介绍","children":[]},{"level":2,"title":"2、token过期处理","slug":"_2、token过期处理","link":"#_2、token过期处理","children":[{"level":3,"title":"2.1 方式一,重新登录","slug":"_2-1-方式一-重新登录","link":"#_2-1-方式一-重新登录","children":[]},{"level":3,"title":"2.2 方式二,续签(刷新)token","slug":"_2-2-方式二-续签-刷新-token","link":"#_2-2-方式二-续签-刷新-token","children":[]}]}],"git":{"createdTime":1683634730000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenxk","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":2.1,"words":631},"filePathRelative":"other/web/RefreshToken.md","localizedDate":"2023年5月8日","excerpt":"

                1、 refresh_token介绍

                \\n

                ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_token和refresh_token,并在access_token过期时使用refresh_token申请新的access_token。refresh_token的安全性非常重要,授权服务器会根据设定的有效期对refresh_token进行管理,以确保对用户的信息和资源进行保护。同时,在使用refresh_token时,也需要进行国家或行业标准的数据保护和安全性认证,以避免出现不必要的风险和隐私泄漏。

                ","autoDesc":true}');export{l as comp,p as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as o}from"./app-_Oi5YZFn.js";const r={},s=o('

                1、 refresh_token介绍

                ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_token和refresh_token,并在access_token过期时使用refresh_token申请新的access_token。refresh_token的安全性非常重要,授权服务器会根据设定的有效期对refresh_token进行管理,以确保对用户的信息和资源进行保护。同时,在使用refresh_token时,也需要进行国家或行业标准的数据保护和安全性认证,以避免出现不必要的风险和隐私泄漏。

                2、token过期处理

                2.1 方式一,重新登录

                让用户重新登录,获得新的Token,但是这种方式体验很差,通常Token过期时间都比较短,每次都要重新登录操作。

                2.2 方式二,续签(刷新)token

                续签token,避免用户在操作的过程中被强制下线,续签也有多种方式

                1. 在每个请求响应后进行拦截,如果发现请求失败(Token过期导致的)时,刷新Token再刷新请求接口。这种方式的优点是无需Token过期时间字段且无需判断时间,缺点在于多消耗一次请求。
                2. 在每个请求发起前进行拦截,根据expires_in判断token是否过期,如果过期则会刷新后再继续请求接口。这个方法的优点是请求前拦截处理,能节省请求次数,缺点是后端需要提供Token过期时间字段,并且需要结合计算机本地时间判断,如果计算机时间被篡改,拦截就会失败。
                3. 添加一个过滤器,每个请求进来都重置token的过期时间,这种方式对服务器资源消耗增加了
                4. 在OAuth2.0协议中,可以使用refresh_token实现自动续期token。这个方案需要你的应用程序使用OAuth2.0的认证机制,并且获取到了refresh_token。
                5. 使用JSON Web Token:对于使用JSON Web Token (JWT)的应用程序,可以在JWT中设置token的过期时间,预先考虑过期时间,合理设置有效期,通常情况下,token的有效期建议短一些,这样更安全。在即将到期时,在后端进行更新或延长有效期。
                ',8),a=[s];function h(c,k){return t(),n("div",null,a)}const l=e(r,[["render",h],["__file","RefreshToken.html.vue"]]),p=JSON.parse('{"path":"/other/web/RefreshToken.html","title":"refresh_token","lang":"zh-CN","frontmatter":{"title":"refresh_token","date":"2023-05-08T00:00:00.000Z","isOriginal":true,"description":"1、 refresh_token介绍 ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_tok...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/web/RefreshToken.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"refresh_token"}],["meta",{"property":"og:description","content":"1、 refresh_token介绍 ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_tok..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-05-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"refresh_token\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-05-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、 refresh_token介绍","slug":"_1、-refresh-token介绍","link":"#_1、-refresh-token介绍","children":[]},{"level":2,"title":"2、token过期处理","slug":"_2、token过期处理","link":"#_2、token过期处理","children":[{"level":3,"title":"2.1 方式一,重新登录","slug":"_2-1-方式一-重新登录","link":"#_2-1-方式一-重新登录","children":[]},{"level":3,"title":"2.2 方式二,续签(刷新)token","slug":"_2-2-方式二-续签-刷新-token","link":"#_2-2-方式二-续签-刷新-token","children":[]}]}],"git":{"createdTime":1683634730000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenxk","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":2.1,"words":631},"filePathRelative":"other/web/RefreshToken.md","localizedDate":"2023年5月8日","excerpt":"

                1、 refresh_token介绍

                \\n

                ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_token和refresh_token,并在access_token过期时使用refresh_token申请新的access_token。refresh_token的安全性非常重要,授权服务器会根据设定的有效期对refresh_token进行管理,以确保对用户的信息和资源进行保护。同时,在使用refresh_token时,也需要进行国家或行业标准的数据保护和安全性认证,以避免出现不必要的风险和隐私泄漏。

                ","autoDesc":true}');export{l as comp,p as data}; diff --git a/assets/Restful.html-X9thQzel.js b/assets/Restful.html-BeZrEgn9.js similarity index 97% rename from assets/Restful.html-X9thQzel.js rename to assets/Restful.html-BeZrEgn9.js index eae11cd39c..02cb2555c2 100644 --- a/assets/Restful.html-X9thQzel.js +++ b/assets/Restful.html-BeZrEgn9.js @@ -1 +1 @@ -import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as l,c,a as e,b as r,d as n}from"./app-CVMfKeWw.js";const i={},s=e("h2",{id:"一、参考",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#一、参考"},[e("span",null,"一、参考")])],-1),p=e("hr",null,null,-1),h={href:"https://www.ruanyifeng.com/blog/2011/09/restful.html",target:"_blank",rel:"noopener noreferrer"},u={href:"https://www.ruanyifeng.com/blog/2014/05/restful_api.html",target:"_blank",rel:"noopener noreferrer"};function m(f,d){const t=a("ExternalLinkIcon");return l(),c("div",null,[s,p,e("p",null,[e("a",h,[r("RESTful架构"),n(t)])]),e("p",null,[e("a",u,[r("RESTful API 设计"),n(t)])])])}const T=o(i,[["render",m],["__file","Restful.html.vue"]]),b=JSON.parse('{"path":"/other/web/Restful.html","title":"HTTP Restful","lang":"zh-CN","frontmatter":{"title":"HTTP Restful","date":"2022-07-11T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["web"],"tag":["web"],"description":"一、参考 RESTful架构 RESTful API 设计","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/web/Restful.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"HTTP Restful"}],["meta",{"property":"og:description","content":"一、参考 RESTful架构 RESTful API 设计"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:tag","content":"web"}],["meta",{"property":"article:published_time","content":"2022-07-11T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"HTTP Restful\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-11T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"一、参考","slug":"一、参考","link":"#一、参考","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.1,"words":30},"filePathRelative":"other/web/Restful.md","localizedDate":"2022年7月11日","excerpt":"

                一、参考

                \\n
                \\n

                RESTful架构

                \\n

                RESTful API 设计

                \\n","autoDesc":true}');export{T as comp,b as data}; +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as l,c,a as e,b as r,d as n}from"./app-_Oi5YZFn.js";const i={},s=e("h2",{id:"一、参考",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#一、参考"},[e("span",null,"一、参考")])],-1),p=e("hr",null,null,-1),h={href:"https://www.ruanyifeng.com/blog/2011/09/restful.html",target:"_blank",rel:"noopener noreferrer"},u={href:"https://www.ruanyifeng.com/blog/2014/05/restful_api.html",target:"_blank",rel:"noopener noreferrer"};function m(f,d){const t=a("ExternalLinkIcon");return l(),c("div",null,[s,p,e("p",null,[e("a",h,[r("RESTful架构"),n(t)])]),e("p",null,[e("a",u,[r("RESTful API 设计"),n(t)])])])}const T=o(i,[["render",m],["__file","Restful.html.vue"]]),b=JSON.parse('{"path":"/other/web/Restful.html","title":"HTTP Restful","lang":"zh-CN","frontmatter":{"title":"HTTP Restful","date":"2022-07-11T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["web"],"tag":["web"],"description":"一、参考 RESTful架构 RESTful API 设计","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/web/Restful.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"HTTP Restful"}],["meta",{"property":"og:description","content":"一、参考 RESTful架构 RESTful API 设计"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:tag","content":"web"}],["meta",{"property":"article:published_time","content":"2022-07-11T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"HTTP Restful\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-11T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"一、参考","slug":"一、参考","link":"#一、参考","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.1,"words":30},"filePathRelative":"other/web/Restful.md","localizedDate":"2022年7月11日","excerpt":"

                一、参考

                \\n
                \\n

                RESTful架构

                \\n

                RESTful API 设计

                \\n","autoDesc":true}');export{T as comp,b as data}; diff --git a/assets/SQLOptimization.html-CzzwcOPq.js b/assets/SQLOptimization.html-DB9Wuz97.js similarity index 98% rename from assets/SQLOptimization.html-CzzwcOPq.js rename to assets/SQLOptimization.html-DB9Wuz97.js index 29e5c1cfc3..83aa93f82c 100644 --- a/assets/SQLOptimization.html-CzzwcOPq.js +++ b/assets/SQLOptimization.html-DB9Wuz97.js @@ -1,4 +1,4 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as s,e as a}from"./app-CVMfKeWw.js";const i={},n=a(`

                背景:

                最近在改一个老项目,其中使用到框架是mybatis,有一个业务表install_record,代表装机记录,一个accessory代表备件表,一个sys_file代表文件表,业务关系是一个install_record对应多个accessory、以及多个sys_file,在一开始使用的是mybatis的嵌套查询的方式,但此方式有N+1的问题,比如一个装机表对应10个accessory、20个sys_file,则就要查询1+10+20 = 31次数据库,效率是很低的,因此想改成嵌套查询的方式。

                1、改造后的语句如下

                SELECT xxx   FROM
                +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as s,e as a}from"./app-_Oi5YZFn.js";const i={},n=a(`

                背景:

                最近在改一个老项目,其中使用到框架是mybatis,有一个业务表install_record,代表装机记录,一个accessory代表备件表,一个sys_file代表文件表,业务关系是一个install_record对应多个accessory、以及多个sys_file,在一开始使用的是mybatis的嵌套查询的方式,但此方式有N+1的问题,比如一个装机表对应10个accessory、20个sys_file,则就要查询1+10+20 = 31次数据库,效率是很低的,因此想改成嵌套查询的方式。

                1、改造后的语句如下

                SELECT xxx   FROM
                 	ccsx_weibao.install_record ir
                 	LEFT JOIN ccsx_weibao.install_record_accessory ira ON ira.host_id = ir.id
                 	LEFT JOIN ccsx.sys_file f ON f.business_type = 1 
                diff --git a/assets/SSO.html-DgwliJOO.js b/assets/SSO.html-DB2D0akT.js
                similarity index 99%
                rename from assets/SSO.html-DgwliJOO.js
                rename to assets/SSO.html-DB2D0akT.js
                index ecfc6568bb..b9198509c9 100644
                --- a/assets/SSO.html-DgwliJOO.js
                +++ b/assets/SSO.html-DB2D0akT.js
                @@ -1 +1 @@
                -import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as a,c as r,a as e,b as n,d as s,e as l}from"./app-CVMfKeWw.js";const h={},c=l('

                1、SSO

                1.1 SSO介绍

                单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

                1.2 SSO使用场景(解决了什么问题)

                很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都要进行注册登录,退出的时候又要一个个退出。用户体验很不好!你可以想象一下,上豆瓣 要登录豆瓣FM、豆瓣读书、豆瓣电影豆瓣日记......真的会让人崩溃的。我们想要另一种登录体验:一家企业下的服务只要一次注册,登录的时候只要一次登录,退出的时候只要一次退出。怎么做?

                一次注册不难,想一下是不是只要Server之间同步用户信息就行了?可以,但这样描述不太完整,后续讲用户注册的时候详细说。实际上用户信息的管理才是SSO真正的难点,只是作为初学者,我们的难点在于实现SSO的技术!我们先讨论实现手段。

                一次登录与一次退出。 回头看看普通商场的故事,什么东西才是保持登录状态关键的东西?记录器(session)?那种叫做cookie的纸张?写在纸张上的ID? 是session里面记录的信息跟那个ID,cookie不只是记录ID的工具而已。客户端持有ID,服务端持有session,两者一起用来保持登录状态。客户端需要用ID来作为凭证,而服务端需要用session来验证ID的有效性(ID可能过期、可能根本就是伪造的找不到对应的信息、ID下对应的客户端还没有进行登录验证等)。但是session这东西一开始是每个server自己独有的,豆瓣FM有自己的session、豆瓣读书有自己的session,而记录ID的cookie又是不能跨域的。所以,我们要实现一次登录一次退出,只需要想办法让各个server的共用一个session的信息,让客户端在各个域名下都能持有这个ID就好了。再进一步讲,只要各个server拿到同一个ID,都能有办法检验出ID的有效性、并且能得到ID对应的用户信息就行了,也就是能检验ID 。

                SSO就是解决以上问题,当使用浏览器访问公司任何一个服务时,只要其中一个服务登陆了,那么再打开一个新页面访问另一个服务,是无需再次输入用户名和密码。比如:登陆了OA后,再访问考勤系统是无需登陆。

                1.3 使用SSO的好处

                • 方便用户,用户使用应用系统时,能够一次登录,多次使用。用户不再需要每次输入用户名称和用户密码,也不需要牢记多套用户名称和用户密码。单点登录平台能够改善用户使用应用系统的体验。
                • 方便管理员,系统管理员只需要维护一套统一的用户账号,方便、简单。相比之下,系统管理员以前需要管理很多套的用户账号。每一个应用系统就有一套用户账号,不仅给管理上带来不方便,而且,也容易出现管理漏洞。
                • 简化应用系统开发,开发新的应用系统时,可以直接使用单点登录平台的用户认证服务,简化开发流程。单点登录平台通过提供统一的认证平台,实现单点登录。因此,应用系统并不需要开发用户认证程序。

                2、SSO常见实现方案

                注意

                SSO只是一个规范,具体的实现有多种途径,类似Jdbc定义了java数据库连接的规范,具体的实现方案由各数据库厂商自行实现。

                单点登录的实现方案一般包含:Cookies、Session 同步、分布式 Session 方式、统一认证授权方式等。目前的大型网站通常采用分布式 Session 及第三方认证授权的方式。

                基于 Cookie 的单点登录是最简单的单点登录实现方式,它使用 Cookie 作为媒介,存放用户凭证。 用户登录父应用之后,应用返回一个加密的 Cookie,用户访问子应用的时候,携带上这个 Cookie,授权应用解密 Cookie 并进行校验,校验通过则登录当前用户。 这种方式虽然实现简单,但 Cookie 不够安全,容易泄漏,且不能跨域实现免登。

                2.2 分布式 Session 实现单点登录

                分布式 Session 实现单点登录原理是将用户认证信息保存于 Session 中,即以 Session 内存储的值为用户凭证,一般采用 Cache 中间件实现(如 Redis)。用户再次登录时,应用服务端获取分布式 Session 来校验用户信息。如图所示: 20221011175808 一般情况下都是基于 Redis 实现 Session 共享,将 Session 存储于 Redis 上,然后将整个系统的全局 Cookie Domain 设置于顶级域名上,这样 SessionID 就能在各个子系统间共享。 这种方式也有一个问题,共享 Session 无法处理跨顶级域名。

                2.3 统一认证授权方式实现单点登录

                20221011175916

                由图可知,通过统一认证授权方式实现单点登录,需要有一个独立的认证系统。

                用户第一次访问应用系统时,由于还未登录,被引导到认证系统中进行登录,认证系统接受用户名密码等安全信息,生成访问令牌(ticket)。用户通过 ticket 访问应用系统,应用系统接受到请求之后会访问认证系统检查 ticket 的合法性,如果检查通过,用户就可以在不用再次登录的情况下访问应用系统资源。

                以上介绍了单点登录常见的 3 种实现方案,在实际应用中还需要根据具体场景来实现。

                接下来通过几个实际生产场景,来阐述单点登录的详细实现方案。

                3、实际场景中SSO实现

                3.1 JWT(完全跨域方案)

                JWT (JSON Web Token)是一个开放标准(RFC7519),它是一个含签名并携带用户相关信息的加密串。页面请求校验登录接口时,请求头中携带 JWT 串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改,校验通过则认为是可靠的请求,将正常返回数据。

                DataSimba(奇点云数据中台产品)结合 JWT 与分布式 session,实现多域多空间单点登录。通过 JWT 生成和校验令牌,将刷新令牌存储在 redis 中,网关统一校验令牌,校验通过后将用户信息设置在请求头中,应用在拦截器中获取到用户信息后即可验证通过。

                不同域中的 DataSimba 共用一套密钥并且实时同步用户信息,通过 JWT 生成和校验令牌,用户登录其中一个域后,前端获取 JWT 加密串并存储在 Local Storage 中,当用户切换到其他域时前端传入加密串,后端网关校验,由此实现免登录访问其他域资源,如下图所示: 20221011180112

                3.2 使用 OAuth2.0 实现单点登录

                OAuth2.0是可以实现SSO的功能,但是OAuth 诞生之初就是为了解决 Web 浏览器场景下的授权问题

                4、参考资料

                ',31),p={href:"https://zq99299.github.io/note-book/oath2/01/02.html",target:"_blank",rel:"noopener noreferrer"},S={href:"https://blog.csdn.net/qq_38292691/article/details/124802159",target:"_blank",rel:"noopener noreferrer"},d={href:"https://stackoverflow.com/questions/13387698/why-is-there-an-authorization-code-flow-in-oauth2-when-implicit-flow-works-s/13389335#13389335",target:"_blank",rel:"noopener noreferrer"},_={href:"https://stackoverflow.com/questions/7522831/what-is-the-purpose-of-the-implicit-grant-authorization-type-in-oauth-2?noredirect=1&lq=1",target:"_blank",rel:"noopener noreferrer"};function g(u,m){const t=i("ExternalLinkIcon");return a(),r("div",null,[c,e("p",null,[e("a",p,[n("OAuth2.0授权码模式中为什么一定要有code"),s(t)]),e("a",S,[n("授权码的补充"),s(t)]),e("a",d,[n("授权码模式中code的意义"),s(t)]),e("a",_,[n("授权码模式是让token在服务器和服务器之间传递,不会经过资源拥有者,资源拥有者无需看到token"),s(t)])])])}const f=o(h,[["render",g],["__file","SSO.html.vue"]]),b=JSON.parse('{"path":"/other/training/SSO.html","title":"OAuth2分享","lang":"zh-CN","frontmatter":{"title":"OAuth2分享","date":"2022-10-09T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["小组分享"],"description":"1、SSO 1.1 SSO介绍 单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 1.2 SSO使用场景(解决了什么问题) 很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/training/SSO.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"OAuth2分享"}],["meta",{"property":"og:description","content":"1、SSO 1.1 SSO介绍 单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 1.2 SSO使用场景(解决了什么问题) 很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221011175808.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-10-09T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"OAuth2分享\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221011175808.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221011175916.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221011180112.png\\"],\\"datePublished\\":\\"2022-10-09T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、SSO","slug":"_1、sso","link":"#_1、sso","children":[{"level":3,"title":"1.1 SSO介绍","slug":"_1-1-sso介绍","link":"#_1-1-sso介绍","children":[]},{"level":3,"title":"1.2 SSO使用场景(解决了什么问题)","slug":"_1-2-sso使用场景-解决了什么问题","link":"#_1-2-sso使用场景-解决了什么问题","children":[]},{"level":3,"title":"1.3 使用SSO的好处","slug":"_1-3-使用sso的好处","link":"#_1-3-使用sso的好处","children":[]}]},{"level":2,"title":"2、SSO常见实现方案","slug":"_2、sso常见实现方案","link":"#_2、sso常见实现方案","children":[{"level":3,"title":"2.1 基于 Cookie 的单点登录","slug":"_2-1-基于-cookie-的单点登录","link":"#_2-1-基于-cookie-的单点登录","children":[]},{"level":3,"title":"2.2 分布式 Session 实现单点登录","slug":"_2-2-分布式-session-实现单点登录","link":"#_2-2-分布式-session-实现单点登录","children":[]},{"level":3,"title":"2.3 统一认证授权方式实现单点登录","slug":"_2-3-统一认证授权方式实现单点登录","link":"#_2-3-统一认证授权方式实现单点登录","children":[]}]},{"level":2,"title":"3、实际场景中SSO实现","slug":"_3、实际场景中sso实现","link":"#_3、实际场景中sso实现","children":[{"level":3,"title":"3.1 JWT(完全跨域方案)","slug":"_3-1-jwt-完全跨域方案","link":"#_3-1-jwt-完全跨域方案","children":[]},{"level":3,"title":"3.2 使用 OAuth2.0 实现单点登录","slug":"_3-2-使用-oauth2-0-实现单点登录","link":"#_3-2-使用-oauth2-0-实现单点登录","children":[]}]},{"level":2,"title":"4、参考资料","slug":"_4、参考资料","link":"#_4、参考资料","children":[]}],"git":{"createdTime":1665557335000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2}]},"readingTime":{"minutes":7.15,"words":2144},"filePathRelative":"other/training/SSO.md","localizedDate":"2022年10月9日","excerpt":"

                1、SSO

                \\n

                1.1 SSO介绍

                \\n

                单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

                \\n

                1.2 SSO使用场景(解决了什么问题)

                \\n

                很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都要进行注册登录,退出的时候又要一个个退出。用户体验很不好!你可以想象一下,上豆瓣 要登录豆瓣FM、豆瓣读书、豆瓣电影豆瓣日记......真的会让人崩溃的。我们想要另一种登录体验:一家企业下的服务只要一次注册,登录的时候只要一次登录,退出的时候只要一次退出。怎么做?

                ","autoDesc":true}');export{f as comp,b as data}; +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as a,c as r,a as e,b as n,d as s,e as l}from"./app-_Oi5YZFn.js";const h={},c=l('

                1、SSO

                1.1 SSO介绍

                单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

                1.2 SSO使用场景(解决了什么问题)

                很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都要进行注册登录,退出的时候又要一个个退出。用户体验很不好!你可以想象一下,上豆瓣 要登录豆瓣FM、豆瓣读书、豆瓣电影豆瓣日记......真的会让人崩溃的。我们想要另一种登录体验:一家企业下的服务只要一次注册,登录的时候只要一次登录,退出的时候只要一次退出。怎么做?

                一次注册不难,想一下是不是只要Server之间同步用户信息就行了?可以,但这样描述不太完整,后续讲用户注册的时候详细说。实际上用户信息的管理才是SSO真正的难点,只是作为初学者,我们的难点在于实现SSO的技术!我们先讨论实现手段。

                一次登录与一次退出。 回头看看普通商场的故事,什么东西才是保持登录状态关键的东西?记录器(session)?那种叫做cookie的纸张?写在纸张上的ID? 是session里面记录的信息跟那个ID,cookie不只是记录ID的工具而已。客户端持有ID,服务端持有session,两者一起用来保持登录状态。客户端需要用ID来作为凭证,而服务端需要用session来验证ID的有效性(ID可能过期、可能根本就是伪造的找不到对应的信息、ID下对应的客户端还没有进行登录验证等)。但是session这东西一开始是每个server自己独有的,豆瓣FM有自己的session、豆瓣读书有自己的session,而记录ID的cookie又是不能跨域的。所以,我们要实现一次登录一次退出,只需要想办法让各个server的共用一个session的信息,让客户端在各个域名下都能持有这个ID就好了。再进一步讲,只要各个server拿到同一个ID,都能有办法检验出ID的有效性、并且能得到ID对应的用户信息就行了,也就是能检验ID 。

                SSO就是解决以上问题,当使用浏览器访问公司任何一个服务时,只要其中一个服务登陆了,那么再打开一个新页面访问另一个服务,是无需再次输入用户名和密码。比如:登陆了OA后,再访问考勤系统是无需登陆。

                1.3 使用SSO的好处

                • 方便用户,用户使用应用系统时,能够一次登录,多次使用。用户不再需要每次输入用户名称和用户密码,也不需要牢记多套用户名称和用户密码。单点登录平台能够改善用户使用应用系统的体验。
                • 方便管理员,系统管理员只需要维护一套统一的用户账号,方便、简单。相比之下,系统管理员以前需要管理很多套的用户账号。每一个应用系统就有一套用户账号,不仅给管理上带来不方便,而且,也容易出现管理漏洞。
                • 简化应用系统开发,开发新的应用系统时,可以直接使用单点登录平台的用户认证服务,简化开发流程。单点登录平台通过提供统一的认证平台,实现单点登录。因此,应用系统并不需要开发用户认证程序。

                2、SSO常见实现方案

                注意

                SSO只是一个规范,具体的实现有多种途径,类似Jdbc定义了java数据库连接的规范,具体的实现方案由各数据库厂商自行实现。

                单点登录的实现方案一般包含:Cookies、Session 同步、分布式 Session 方式、统一认证授权方式等。目前的大型网站通常采用分布式 Session 及第三方认证授权的方式。

                基于 Cookie 的单点登录是最简单的单点登录实现方式,它使用 Cookie 作为媒介,存放用户凭证。 用户登录父应用之后,应用返回一个加密的 Cookie,用户访问子应用的时候,携带上这个 Cookie,授权应用解密 Cookie 并进行校验,校验通过则登录当前用户。 这种方式虽然实现简单,但 Cookie 不够安全,容易泄漏,且不能跨域实现免登。

                2.2 分布式 Session 实现单点登录

                分布式 Session 实现单点登录原理是将用户认证信息保存于 Session 中,即以 Session 内存储的值为用户凭证,一般采用 Cache 中间件实现(如 Redis)。用户再次登录时,应用服务端获取分布式 Session 来校验用户信息。如图所示: 20221011175808 一般情况下都是基于 Redis 实现 Session 共享,将 Session 存储于 Redis 上,然后将整个系统的全局 Cookie Domain 设置于顶级域名上,这样 SessionID 就能在各个子系统间共享。 这种方式也有一个问题,共享 Session 无法处理跨顶级域名。

                2.3 统一认证授权方式实现单点登录

                20221011175916

                由图可知,通过统一认证授权方式实现单点登录,需要有一个独立的认证系统。

                用户第一次访问应用系统时,由于还未登录,被引导到认证系统中进行登录,认证系统接受用户名密码等安全信息,生成访问令牌(ticket)。用户通过 ticket 访问应用系统,应用系统接受到请求之后会访问认证系统检查 ticket 的合法性,如果检查通过,用户就可以在不用再次登录的情况下访问应用系统资源。

                以上介绍了单点登录常见的 3 种实现方案,在实际应用中还需要根据具体场景来实现。

                接下来通过几个实际生产场景,来阐述单点登录的详细实现方案。

                3、实际场景中SSO实现

                3.1 JWT(完全跨域方案)

                JWT (JSON Web Token)是一个开放标准(RFC7519),它是一个含签名并携带用户相关信息的加密串。页面请求校验登录接口时,请求头中携带 JWT 串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改,校验通过则认为是可靠的请求,将正常返回数据。

                DataSimba(奇点云数据中台产品)结合 JWT 与分布式 session,实现多域多空间单点登录。通过 JWT 生成和校验令牌,将刷新令牌存储在 redis 中,网关统一校验令牌,校验通过后将用户信息设置在请求头中,应用在拦截器中获取到用户信息后即可验证通过。

                不同域中的 DataSimba 共用一套密钥并且实时同步用户信息,通过 JWT 生成和校验令牌,用户登录其中一个域后,前端获取 JWT 加密串并存储在 Local Storage 中,当用户切换到其他域时前端传入加密串,后端网关校验,由此实现免登录访问其他域资源,如下图所示: 20221011180112

                3.2 使用 OAuth2.0 实现单点登录

                OAuth2.0是可以实现SSO的功能,但是OAuth 诞生之初就是为了解决 Web 浏览器场景下的授权问题

                4、参考资料

                ',31),p={href:"https://zq99299.github.io/note-book/oath2/01/02.html",target:"_blank",rel:"noopener noreferrer"},S={href:"https://blog.csdn.net/qq_38292691/article/details/124802159",target:"_blank",rel:"noopener noreferrer"},d={href:"https://stackoverflow.com/questions/13387698/why-is-there-an-authorization-code-flow-in-oauth2-when-implicit-flow-works-s/13389335#13389335",target:"_blank",rel:"noopener noreferrer"},_={href:"https://stackoverflow.com/questions/7522831/what-is-the-purpose-of-the-implicit-grant-authorization-type-in-oauth-2?noredirect=1&lq=1",target:"_blank",rel:"noopener noreferrer"};function g(u,m){const t=i("ExternalLinkIcon");return a(),r("div",null,[c,e("p",null,[e("a",p,[n("OAuth2.0授权码模式中为什么一定要有code"),s(t)]),e("a",S,[n("授权码的补充"),s(t)]),e("a",d,[n("授权码模式中code的意义"),s(t)]),e("a",_,[n("授权码模式是让token在服务器和服务器之间传递,不会经过资源拥有者,资源拥有者无需看到token"),s(t)])])])}const f=o(h,[["render",g],["__file","SSO.html.vue"]]),b=JSON.parse('{"path":"/other/training/SSO.html","title":"OAuth2分享","lang":"zh-CN","frontmatter":{"title":"OAuth2分享","date":"2022-10-09T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["小组分享"],"description":"1、SSO 1.1 SSO介绍 单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 1.2 SSO使用场景(解决了什么问题) 很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/training/SSO.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"OAuth2分享"}],["meta",{"property":"og:description","content":"1、SSO 1.1 SSO介绍 单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 1.2 SSO使用场景(解决了什么问题) 很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20221011175808.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-10-09T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"OAuth2分享\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20221011175808.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221011175916.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20221011180112.png\\"],\\"datePublished\\":\\"2022-10-09T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1、SSO","slug":"_1、sso","link":"#_1、sso","children":[{"level":3,"title":"1.1 SSO介绍","slug":"_1-1-sso介绍","link":"#_1-1-sso介绍","children":[]},{"level":3,"title":"1.2 SSO使用场景(解决了什么问题)","slug":"_1-2-sso使用场景-解决了什么问题","link":"#_1-2-sso使用场景-解决了什么问题","children":[]},{"level":3,"title":"1.3 使用SSO的好处","slug":"_1-3-使用sso的好处","link":"#_1-3-使用sso的好处","children":[]}]},{"level":2,"title":"2、SSO常见实现方案","slug":"_2、sso常见实现方案","link":"#_2、sso常见实现方案","children":[{"level":3,"title":"2.1 基于 Cookie 的单点登录","slug":"_2-1-基于-cookie-的单点登录","link":"#_2-1-基于-cookie-的单点登录","children":[]},{"level":3,"title":"2.2 分布式 Session 实现单点登录","slug":"_2-2-分布式-session-实现单点登录","link":"#_2-2-分布式-session-实现单点登录","children":[]},{"level":3,"title":"2.3 统一认证授权方式实现单点登录","slug":"_2-3-统一认证授权方式实现单点登录","link":"#_2-3-统一认证授权方式实现单点登录","children":[]}]},{"level":2,"title":"3、实际场景中SSO实现","slug":"_3、实际场景中sso实现","link":"#_3、实际场景中sso实现","children":[{"level":3,"title":"3.1 JWT(完全跨域方案)","slug":"_3-1-jwt-完全跨域方案","link":"#_3-1-jwt-完全跨域方案","children":[]},{"level":3,"title":"3.2 使用 OAuth2.0 实现单点登录","slug":"_3-2-使用-oauth2-0-实现单点登录","link":"#_3-2-使用-oauth2-0-实现单点登录","children":[]}]},{"level":2,"title":"4、参考资料","slug":"_4、参考资料","link":"#_4、参考资料","children":[]}],"git":{"createdTime":1665557335000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2}]},"readingTime":{"minutes":7.15,"words":2144},"filePathRelative":"other/training/SSO.md","localizedDate":"2022年10月9日","excerpt":"

                1、SSO

                \\n

                1.1 SSO介绍

                \\n

                单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

                \\n

                1.2 SSO使用场景(解决了什么问题)

                \\n

                很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都要进行注册登录,退出的时候又要一个个退出。用户体验很不好!你可以想象一下,上豆瓣 要登录豆瓣FM、豆瓣读书、豆瓣电影豆瓣日记......真的会让人崩溃的。我们想要另一种登录体验:一家企业下的服务只要一次注册,登录的时候只要一次登录,退出的时候只要一次退出。怎么做?

                ","autoDesc":true}');export{f as comp,b as data}; diff --git a/assets/SSO.html-DBoqluIC.js b/assets/SSO.html-DKOv-tJ8.js similarity index 98% rename from assets/SSO.html-DBoqluIC.js rename to assets/SSO.html-DKOv-tJ8.js index 9ca54c1e8d..2cc0319217 100644 --- a/assets/SSO.html-DBoqluIC.js +++ b/assets/SSO.html-DKOv-tJ8.js @@ -1 +1 @@ -import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as s,c as h,a as e,b as t,d as a,e as o}from"./app-CVMfKeWw.js";const d={},l=o('

                1、写在前面

                一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。

                • 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录
                • 授权(Authorization),即用户要访问某资源,必须要拥有对应权限

                也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源时,系统会校验其拥有的权限是否可以访问这个资源。

                2、SSO和OAUTH2介绍

                SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。

                oauth是一个协议,它们定义了一套流程规范,你要实现我这个协议,就得按照我的规范来,和java中jdbc一样,sun公司负责规定一套规范,提供一套接口,下游的用户自行负责实现,你具体怎么实现我不关心,你要做的就是实现我规定的一套规范。

                OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。OAuth强调的是授权,当然要想获取授权前你必须认证,就像第一节说的,用户只有登录后才能获得对应用户的权限,这一步是无法绕开的。

                3、SSO实现

                单点登录实现中,系统之间的协议对接是非常重要的一环,一般涉及的标准协议类型有 CAS、OAuth、OpenID Connect、SAML。

                另外国产的SA-TOKEN项目也有sso和oauth实现

                3.1 CAS

                Central Authentication Service简称CAS,是一种常见的B/S架构的SSO协议。和其他任何SSO协议一样,用户仅需登陆一次,访问其他应用则无需再次登陆。顾名思义,CAS是一种仅用于Authentication的服务,它和OAuth/OIDC协议不一样,并不能作为一种Authorization的协议。当前CAS协议包括CAS 1.0、CAS2.0、CAS3.0版本,这三个版本的认证流程基本类似。CAS的认证流程通过包括几部分参与者:

                Client: 通常为使用浏览器的用户 CAS Client: 实现CAS协议的Web应用 CAS Server: 作为统一认证的CAS服务器

                4、各种协议对比

                单纯的单点登录,其实无需授权中心有一套自己的用户和权限,单点登录是只管登录,登录后从业务系统自行获取自己的权限,所以从这个角度来说sso也有它自己的优势,它不像oauth那样还要维护一套用户权限系统,如果仅仅作单点登录不用权限的话用cas显然更有优势。

                ',16),c={href:"https://zhuanlan.zhihu.com/p/267845330",target:"_blank",rel:"noopener noreferrer"},p=e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/20230104171826.png",alt:"20230104171826"},null,-1),S={href:"https://sa-token.cc/doc.html#/fun/sso-vs-oauth2",target:"_blank",rel:"noopener noreferrer"},u=o("
                功能点SSO单点登录OAuth2.0
                统一认证支持度高支持度高
                统一注销支持度高支持度低
                多个系统会话一致性强一致弱一致
                第三方应用授权管理不支持支持度高
                自有系统授权管理支持度高支持度低
                Client级的权限校验不支持支持度高
                集成简易度比较简单难度中等
                ",1);function m(_,g){const n=i("ExternalLinkIcon");return s(),h("div",null,[l,e("p",null,[t("以下图片是各种实现的对比图"),e("a",c,[t("来自知乎"),a(n)]),t(",我并不赞同他的观点,仅作参考 "),p]),e("p",null,[t("以下对比来自"),e("a",S,[t("sa-token官网"),a(n)])]),u])}const C=r(d,[["render",m],["__file","SSO.html.vue"]]),f=JSON.parse('{"path":"/java/framework/security/SSO.html","title":"SSO协议","lang":"zh-CN","frontmatter":{"title":"SSO协议","date":"2023-01-03T00:00:00.000Z","tag":["oauth","sso"],"description":"1、写在前面 一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录 授权(Authorization),即用户要访问某资源,必须要拥有对应权限 也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/SSO.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"SSO协议"}],["meta",{"property":"og:description","content":"1、写在前面 一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录 授权(Authorization),即用户要访问某资源,必须要拥有对应权限 也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20230104171826.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"oauth"}],["meta",{"property":"article:tag","content":"sso"}],["meta",{"property":"article:published_time","content":"2023-01-03T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"SSO协议\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104171826.png\\"],\\"datePublished\\":\\"2023-01-03T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、写在前面","slug":"_1、写在前面","link":"#_1、写在前面","children":[]},{"level":2,"title":"2、SSO和OAUTH2介绍","slug":"_2、sso和oauth2介绍","link":"#_2、sso和oauth2介绍","children":[]},{"level":2,"title":"3、SSO实现","slug":"_3、sso实现","link":"#_3、sso实现","children":[{"level":3,"title":"3.1 CAS","slug":"_3-1-cas","link":"#_3-1-cas","children":[]}]},{"level":2,"title":"4、各种协议对比","slug":"_4、各种协议对比","link":"#_4、各种协议对比","children":[]}],"git":{"createdTime":1672824556000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2}]},"readingTime":{"minutes":3.65,"words":1094},"filePathRelative":"java/framework/security/SSO.md","localizedDate":"2023年1月3日","excerpt":"

                1、写在前面

                \\n

                一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。

                \\n
                  \\n
                • 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录
                • \\n
                • 授权(Authorization),即用户要访问某资源,必须要拥有对应权限
                • \\n
                \\n

                也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源时,系统会校验其拥有的权限是否可以访问这个资源。

                \\n

                2、SSO和OAUTH2介绍

                \\n

                SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。

                ","autoDesc":true}');export{C as comp,f as data}; +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as s,c as h,a as e,b as t,d as a,e as o}from"./app-_Oi5YZFn.js";const d={},l=o('

                1、写在前面

                一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。

                • 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录
                • 授权(Authorization),即用户要访问某资源,必须要拥有对应权限

                也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源时,系统会校验其拥有的权限是否可以访问这个资源。

                2、SSO和OAUTH2介绍

                SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。

                oauth是一个协议,它们定义了一套流程规范,你要实现我这个协议,就得按照我的规范来,和java中jdbc一样,sun公司负责规定一套规范,提供一套接口,下游的用户自行负责实现,你具体怎么实现我不关心,你要做的就是实现我规定的一套规范。

                OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。OAuth强调的是授权,当然要想获取授权前你必须认证,就像第一节说的,用户只有登录后才能获得对应用户的权限,这一步是无法绕开的。

                3、SSO实现

                单点登录实现中,系统之间的协议对接是非常重要的一环,一般涉及的标准协议类型有 CAS、OAuth、OpenID Connect、SAML。

                另外国产的SA-TOKEN项目也有sso和oauth实现

                3.1 CAS

                Central Authentication Service简称CAS,是一种常见的B/S架构的SSO协议。和其他任何SSO协议一样,用户仅需登陆一次,访问其他应用则无需再次登陆。顾名思义,CAS是一种仅用于Authentication的服务,它和OAuth/OIDC协议不一样,并不能作为一种Authorization的协议。当前CAS协议包括CAS 1.0、CAS2.0、CAS3.0版本,这三个版本的认证流程基本类似。CAS的认证流程通过包括几部分参与者:

                Client: 通常为使用浏览器的用户 CAS Client: 实现CAS协议的Web应用 CAS Server: 作为统一认证的CAS服务器

                4、各种协议对比

                单纯的单点登录,其实无需授权中心有一套自己的用户和权限,单点登录是只管登录,登录后从业务系统自行获取自己的权限,所以从这个角度来说sso也有它自己的优势,它不像oauth那样还要维护一套用户权限系统,如果仅仅作单点登录不用权限的话用cas显然更有优势。

                ',16),c={href:"https://zhuanlan.zhihu.com/p/267845330",target:"_blank",rel:"noopener noreferrer"},p=e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/20230104171826.png",alt:"20230104171826"},null,-1),S={href:"https://sa-token.cc/doc.html#/fun/sso-vs-oauth2",target:"_blank",rel:"noopener noreferrer"},u=o("
                功能点SSO单点登录OAuth2.0
                统一认证支持度高支持度高
                统一注销支持度高支持度低
                多个系统会话一致性强一致弱一致
                第三方应用授权管理不支持支持度高
                自有系统授权管理支持度高支持度低
                Client级的权限校验不支持支持度高
                集成简易度比较简单难度中等
                ",1);function m(_,g){const n=i("ExternalLinkIcon");return s(),h("div",null,[l,e("p",null,[t("以下图片是各种实现的对比图"),e("a",c,[t("来自知乎"),a(n)]),t(",我并不赞同他的观点,仅作参考 "),p]),e("p",null,[t("以下对比来自"),e("a",S,[t("sa-token官网"),a(n)])]),u])}const C=r(d,[["render",m],["__file","SSO.html.vue"]]),f=JSON.parse('{"path":"/java/framework/security/SSO.html","title":"SSO协议","lang":"zh-CN","frontmatter":{"title":"SSO协议","date":"2023-01-03T00:00:00.000Z","tag":["oauth","sso"],"description":"1、写在前面 一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录 授权(Authorization),即用户要访问某资源,必须要拥有对应权限 也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/SSO.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"SSO协议"}],["meta",{"property":"og:description","content":"1、写在前面 一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录 授权(Authorization),即用户要访问某资源,必须要拥有对应权限 也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20230104171826.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"oauth"}],["meta",{"property":"article:tag","content":"sso"}],["meta",{"property":"article:published_time","content":"2023-01-03T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"SSO协议\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20230104171826.png\\"],\\"datePublished\\":\\"2023-01-03T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、写在前面","slug":"_1、写在前面","link":"#_1、写在前面","children":[]},{"level":2,"title":"2、SSO和OAUTH2介绍","slug":"_2、sso和oauth2介绍","link":"#_2、sso和oauth2介绍","children":[]},{"level":2,"title":"3、SSO实现","slug":"_3、sso实现","link":"#_3、sso实现","children":[{"level":3,"title":"3.1 CAS","slug":"_3-1-cas","link":"#_3-1-cas","children":[]}]},{"level":2,"title":"4、各种协议对比","slug":"_4、各种协议对比","link":"#_4、各种协议对比","children":[]}],"git":{"createdTime":1672824556000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":2}]},"readingTime":{"minutes":3.65,"words":1094},"filePathRelative":"java/framework/security/SSO.md","localizedDate":"2023年1月3日","excerpt":"

                1、写在前面

                \\n

                一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。

                \\n
                  \\n
                • 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录
                • \\n
                • 授权(Authorization),即用户要访问某资源,必须要拥有对应权限
                • \\n
                \\n

                也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源时,系统会校验其拥有的权限是否可以访问这个资源。

                \\n

                2、SSO和OAUTH2介绍

                \\n

                SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。

                ","autoDesc":true}');export{C as comp,f as data}; diff --git a/assets/SUC.html-BJ48MGxE.js b/assets/SUC.html-DtVpWo_q.js similarity index 99% rename from assets/SUC.html-BJ48MGxE.js rename to assets/SUC.html-DtVpWo_q.js index bd6f5d748b..6175ab6794 100644 --- a/assets/SUC.html-BJ48MGxE.js +++ b/assets/SUC.html-DtVpWo_q.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

                1、基础环境搭建

                1.1 引入依赖

                       <dependency>
                +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

                1、基础环境搭建

                1.1 引入依赖

                       <dependency>
                             <groupId>org.springframework.security.oauth.boot</groupId>
                             <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                             <version>2.1.2.RELEASE</version>
                diff --git a/assets/Samba.html-DATE_iKU.js b/assets/Samba.html-D8HLrsJP.js
                similarity index 99%
                rename from assets/Samba.html-DATE_iKU.js
                rename to assets/Samba.html-D8HLrsJP.js
                index 1c23b056b6..a686053571 100644
                --- a/assets/Samba.html-DATE_iKU.js
                +++ b/assets/Samba.html-D8HLrsJP.js
                @@ -1,4 +1,4 @@
                -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as a,e as s}from"./app-CVMfKeWw.js";const i={},t=s(`

                1、安装过程省略

                2、配置

                2.1 配置文件

                配置目录在 /etc/samba,修改smb.conf在最后加一组[test],同时修改[global]在里面加上ntlm auth = yes,最终加完如下

                # See smb.conf.example for a more detailed config file or
                +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as a,e as s}from"./app-_Oi5YZFn.js";const i={},t=s(`

                1、安装过程省略

                2、配置

                2.1 配置文件

                配置目录在 /etc/samba,修改smb.conf在最后加一组[test],同时修改[global]在里面加上ntlm auth = yes,最终加完如下

                # See smb.conf.example for a more detailed config file or
                 # read the smb.conf manpage.
                 # Run 'testparm' to verify the config is correct after
                 # you modified it.
                diff --git a/assets/SecurityFilterChain.html-D0yc0vvl.js b/assets/SecurityFilterChain.html-j_4ZmVEf.js
                similarity index 99%
                rename from assets/SecurityFilterChain.html-D0yc0vvl.js
                rename to assets/SecurityFilterChain.html-j_4ZmVEf.js
                index 569dadb3fa..93a6f6bf56 100644
                --- a/assets/SecurityFilterChain.html-D0yc0vvl.js
                +++ b/assets/SecurityFilterChain.html-j_4ZmVEf.js
                @@ -1,4 +1,4 @@
                -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},i=t(`

                1、配置类

                1. 5.7版本后Security把WebSecurityConfigurerAdapter标记为废弃,鼓励程序员使用SecurityFilterChain进行配置,如果看过官网Security的架构图对SecurityFilterChain一定不会陌生,此类是Security过滤器的核心,所以用它来配置寓意更为明显。
                +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},i=t(`

                1、配置类

                1. 5.7版本后Security把WebSecurityConfigurerAdapter标记为废弃,鼓励程序员使用SecurityFilterChain进行配置,如果看过官网Security的架构图对SecurityFilterChain一定不会陌生,此类是Security过滤器的核心,所以用它来配置寓意更为明显。
                 
                    @Bean
                     @Order(3)
                     public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
                diff --git a/assets/Serialization.html-BPyqpG-l.js b/assets/Serialization.html-BJVNALhI.js
                similarity index 99%
                rename from assets/Serialization.html-BPyqpG-l.js
                rename to assets/Serialization.html-BJVNALhI.js
                index 793a28e2ff..68b7a86e3c 100644
                --- a/assets/Serialization.html-BPyqpG-l.js
                +++ b/assets/Serialization.html-BJVNALhI.js
                @@ -1,4 +1,4 @@
                -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

                1、序列化、反序列化是什么?

                • 序列化:把对象转化成字节码
                • 反序列化:把字节码(从IO流获取或者从硬盘文件读取)转化为对象

                2、 举例说明作用?

                只有序列化成字节码文件后,对象才能在网络中通过IO流(传输的是字节码)传输或者存到硬盘上

                2.1 实现分布式对象

                例如在RMI(Remote Method Invoke)中利用对象序列化来运行远程主机上的服务,就像执行本地的对象方法一样;

                2.2 Java对象序列化不仅保留一个对象的数据,而且会递归保留引用的对象

                序列化时,把虚拟机内存中的对象保存到文件,反序列化时再把文件中的对象还原到内存中去

                注意如果序列化Student时,Teacher类没有被序列化则会抛出异常~~~ java.io.NotSerializableException: com.chen.Teacher~~~

                如果想不抛出异常有两种方法:

                1. 老老实实把Teacher类也实现Serializable接口
                2. 在Student类中把Teacher字段用transient修饰,则反序列化后看到的teacher是null这是和第一种方法不一样的地方
                //学生类实现序列化就接口,并且学生有一个老师属性字段
                +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

                1、序列化、反序列化是什么?

                • 序列化:把对象转化成字节码
                • 反序列化:把字节码(从IO流获取或者从硬盘文件读取)转化为对象

                2、 举例说明作用?

                只有序列化成字节码文件后,对象才能在网络中通过IO流(传输的是字节码)传输或者存到硬盘上

                2.1 实现分布式对象

                例如在RMI(Remote Method Invoke)中利用对象序列化来运行远程主机上的服务,就像执行本地的对象方法一样;

                2.2 Java对象序列化不仅保留一个对象的数据,而且会递归保留引用的对象

                序列化时,把虚拟机内存中的对象保存到文件,反序列化时再把文件中的对象还原到内存中去

                注意如果序列化Student时,Teacher类没有被序列化则会抛出异常~~~ java.io.NotSerializableException: com.chen.Teacher~~~

                如果想不抛出异常有两种方法:

                1. 老老实实把Teacher类也实现Serializable接口
                2. 在Student类中把Teacher字段用transient修饰,则反序列化后看到的teacher是null这是和第一种方法不一样的地方
                //学生类实现序列化就接口,并且学生有一个老师属性字段
                 
                 public class Student implements Serializable {
                     private Teacher teacher = new Teacher("James Li", 33);
                diff --git a/assets/ServiceInstall.html-CCIiUtbe.js b/assets/ServiceInstall.html-BaooIkHM.js
                similarity index 99%
                rename from assets/ServiceInstall.html-CCIiUtbe.js
                rename to assets/ServiceInstall.html-BaooIkHM.js
                index f5489a753f..c9cea6b3bd 100644
                --- a/assets/ServiceInstall.html-CCIiUtbe.js
                +++ b/assets/ServiceInstall.html-BaooIkHM.js
                @@ -1,4 +1,4 @@
                -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as a,e as s}from"./app-CVMfKeWw.js";const t={},i=s(`

                1. docker安装Redis

                起初的需求是用docker启动一个redis,并且指定一个配置,死活不成功,主要是少设置了data目录的映射

                注意

                使用docker-compose安装redis时,若指定外置redis.conf配置文件,要切记同时设置data存储目录,默认docker中的数据存在/data下,所以 使用卷映射时需要映射到容器内的/data docker-compose.yml

                目录结构如下,需要事先准备一个redis.conf放到项目suc/redis/conf,另外需要创建目录suc/redis/data

                suc
                +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as a,e as s}from"./app-_Oi5YZFn.js";const t={},i=s(`

                1. docker安装Redis

                起初的需求是用docker启动一个redis,并且指定一个配置,死活不成功,主要是少设置了data目录的映射

                注意

                使用docker-compose安装redis时,若指定外置redis.conf配置文件,要切记同时设置data存储目录,默认docker中的数据存在/data下,所以 使用卷映射时需要映射到容器内的/data docker-compose.yml

                目录结构如下,需要事先准备一个redis.conf放到项目suc/redis/conf,另外需要创建目录suc/redis/data

                suc
                 ├── redis
                 │   ├── conf
                 │   │   └── redis.conf
                diff --git a/assets/Session.html-PVXvBLSr.js b/assets/Session.html-CKk3uN_p.js
                similarity index 98%
                rename from assets/Session.html-PVXvBLSr.js
                rename to assets/Session.html-CKk3uN_p.js
                index 57a12a71e0..52b6bf536d 100644
                --- a/assets/Session.html-PVXvBLSr.js
                +++ b/assets/Session.html-CKk3uN_p.js
                @@ -1,4 +1,4 @@
                -import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o,c as r,a as e,b as t,d as s,e as c}from"./app-CVMfKeWw.js";const l={},d=c(`

                1、背景(问题复现)

                
                +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o,c as r,a as e,b as t,d as s,e as c}from"./app-_Oi5YZFn.js";const l={},d=c(`

                1、背景(问题复现)

                
                 1. 使用idea新建一个springboot项目,引入web和security框架,不做任何配置直接启动web项目
                 2. 随便访问一个接口比如:http://localhost:8080/hello/aa,此时由于接口被security默认保护,会重定向到登录页面(如图一),此时查看sessionid(也就是name为JSESSIONID的cookie)是91E9629F748637154F86CCB44FB2B23D
                 3. 然后输入用户名:user,密码:控制台随机生成的,登录后会重定向到之前访问的接口,但此时
                diff --git a/assets/SetObjectNull.html-pva3Gj1n.js b/assets/SetObjectNull.html-BA6rF5JL.js
                similarity index 99%
                rename from assets/SetObjectNull.html-pva3Gj1n.js
                rename to assets/SetObjectNull.html-BA6rF5JL.js
                index 38f4697e8c..b892daac68 100644
                --- a/assets/SetObjectNull.html-pva3Gj1n.js
                +++ b/assets/SetObjectNull.html-BA6rF5JL.js
                @@ -1,4 +1,4 @@
                -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as t,c as p,a as n,b as o,d as c,e as l}from"./app-CVMfKeWw.js";const i={},u=l(`

                前言:是否需要把不用的对象设置为null?

                1、开始写代码测试(所有测试都要加上以下指令)

                jvm参数-Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=2097152

                简单解释一下:

                • -Xms20m -Xmx20m这两个指令限制堆内存固定为20m不允许扩容
                • -Xmn10m代表分配给新生代的总内存为10m
                • -XX:SurvivorRatio=8代表Eden区和Survivor的比例8:1,即新生代被分为3部分,分别8m,1m,1m
                • XX:PretenureSizeThreshold=2097152,这个指令用的比较少,在虚拟机中,普通对象都在新生代分配内存,但是大对象是直接在老年代分配,至于多大算大对象,就是这个参数来设置的,我设置的是2m用来测试(2097152 =2 * 1024 * 1024 ),设置2m是方便我测试,保证我在下面代码设置1m的MB_1对象,内存是在新生代分配,而不是直接进入老年代
                • -XX:+PrintGCDetails打印垃圾回收日志

                1.1 第一次测试,直接创建一个512kb的数组,调用回收

                  public static void main(String[] args) throws InterruptedException {
                +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as t,c as p,a as n,b as o,d as c,e as l}from"./app-_Oi5YZFn.js";const i={},u=l(`

                前言:是否需要把不用的对象设置为null?

                1、开始写代码测试(所有测试都要加上以下指令)

                jvm参数-Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=2097152

                简单解释一下:

                • -Xms20m -Xmx20m这两个指令限制堆内存固定为20m不允许扩容
                • -Xmn10m代表分配给新生代的总内存为10m
                • -XX:SurvivorRatio=8代表Eden区和Survivor的比例8:1,即新生代被分为3部分,分别8m,1m,1m
                • XX:PretenureSizeThreshold=2097152,这个指令用的比较少,在虚拟机中,普通对象都在新生代分配内存,但是大对象是直接在老年代分配,至于多大算大对象,就是这个参数来设置的,我设置的是2m用来测试(2097152 =2 * 1024 * 1024 ),设置2m是方便我测试,保证我在下面代码设置1m的MB_1对象,内存是在新生代分配,而不是直接进入老年代
                • -XX:+PrintGCDetails打印垃圾回收日志

                1.1 第一次测试,直接创建一个512kb的数组,调用回收

                  public static void main(String[] args) throws InterruptedException {
                         byte[] KB_512 = new byte[1 * 1024 * 512];
                         System.gc();
                     }
                diff --git a/assets/ShareBetweenWindowsAndLinux.html-Vr4o6QxT.js b/assets/ShareBetweenWindowsAndLinux.html-CJ7U7icg.js
                similarity index 97%
                rename from assets/ShareBetweenWindowsAndLinux.html-Vr4o6QxT.js
                rename to assets/ShareBetweenWindowsAndLinux.html-CJ7U7icg.js
                index 9a414a01e6..76c303ced4 100644
                --- a/assets/ShareBetweenWindowsAndLinux.html-Vr4o6QxT.js
                +++ b/assets/ShareBetweenWindowsAndLinux.html-CJ7U7icg.js
                @@ -1,4 +1,4 @@
                -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as t,c as o,a as e,b as i,d as r,e as d}from"./app-CVMfKeWw.js";const c={},l=d(`

                1、在windows设置共享目录

                设置过程省略……

                2、在linux下挂载

                    ## 1. 创建空白目录
                +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as t,c as o,a as e,b as i,d as r,e as d}from"./app-_Oi5YZFn.js";const c={},l=d(`

                1、在windows设置共享目录

                设置过程省略……

                2、在linux下挂载

                    ## 1. 创建空白目录
                         mkdir /home/data/share
                     ## 2. 修改/etc/fstab开机自动挂载
                     //10.10.102.97/tempfile    /home/data/share  cifs    defaults,user=xxx,password=xxx,uid=1000,gid=1000  0 0
                diff --git a/assets/SoftWare.html-DwRE_esc.js b/assets/SoftWare.html-6EROxq9x.js
                similarity index 97%
                rename from assets/SoftWare.html-DwRE_esc.js
                rename to assets/SoftWare.html-6EROxq9x.js
                index 528d3d7216..5691259a49 100644
                --- a/assets/SoftWare.html-DwRE_esc.js
                +++ b/assets/SoftWare.html-6EROxq9x.js
                @@ -1,4 +1,4 @@
                -import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o as s,c as i,a as e,b as t,d as a,e as c}from"./app-CVMfKeWw.js";const l={},p=e("h3",{id:"_1、beyond-compare3",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、beyond-compare3"},[e("span",null,"1、Beyond Compare3")])],-1),d=e("h4",{id:"_1-1、下载",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-1、下载"},[e("span",null,"1.1、下载")])],-1),m={href:"https://ddns.chensina.cn:29000/afatpig/software/beyond_compare3.zip",target:"_blank",rel:"noopener noreferrer"},h=e("h4",{id:"_1-2-、集成到git-对比",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-2-、集成到git-对比"},[e("span",null,"1.2 、集成到git 对比")])],-1),u={href:"https://www.cnblogs.com/ayseeing/p/4268729.html",target:"_blank",rel:"noopener noreferrer"},g=c(`

                第一步,设置:

                 git config --global merge.tool bc3
                +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o as s,c as i,a as e,b as t,d as a,e as c}from"./app-_Oi5YZFn.js";const l={},p=e("h3",{id:"_1、beyond-compare3",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、beyond-compare3"},[e("span",null,"1、Beyond Compare3")])],-1),d=e("h4",{id:"_1-1、下载",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-1、下载"},[e("span",null,"1.1、下载")])],-1),m={href:"https://ddns.chensina.cn:29000/afatpig/software/beyond_compare3.zip",target:"_blank",rel:"noopener noreferrer"},h=e("h4",{id:"_1-2-、集成到git-对比",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-2-、集成到git-对比"},[e("span",null,"1.2 、集成到git 对比")])],-1),u={href:"https://www.cnblogs.com/ayseeing/p/4268729.html",target:"_blank",rel:"noopener noreferrer"},g=c(`

                第一步,设置:

                 git config --global merge.tool bc3
                 $ git config --global mergetool.bc3.path "c:/program files/beyond compare 3/bcomp.exe"
                 

                第二部,使用:

                #注意对比使用git difftool不要用git diff
                 git difftool [xxx]
                diff --git a/assets/SpringAOP.html-0F2yJUPF.js b/assets/SpringAOP.html-BR3lenMn.js
                similarity index 99%
                rename from assets/SpringAOP.html-0F2yJUPF.js
                rename to assets/SpringAOP.html-BR3lenMn.js
                index 68e26ffa68..77c67298dd 100644
                --- a/assets/SpringAOP.html-0F2yJUPF.js
                +++ b/assets/SpringAOP.html-BR3lenMn.js
                @@ -1,4 +1,4 @@
                -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

                1、SpringAOP

                > SpringAOP的本质就是动态代理,底层使用JDK动态代理或者CGlib动态代理,通过代理框架生成代理类,实现对目标类的增强,Spring代理是方法级别的代理,是对方法增强,
                +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

                1、SpringAOP

                > SpringAOP的本质就是动态代理,底层使用JDK动态代理或者CGlib动态代理,通过代理框架生成代理类,实现对目标类的增强,Spring代理是方法级别的代理,是对方法增强,
                 > 
                 > 代理有四个要素:
                 > 1. 目标类 
                diff --git a/assets/SpringBootAutoConfiguration.html-B2Ew9z9M.js b/assets/SpringBootAutoConfiguration.html-DyJd0iCn.js
                similarity index 99%
                rename from assets/SpringBootAutoConfiguration.html-B2Ew9z9M.js
                rename to assets/SpringBootAutoConfiguration.html-DyJd0iCn.js
                index 84264de5c7..eba11d81e3 100644
                --- a/assets/SpringBootAutoConfiguration.html-B2Ew9z9M.js
                +++ b/assets/SpringBootAutoConfiguration.html-DyJd0iCn.js
                @@ -1,4 +1,4 @@
                -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const o={},p=t(`

                1. springboot自动配置的原理初探

                ​以下注解都在springboot的自动化配置包中:spring-boot-autoconfigure

                1. springboot程序的入口是在启动类,该类有个关键注解SpringBootApplication

                  @Target(ElementType.TYPE)
                  +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const o={},p=t(`

                  1. springboot自动配置的原理初探

                  ​以下注解都在springboot的自动化配置包中:spring-boot-autoconfigure

                  1. springboot程序的入口是在启动类,该类有个关键注解SpringBootApplication

                    @Target(ElementType.TYPE)
                     @Retention(RetentionPolicy.RUNTIME)
                     @Documented
                     @Inherited
                    diff --git a/assets/SpringCache.html-BcT_2YyF.js b/assets/SpringCache.html-B2PNt9GB.js
                    similarity index 99%
                    rename from assets/SpringCache.html-BcT_2YyF.js
                    rename to assets/SpringCache.html-B2PNt9GB.js
                    index 76dca70767..4f3aa18e5a 100644
                    --- a/assets/SpringCache.html-BcT_2YyF.js
                    +++ b/assets/SpringCache.html-B2PNt9GB.js
                    @@ -1,4 +1,4 @@
                    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-CVMfKeWw.js";const t={},p=e(`

                    Spring缓存大揭秘

                    注意

                    使用springboot测试springcache切记需要在启动类加上@EnableCache

                    1、背景

                    使用springboot项目时看到很多对spring缓存注解的使用,比如@Cacheable、@Evict、@CachePut等,之前一直都是知道个大概,使用方式也就局限于配置好redisTemplate然后直接开始使用,但是一直不明白为什么我配置好redisTemplate就可以用了,甚至不配置redis也能有缓存效果。之前还一直有个疑问,新增数据spring把返回结果缓存后,那么下次如果更新了数据,如何同时更新缓存列表呢?

                    2、spring-cache介绍

                    springcache不是一个单独的jar包,它位于spring-context的org.springframework.cache包下,spring提供了各种注解来使用缓存,并且提供了多种缓存实现,比如常见的redis,EhCache等。

                    image-20211014105423666

                    3、springcache的实现

                    如果不引入redis等第三方包,则spring默认采用的是ConcurrentMapCache来管理缓存,它里面有个ConcurrentMap(具体实现是ConcurrentHashMap,在ConcurrentMapCacheManager类中传入的),如果引入第三方比如redis,则会自动使用redis

                    image-20211014105857365

                    image-20211014110150052

                    在上图中可以看到ConcurrentMapCacheManager创建了一个ConcurrentHashMap对象,用来初始化ConcurrentMapCache,所以由此我们可以知道spring默认缓存实际上是一个Map对象,占用的是JVM内存,一旦创建后就永不消失,因为它不像redis有过期时间,所以使用默认缓存要慎重,注意OOM

                    总结一下spring缓存默认实现:

                    1. SimpleCacheConfiguration是一个springboot的配置类,类中会实例化一个ConcurrentMapCacheManager的Bean,
                    @Configuration(proxyBeanMethods = false)
                    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

                    Spring缓存大揭秘

                    注意

                    使用springboot测试springcache切记需要在启动类加上@EnableCache

                    1、背景

                    使用springboot项目时看到很多对spring缓存注解的使用,比如@Cacheable、@Evict、@CachePut等,之前一直都是知道个大概,使用方式也就局限于配置好redisTemplate然后直接开始使用,但是一直不明白为什么我配置好redisTemplate就可以用了,甚至不配置redis也能有缓存效果。之前还一直有个疑问,新增数据spring把返回结果缓存后,那么下次如果更新了数据,如何同时更新缓存列表呢?

                    2、spring-cache介绍

                    springcache不是一个单独的jar包,它位于spring-context的org.springframework.cache包下,spring提供了各种注解来使用缓存,并且提供了多种缓存实现,比如常见的redis,EhCache等。

                    image-20211014105423666

                    3、springcache的实现

                    如果不引入redis等第三方包,则spring默认采用的是ConcurrentMapCache来管理缓存,它里面有个ConcurrentMap(具体实现是ConcurrentHashMap,在ConcurrentMapCacheManager类中传入的),如果引入第三方比如redis,则会自动使用redis

                    image-20211014105857365

                    image-20211014110150052

                    在上图中可以看到ConcurrentMapCacheManager创建了一个ConcurrentHashMap对象,用来初始化ConcurrentMapCache,所以由此我们可以知道spring默认缓存实际上是一个Map对象,占用的是JVM内存,一旦创建后就永不消失,因为它不像redis有过期时间,所以使用默认缓存要慎重,注意OOM

                    总结一下spring缓存默认实现:

                    1. SimpleCacheConfiguration是一个springboot的配置类,类中会实例化一个ConcurrentMapCacheManager的Bean,
                    @Configuration(proxyBeanMethods = false)
                     @ConditionalOnMissingBean(CacheManager.class)
                     @Conditional(CacheCondition.class)
                     class SimpleCacheConfiguration {
                    diff --git a/assets/SpringCloudGateway.html-DptX-4Hc.js b/assets/SpringCloudGateway.html-Dc9fg5OX.js
                    similarity index 99%
                    rename from assets/SpringCloudGateway.html-DptX-4Hc.js
                    rename to assets/SpringCloudGateway.html-Dc9fg5OX.js
                    index e443265923..0ad87eef55 100644
                    --- a/assets/SpringCloudGateway.html-DptX-4Hc.js
                    +++ b/assets/SpringCloudGateway.html-Dc9fg5OX.js
                    @@ -1,4 +1,4 @@
                    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-CVMfKeWw.js";const t={},p=e(`

                    问题描述

                    有一个业务服务,启动了两个做成负载均衡,分别为10.6.6.11:2221,10.6.6.11:5221,为了调试,所以把route修改为只路由到5221,但是网关服务配置好route后,发送请求无法路由到指定的10.6.6.11:5221服务,一直路由到另一个服务10.6.6.11:2221上,并且连自定义的gatewayfilter都失效了

                    请求路径为:http://gateway:port/mcs/test 配置如下

                    spring:
                    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

                    问题描述

                    有一个业务服务,启动了两个做成负载均衡,分别为10.6.6.11:2221,10.6.6.11:5221,为了调试,所以把route修改为只路由到5221,但是网关服务配置好route后,发送请求无法路由到指定的10.6.6.11:5221服务,一直路由到另一个服务10.6.6.11:2221上,并且连自定义的gatewayfilter都失效了

                    请求路径为:http://gateway:port/mcs/test 配置如下

                    spring:
                       cloud:
                         gateway:
                           discovery:
                    diff --git a/assets/SpringExtensionPoint.html-BY3OGoZJ.js b/assets/SpringExtensionPoint.html-BdwK_BjA.js
                    similarity index 99%
                    rename from assets/SpringExtensionPoint.html-BY3OGoZJ.js
                    rename to assets/SpringExtensionPoint.html-BdwK_BjA.js
                    index 929d9a913a..c57792fb49 100644
                    --- a/assets/SpringExtensionPoint.html-BY3OGoZJ.js
                    +++ b/assets/SpringExtensionPoint.html-BdwK_BjA.js
                    @@ -1,4 +1,4 @@
                    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-CVMfKeWw.js";const t={},p=e(`

                    [TOC]

                    1. spring生命周期

                    spring容器实例化一个对象往大说主要是分为两步

                    1.1 第一步:根据配置生成BeanDefinition

                    根据配置文件(properties、xml)、注解等生成Bean的定义,BeanDefinition的作用是用来描述如何生成真正的对象,对象的生成是通过反射实现,这里的关键词是“描述”,它的作用是告诉容器如何去生成一个真正的对象,它不是真正的对象!!!!

                    BeanDefinition里包含信息如下:

                    1. class:有了bean的class用反射就可以创建出一个真正的对象;
                    2. scope :可取singleton、prototype,即单例还是多例;
                    3. lazyInit:表明此bean是否为懒加载,如果是true代表容器启动后自动创建出bean真实对象,若为false,则代表需要显示的调用容器的getBean()方法时才会实例化出对象;
                    4. 其它字段不再介绍

                    1.2 第二步:根据BeanDefinition来生成真正的对象

                    image-20210526182642991

                    2. 扩展点介绍

                    2.1 扩展点1-BeanFactoryPostProcessor

                    此接口在容器启动后,并且BeanDefinition已经注册到容器中以后,调用其回调函数,作用就是能拿到ConfigurableListableBeanFactory,然后操作里面的容器里面的BeanDefinition

                    前面说了第一步是生成BeanDefinition此时真实的对象还未生成,所以可以用spring预留的扩展接口做一些事情,比如在代码中修改一个bean的BeanDefinition

                    举例说明(此场景可能没啥卵用,只是为了证明可以在实例化出对象之前可以修改它的BeanDefinition):

                    第一次测试:在xml随便配一个bean,bean标签有个lazyInit,默认是false

                      <bean id="address" class="com.chen.bean.Address">
                    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

                    [TOC]

                    1. spring生命周期

                    spring容器实例化一个对象往大说主要是分为两步

                    1.1 第一步:根据配置生成BeanDefinition

                    根据配置文件(properties、xml)、注解等生成Bean的定义,BeanDefinition的作用是用来描述如何生成真正的对象,对象的生成是通过反射实现,这里的关键词是“描述”,它的作用是告诉容器如何去生成一个真正的对象,它不是真正的对象!!!!

                    BeanDefinition里包含信息如下:

                    1. class:有了bean的class用反射就可以创建出一个真正的对象;
                    2. scope :可取singleton、prototype,即单例还是多例;
                    3. lazyInit:表明此bean是否为懒加载,如果是true代表容器启动后自动创建出bean真实对象,若为false,则代表需要显示的调用容器的getBean()方法时才会实例化出对象;
                    4. 其它字段不再介绍

                    1.2 第二步:根据BeanDefinition来生成真正的对象

                    image-20210526182642991

                    2. 扩展点介绍

                    2.1 扩展点1-BeanFactoryPostProcessor

                    此接口在容器启动后,并且BeanDefinition已经注册到容器中以后,调用其回调函数,作用就是能拿到ConfigurableListableBeanFactory,然后操作里面的容器里面的BeanDefinition

                    前面说了第一步是生成BeanDefinition此时真实的对象还未生成,所以可以用spring预留的扩展接口做一些事情,比如在代码中修改一个bean的BeanDefinition

                    举例说明(此场景可能没啥卵用,只是为了证明可以在实例化出对象之前可以修改它的BeanDefinition):

                    第一次测试:在xml随便配一个bean,bean标签有个lazyInit,默认是false

                      <bean id="address" class="com.chen.bean.Address">
                             <property name="addressName" value="startName"/>
                         </bean>
                     

                    第二次测试:在实例化之前通过扩展点的功能把lazyInit改为true

                    实现方式:

                    @Component
                    diff --git a/assets/SpringIOC.html-D2ZKIq-l.js b/assets/SpringIOC.html-BqsqDvdX.js
                    similarity index 99%
                    rename from assets/SpringIOC.html-D2ZKIq-l.js
                    rename to assets/SpringIOC.html-BqsqDvdX.js
                    index 185ad8cc1e..316f4dbb40 100644
                    --- a/assets/SpringIOC.html-D2ZKIq-l.js
                    +++ b/assets/SpringIOC.html-BqsqDvdX.js
                    @@ -1,4 +1,4 @@
                    -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as s,b as n,d as e,e as a}from"./app-CVMfKeWw.js";const i={},u=a(`

                    Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器。既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文并不能让你成为 Spring 专家,不过一定有助于大家理解 Spring 的很多概念,帮助大家排查应用中和 Spring 相关的一些问题。

                    本文采用的源码版本是 4.3.11.RELEASE,算是 5.0.x 前比较新的版本了。为了降低难度,本文所说的所有的内容都是基于 xml 的配置的方式,实际使用已经很少人这么做了,至少不是纯 xml 配置,不过从理解源码的角度来看用这种方式来说无疑是最合适的。

                    阅读建议:读者至少需要知道怎么配置 Spring,了解 Spring 中的各种概念,少部分内容我还假设读者使用过 SpringMVC。本文要说的 IOC 总体来说有两处地方最重要,一个是创建 Bean 容器,一个是初始化 Bean,如果读者觉得一次性看完本文压力有点大,那么可以按这个思路分两次消化。读者不一定对 Spring 容器的源码感兴趣,也许附录部分介绍的知识对读者有些许作用。

                    希望通过本文可以让读者不惧怕阅读 Spring 源码,也希望大家能反馈表述错误或不合理的地方。

                    引言

                    先看下最基本的启动 Spring 容器的例子:

                    public static void main(String[] args) {
                    +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as s,b as n,d as e,e as a}from"./app-_Oi5YZFn.js";const i={},u=a(`

                    Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器。既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文并不能让你成为 Spring 专家,不过一定有助于大家理解 Spring 的很多概念,帮助大家排查应用中和 Spring 相关的一些问题。

                    本文采用的源码版本是 4.3.11.RELEASE,算是 5.0.x 前比较新的版本了。为了降低难度,本文所说的所有的内容都是基于 xml 的配置的方式,实际使用已经很少人这么做了,至少不是纯 xml 配置,不过从理解源码的角度来看用这种方式来说无疑是最合适的。

                    阅读建议:读者至少需要知道怎么配置 Spring,了解 Spring 中的各种概念,少部分内容我还假设读者使用过 SpringMVC。本文要说的 IOC 总体来说有两处地方最重要,一个是创建 Bean 容器,一个是初始化 Bean,如果读者觉得一次性看完本文压力有点大,那么可以按这个思路分两次消化。读者不一定对 Spring 容器的源码感兴趣,也许附录部分介绍的知识对读者有些许作用。

                    希望通过本文可以让读者不惧怕阅读 Spring 源码,也希望大家能反馈表述错误或不合理的地方。

                    引言

                    先看下最基本的启动 Spring 容器的例子:

                    public static void main(String[] args) {
                         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationfile.xml");
                     }
                     

                    以上代码就可以利用配置文件来启动一个 Spring 容器了,请使用 maven 的小伙伴直接在 dependencies 中加上以下依赖即可,个人比较反对那些不知道要添加什么依赖,然后把 Spring 的所有相关的东西都加进来的方式。

                    <dependency>
                    diff --git a/assets/SpringMVC.html-CW7qiD-c.js b/assets/SpringMVC.html-D8cETPq8.js
                    similarity index 99%
                    rename from assets/SpringMVC.html-CW7qiD-c.js
                    rename to assets/SpringMVC.html-D8cETPq8.js
                    index 245bcbb4f7..73ebbd087d 100644
                    --- a/assets/SpringMVC.html-CW7qiD-c.js
                    +++ b/assets/SpringMVC.html-D8cETPq8.js
                    @@ -1,4 +1,4 @@
                    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as p}from"./app-CVMfKeWw.js";const t={},e=p(`

                    SpringMVC处理请求的流程

                    20230625180034

                    前端控制器源码

                    
                    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as p}from"./app-_Oi5YZFn.js";const t={},e=p(`

                    SpringMVC处理请求的流程

                    20230625180034

                    前端控制器源码

                    
                     protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
                       HttpServletRequest processedRequest = request;
                       HandlerExecutionChain mappedHandler = null;
                    diff --git a/assets/SpringSourceAnalize.html-C86lj3k3.js b/assets/SpringSourceAnalize.html-D0vztr7E.js
                    similarity index 96%
                    rename from assets/SpringSourceAnalize.html-C86lj3k3.js
                    rename to assets/SpringSourceAnalize.html-D0vztr7E.js
                    index 3dbea8058f..06a38f483a 100644
                    --- a/assets/SpringSourceAnalize.html-C86lj3k3.js
                    +++ b/assets/SpringSourceAnalize.html-D0vztr7E.js
                    @@ -1 +1 @@
                    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n}from"./app-CVMfKeWw.js";const r={};function o(i,a){return t(),n("div")}const m=e(r,[["render",o],["__file","SpringSourceAnalize.html.vue"]]),l=JSON.parse('{"path":"/java/framework/spring/SpringSourceAnalize.html","title":"SpringIOC源码分析","lang":"zh-CN","frontmatter":{"title":"SpringIOC源码分析","date":"2023-02-14T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/spring/SpringSourceAnalize.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"SpringIOC源码分析"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-02-14T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"SpringIOC源码分析\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-02-14T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1676623349000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenxk","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.03,"words":10},"filePathRelative":"java/framework/spring/SpringSourceAnalize.md","localizedDate":"2023年2月14日","excerpt":""}');export{m as comp,l as data};
                    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n}from"./app-_Oi5YZFn.js";const r={};function o(i,a){return t(),n("div")}const m=e(r,[["render",o],["__file","SpringSourceAnalize.html.vue"]]),l=JSON.parse('{"path":"/java/framework/spring/SpringSourceAnalize.html","title":"SpringIOC源码分析","lang":"zh-CN","frontmatter":{"title":"SpringIOC源码分析","date":"2023-02-14T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/spring/SpringSourceAnalize.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"SpringIOC源码分析"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-02-14T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"SpringIOC源码分析\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-02-14T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1676623349000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenxk","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.03,"words":10},"filePathRelative":"java/framework/spring/SpringSourceAnalize.md","localizedDate":"2023年2月14日","excerpt":""}');export{m as comp,l as data};
                    diff --git a/assets/String.html-CI6Ty1q7.js b/assets/String.html-Ca6nK_a0.js
                    similarity index 99%
                    rename from assets/String.html-CI6Ty1q7.js
                    rename to assets/String.html-Ca6nK_a0.js
                    index 16af151acb..78bf2ebbb0 100644
                    --- a/assets/String.html-CI6Ty1q7.js
                    +++ b/assets/String.html-Ca6nK_a0.js
                    @@ -1,4 +1,4 @@
                    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as t,c as i,a as n,b as l,d as c,e as o}from"./app-CVMfKeWw.js";const r={},d=n("h5",{id:"_1、string类",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1、string类"},[n("span",null,"1、String类")])],-1),v=n("p",null,"为什么String类要被设计为不可变?",-1),p={href:"https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/",target:"_blank",rel:"noopener noreferrer"},u=o(`
                    Thanks to the immutability of Strings in Java, the JVM can optimize the amount of memory allocated for them by storing only one copy of each literal String in the pool. This process is called interning.
                    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as t,c as i,a as n,b as l,d as c,e as o}from"./app-_Oi5YZFn.js";const r={},d=n("h5",{id:"_1、string类",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1、string类"},[n("span",null,"1、String类")])],-1),v=n("p",null,"为什么String类要被设计为不可变?",-1),p={href:"https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/",target:"_blank",rel:"noopener noreferrer"},u=o(`
                    Thanks to the immutability of Strings in Java, the JVM can optimize the amount of memory allocated for them by storing only one copy of each literal String in the pool. This process is called interning.
                     
                     When we create a String variable and assign a value to it, the JVM searches the pool for a String of equal value.
                     
                    diff --git a/assets/StringAdd.html-BihzcLHW.js b/assets/StringAdd.html-fGViknMr.js
                    similarity index 99%
                    rename from assets/StringAdd.html-BihzcLHW.js
                    rename to assets/StringAdd.html-fGViknMr.js
                    index e5231aef7b..36014309c2 100644
                    --- a/assets/StringAdd.html-BihzcLHW.js
                    +++ b/assets/StringAdd.html-fGViknMr.js
                    @@ -1,4 +1,4 @@
                    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c as l,a as n,b as s,d as c,e as a}from"./app-CVMfKeWw.js";const i={},u=a(`

                    1、先看问题,以下结果是什么?

                    String s1 = "Hello";
                    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c as l,a as n,b as s,d as c,e as a}from"./app-_Oi5YZFn.js";const i={},u=a(`

                    1、先看问题,以下结果是什么?

                    String s1 = "Hello";
                     String s2 = "Hello";
                     String s3 = "Hel" + "lo";
                     String s4 = "Hel" + new String("lo");
                    diff --git a/assets/Swagger.html-BrC3rGwr.js b/assets/Swagger.html-QZsEFOI2.js
                    similarity index 99%
                    rename from assets/Swagger.html-BrC3rGwr.js
                    rename to assets/Swagger.html-QZsEFOI2.js
                    index 97557012b7..facab47566 100644
                    --- a/assets/Swagger.html-BrC3rGwr.js
                    +++ b/assets/Swagger.html-QZsEFOI2.js
                    @@ -1,4 +1,4 @@
                    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

                    Swagger和SpringFox关系

                    java项目在引入swagger的时候,一般会引入如下依赖,当时只知道照着博客抄,也有好奇为啥叫springfox,怎么没有swagger,不知你是否有同样的疑问?直到今天问了一下GPT

                        <dependency>
                    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

                    Swagger和SpringFox关系

                    java项目在引入swagger的时候,一般会引入如下依赖,当时只知道照着博客抄,也有好奇为啥叫springfox,怎么没有swagger,不知你是否有同样的疑问?直到今天问了一下GPT

                        <dependency>
                             <groupId>io.springfox</groupId>
                             <artifactId>springfox-boot-starter</artifactId>
                         </dependency>
                    diff --git a/assets/Synchronized.html-Dhr9EviB.js b/assets/Synchronized.html-Bz2_U7rD.js
                    similarity index 97%
                    rename from assets/Synchronized.html-Dhr9EviB.js
                    rename to assets/Synchronized.html-Bz2_U7rD.js
                    index f2ddb2bf94..2314d0feb3 100644
                    --- a/assets/Synchronized.html-Dhr9EviB.js
                    +++ b/assets/Synchronized.html-Bz2_U7rD.js
                    @@ -1 +1 @@
                    -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as o,a as e}from"./app-CVMfKeWw.js";const a={},i=e("h2",{id:"_1、",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、"},[e("span",null,"1、")])],-1),r=[i];function c(h,d){return n(),o("div",null,r)}const m=t(a,[["render",c],["__file","Synchronized.html.vue"]]),l=JSON.parse('{"path":"/java/advance/Synchronized.html","title":"synchronized实现 原理","lang":"zh-CN","frontmatter":{"title":"synchronized实现 原理","date":"2023-01-31T00:00:00.000Z","category":["对象锁"],"description":"1、","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/advance/Synchronized.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"synchronized实现 原理"}],["meta",{"property":"og:description","content":"1、"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-01-31T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"synchronized实现 原理\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-01-31T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、","slug":"_1、","link":"#_1、","children":[]}],"git":{"createdTime":1675240920000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.05,"words":15},"filePathRelative":"java/advance/Synchronized.md","localizedDate":"2023年1月31日","excerpt":"

                    1、

                    \\n","autoDesc":true}');export{m as comp,l as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as o,a as e}from"./app-_Oi5YZFn.js";const a={},i=e("h2",{id:"_1、",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、"},[e("span",null,"1、")])],-1),r=[i];function c(h,d){return n(),o("div",null,r)}const m=t(a,[["render",c],["__file","Synchronized.html.vue"]]),l=JSON.parse('{"path":"/java/advance/Synchronized.html","title":"synchronized实现 原理","lang":"zh-CN","frontmatter":{"title":"synchronized实现 原理","date":"2023-01-31T00:00:00.000Z","category":["对象锁"],"description":"1、","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/advance/Synchronized.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"synchronized实现 原理"}],["meta",{"property":"og:description","content":"1、"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-01-31T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"synchronized实现 原理\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-01-31T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、","slug":"_1、","link":"#_1、","children":[]}],"git":{"createdTime":1675240920000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.05,"words":15},"filePathRelative":"java/advance/Synchronized.md","localizedDate":"2023年1月31日","excerpt":"

                    1、

                    \\n","autoDesc":true}');export{m as comp,l as data}; diff --git a/assets/TCP-IP.html-DSyGo702.js b/assets/TCP-IP.html-obsJBTgk.js similarity index 99% rename from assets/TCP-IP.html-DSyGo702.js rename to assets/TCP-IP.html-obsJBTgk.js index 21361a7a46..9f3e9e1e64 100644 --- a/assets/TCP-IP.html-DSyGo702.js +++ b/assets/TCP-IP.html-obsJBTgk.js @@ -1,4 +1,4 @@ -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as l,c as o,a as n,b as s,d as e,e as c}from"./app-CVMfKeWw.js";const i={},r=n("h2",{id:"_1、tcp-ip与osi的关系",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1、tcp-ip与osi的关系"},[n("span",null,"1、TCP/IP与OSI的关系")])],-1),u={href:"https://cloud.tencent.com/developer/article/2003231",target:"_blank",rel:"noopener noreferrer"},m=c(`

                    TCP(Transport Control Protocol)传输控制协议

                    IP(Internetworking Protocol)网间网协议

                    UDP(User Datagram Protocol)用户数据报协议

                    ICMP(Internet Control Message Protocol)互联网控制信息协议

                    SMTP(Simple Mail Transfer Protocol)简单邮件传输协议

                    SNMP(Simple Network manage Protocol)简单网络管理协议

                    FTP(File Transfer Protocol)文件传输协议

                    ARP(Address Resolation Protocol)地址解析协议

                    20230130104228

                    20230130103942

                    2、TCP/IP

                    完整的osi七层模型,在tcp/ip中被简化为了4层模型,其中应用层、表示层、会话层被压缩成了一个应用层,数据链路层、物理层被压缩为数据链路层。

                    flowchart LR
                    +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as l,c as o,a as n,b as s,d as e,e as c}from"./app-_Oi5YZFn.js";const i={},r=n("h2",{id:"_1、tcp-ip与osi的关系",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1、tcp-ip与osi的关系"},[n("span",null,"1、TCP/IP与OSI的关系")])],-1),u={href:"https://cloud.tencent.com/developer/article/2003231",target:"_blank",rel:"noopener noreferrer"},m=c(`

                    TCP(Transport Control Protocol)传输控制协议

                    IP(Internetworking Protocol)网间网协议

                    UDP(User Datagram Protocol)用户数据报协议

                    ICMP(Internet Control Message Protocol)互联网控制信息协议

                    SMTP(Simple Mail Transfer Protocol)简单邮件传输协议

                    SNMP(Simple Network manage Protocol)简单网络管理协议

                    FTP(File Transfer Protocol)文件传输协议

                    ARP(Address Resolation Protocol)地址解析协议

                    20230130104228

                    20230130103942

                    2、TCP/IP

                    完整的osi七层模型,在tcp/ip中被简化为了4层模型,其中应用层、表示层、会话层被压缩成了一个应用层,数据链路层、物理层被压缩为数据链路层。

                    flowchart LR
                         应用层 --> 传输控制层 --> 网络层 --> 链路层
                     

                    2.1 应用层

                    应用层顾名思义退就是和应用相关,既然和应用相关,那么不同应用肯定就不一样,这一部分的东西需要自定义,就是程序员需要实现的,比如HTTP、FTP、SMTP、NFS等。既然是互联网应用,数据传输肯定得有双方,就是我们说的客户端应用、服务端应用,而应用层这里就是制定客户端和服务端交互的规范,说白了就是保证客户端发的东西服务端能识别,服务端发的东西客户端也能识别。以http为例,http中就制定了请求行、请求体、请求头等,发送的时候必须有这些东西,服务端才能够解析,而相应的服务端响应时也得有对应的东西,这样客户端才能够解析出响应信息。这里只是制定规范,真正的传输还要用到后面的tcp,所以通常说http是基于tcp,就是这个道理。

                    2.2 传输控制层(TCP,不讲UDP)

                    关键词: 可靠性、三次握手四次挥手、拆包

                    tcp负责建立连接,断开连接,控制数据包大小等,建立连接就是常说的三次握手四次挥手。建立连接的过程分为3步:

                    1. 客户端发送连接请求的标识符SYN
                    2. 服务端回复SYN+ACK
                    3. 客户端回复ACK

                    具体的交互可以使用抓包工具wireshark查看,或者使用tcpdump命令行工具。

                    # 先打开监听
                     sudo tcpdump -nn -i <网卡> port 80
                    diff --git a/assets/TcpDump.html-cL04DF4f.js b/assets/TcpDump.html-DFEN5LYg.js
                    similarity index 99%
                    rename from assets/TcpDump.html-cL04DF4f.js
                    rename to assets/TcpDump.html-DFEN5LYg.js
                    index dd87168852..334ea5545c 100644
                    --- a/assets/TcpDump.html-cL04DF4f.js
                    +++ b/assets/TcpDump.html-DFEN5LYg.js
                    @@ -1,4 +1,4 @@
                    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as t,e as n}from"./app-CVMfKeWw.js";const s={},p=n(`

                    简要介绍Linux抓报

                    作为web开发我抓报主要是针对http请求,主要是看请求行,请求头,请求体,以及响应

                    1、所用抓报命令

                    sudo tcpdump tcp -i eth1 -t -s 0 -c 100 and dst port ! 22 and src net 192.168.1.0/24 -w ./target.cap
                    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as t,e as n}from"./app-_Oi5YZFn.js";const s={},p=n(`

                    简要介绍Linux抓报

                    作为web开发我抓报主要是针对http请求,主要是看请求行,请求头,请求体,以及响应

                    1、所用抓报命令

                    sudo tcpdump tcp -i eth1 -t -s 0 -c 100 and dst port ! 22 and src net 192.168.1.0/24 -w ./target.cap
                     

                    (1)tcp: ip icmp arp rarp 和 tcp、udp、icmp这些选项等都要放到第一个参数的位置,用来过滤数据报的类型 (2)-i enp3s0f1 : 只抓经过接口eth1的包 (3)-t : 不显示时间戳 (4)-s 0 : 抓取数据包时默认抓取长度为68字节。加上-S 0 后可以抓到完整的数据包 (5)-c 100 : 只抓取100个数据包 (6)dst port ! 22 : 不抓取目标端口是22的数据包 (7)src net 192.168.1.0/24 : 数据包的源网络地址为192.168.1.0/24 (8)-w ./target.cap : 保存成cap文件,方便用ethereal(即wireshark)分析

                    2、抓包后的处理

                    #把所抓的报文解析成可阅读的文本
                     strings target.cap>temp.txt
                     

                    3、其他使用实例

                    # port指定要抓包端口,
                    diff --git a/assets/ThreadLocal.html-CHq5iQUn.js b/assets/ThreadLocal.html-YdsPoeQ1.js
                    similarity index 99%
                    rename from assets/ThreadLocal.html-CHq5iQUn.js
                    rename to assets/ThreadLocal.html-YdsPoeQ1.js
                    index 5139fa49e7..58e6775fb1 100644
                    --- a/assets/ThreadLocal.html-CHq5iQUn.js
                    +++ b/assets/ThreadLocal.html-YdsPoeQ1.js
                    @@ -1,4 +1,4 @@
                    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

                    1、介绍

                    2、使用

                    3、 使用反射在当前线程获取所有的ThreadLocal

                    一个线程是可能有多个ThreadLocal的,所以源码中字段使用的复数形式threadLocals

                    public class ThreadLocalUtil {
                    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

                    1、介绍

                    2、使用

                    3、 使用反射在当前线程获取所有的ThreadLocal

                    一个线程是可能有多个ThreadLocal的,所以源码中字段使用的复数形式threadLocals

                    public class ThreadLocalUtil {
                     
                         public static Map<ThreadLocal, Object> getThreadLocalMap(){
                             Map<ThreadLocal, Object> threadLocals = new HashMap<>();
                    diff --git a/assets/ThreadPool.html-B4bZt9rf.js b/assets/ThreadPool.html-D9vbBMnV.js
                    similarity index 99%
                    rename from assets/ThreadPool.html-B4bZt9rf.js
                    rename to assets/ThreadPool.html-D9vbBMnV.js
                    index 035319486b..e1bb42b7df 100644
                    --- a/assets/ThreadPool.html-B4bZt9rf.js
                    +++ b/assets/ThreadPool.html-D9vbBMnV.js
                    @@ -1,4 +1,4 @@
                    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

                    线程池

                    前言:

                    线程池使用submit提交任务若遇到异常,线程不会直接抛出异常,在开发中要注意处理异常情况

                    1、先上测试代码

                        public static void main(String[] args) throws InterruptedException {
                    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

                    线程池

                    前言:

                    线程池使用submit提交任务若遇到异常,线程不会直接抛出异常,在开发中要注意处理异常情况

                    1、先上测试代码

                        public static void main(String[] args) throws InterruptedException {
                             ExecutorService executorService = Executors.newFixedThreadPool(3);
                             executorService.submit(() -> {
                                 System.out.println(Thread.currentThread().getName());
                    diff --git a/assets/TooManyOpenFiles.html-ChBajVz6.js b/assets/TooManyOpenFiles.html-DBlFfNwX.js
                    similarity index 99%
                    rename from assets/TooManyOpenFiles.html-ChBajVz6.js
                    rename to assets/TooManyOpenFiles.html-DBlFfNwX.js
                    index e0f59a843c..9215434a34 100644
                    --- a/assets/TooManyOpenFiles.html-ChBajVz6.js
                    +++ b/assets/TooManyOpenFiles.html-DBlFfNwX.js
                    @@ -1,4 +1,4 @@
                    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as e,e as s}from"./app-CVMfKeWw.js";const t={},i=s(`

                    Linux服务器打开文件过多

                    线上异常

                    java.io.IOException: Too many open files
                    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as e,e as s}from"./app-_Oi5YZFn.js";const t={},i=s(`

                    Linux服务器打开文件过多

                    线上异常

                    java.io.IOException: Too many open files
                             at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
                             at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)
                             at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250)
                    diff --git a/assets/TyporaPicgo.html-4B31qBXz.js b/assets/TyporaPicgo.html-DyxSZmxq.js
                    similarity index 99%
                    rename from assets/TyporaPicgo.html-4B31qBXz.js
                    rename to assets/TyporaPicgo.html-DyxSZmxq.js
                    index b26314b74f..01c3f3837b 100644
                    --- a/assets/TyporaPicgo.html-4B31qBXz.js
                    +++ b/assets/TyporaPicgo.html-DyxSZmxq.js
                    @@ -1,4 +1,4 @@
                    -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as p,c as i,a as n,b as s,d as e,e as c}from"./app-CVMfKeWw.js";const r={},l=n("h3",{id:"前言",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#前言"},[n("span",null,"前言")])],-1),u=n("p",null,"平时用MarkDown写博客少不了需要截图,我用的是Typora,刚开始截图是保存在本地,有时想把博客分享到网上,就发现各种图全挂了,需要手动一个一个再复制一下,着实麻烦,今天无意间发现有个叫PicGo的工具,此工具专门上传图到各大图床,着实方便。",-1),d={href:"https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-github",target:"_blank",rel:"noopener noreferrer"},g={href:"https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-tcyun",target:"_blank",rel:"noopener noreferrer"},m=c(`

                    ==picgo的ui剪贴板上传功能,需要win10+==

                    1、搭建Typora + PicGo + gitee

                    我的电脑环境如下,

                    								  OS: Manjaro 21.2.4 Qonos
                    +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as p,c as i,a as n,b as s,d as e,e as c}from"./app-_Oi5YZFn.js";const r={},l=n("h3",{id:"前言",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#前言"},[n("span",null,"前言")])],-1),u=n("p",null,"平时用MarkDown写博客少不了需要截图,我用的是Typora,刚开始截图是保存在本地,有时想把博客分享到网上,就发现各种图全挂了,需要手动一个一个再复制一下,着实麻烦,今天无意间发现有个叫PicGo的工具,此工具专门上传图到各大图床,着实方便。",-1),d={href:"https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-github",target:"_blank",rel:"noopener noreferrer"},g={href:"https://picgo.github.io/PicGo-Core-Doc/zh/guide/config.html#picbed-tcyun",target:"_blank",rel:"noopener noreferrer"},m=c(`

                    ==picgo的ui剪贴板上传功能,需要win10+==

                    1、搭建Typora + PicGo + gitee

                    我的电脑环境如下,

                    								  OS: Manjaro 21.2.4 Qonos
                      ██████████████████  ████████     Kernel: x86_64 Linux 5.15.25-1-MANJARO
                      ██████████████████  ████████     Uptime: 5h 26m
                      ████████            ████████     Packages: 1556
                    diff --git a/assets/Undertow.xxxNotFount.html-CjycD9Jx.js b/assets/Undertow.xxxNotFount.html-X1F463A1.js
                    similarity index 99%
                    rename from assets/Undertow.xxxNotFount.html-CjycD9Jx.js
                    rename to assets/Undertow.xxxNotFount.html-X1F463A1.js
                    index 83287b29ce..8404c33fbd 100644
                    --- a/assets/Undertow.xxxNotFount.html-CjycD9Jx.js
                    +++ b/assets/Undertow.xxxNotFount.html-X1F463A1.js
                    @@ -1,4 +1,4 @@
                    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-CVMfKeWw.js";const o={},i=a(`

                    背景

                    用户反馈上传图片失败,查看日志报错上传文件提示:java.nio.file.NoSuchFileException: /tmp/undertow.xxxx.xxxxx,解决方案,问题是这个问题之前用户就反馈过,之前是直接重启就好了,之后也没深究。最近用户又反馈不行了,所以花了点时间深究一下从根源解决问题。

                    原因

                    在 Linux 系统中,Spring Boot 应用以 java -jar 命令启动时,会在操作系统的 /tmp 目录下随机生成一个 tomcat(或 undertow )临时目录,上传的文件先要转换成临时文件保存在这个文件夹中。 由于临时 /tmp 目录下的文件,在长时间(默认10天)没有使用的情况下,操作系统会执行 tmp 目录清理服务(systemd-tmpfiles-clean.service),导致 /tmp/undertow.xxxx.xxxxxxx 文件被清理; 导致在上传文件时,java调用 Files.createFile(…) 在目录/tmp/undertow.xxxx.xxxxxxx下创建临时文件时,发现找不到目录,就会抛出以上的错误。

                    解决方法

                    方法一

                    可以根据报错信息,新建 /tmp/undertow.xxxx.xxxxxxxx 目录,不影响用户正常使用。 执行 mkdir -p /tmp/undertow.8760.570269926767628882命令;

                    方法二

                    先创建好文件,配置问yml中

                    spring:
                    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-_Oi5YZFn.js";const o={},i=a(`

                    背景

                    用户反馈上传图片失败,查看日志报错上传文件提示:java.nio.file.NoSuchFileException: /tmp/undertow.xxxx.xxxxx,解决方案,问题是这个问题之前用户就反馈过,之前是直接重启就好了,之后也没深究。最近用户又反馈不行了,所以花了点时间深究一下从根源解决问题。

                    原因

                    在 Linux 系统中,Spring Boot 应用以 java -jar 命令启动时,会在操作系统的 /tmp 目录下随机生成一个 tomcat(或 undertow )临时目录,上传的文件先要转换成临时文件保存在这个文件夹中。 由于临时 /tmp 目录下的文件,在长时间(默认10天)没有使用的情况下,操作系统会执行 tmp 目录清理服务(systemd-tmpfiles-clean.service),导致 /tmp/undertow.xxxx.xxxxxxx 文件被清理; 导致在上传文件时,java调用 Files.createFile(…) 在目录/tmp/undertow.xxxx.xxxxxxx下创建临时文件时,发现找不到目录,就会抛出以上的错误。

                    解决方法

                    方法一

                    可以根据报错信息,新建 /tmp/undertow.xxxx.xxxxxxxx 目录,不影响用户正常使用。 执行 mkdir -p /tmp/undertow.8760.570269926767628882命令;

                    方法二

                    先创建好文件,配置问yml中

                    spring:
                       servlet:
                         multipart:
                           location: xxxx
                    diff --git a/assets/Validator.html-CvrJ26C9.js b/assets/Validator.html-DOW_vPxb.js
                    similarity index 99%
                    rename from assets/Validator.html-CvrJ26C9.js
                    rename to assets/Validator.html-DOW_vPxb.js
                    index 17862b659c..63f3e0d08d 100644
                    --- a/assets/Validator.html-CvrJ26C9.js
                    +++ b/assets/Validator.html-DOW_vPxb.js
                    @@ -1,4 +1,4 @@
                    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},o=t(`

                    自定义校验

                    
                    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},o=t(`

                    自定义校验

                    
                     import com.chensino.core.api.dto.UserLoginDTO;
                     import com.chensino.core.api.validate.group.PhoneLogin;
                     import com.chensino.core.api.validate.group.UserNameLogin;
                    diff --git a/assets/VsCode.html-o-J8uPWj.js b/assets/VsCode.html-BCn8Egxl.js
                    similarity index 99%
                    rename from assets/VsCode.html-o-J8uPWj.js
                    rename to assets/VsCode.html-BCn8Egxl.js
                    index c91f64da3c..336d3ce3e2 100644
                    --- a/assets/VsCode.html-o-J8uPWj.js
                    +++ b/assets/VsCode.html-BCn8Egxl.js
                    @@ -1,4 +1,4 @@
                    -import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as c,c as l,a as n,b as s,d as e,e as t}from"./app-CVMfKeWw.js";const i={},r=n("blockquote",null,[n("p",null,"版本: 1.69.1 (user setup) 提交: b06ae3b2d2dbfe28bca3134cc6be65935cdfea6a 日期: 2022-07-12T08:21:24.514Z Electron: 18.3.5 Chromium: 100.0.4896.160 Node.js: 16.13.2 V8: 10.0.139.17-electron.0 OS: Windows_NT x64 6.1.7601")],-1),u=n("h3",{id:"_1-vscode终端无法打开",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1-vscode终端无法打开"},[n("span",null,"1. vscode终端无法打开")])],-1),d=n("blockquote",null,[n("p",null,"现象,使用vscod打开终端一直卡着。"),n("p",null,"解决方法是禁用GPU加速,猜测是因为公司电脑没有独显的原因")],-1),k={href:"https://so.csdn.net/so/search?q=gpu&spm=1001.2101.3001.7020",target:"_blank",rel:"noopener noreferrer"},m=t(`

                    1

                    2、在vscode中使用git-bash作为默认终端

                    1. 给vscode设置以管理员方式运行,
                    2. 在配置文件加入以下配置,把git的path改成自己的
                      "terminal.integrated.defaultProfile.windows": "GitBash",
                    +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as c,c as l,a as n,b as s,d as e,e as t}from"./app-_Oi5YZFn.js";const i={},r=n("blockquote",null,[n("p",null,"版本: 1.69.1 (user setup) 提交: b06ae3b2d2dbfe28bca3134cc6be65935cdfea6a 日期: 2022-07-12T08:21:24.514Z Electron: 18.3.5 Chromium: 100.0.4896.160 Node.js: 16.13.2 V8: 10.0.139.17-electron.0 OS: Windows_NT x64 6.1.7601")],-1),u=n("h3",{id:"_1-vscode终端无法打开",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1-vscode终端无法打开"},[n("span",null,"1. vscode终端无法打开")])],-1),d=n("blockquote",null,[n("p",null,"现象,使用vscod打开终端一直卡着。"),n("p",null,"解决方法是禁用GPU加速,猜测是因为公司电脑没有独显的原因")],-1),k={href:"https://so.csdn.net/so/search?q=gpu&spm=1001.2101.3001.7020",target:"_blank",rel:"noopener noreferrer"},m=t(`

                    1

                    2、在vscode中使用git-bash作为默认终端

                    1. 给vscode设置以管理员方式运行,
                    2. 在配置文件加入以下配置,把git的path改成自己的
                      "terminal.integrated.defaultProfile.windows": "GitBash",
                         "terminal.integrated.profiles.windows": {
                             "PowerShell": {
                                 "source": "PowerShell",
                    diff --git a/assets/WSL.html-irNHs4ez.js b/assets/WSL.html-BIz7aujd.js
                    similarity index 98%
                    rename from assets/WSL.html-irNHs4ez.js
                    rename to assets/WSL.html-BIz7aujd.js
                    index 97a72c89d0..e9ad2b3126 100644
                    --- a/assets/WSL.html-irNHs4ez.js
                    +++ b/assets/WSL.html-BIz7aujd.js
                    @@ -1,4 +1,4 @@
                    -import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o as a,c as i,a as e,b as t,d as s,e as o}from"./app-CVMfKeWw.js";const h={},p=e("h2",{id:"_1-wsl系统设置桥接网络",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-wsl系统设置桥接网络"},[e("span",null,"1. wsl系统设置桥接网络")])],-1),d={href:"https://www.cnblogs.com/cheyunhua/p/17577895.html",target:"_blank",rel:"noopener noreferrer"},c=o(`

                    1.1 开启hyper-v

                    桥接功能需要windows的hyper-v组件支持,但是win10/11家庭版是不包含hyper-v的,专业版才包含。网上也有文章提到家庭版安装hyper-v的方法,但是我没有测试,以下内容都是在win11专业版上进行的测试,win10专业版应该也是一样的。 首先,进入控制面板—程序—启用或关闭windows功能,勾选hyper-v,确认后重启电脑。

                    1.2 桥接网络

                    WSL2 默认采用了一个 NAT 网络,这对于大多数情况而言都是没有问题的,但是如果想要把 WSL 中的服务直接暴露出来,就不得不考虑做端口转发等问题。以及如果要使用 IPv6,自带的 NAT 方案也不能满足。

                    因此,这种时候如果能让 WSL2 使用直接接在 NIC 上自然是最好的,可惜 Windows 中没有直接提供这样的配置选项,如果在 Hyper-V 管理器中配置 WSL 网卡为外部网络则会直接报错。

                    万幸的是,可以使用 PowerShell 直接进行配置,本文则记录使用 PowerShell 让 WSL2 用上桥接网络的方法。

                    以下内容需要以管理员身份在PowerShell内执行。

                    一切开始之前首先需要启动 WSL,直接运行 wsl 即可,这样 WSL 的网卡才会被自动创建出来。

                    重启后首先运行wsl2(这样才能出现WSL的虚拟网卡),以管理员方式打开powershell,执行Get-NetAdapter,可以列出系统所有的网卡,记住想要桥接的网卡名称,比如我想桥接到有线网络其名称为“以太网”。

                    桥接网卡输入以下代码:

                    Set-VMSwitch WSL -NetAdapterName <你的网卡名字>
                    +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o as a,c as i,a as e,b as t,d as s,e as o}from"./app-_Oi5YZFn.js";const h={},p=e("h2",{id:"_1-wsl系统设置桥接网络",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-wsl系统设置桥接网络"},[e("span",null,"1. wsl系统设置桥接网络")])],-1),d={href:"https://www.cnblogs.com/cheyunhua/p/17577895.html",target:"_blank",rel:"noopener noreferrer"},c=o(`

                    1.1 开启hyper-v

                    桥接功能需要windows的hyper-v组件支持,但是win10/11家庭版是不包含hyper-v的,专业版才包含。网上也有文章提到家庭版安装hyper-v的方法,但是我没有测试,以下内容都是在win11专业版上进行的测试,win10专业版应该也是一样的。 首先,进入控制面板—程序—启用或关闭windows功能,勾选hyper-v,确认后重启电脑。

                    1.2 桥接网络

                    WSL2 默认采用了一个 NAT 网络,这对于大多数情况而言都是没有问题的,但是如果想要把 WSL 中的服务直接暴露出来,就不得不考虑做端口转发等问题。以及如果要使用 IPv6,自带的 NAT 方案也不能满足。

                    因此,这种时候如果能让 WSL2 使用直接接在 NIC 上自然是最好的,可惜 Windows 中没有直接提供这样的配置选项,如果在 Hyper-V 管理器中配置 WSL 网卡为外部网络则会直接报错。

                    万幸的是,可以使用 PowerShell 直接进行配置,本文则记录使用 PowerShell 让 WSL2 用上桥接网络的方法。

                    以下内容需要以管理员身份在PowerShell内执行。

                    一切开始之前首先需要启动 WSL,直接运行 wsl 即可,这样 WSL 的网卡才会被自动创建出来。

                    重启后首先运行wsl2(这样才能出现WSL的虚拟网卡),以管理员方式打开powershell,执行Get-NetAdapter,可以列出系统所有的网卡,记住想要桥接的网卡名称,比如我想桥接到有线网络其名称为“以太网”。

                    桥接网卡输入以下代码:

                    Set-VMSwitch WSL -NetAdapterName <你的网卡名字>
                     

                    将wsl虚拟网络和主机有线网络桥接起来。

                    1.3 修改wsl

                    接下来进入 WSL 配置 IP 地址和网关,假设WSL的有线网络为eth0,网关为 192.168.1.1,IP 设置为 192.168.1.64/24:

                    ip addr del $(ip addr show eth0 | grep 'inet\\b' | awk '{print $2}' | head -n 1) dev eth0
                     ip addr add 192.168.1.64/24 broadcast 192.168.1.255 dev eth0
                     ip route add 0.0.0.0/0 via 192.168.1.1 dev eth0
                    diff --git a/assets/Wifi.html-D7OcL7nb.js b/assets/Wifi.html-DCwxVnq6.js
                    similarity index 98%
                    rename from assets/Wifi.html-D7OcL7nb.js
                    rename to assets/Wifi.html-DCwxVnq6.js
                    index 6d61121813..cae13bac55 100644
                    --- a/assets/Wifi.html-D7OcL7nb.js
                    +++ b/assets/Wifi.html-DCwxVnq6.js
                    @@ -1 +1 @@
                    -import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as r,a as e,b as t,d as a}from"./app-CVMfKeWw.js";const c={},s=e("h2",{id:"_1、需求场景",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、需求场景"},[e("span",null,"1、需求场景")])],-1),u=e("p",null,"场景1:",-1),p=e("blockquote",null,[e("p",null,"有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡, 但此方法有个问题是一般USB无线网卡不会对Linux系统进行适配,即使有适配也是对系统条件要求很苛刻,比如只能用ubuntu,系统内核限制为指定版本等;")],-1),h=e("p",null,"场景2:",-1),d=e("blockquote",null,[e("p",null,"有一个笔记本电脑,自带的有wifi模块,但是此无线网卡不支持5G、WIFI6等,此时可以根据情况升级无线网卡;")],-1),m=e("h2",{id:"_2、解决方案——使用板载无线网卡",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2、解决方案——使用板载无线网卡"},[e("span",null,"2、解决方案——使用板载无线网卡")])],-1),_={href:"https://www.intel.cn/content/www/cn/zh/support/articles/000005511/network-and-i-o/wireless-networking.html",target:"_blank",rel:"noopener noreferrer"},f=e("p",null,"使用板载网卡一般使用的是NGFF接口,也就是常见的M2接口,一般2015年以后的主板都有这个扩展口。",-1);function w(x,b){const n=o("ExternalLinkIcon");return l(),r("div",null,[s,u,p,h,d,m,e("p",null,[t("使用板载无线网卡相对usb无线网卡来说,不会存在驱动的问题,驱动由Intel提供了支持,具体可以参考"),e("a",_,[t("此处"),a(n)])]),f])}const y=i(c,[["render",w],["__file","Wifi.html.vue"]]),S=JSON.parse('{"path":"/other/linux/Wifi.html","title":"Linux下加装wifi模块","lang":"zh-CN","frontmatter":{"title":"Linux下加装wifi模块","date":"2022-08-28T00:00:00.000Z","keys":null,"description":"1、需求场景 场景1: 有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡, 但此方法有个问题是一般USB无线网卡不会对Linux系统进行适配,即使有适配也是对系统条件要求很苛刻,比如只能用ubuntu,系统内核限制为指定版本等; 场景2: 有一个笔...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/linux/Wifi.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Linux下加装wifi模块"}],["meta",{"property":"og:description","content":"1、需求场景 场景1: 有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡, 但此方法有个问题是一般USB无线网卡不会对Linux系统进行适配,即使有适配也是对系统条件要求很苛刻,比如只能用ubuntu,系统内核限制为指定版本等; 场景2: 有一个笔..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-08-28T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Linux下加装wifi模块\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-28T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、需求场景","slug":"_1、需求场景","link":"#_1、需求场景","children":[]},{"level":2,"title":"2、解决方案——使用板载无线网卡","slug":"_2、解决方案——使用板载无线网卡","link":"#_2、解决方案——使用板载无线网卡","children":[]}],"git":{"createdTime":1661677278000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.95,"words":284},"filePathRelative":"other/linux/Wifi.md","localizedDate":"2022年8月28日","excerpt":"

                    1、需求场景

                    \\n

                    场景1:

                    \\n
                    \\n

                    有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡,\\n但此方法有个问题是一般USB无线网卡不会对Linux系统进行适配,即使有适配也是对系统条件要求很苛刻,比如只能用ubuntu,系统内核限制为指定版本等;

                    \\n
                    \\n

                    场景2:

                    \\n
                    \\n

                    有一个笔记本电脑,自带的有wifi模块,但是此无线网卡不支持5G、WIFI6等,此时可以根据情况升级无线网卡;

                    \\n
                    ","autoDesc":true}');export{y as comp,S as data}; +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as r,a as e,b as t,d as a}from"./app-_Oi5YZFn.js";const c={},s=e("h2",{id:"_1、需求场景",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、需求场景"},[e("span",null,"1、需求场景")])],-1),u=e("p",null,"场景1:",-1),p=e("blockquote",null,[e("p",null,"有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡, 但此方法有个问题是一般USB无线网卡不会对Linux系统进行适配,即使有适配也是对系统条件要求很苛刻,比如只能用ubuntu,系统内核限制为指定版本等;")],-1),h=e("p",null,"场景2:",-1),d=e("blockquote",null,[e("p",null,"有一个笔记本电脑,自带的有wifi模块,但是此无线网卡不支持5G、WIFI6等,此时可以根据情况升级无线网卡;")],-1),m=e("h2",{id:"_2、解决方案——使用板载无线网卡",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2、解决方案——使用板载无线网卡"},[e("span",null,"2、解决方案——使用板载无线网卡")])],-1),_={href:"https://www.intel.cn/content/www/cn/zh/support/articles/000005511/network-and-i-o/wireless-networking.html",target:"_blank",rel:"noopener noreferrer"},f=e("p",null,"使用板载网卡一般使用的是NGFF接口,也就是常见的M2接口,一般2015年以后的主板都有这个扩展口。",-1);function w(x,b){const n=o("ExternalLinkIcon");return l(),r("div",null,[s,u,p,h,d,m,e("p",null,[t("使用板载无线网卡相对usb无线网卡来说,不会存在驱动的问题,驱动由Intel提供了支持,具体可以参考"),e("a",_,[t("此处"),a(n)])]),f])}const y=i(c,[["render",w],["__file","Wifi.html.vue"]]),S=JSON.parse('{"path":"/other/linux/Wifi.html","title":"Linux下加装wifi模块","lang":"zh-CN","frontmatter":{"title":"Linux下加装wifi模块","date":"2022-08-28T00:00:00.000Z","keys":null,"description":"1、需求场景 场景1: 有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡, 但此方法有个问题是一般USB无线网卡不会对Linux系统进行适配,即使有适配也是对系统条件要求很苛刻,比如只能用ubuntu,系统内核限制为指定版本等; 场景2: 有一个笔...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/linux/Wifi.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Linux下加装wifi模块"}],["meta",{"property":"og:description","content":"1、需求场景 场景1: 有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡, 但此方法有个问题是一般USB无线网卡不会对Linux系统进行适配,即使有适配也是对系统条件要求很苛刻,比如只能用ubuntu,系统内核限制为指定版本等; 场景2: 有一个笔..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-08-28T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Linux下加装wifi模块\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-28T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、需求场景","slug":"_1、需求场景","link":"#_1、需求场景","children":[]},{"level":2,"title":"2、解决方案——使用板载无线网卡","slug":"_2、解决方案——使用板载无线网卡","link":"#_2、解决方案——使用板载无线网卡","children":[]}],"git":{"createdTime":1661677278000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.95,"words":284},"filePathRelative":"other/linux/Wifi.md","localizedDate":"2022年8月28日","excerpt":"

                    1、需求场景

                    \\n

                    场景1:

                    \\n
                    \\n

                    有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡,\\n但此方法有个问题是一般USB无线网卡不会对Linux系统进行适配,即使有适配也是对系统条件要求很苛刻,比如只能用ubuntu,系统内核限制为指定版本等;

                    \\n
                    \\n

                    场景2:

                    \\n
                    \\n

                    有一个笔记本电脑,自带的有wifi模块,但是此无线网卡不支持5G、WIFI6等,此时可以根据情况升级无线网卡;

                    \\n
                    ","autoDesc":true}');export{y as comp,S as data}; diff --git a/assets/aboutAsync.html-CVw3VyIR.js b/assets/aboutAsync.html-CukGl2Bf.js similarity index 99% rename from assets/aboutAsync.html-CVw3VyIR.js rename to assets/aboutAsync.html-CukGl2Bf.js index 884a00ecd2..12c72b6996 100644 --- a/assets/aboutAsync.html-CVw3VyIR.js +++ b/assets/aboutAsync.html-CukGl2Bf.js @@ -1,4 +1,4 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as p,c,a as n,b as s,d as o,e as i}from"./app-CVMfKeWw.js";const l={},u=n("h2",{id:"一-async函数的定义",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#一-async函数的定义"},[n("span",null,"一,async函数的定义")])],-1),r={href:"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise",target:"_blank",rel:"noopener noreferrer"},k=n("strong",null,"异步行为",-1),d=i(`

                    备注:async/await的目的为了简化使用基于 promise 的 API 时所需的语法。async/await的行为就好像搭配使用了生成器和 promise。

                    async函数的书写方式如下:

                    // 函数声明
                    +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as p,c,a as n,b as s,d as o,e as i}from"./app-_Oi5YZFn.js";const l={},u=n("h2",{id:"一-async函数的定义",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#一-async函数的定义"},[n("span",null,"一,async函数的定义")])],-1),r={href:"https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise",target:"_blank",rel:"noopener noreferrer"},k=n("strong",null,"异步行为",-1),d=i(`

                    备注:async/await的目的为了简化使用基于 promise 的 API 时所需的语法。async/await的行为就好像搭配使用了生成器和 promise。

                    async函数的书写方式如下:

                    // 函数声明
                     async function foo() {}
                     
                     // 函数表达式
                    diff --git a/assets/aboutEvent.html-1fGRZu30.js b/assets/aboutEvent.html-CvhiVca8.js
                    similarity index 99%
                    rename from assets/aboutEvent.html-1fGRZu30.js
                    rename to assets/aboutEvent.html-CvhiVca8.js
                    index abb3e6d09d..181f0e3882 100644
                    --- a/assets/aboutEvent.html-1fGRZu30.js
                    +++ b/assets/aboutEvent.html-CvhiVca8.js
                    @@ -1,4 +1,4 @@
                    -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as e,c as o,a as n,b as c,d as l,e as s}from"./app-CVMfKeWw.js";const i={},u=s(`

                    一,事件注册的三种方式

                    1,通过 HTML 元素指定事件属性来绑定

                    <button onclick ="clickFu()">点我吧</button>
                    +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as e,c as o,a as n,b as c,d as l,e as s}from"./app-_Oi5YZFn.js";const i={},u=s(`

                    一,事件注册的三种方式

                    1,通过 HTML 元素指定事件属性来绑定

                    <button onclick ="clickFu()">点我吧</button>
                     <script>
                        function clickFu(){
                            alert(3333)
                    diff --git a/assets/aboutThis.html-DlclfKVB.js b/assets/aboutThis.html-B-2Ir7Kw.js
                    similarity index 99%
                    rename from assets/aboutThis.html-DlclfKVB.js
                    rename to assets/aboutThis.html-B-2Ir7Kw.js
                    index a7f423b67b..168e2e1b0d 100644
                    --- a/assets/aboutThis.html-DlclfKVB.js
                    +++ b/assets/aboutThis.html-B-2Ir7Kw.js
                    @@ -1,4 +1,4 @@
                    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

                    一,函数内部的this指向

                    函数内this指向,是当我们调用函数的时候才能确定。调用方式的不同决定this的指向不同。
                    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

                    一,函数内部的this指向

                    函数内this指向,是当我们调用函数的时候才能确定。调用方式的不同决定this的指向不同。
                     this的指向,是在调用函数时根据执行上下文所动态确定的。
                     
                    调用方式this指向
                    普通函数调用window
                    构造函数调用实例对象,原型对象里的方法也指向实例对象
                    对象方法调用该方法所属的对象
                    事件绑定方法调用绑定事件对象
                    定时器函数window
                    立即执行函数window

                    二,setTimeout & setInterval

                    对于延时函数内部的回调函数的this指向全局对象window(当然我们可以通过bind方法改变其内部函数的this指向.

                    function Person() {  
                         this.age = 0;  
                    diff --git a/assets/action-usage.html-xDUlIzb_.js b/assets/action-usage.html-DFNFrJyT.js
                    similarity index 99%
                    rename from assets/action-usage.html-xDUlIzb_.js
                    rename to assets/action-usage.html-DFNFrJyT.js
                    index f21d996710..2a57626748 100644
                    --- a/assets/action-usage.html-xDUlIzb_.js
                    +++ b/assets/action-usage.html-DFNFrJyT.js
                    @@ -1,4 +1,4 @@
                    -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c as e,a as n,b as s,d as c,e as l}from"./app-CVMfKeWw.js";const u={},i=l(`

                    一,利用typeScript实现新增,删除一行数据

                    这里基本涵盖了typeScript在项目中的实战用法

                    1,html部分

                    <!DOCTYPE html>
                    +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c as e,a as n,b as s,d as c,e as l}from"./app-_Oi5YZFn.js";const u={},i=l(`

                    一,利用typeScript实现新增,删除一行数据

                    这里基本涵盖了typeScript在项目中的实战用法

                    1,html部分

                    <!DOCTYPE html>
                     <html lang="en">
                     <head>
                     	<meta charset="UTF-8">
                    diff --git a/assets/app-CVMfKeWw.js b/assets/app-_Oi5YZFn.js
                    similarity index 63%
                    rename from assets/app-CVMfKeWw.js
                    rename to assets/app-_Oi5YZFn.js
                    index 8f98f9935c..41b355a7b8 100644
                    --- a/assets/app-CVMfKeWw.js
                    +++ b/assets/app-_Oi5YZFn.js
                    @@ -1,6 +1,6 @@
                     function __vite__mapDeps(indexes) {
                       if (!__vite__mapDeps.viteFileDeps) {
                    -    __vite__mapDeps.viteFileDeps = ["assets/index.html-Ah6mlgvk.js","assets/plugin-vue_export-helper-DlAUqK2U.js","assets/home.html-CdI4hI0D.js","assets/slide.html-_uY0duH1.js","assets/index.html-DYLEdXjZ.js","assets/建造者模式.html-BpLKsWf9.js","assets/模板方法模式.html-DLubeSKF.js","assets/策略模式.html-BzaYdrRx.js","assets/装饰模式.html-NVA1JlXh.js","assets/责任链模式.html-WcnUM2LR.js","assets/index.html-B-rzW1p0.js","assets/index.html-BXBwg_XP.js","assets/disable.html-D2cQj4ic.js","assets/encrypt.html-BEDsFZkv.js","assets/markdown.html-Ku2vuX_R.js","assets/page.html-CNLLwtGV.js","assets/index.html-iq2vqpLb.js","assets/Jellyfin搭建.html-CLPGwkSq.js","assets/index.html-COH-CgE0.js","assets/x86_openwrt.html-Rfp5hWIu.js","assets/旁路由网关.html-2p0UA79e.js","assets/网络设置.html-DbRAJ3uC.js","assets/自建nas.html-BhQRnqm0.js","assets/index.html-BqSgUMIT.js","assets/1.html-BsLOzPLz.js","assets/2.html-CDG7SSJK.js","assets/index.html-09kdO8uk.js","assets/index.html-BJvieq0-.js","assets/aboutAsync.html-CVw3VyIR.js","assets/aboutEvent.html-1fGRZu30.js","assets/aboutThis.html-DlclfKVB.js","assets/asyncError.html-WxV4KJUL.js","assets/crossDomain.html-DCiVSBRz.js","assets/crossDomain2.html-DTphraX3.js","assets/lazyLoad.html-Cdfyboon.js","assets/throttle.html-BVURcdFS.js","assets/index.html-BtjOnGEp.js","assets/js中整数的最大值.html-BzW3aeox.js","assets/promise.html-BUt4IEbi.js","assets/useModule.html-C54V2441.js","assets/useNpm.html-DJJdqwXN.js","assets/usePnpm.html-mIezI9dx.js","assets/useYarn.html-DUPdL_R5.js","assets/index.html-_iWg3Z3W.js","assets/安装问题.html-BUB5ckKH.js","assets/index.html-DQDf-iB8.js","assets/action-usage.html-xDUlIzb_.js","assets/axios.html-DIiEtqYW.js","assets/basic-usage.html-CEUsK9ry.js","assets/fanType.html-DHpjy03Z.js","assets/tsAndvue3.html-DuJXkf-s.js","assets/index.html-CHghYPfg.js","assets/index.html-CToWg7cr.js","assets/elementui表单自定义校验.html-Bl7vnRpA.js","assets/eventBus.html-DxETLIkG.js","assets/vue-Direactive.html-CrUvqxXL.js","assets/vue-authority.html-hGxiTJj0.js","assets/vue-in-action.html-6hfWli8S.js","assets/vue-nextTick.html-D03AWrxQ.js","assets/vue-pic.html-CpOJwwqn.js","assets/vue-router1.html-DrI04ly7.js","assets/vue-router2.html-C4QGqnFR.js","assets/vue3Emit.html-DJy1ivmL.js","assets/vue3LifeTime.html-CgbHBDaz.js","assets/vueExtend.html-Dj4ZUdmc.js","assets/自定义指令的实践.html-C1dSRMpn.js","assets/Arthas.html-D1k7ux4i.js","assets/Collection.html-CeAHa-2T.js","assets/CompileJdk11.html-DQ_9XJev.js","assets/Concurrent.html-ClwuWlCD.js","assets/Future.html-C-IGHrDE.js","assets/IO-Model.html-BPdWwFrU.js","assets/IO-model1.html-DbFAWkDL.js","assets/ImplementSameInterface.html-8aOTv4So.js","assets/MysqlMasterSlave.html-CfozUk_D.js","assets/NativeMethod.html-BICH8rB4.js","assets/ParentDelegationClassLoader.html-lWjNd7F6.js","assets/ProxyInJava.html-C0ibZtz7.js","assets/index.html-DaPOtDhI.js","assets/Synchronized.html-Dhr9EviB.js","assets/ThreadLocal.html-CHq5iQUn.js","assets/ThreadPool.html-B4bZt9rf.js","assets/io.html-DYLJUoUT.js","assets/ConstantPool.html-BlmpO7p_.js","assets/CustomLRU.html-CX9zw1r2.js","assets/IntegerConstantPool.html-BgqTmJIN.js","assets/index.html-hsfyfYXC.js","assets/Serialization.html-BPyqpG-l.js","assets/String.html-CI6Ty1q7.js","assets/ClassLoader.html-9dp_fs5y.js","assets/MemoryModel.html-Bzwww5iG.js","assets/NewObject.html-_zRwVPgF.js","assets/ObjectReference.html-BttKKJld.js","assets/index.html-COzc4rHJ.js","assets/SetObjectNull.html-pva3Gj1n.js","assets/StringAdd.html-BihzcLHW.js","assets/volatile.html-ChArXn5X.js","assets/名词解释.html-D6DWrpIk.js","assets/JdkVersion.html-BvKsTRiv.js","assets/index.html-DAKYBthR.js","assets/index.html-C3kmdsDq.js","assets/ebooks.html-D9FixpLr.js","assets/TCP-IP.html-DSyGo702.js","assets/CPUOverLoad.html-Cl4MUidN.js","assets/MysqlCollate.html-D6Cxtc4N.js","assets/MysqlNote.html-C-U71JLc.js","assets/MysqlRemoteConnect.html-DGYiJFHo.js","assets/index.html-oz23N8Lk.js","assets/Recurse.html-6jai2Fmm.js","assets/SQLOptimization.html-CzzwcOPq.js","assets/数据库备份.html-UG8-nnQB.js","assets/DistributeLock.html-PlGmUQ21.js","assets/Nacos.html-D6kbUFLh.js","assets/index.html-Beu4CvPJ.js","assets/Docker.html-BaiyWfkU.js","assets/ServiceInstall.html-CCIiUtbe.js","assets/2022-04-12.html-BnI2oaJK.js","assets/BTree.html-CuSxbXvJ.js","assets/CDN.html-rIf3-Wbw.js","assets/ChromeDevTools.html-BoMI5RzK.js","assets/CloudService.html-DjGDK1Z4.js","assets/DeployGithubPage.html-2HbSyCiP.js","assets/IM即时通信技术选型.html-DiDYCl7r.js","assets/Jenkins.html-D5aTJlwz.js","assets/index.html-EDXVLWDR.js","assets/TyporaPicgo.html-4B31qBXz.js","assets/elasticSearch操作.html-B7hezSq2.js","assets/elk部署.html-DF5G_BDL.js","assets/im即时通信的需求.html-qDdBmZbJ.js","assets/windows下服务注册.html-DuNb2TND.js","assets/操作系统引导.html-DD7Rd6wj.js","assets/BatchDeleteGitHubRepo.html-WNdAC_un.js","assets/GitCommands.html-_C8ZQnH_.js","assets/index.html-BMhOFPmp.js","assets/branch01.html--1ri9Qlh.js","assets/branch02.html-CScQPDBP.js","assets/fatal.html-CLx7xyLl.js","assets/gitConflict.html-XWxwXqnJ.js","assets/gitRebase.html-Bh0TFroo.js","assets/gitwork.html-DCYwt7rE.js","assets/mergeBranch.html-4sgMZ_ic.js","assets/proxy.html-DRPn2ULh.js","assets/rebaseAndMerge.html-LfOBbMMt.js","assets/reset.html-CjbSN2yZ.js","assets/stash.html-DJPmBi5v.js","assets/CPU.html-D5a-tI0p.js","assets/CentOS.html-C59_Q0zj.js","assets/CommonUsedCMD.html-WpgS454f.js","assets/Curl.html-DlrJSikw.js","assets/InstallMysqlWithDocker.html-cGZHFWnE.js","assets/Manjaro.html-CTuhb7rc.js","assets/MountDisk.html-AWo0fohf.js","assets/MultiNetworkCard.html-ZHqfhuQy.js","assets/index.html-B2inZhc8.js","assets/Samba.html-DATE_iKU.js","assets/ShareBetweenWindowsAndLinux.html-Vr4o6QxT.js","assets/TcpDump.html-cL04DF4f.js","assets/Wifi.html-D7OcL7nb.js","assets/firewall.html-axsN7QwQ.js","assets/wsl.html-DoVf1_uE.js","assets/截图.html-BRM_4DWV.js","assets/index.html-DZIt25hg.js","assets/01.html-BpsuZlEh.js","assets/02.html-9y4kTt4c.js","assets/03.html-_Fpqszha.js","assets/04.html-BA8_N2LW.js","assets/05.html-C12aFhzW.js","assets/06.html-fv-ofJNx.js","assets/07.html-C8FkfsLs.js","assets/08.html-DoM55lqI.js","assets/09.html-Aa1WASXU.js","assets/10.html-D50UJxV3.js","assets/11.html-DLQhthQQ.js","assets/12.html-By4kPY-J.js","assets/13.html-DbVAkfja.js","assets/14.html-DC_xS0f7.js","assets/index.html-CrtTLfwo.js","assets/index.html-D5JX-dxI.js","assets/ddns申请证书.html-D3MLskiS.js","assets/firewall.html-CDBaOBQ8.js","assets/index.html-CTOJOPSY.js","assets/破解软件.html-CYd3SkT3.js","assets/SUC.html-BJ48MGxE.js","assets/Idea.html-N3gMCtcp.js","assets/index.html-DxrpWTG5.js","assets/SoftWare.html-DwRE_esc.js","assets/VsCode.html-o-J8uPWj.js","assets/CloudServiceTraining.html-C8NinniP.js","assets/index.html-DKR14bqO.js","assets/SSO.html-DgwliJOO.js","assets/draw.html-3iC56TfH.js","assets/BuildWebProject.html-D1F1nzup.js","assets/Cookie.html-ffctXEz0.js","assets/Http.html-C3TqYNG0.js","assets/Jwt.html-BI9W3_p_.js","assets/OAUTH_LOGIN.html-Dt5KbJsg.js","assets/index.html-7hihg5cE.js","assets/RefreshToken.html-CFwkbthk.js","assets/Restful.html-X9thQzel.js","assets/WSL.html-irNHs4ez.js","assets/MybatisPlusDataSource.html-2C9lgUhC.js","assets/index.html-CeTjFoZy.js","assets/mybatis.html-ZRAnLlvk.js","assets/Authorization.html-BqA7v7f3.js","assets/CustomAuthenticationProvider.html-D7qaxkkr.js","assets/CustomLoginPage.html-DheOzz7K.js","assets/CustomTokenAuthentication.html-BAwukKcd.js","assets/DelegatingFilterProxy.html-BfP7rcad.js","assets/OAuth2Authentication.html-BPOz3SI3.js","assets/OncePerRequestFilter.html-BEOxLqvE.js","assets/PreAuthorize.html-B_BWAtGw.js","assets/index.html-SQqXSiBs.js","assets/SSO.html-DBoqluIC.js","assets/SecurityFilterChain.html-D0yc0vvl.js","assets/Session.html-PVXvBLSr.js","assets/note.html-CoTL_cZp.js","assets/spring-security-oauth2-authorization-server.html-BU0wlxJK.js","assets/Annotation.html-Bu-Jo73Z.js","assets/BeanPostProcessor.html-2fGhuRBC.js","assets/CircularDependency.html-Cy-x1Wkb.js","assets/DesignPatternInSpring.html-Cz-ZqYXH.js","assets/OncePerRequestFilter.html-C_TVU5f6.js","assets/index.html-B9UffgOy.js","assets/SpringAOP.html-0F2yJUPF.js","assets/SpringCache.html-BcT_2YyF.js","assets/SpringExtensionPoint.html-BY3OGoZJ.js","assets/SpringIOC.html-D2ZKIq-l.js","assets/SpringMVC.html-CW7qiD-c.js","assets/SpringSourceAnalize.html-C86lj3k3.js","assets/Validator.html-CvrJ26C9.js","assets/AOPLog.html-jSrFg30s.js","assets/CollectionInject.html-Ds4tphSm.js","assets/Http2.html-yFvJ3oRy.js","assets/index.html-hGAHmgqz.js","assets/SpringBootAutoConfiguration.html-B2Ew9z9M.js","assets/Swagger.html-BrC3rGwr.js","assets/部署.html-FCiywWGM.js","assets/SpringCloudGateway.html-DptX-4Hc.js","assets/index.html-Cnhk4PBp.js","assets/wrapper.html-Do0j3LhC.js","assets/index.html-DiiTcF6s.js","assets/TooManyOpenFiles.html-ChBajVz6.js","assets/Undertow.xxxNotFount.html-CjycD9Jx.js","assets/各种框架版本的坑.html-H7SaEEcF.js","assets/各种问题.html-DVSHYPDc.js","assets/logback.html-DOiHN3-e.js","assets/index.html-Dvn_QmO3.js","assets/build标签.html-uLUm0P1E.js","assets/import.html-DtYCD-Aq.js","assets/multiModule.html-k-P4UAvN.js","assets/problem.html-dW-7sW20.js","assets/生命周期.html-DmTGecRc.js","assets/404.html-2xAmqrLX.js","assets/index.html-CuWpjIbb.js","assets/index.html-CFkGmFia.js","assets/index.html-CPxFcKAG.js","assets/index.html-dkKrYb2Y.js","assets/index.html-CmHVopsM.js","assets/index.html-EMVH5RI8.js","assets/index.html-dhU_SREp.js","assets/index.html-DD8LSjG9.js","assets/index.html-0QvH_nXT.js","assets/index.html-eHCo69H8.js","assets/index.html-aM10vDis.js","assets/index.html-Dhyapq1H.js","assets/index.html-FIqAJepD.js","assets/index.html-BKgtb9Ah.js","assets/index.html-C7LrpzTI.js","assets/index.html-C6jwACfV.js","assets/index.html-D38QGC2C.js","assets/index.html-CJudebxY.js","assets/index.html-BDPTsTEv.js","assets/index.html-CzP4p5XK.js","assets/index.html-BHP5lSWG.js","assets/index.html-DDz84u40.js","assets/index.html-CL8Rn6LN.js","assets/index.html-BnfXAjNS.js","assets/index.html-zTuDnnQ9.js","assets/index.html-B1xDsJG7.js","assets/index.html-BQbSFfwU.js","assets/index.html-BX9tTXXW.js","assets/index.html-B-zCQLtF.js","assets/index.html-CY45JW6T.js","assets/index.html-f8Jf88Pj.js","assets/index.html-Ds23VMOY.js","assets/index.html-Cu9v8LOw.js","assets/index.html-DCau-SSn.js","assets/index.html-uyHOrigb.js","assets/index.html-CpRQKI06.js","assets/index.html-pad8GR4o.js","assets/index.html-jpPjIq77.js","assets/index.html-CF8Xpy4A.js","assets/index.html-RGleUAuC.js","assets/index.html-BQhu6ulG.js","assets/index.html-BvXx7kZb.js","assets/index.html-DJeEzsTa.js","assets/index.html-Ci4VSHuy.js","assets/index.html-CLj8LoVt.js","assets/index.html-3CT6OGMP.js","assets/index.html-vvugCgoe.js","assets/index.html-E2fKYy3S.js","assets/index.html-BvSZt_Zx.js","assets/index.html-LnP3a8gR.js","assets/index.html-B35-Vn16.js","assets/index.html-CJbe2oEP.js","assets/index.html-Dn4DJ2Jv.js","assets/index.html-B6iZ__OM.js","assets/index.html-B3FdepDG.js","assets/index.html-CaPxFNNf.js","assets/index.html-BfisB8tx.js","assets/index.html-DKQaObvm.js","assets/index.html-CDsKkWs-.js","assets/index.html-XxdR190C.js","assets/index.html-BXMP0bPq.js","assets/index.html-AC98BttZ.js","assets/index.html-xL8R2rvI.js","assets/index.html-D2gpmgi4.js","assets/index.html-HvLMGTgp.js","assets/index.html-BOnFN1d7.js","assets/index.html-Dv16igET.js","assets/index.html-CxAtUmc7.js","assets/index.html-BUg70Vrr.js","assets/index.html-CCNjkOtl.js","assets/index.html-CUh5IBbk.js","assets/index.html-DZIE4r_x.js","assets/index.html-Bo2PC9xb.js","assets/index.html-BOWROSmg.js","assets/index.html-CeUSh-YY.js","assets/index.html-DKhBFgRg.js","assets/index.html-D3LGsC8Z.js","assets/index.html-DqHDZd6R.js","assets/index.html-76c_E0sL.js"]
                    +    __vite__mapDeps.viteFileDeps = ["assets/index.html-Di3gSe-7.js","assets/plugin-vue_export-helper-DlAUqK2U.js","assets/home.html-Ds_ny-Cd.js","assets/slide.html-gu2cHfI1.js","assets/index.html-DyGScxDV.js","assets/index.html-Zxb4dqDx.js","assets/建造者模式.html-CPvB1dQQ.js","assets/模板方法模式.html-D86ZbVNZ.js","assets/策略模式.html-1pJjZLuA.js","assets/装饰模式.html-Bq_Ohlm8.js","assets/责任链模式.html-C6zRijOG.js","assets/index.html-_Hk-UKaP.js","assets/disable.html-D_vnyg33.js","assets/encrypt.html-BVOYksPN.js","assets/markdown.html-DnotFn9F.js","assets/page.html-CalA3aZV.js","assets/index.html-DLU057mi.js","assets/Jellyfin搭建.html-B4JCtBiz.js","assets/index.html-raRbuKfh.js","assets/x86_openwrt.html-CASth0J6.js","assets/旁路由网关.html-JyFgPLU6.js","assets/网络设置.html-CT2typrG.js","assets/自建nas.html-D-Ky5W6L.js","assets/index.html-ZWa93oKC.js","assets/1.html-CeguXEpG.js","assets/2.html-Dp6E9lgb.js","assets/index.html-UPH_9_-C.js","assets/index.html-DESeP-r7.js","assets/aboutAsync.html-CukGl2Bf.js","assets/aboutEvent.html-CvhiVca8.js","assets/aboutThis.html-B-2Ir7Kw.js","assets/asyncError.html-npKDZnPq.js","assets/crossDomain.html-CSDExzG7.js","assets/crossDomain2.html-CA7WfkLl.js","assets/lazyLoad.html-B0wYE_Yw.js","assets/throttle.html-CDssB4Vw.js","assets/index.html-C6kiZ_HF.js","assets/js中整数的最大值.html-BiKvCKWl.js","assets/promise.html-DlbJ5Sxz.js","assets/useModule.html-BHtXy4j3.js","assets/useNpm.html-CKLfIIZn.js","assets/usePnpm.html-C3QlcelL.js","assets/useYarn.html-BrgPHvAk.js","assets/index.html-D5egMLMa.js","assets/安装问题.html-B-h8Zfob.js","assets/index.html-CJQlCK8P.js","assets/action-usage.html-DFNFrJyT.js","assets/axios.html-BXoNbwVV.js","assets/basic-usage.html-CBqhMzrq.js","assets/fanType.html-34EbXHhU.js","assets/tsAndvue3.html-8xWV3_Wf.js","assets/index.html-DAGR5a1Q.js","assets/index.html-DHWvYkOD.js","assets/elementui表单自定义校验.html-BDHhWiHk.js","assets/eventBus.html-B0dTFe1w.js","assets/vue-Direactive.html-CwIH1XaN.js","assets/vue-authority.html-HH15bglq.js","assets/vue-in-action.html-DaPOjPap.js","assets/vue-nextTick.html-BAWrVlUH.js","assets/vue-pic.html-BGsMqmKX.js","assets/vue-router1.html-DZlcHgjO.js","assets/vue-router2.html-DNXG8jNK.js","assets/vue3Emit.html-C3GB9oZp.js","assets/vue3LifeTime.html-L4zQOdiA.js","assets/vueExtend.html-DnRRc5Za.js","assets/自定义指令的实践.html-dDt_1gUV.js","assets/Arthas.html-BDHUU6lY.js","assets/Collection.html-D-nzOS-a.js","assets/CompileJdk11.html-k8rk73iW.js","assets/Concurrent.html-DQRS-IpH.js","assets/Future.html-CI2mkHT2.js","assets/IO-Model.html-O-6ElLZv.js","assets/IO-model1.html-Bko-tL29.js","assets/ImplementSameInterface.html-iv1dGOUt.js","assets/MysqlMasterSlave.html-CPoF9taw.js","assets/NativeMethod.html-CW2rOE3c.js","assets/ParentDelegationClassLoader.html-BE03CURY.js","assets/ProxyInJava.html-Craly_CB.js","assets/index.html-BLiK3lZA.js","assets/Synchronized.html-Bz2_U7rD.js","assets/ThreadLocal.html-YdsPoeQ1.js","assets/ThreadPool.html-D9vbBMnV.js","assets/io.html-C1zkJgLS.js","assets/ConstantPool.html-C5zSxd7l.js","assets/CustomLRU.html-DHBi8rnr.js","assets/IntegerConstantPool.html-B-ijklmd.js","assets/index.html-yLnx4YcF.js","assets/Serialization.html-BJVNALhI.js","assets/String.html-Ca6nK_a0.js","assets/ClassLoader.html-fNJjFm9m.js","assets/MemoryModel.html-CRsJwAOr.js","assets/NewObject.html-DNLlDT5H.js","assets/ObjectReference.html-CG8KX2PY.js","assets/index.html-Bn0M7tTB.js","assets/SetObjectNull.html-BA6rF5JL.js","assets/StringAdd.html-fGViknMr.js","assets/volatile.html-B-u5KWIK.js","assets/名词解释.html-B5GJmRJ1.js","assets/JdkVersion.html-CS9P09_u.js","assets/index.html-Cjv6EKnU.js","assets/index.html-DDdnWKF-.js","assets/ebooks.html-BgNEqnIF.js","assets/TCP-IP.html-obsJBTgk.js","assets/CPUOverLoad.html-B_JZj2Bd.js","assets/MysqlCollate.html-BXuIpRKi.js","assets/MysqlNote.html-Cy1AGfO1.js","assets/MysqlRemoteConnect.html-B6Rz_Dfl.js","assets/index.html-D5Bas5eh.js","assets/Recurse.html-CzvVNt5P.js","assets/SQLOptimization.html-DB9Wuz97.js","assets/数据库备份.html-rdxyp_Zo.js","assets/DistributeLock.html-C1ihBiBe.js","assets/Nacos.html-DC2XGbQ_.js","assets/index.html-DaiGMbWY.js","assets/Docker.html-DzlBwcZ1.js","assets/ServiceInstall.html-BaooIkHM.js","assets/2022-04-12.html-omw7Q29H.js","assets/BTree.html-DJQoEL8B.js","assets/CDN.html-Cd7ildNJ.js","assets/ChromeDevTools.html-jvUjLE8a.js","assets/CloudService.html-DKHWbJgc.js","assets/DeployGithubPage.html-COQNI509.js","assets/IM即时通信技术选型.html-B2_U3umv.js","assets/Jenkins.html-e7mi9H_K.js","assets/index.html-BBsSpY4m.js","assets/TyporaPicgo.html-DyxSZmxq.js","assets/elasticSearch操作.html-DivcwrqA.js","assets/elk部署.html-hYHFl7Qx.js","assets/im即时通信的需求.html-Disuf4MT.js","assets/windows下服务注册.html-CH2QvxT3.js","assets/操作系统引导.html-BKBwfgmF.js","assets/BatchDeleteGitHubRepo.html-DtqMXGl3.js","assets/GitCommands.html-BB60xrRT.js","assets/index.html-BtN-4LZa.js","assets/branch01.html-CEgmUaaW.js","assets/branch02.html-CkbKOtQo.js","assets/fatal.html-CuBowvDc.js","assets/gitConflict.html-CD4vs1Ct.js","assets/gitRebase.html-DF3bIliz.js","assets/gitwork.html-BenGZ5gF.js","assets/mergeBranch.html-CCnBpahJ.js","assets/proxy.html-CIpuLV1M.js","assets/rebaseAndMerge.html-DZipbfd7.js","assets/reset.html-JinN7PbC.js","assets/stash.html-D1rEWs9W.js","assets/CPU.html-BmK1dlqs.js","assets/CentOS.html-CSukTNYu.js","assets/CommonUsedCMD.html-CHwLkMH3.js","assets/Curl.html-BO77gaot.js","assets/InstallMysqlWithDocker.html-Dlzu3km3.js","assets/Manjaro.html-Dy2e0Hrp.js","assets/MountDisk.html-BqspOYo5.js","assets/MultiNetworkCard.html-BdZVkWRH.js","assets/index.html-BIoY637M.js","assets/Samba.html-D8HLrsJP.js","assets/ShareBetweenWindowsAndLinux.html-CJ7U7icg.js","assets/TcpDump.html-DFEN5LYg.js","assets/Wifi.html-DCwxVnq6.js","assets/firewall.html-Bcf4dLh0.js","assets/wsl.html-DiCvzzYG.js","assets/截图.html-DV7uCl6s.js","assets/index.html-BgeMLVVF.js","assets/01.html-jkQdEnIh.js","assets/02.html-B3UlmDxL.js","assets/03.html-CK1TN99Q.js","assets/04.html-CeKJQimw.js","assets/05.html-CMuhT-M0.js","assets/06.html-BX6nV64C.js","assets/07.html-CCBD9WTC.js","assets/08.html-BJMKXxmH.js","assets/09.html-7MXGKP1a.js","assets/10.html-IqVcln-C.js","assets/11.html-kw9nHLna.js","assets/12.html-DxvMj_JN.js","assets/13.html-CBYdZLrV.js","assets/14.html-B-d6pKTK.js","assets/index.html-0gfJBSgv.js","assets/index.html-DEubCK6b.js","assets/ddns申请证书.html-ykZMVUjH.js","assets/firewall.html-CnVk-61G.js","assets/index.html-CuULl194.js","assets/破解软件.html-DO8LI6dF.js","assets/SUC.html-DtVpWo_q.js","assets/Idea.html-DFw9lvM7.js","assets/index.html--jCoRugf.js","assets/SoftWare.html-6EROxq9x.js","assets/VsCode.html-BCn8Egxl.js","assets/CloudServiceTraining.html-CsW1-SEa.js","assets/index.html-Bm2QW3Yx.js","assets/SSO.html-DB2D0akT.js","assets/draw.html-DWXvV2Rt.js","assets/BuildWebProject.html-D4yumRMO.js","assets/Cookie.html-BobniOCU.js","assets/Http.html-Cx1NIixk.js","assets/Jwt.html-CP1cMj9m.js","assets/OAUTH_LOGIN.html-DZ9PB-Wb.js","assets/index.html-DZ7gwFx0.js","assets/RefreshToken.html-B7uT6L3n.js","assets/Restful.html-BeZrEgn9.js","assets/WSL.html-BIz7aujd.js","assets/MybatisPlusDataSource.html-J7-G54ls.js","assets/index.html-CntKYRgb.js","assets/mybatis.html-CU1fMlIQ.js","assets/Authorization.html-DYO-IVFw.js","assets/CustomAuthenticationProvider.html-C05ZFsDP.js","assets/CustomLoginPage.html-R1ammlY9.js","assets/CustomTokenAuthentication.html-DcEKKC-z.js","assets/DelegatingFilterProxy.html-CK8Yiklp.js","assets/OAuth2Authentication.html-ChmdjSKd.js","assets/OncePerRequestFilter.html-DIikqGft.js","assets/PreAuthorize.html-DFU2o_nN.js","assets/index.html-52PFUvm3.js","assets/SSO.html-DKOv-tJ8.js","assets/SecurityFilterChain.html-j_4ZmVEf.js","assets/Session.html-CKk3uN_p.js","assets/note.html-CZMFgVaM.js","assets/spring-security-oauth2-authorization-server.html-o1Bz8Yim.js","assets/Annotation.html-CN1Pgot9.js","assets/BeanPostProcessor.html-CLtCpKMO.js","assets/CircularDependency.html-DRv79HhX.js","assets/DesignPatternInSpring.html-B1tCH6O1.js","assets/OncePerRequestFilter.html-DvJ1C4ir.js","assets/index.html-D_iYE-Ij.js","assets/SpringAOP.html-BR3lenMn.js","assets/SpringCache.html-B2PNt9GB.js","assets/SpringExtensionPoint.html-BdwK_BjA.js","assets/SpringIOC.html-BqsqDvdX.js","assets/SpringMVC.html-D8cETPq8.js","assets/SpringSourceAnalize.html-D0vztr7E.js","assets/Validator.html-DOW_vPxb.js","assets/AOPLog.html-WkAcygqg.js","assets/CollectionInject.html-Wk5anYr4.js","assets/Http2.html-B3cAbYAj.js","assets/index.html-D4BNQ3HG.js","assets/SpringBootAutoConfiguration.html-DyJd0iCn.js","assets/Swagger.html-QZsEFOI2.js","assets/部署.html-Cup61qP7.js","assets/SpringCloudGateway.html-Dc9fg5OX.js","assets/index.html-BGEnglY6.js","assets/wrapper.html-aaFCqiMF.js","assets/index.html-Bx1FwJMs.js","assets/TooManyOpenFiles.html-DBlFfNwX.js","assets/Undertow.xxxNotFount.html-X1F463A1.js","assets/各种框架版本的坑.html-CpfYP_fL.js","assets/各种问题.html-BVVorpF6.js","assets/logback.html-bWzTkpCR.js","assets/index.html-l0ianYW6.js","assets/build标签.html-CyXt4kH2.js","assets/import.html-CNG84pM6.js","assets/multiModule.html-vU5Z_GHP.js","assets/problem.html-220H-omj.js","assets/生命周期.html-CGxeIKAF.js","assets/404.html-CFV1KPyG.js","assets/index.html-Gf4fmU9c.js","assets/index.html-CbyN5TWa.js","assets/index.html-B0HiH-Xn.js","assets/index.html-ByKY6u5T.js","assets/index.html-CTGnNX68.js","assets/index.html-Bxi5AXy-.js","assets/index.html-DFsL5gjr.js","assets/index.html-De67gXK7.js","assets/index.html-DcPH7P5y.js","assets/index.html-jAf7-iM1.js","assets/index.html-ZenQwYfE.js","assets/index.html-BA11w6YR.js","assets/index.html-D0WBxxKk.js","assets/index.html-BJ_XZI-3.js","assets/index.html-DpIMle5T.js","assets/index.html-Bk4Xl8z2.js","assets/index.html-n0MBVKbb.js","assets/index.html-CvVo_Iy8.js","assets/index.html-DmeObyHP.js","assets/index.html-CiPcuEin.js","assets/index.html-ClRCgRtJ.js","assets/index.html-DtMYUjJK.js","assets/index.html-C2vp8KMJ.js","assets/index.html-CEilrxfK.js","assets/index.html-1bodzj_2.js","assets/index.html-Du2EK4qm.js","assets/index.html-QOoLQt7U.js","assets/index.html-2E2sJd9T.js","assets/index.html-KBuRJKDT.js","assets/index.html-gaUfJQSN.js","assets/index.html-CtpU9Ex-.js","assets/index.html-BGmSAx78.js","assets/index.html-BOFOt1os.js","assets/index.html-CV_dUHJe.js","assets/index.html-Bt1B_fZ7.js","assets/index.html-BgghxhP9.js","assets/index.html-BNCitO52.js","assets/index.html-ukDkHwB4.js","assets/index.html-BR_hmahK.js","assets/index.html-BluLs2ps.js","assets/index.html-BAA1oPlT.js","assets/index.html-DERaj909.js","assets/index.html-D1zTD58M.js","assets/index.html-Bq73GOIG.js","assets/index.html-CONpmD-V.js","assets/index.html-DfN87M_p.js","assets/index.html-YlcxYzGW.js","assets/index.html-C_jbP7SN.js","assets/index.html-DnWLnH1N.js","assets/index.html-DsIag3k6.js","assets/index.html-BB0RPG_h.js","assets/index.html-BwnKcSJN.js","assets/index.html-DNyKapJd.js","assets/index.html-D3l9AP0G.js","assets/index.html-k__z7vRJ.js","assets/index.html-tdfwMWEj.js","assets/index.html-CNk3LVBO.js","assets/index.html-C8vCtcrV.js","assets/index.html-bCycLo4M.js","assets/index.html-BDDCxsP9.js","assets/index.html-l2dMz0nJ.js","assets/index.html-43Da7xtg.js","assets/index.html-Be9YOXiD.js","assets/index.html-DYNHttZu.js","assets/index.html-CrqLYD8s.js","assets/index.html-DI5pE721.js","assets/index.html-CXTrskFo.js","assets/index.html-UDfVv4MI.js","assets/index.html-BJxwK7AA.js","assets/index.html-DqEIAryj.js","assets/index.html-BkurDFO4.js","assets/index.html-C4Z9a5GS.js","assets/index.html-Cwy-RPtK.js","assets/index.html-C6FMNcFL.js","assets/index.html-da2hiqxj.js","assets/index.html-DH61zfDk.js","assets/index.html-3y6s76rT.js","assets/index.html-BoICU8sT.js","assets/index.html-ChsojBeT.js"]
                       }
                       return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
                     }
                    @@ -20,7 +20,7 @@ function __vite__mapDeps(indexes) {
                     * @vue/runtime-dom v3.4.21
                     * (c) 2018-present Yuxi (Evan) You and Vue contributors
                     * @license MIT
                    -**/const qd="http://www.w3.org/2000/svg",Nd="http://www.w3.org/1998/Math/MathML",Be=typeof document<"u"?document:null,Po=Be&&Be.createElement("template"),Hd={insert:(n,e,t)=>{e.insertBefore(n,t||null)},remove:n=>{const e=n.parentNode;e&&e.removeChild(n)},createElement:(n,e,t,a)=>{const s=e==="svg"?Be.createElementNS(qd,n):e==="mathml"?Be.createElementNS(Nd,n):Be.createElement(n,t?{is:t}:void 0);return n==="select"&&a&&a.multiple!=null&&s.setAttribute("multiple",a.multiple),s},createText:n=>Be.createTextNode(n),createComment:n=>Be.createComment(n),setText:(n,e)=>{n.nodeValue=e},setElementText:(n,e)=>{n.textContent=e},parentNode:n=>n.parentNode,nextSibling:n=>n.nextSibling,querySelector:n=>Be.querySelector(n),setScopeId(n,e){n.setAttribute(e,"")},insertStaticContent(n,e,t,a,s,l){const o=t?t.previousSibling:e.lastChild;if(s&&(s===l||s.nextSibling))for(;e.insertBefore(s.cloneNode(!0),t),!(s===l||!(s=s.nextSibling)););else{Po.innerHTML=a==="svg"?`${n}`:a==="mathml"?`${n}`:n;const i=Po.content;if(a==="svg"||a==="mathml"){const c=i.firstChild;for(;c.firstChild;)i.appendChild(c.firstChild);i.removeChild(c)}e.insertBefore(i,t)}return[o?o.nextSibling:e.firstChild,t?t.previousSibling:e.lastChild]}},Ie="transition",jt="animation",xt=Symbol("_vtc"),$e=(n,{slots:e})=>p(Wu,Ii(n),e);$e.displayName="Transition";const Oi={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},$d=$e.props=On({},ui,Oi),Xe=(n,e=[])=>{Z(n)?n.forEach(t=>t(...e)):n&&n(...e)},jo=n=>n?Z(n)?n.some(e=>e.length>1):n.length>1:!1;function Ii(n){const e={};for(const R in n)R in Oi||(e[R]=n[R]);if(n.css===!1)return e;const{name:t="v",type:a,duration:s,enterFromClass:l=`${t}-enter-from`,enterActiveClass:o=`${t}-enter-active`,enterToClass:i=`${t}-enter-to`,appearFromClass:c=l,appearActiveClass:r=o,appearToClass:u=i,leaveFromClass:d=`${t}-leave-from`,leaveActiveClass:k=`${t}-leave-active`,leaveToClass:g=`${t}-leave-to`}=n,_=zd(s),b=_&&_[0],E=_&&_[1],{onBeforeEnter:w,onEnter:L,onEnterCancelled:f,onLeave:S,onLeaveCancelled:F,onBeforeAppear:A=w,onAppear:H=L,onAppearCancelled:M=f}=e,X=(R,nn,Sn)=>{je(R,nn?u:i),je(R,nn?r:o),Sn&&Sn()},P=(R,nn)=>{R._isLeaving=!1,je(R,d),je(R,g),je(R,k),nn&&nn()},Y=R=>(nn,Sn)=>{const Cn=R?H:L,G=()=>X(nn,R,Sn);Xe(Cn,[nn,G]),Do(()=>{je(nn,R?c:l),ye(nn,R?u:i),jo(Cn)||Ro(nn,a,b,G)})};return On(e,{onBeforeEnter(R){Xe(w,[R]),ye(R,l),ye(R,o)},onBeforeAppear(R){Xe(A,[R]),ye(R,c),ye(R,r)},onEnter:Y(!1),onAppear:Y(!0),onLeave(R,nn){R._isLeaving=!0;const Sn=()=>P(R,nn);ye(R,d),ji(),ye(R,k),Do(()=>{R._isLeaving&&(je(R,d),ye(R,g),jo(S)||Ro(R,a,E,Sn))}),Xe(S,[R,Sn])},onEnterCancelled(R){X(R,!1),Xe(f,[R])},onAppearCancelled(R){X(R,!0),Xe(M,[R])},onLeaveCancelled(R){P(R),Xe(F,[R])}})}function zd(n){if(n==null)return null;if(Ln(n))return[Ls(n.enter),Ls(n.leave)];{const e=Ls(n);return[e,e]}}function Ls(n){return Gr(n)}function ye(n,e){e.split(/\s+/).forEach(t=>t&&n.classList.add(t)),(n[xt]||(n[xt]=new Set)).add(e)}function je(n,e){e.split(/\s+/).forEach(a=>a&&n.classList.remove(a));const t=n[xt];t&&(t.delete(e),t.size||(n[xt]=void 0))}function Do(n){requestAnimationFrame(()=>{requestAnimationFrame(n)})}let Ud=0;function Ro(n,e,t,a){const s=n._endId=++Ud,l=()=>{s===n._endId&&a()};if(t)return setTimeout(l,t);const{type:o,timeout:i,propCount:c}=Pi(n,e);if(!o)return a();const r=o+"end";let u=0;const d=()=>{n.removeEventListener(r,k),l()},k=g=>{g.target===n&&++u>=c&&d()};setTimeout(()=>{u(t[_]||"").split(", "),s=a(`${Ie}Delay`),l=a(`${Ie}Duration`),o=Fo(s,l),i=a(`${jt}Delay`),c=a(`${jt}Duration`),r=Fo(i,c);let u=null,d=0,k=0;e===Ie?o>0&&(u=Ie,d=o,k=l.length):e===jt?r>0&&(u=jt,d=r,k=c.length):(d=Math.max(o,r),u=d>0?o>r?Ie:jt:null,k=u?u===Ie?l.length:c.length:0);const g=u===Ie&&/\b(transform|all)(,|$)/.test(a(`${Ie}Property`).toString());return{type:u,timeout:d,propCount:k,hasTransform:g}}function Fo(n,e){for(;n.lengthBo(t)+Bo(n[a])))}function Bo(n){return n==="auto"?0:Number(n.slice(0,-1).replace(",","."))*1e3}function ji(){return document.body.offsetHeight}function Jd(n,e,t){const a=n[xt];a&&(e=(e?[e,...a]:[...a]).join(" ")),e==null?n.removeAttribute("class"):t?n.setAttribute("class",e):n.className=e}const Mo=Symbol("_vod"),Gd=Symbol("_vsh"),Wd=Symbol(""),Kd=/(^|;)\s*display\s*:/;function Xd(n,e,t){const a=n.style,s=Dn(t);let l=!1;if(t&&!s){if(e)if(Dn(e))for(const o of e.split(";")){const i=o.slice(0,o.indexOf(":")).trim();t[i]==null&&za(a,i,"")}else for(const o in e)t[o]==null&&za(a,o,"");for(const o in t)o==="display"&&(l=!0),za(a,o,t[o])}else if(s){if(e!==t){const o=a[Wd];o&&(t+=";"+o),a.cssText=t,l=Kd.test(t)}}else e&&n.removeAttribute("style");Mo in n&&(n[Mo]=l?a.display:"",n[Gd]&&(a.display="none"))}const Vo=/\s*!important$/;function za(n,e,t){if(Z(t))t.forEach(a=>za(n,e,a));else if(t==null&&(t=""),e.startsWith("--"))n.setProperty(e,t);else{const a=Yd(n,e);Vo.test(t)?n.setProperty(Tt(a),t.replace(Vo,""),"important"):n[a]=t}}const qo=["Webkit","Moz","ms"],Ss={};function Yd(n,e){const t=Ss[e];if(t)return t;let a=te(e);if(a!=="filter"&&a in n)return Ss[e]=a;a=ia(a);for(let s=0;sCs||(sh.then(()=>Cs=0),Cs=Date.now());function oh(n,e){const t=a=>{if(!a._vts)a._vts=Date.now();else if(a._vts<=t.attached)return;ne(ph(a,t.value),e,5,[a])};return t.value=n,t.attached=lh(),t}function ph(n,e){if(Z(e)){const t=n.stopImmediatePropagation;return n.stopImmediatePropagation=()=>{t.call(n),n._stopped=!0},e.map(a=>s=>!s._stopped&&a&&a(s))}else return e}const zo=n=>n.charCodeAt(0)===111&&n.charCodeAt(1)===110&&n.charCodeAt(2)>96&&n.charCodeAt(2)<123,ih=(n,e,t,a,s,l,o,i,c)=>{const r=s==="svg";e==="class"?Jd(n,a,r):e==="style"?Xd(n,t,a):pa(e)?kl(e)||th(n,e,t,a,o):(e[0]==="."?(e=e.slice(1),!0):e[0]==="^"?(e=e.slice(1),!1):ch(n,e,a,r))?Zd(n,e,a,l,o,i,c):(e==="true-value"?n._trueValue=a:e==="false-value"&&(n._falseValue=a),Qd(n,e,a,r))};function ch(n,e,t,a){if(a)return!!(e==="innerHTML"||e==="textContent"||e in n&&zo(e)&&tn(t));if(e==="spellcheck"||e==="draggable"||e==="translate"||e==="form"||e==="list"&&n.tagName==="INPUT"||e==="type"&&n.tagName==="TEXTAREA")return!1;if(e==="width"||e==="height"){const s=n.tagName;if(s==="IMG"||s==="VIDEO"||s==="CANVAS"||s==="SOURCE")return!1}return zo(e)&&Dn(t)?!1:e in n}const Di=new WeakMap,Ri=new WeakMap,Za=Symbol("_moveCb"),Uo=Symbol("_enterCb"),Fi={name:"TransitionGroup",props:On({},$d,{tag:String,moveClass:String}),setup(n,{slots:e}){const t=ha(),a=ri();let s,l;return ki(()=>{if(!s.length)return;const o=n.moveClass||`${n.name||"v"}-move`;if(!gh(s[0].el,t.vnode.el,o))return;s.forEach(dh),s.forEach(hh);const i=s.filter(kh);ji(),i.forEach(c=>{const r=c.el,u=r.style;ye(r,o),u.transform=u.webkitTransform=u.transitionDuration="";const d=r[Za]=k=>{k&&k.target!==r||(!k||/transform$/.test(k.propertyName))&&(r.removeEventListener("transitionend",d),r[Za]=null,je(r,o))};r.addEventListener("transitionend",d)})}),()=>{const o=on(n),i=Ii(o);let c=o.tag||Kn;s=l,l=e.default?Ol(e.default()):[];for(let r=0;rdelete n.mode;Fi.props;const uh=Fi;function dh(n){const e=n.el;e[Za]&&e[Za](),e[Uo]&&e[Uo]()}function hh(n){Ri.set(n,n.el.getBoundingClientRect())}function kh(n){const e=Di.get(n),t=Ri.get(n),a=e.left-t.left,s=e.top-t.top;if(a||s){const l=n.el.style;return l.transform=l.webkitTransform=`translate(${a}px,${s}px)`,l.transitionDuration="0s",n}}function gh(n,e,t){const a=n.cloneNode(),s=n[xt];s&&s.forEach(i=>{i.split(/\s+/).forEach(c=>c&&a.classList.remove(c))}),t.split(/\s+/).forEach(i=>i&&a.classList.add(i)),a.style.display="none";const l=e.nodeType===1?e:e.parentNode;l.appendChild(a);const{hasTransform:o}=Pi(a);return l.removeChild(a),o}const mh=On({patchProp:ih},Hd);let Ts,Jo=!1;function vh(){return Ts=Jo?Ts:bd(mh),Jo=!0,Ts}const fh=(...n)=>{const e=vh().createApp(...n),{mount:t}=e;return e.mount=a=>{const s=yh(a);if(s)return t(s,!0,_h(s))},e};function _h(n){if(n instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&n instanceof MathMLElement)return"mathml"}function yh(n){return Dn(n)?document.querySelector(n):n}var bh=["link","meta","script","style","noscript","template"],wh=["title","base"],Eh=([n,e,t])=>wh.includes(n)?n:bh.includes(n)?n==="meta"&&e.name?`${n}.${e.name}`:n==="template"&&e.id?`${n}.${e.id}`:JSON.stringify([n,Object.entries(e).map(([a,s])=>typeof s=="boolean"?s?[a,""]:null:[a,s]).filter(a=>a!=null).sort(([a],[s])=>a.localeCompare(s)),t]):null,xh=n=>{const e=new Set,t=[];return n.forEach(a=>{const s=Eh(a);s&&!e.has(s)&&(e.add(s),t.push(a))}),t},Ah=n=>n[0]==="/"?n:`/${n}`,Bi=n=>n[n.length-1]==="/"||n.endsWith(".html")?n:`${n}/`,Je=n=>/^(https?:)?\/\//.test(n),Lh=/.md((\?|#).*)?$/,Zt=(n,e="/")=>!!(Je(n)||n.startsWith("/")&&!n.startsWith(e)&&!Lh.test(n)),Mi=n=>/^[a-z][a-z0-9+.-]*:/.test(n),ma=n=>Object.prototype.toString.call(n)==="[object Object]",Sh=n=>{const[e,...t]=n.split(/(\?|#)/);if(!e||e.endsWith("/"))return n;let a=e.replace(/(^|\/)README.md$/i,"$1index.html");return a.endsWith(".md")?a=a.substring(0,a.length-3)+".html":a.endsWith(".html")||(a=a+".html"),a.endsWith("/index.html")&&(a=a.substring(0,a.length-10)),a+t.join("")},Fl=n=>n[n.length-1]==="/"?n.slice(0,-1):n,Vi=n=>n[0]==="/"?n.slice(1):n,Ch=(n,e)=>{const t=Object.keys(n).sort((a,s)=>{const l=s.split("/").length-a.split("/").length;return l!==0?l:s.length-a.length});for(const a of t)if(e.startsWith(a))return a;return"/"},An=n=>typeof n=="string";const Th="modulepreload",Oh=function(n){return"/"+n},Go={},h=function(e,t,a){let s=Promise.resolve();return t&&t.length>0&&(document.getElementsByTagName("link"),s=Promise.all(t.map(l=>{if(l=Oh(l),l in Go)return;Go[l]=!0;const o=l.endsWith(".css"),i=o?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${l}"]${i}`))return;const c=document.createElement("link");if(c.rel=o?"stylesheet":Th,o||(c.as="script",c.crossOrigin=""),c.href=l,document.head.appendChild(c),o)return new Promise((r,u)=>{c.addEventListener("load",r),c.addEventListener("error",()=>u(new Error(`Unable to preload CSS for ${l}`)))})}))),s.then(()=>e()).catch(l=>{const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=l,window.dispatchEvent(o),!o.defaultPrevented)throw l})},Ih=JSON.parse("{}"),Ph=Object.fromEntries([["/",{loader:()=>h(()=>import("./index.html-Ah6mlgvk.js"),__vite__mapDeps([0,1])),meta:{y:"h",t:"ChenSino",i:"home"}}],["/home.html",{loader:()=>h(()=>import("./home.html-CdI4hI0D.js"),__vite__mapDeps([2,1])),meta:{y:"h",t:"Home",i:"home"}}],["/slide.html",{loader:()=>h(()=>import("./slide.html-_uY0duH1.js"),__vite__mapDeps([3,1])),meta:{d:1659087823e3,e:`
                    +**/const qd="http://www.w3.org/2000/svg",Nd="http://www.w3.org/1998/Math/MathML",Be=typeof document<"u"?document:null,Po=Be&&Be.createElement("template"),Hd={insert:(n,e,t)=>{e.insertBefore(n,t||null)},remove:n=>{const e=n.parentNode;e&&e.removeChild(n)},createElement:(n,e,t,a)=>{const s=e==="svg"?Be.createElementNS(qd,n):e==="mathml"?Be.createElementNS(Nd,n):Be.createElement(n,t?{is:t}:void 0);return n==="select"&&a&&a.multiple!=null&&s.setAttribute("multiple",a.multiple),s},createText:n=>Be.createTextNode(n),createComment:n=>Be.createComment(n),setText:(n,e)=>{n.nodeValue=e},setElementText:(n,e)=>{n.textContent=e},parentNode:n=>n.parentNode,nextSibling:n=>n.nextSibling,querySelector:n=>Be.querySelector(n),setScopeId(n,e){n.setAttribute(e,"")},insertStaticContent(n,e,t,a,s,l){const o=t?t.previousSibling:e.lastChild;if(s&&(s===l||s.nextSibling))for(;e.insertBefore(s.cloneNode(!0),t),!(s===l||!(s=s.nextSibling)););else{Po.innerHTML=a==="svg"?`${n}`:a==="mathml"?`${n}`:n;const i=Po.content;if(a==="svg"||a==="mathml"){const c=i.firstChild;for(;c.firstChild;)i.appendChild(c.firstChild);i.removeChild(c)}e.insertBefore(i,t)}return[o?o.nextSibling:e.firstChild,t?t.previousSibling:e.lastChild]}},Ie="transition",jt="animation",xt=Symbol("_vtc"),$e=(n,{slots:e})=>p(Wu,Ii(n),e);$e.displayName="Transition";const Oi={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},$d=$e.props=On({},ui,Oi),Xe=(n,e=[])=>{Z(n)?n.forEach(t=>t(...e)):n&&n(...e)},jo=n=>n?Z(n)?n.some(e=>e.length>1):n.length>1:!1;function Ii(n){const e={};for(const R in n)R in Oi||(e[R]=n[R]);if(n.css===!1)return e;const{name:t="v",type:a,duration:s,enterFromClass:l=`${t}-enter-from`,enterActiveClass:o=`${t}-enter-active`,enterToClass:i=`${t}-enter-to`,appearFromClass:c=l,appearActiveClass:r=o,appearToClass:u=i,leaveFromClass:d=`${t}-leave-from`,leaveActiveClass:k=`${t}-leave-active`,leaveToClass:g=`${t}-leave-to`}=n,_=zd(s),b=_&&_[0],E=_&&_[1],{onBeforeEnter:w,onEnter:L,onEnterCancelled:f,onLeave:S,onLeaveCancelled:F,onBeforeAppear:A=w,onAppear:H=L,onAppearCancelled:M=f}=e,X=(R,nn,Sn)=>{je(R,nn?u:i),je(R,nn?r:o),Sn&&Sn()},P=(R,nn)=>{R._isLeaving=!1,je(R,d),je(R,g),je(R,k),nn&&nn()},Y=R=>(nn,Sn)=>{const Cn=R?H:L,G=()=>X(nn,R,Sn);Xe(Cn,[nn,G]),Do(()=>{je(nn,R?c:l),ye(nn,R?u:i),jo(Cn)||Ro(nn,a,b,G)})};return On(e,{onBeforeEnter(R){Xe(w,[R]),ye(R,l),ye(R,o)},onBeforeAppear(R){Xe(A,[R]),ye(R,c),ye(R,r)},onEnter:Y(!1),onAppear:Y(!0),onLeave(R,nn){R._isLeaving=!0;const Sn=()=>P(R,nn);ye(R,d),ji(),ye(R,k),Do(()=>{R._isLeaving&&(je(R,d),ye(R,g),jo(S)||Ro(R,a,E,Sn))}),Xe(S,[R,Sn])},onEnterCancelled(R){X(R,!1),Xe(f,[R])},onAppearCancelled(R){X(R,!0),Xe(M,[R])},onLeaveCancelled(R){P(R),Xe(F,[R])}})}function zd(n){if(n==null)return null;if(Ln(n))return[Ls(n.enter),Ls(n.leave)];{const e=Ls(n);return[e,e]}}function Ls(n){return Gr(n)}function ye(n,e){e.split(/\s+/).forEach(t=>t&&n.classList.add(t)),(n[xt]||(n[xt]=new Set)).add(e)}function je(n,e){e.split(/\s+/).forEach(a=>a&&n.classList.remove(a));const t=n[xt];t&&(t.delete(e),t.size||(n[xt]=void 0))}function Do(n){requestAnimationFrame(()=>{requestAnimationFrame(n)})}let Ud=0;function Ro(n,e,t,a){const s=n._endId=++Ud,l=()=>{s===n._endId&&a()};if(t)return setTimeout(l,t);const{type:o,timeout:i,propCount:c}=Pi(n,e);if(!o)return a();const r=o+"end";let u=0;const d=()=>{n.removeEventListener(r,k),l()},k=g=>{g.target===n&&++u>=c&&d()};setTimeout(()=>{u(t[_]||"").split(", "),s=a(`${Ie}Delay`),l=a(`${Ie}Duration`),o=Fo(s,l),i=a(`${jt}Delay`),c=a(`${jt}Duration`),r=Fo(i,c);let u=null,d=0,k=0;e===Ie?o>0&&(u=Ie,d=o,k=l.length):e===jt?r>0&&(u=jt,d=r,k=c.length):(d=Math.max(o,r),u=d>0?o>r?Ie:jt:null,k=u?u===Ie?l.length:c.length:0);const g=u===Ie&&/\b(transform|all)(,|$)/.test(a(`${Ie}Property`).toString());return{type:u,timeout:d,propCount:k,hasTransform:g}}function Fo(n,e){for(;n.lengthBo(t)+Bo(n[a])))}function Bo(n){return n==="auto"?0:Number(n.slice(0,-1).replace(",","."))*1e3}function ji(){return document.body.offsetHeight}function Jd(n,e,t){const a=n[xt];a&&(e=(e?[e,...a]:[...a]).join(" ")),e==null?n.removeAttribute("class"):t?n.setAttribute("class",e):n.className=e}const Mo=Symbol("_vod"),Gd=Symbol("_vsh"),Wd=Symbol(""),Kd=/(^|;)\s*display\s*:/;function Xd(n,e,t){const a=n.style,s=Dn(t);let l=!1;if(t&&!s){if(e)if(Dn(e))for(const o of e.split(";")){const i=o.slice(0,o.indexOf(":")).trim();t[i]==null&&za(a,i,"")}else for(const o in e)t[o]==null&&za(a,o,"");for(const o in t)o==="display"&&(l=!0),za(a,o,t[o])}else if(s){if(e!==t){const o=a[Wd];o&&(t+=";"+o),a.cssText=t,l=Kd.test(t)}}else e&&n.removeAttribute("style");Mo in n&&(n[Mo]=l?a.display:"",n[Gd]&&(a.display="none"))}const Vo=/\s*!important$/;function za(n,e,t){if(Z(t))t.forEach(a=>za(n,e,a));else if(t==null&&(t=""),e.startsWith("--"))n.setProperty(e,t);else{const a=Yd(n,e);Vo.test(t)?n.setProperty(Tt(a),t.replace(Vo,""),"important"):n[a]=t}}const qo=["Webkit","Moz","ms"],Ss={};function Yd(n,e){const t=Ss[e];if(t)return t;let a=te(e);if(a!=="filter"&&a in n)return Ss[e]=a;a=ia(a);for(let s=0;sCs||(sh.then(()=>Cs=0),Cs=Date.now());function oh(n,e){const t=a=>{if(!a._vts)a._vts=Date.now();else if(a._vts<=t.attached)return;ne(ph(a,t.value),e,5,[a])};return t.value=n,t.attached=lh(),t}function ph(n,e){if(Z(e)){const t=n.stopImmediatePropagation;return n.stopImmediatePropagation=()=>{t.call(n),n._stopped=!0},e.map(a=>s=>!s._stopped&&a&&a(s))}else return e}const zo=n=>n.charCodeAt(0)===111&&n.charCodeAt(1)===110&&n.charCodeAt(2)>96&&n.charCodeAt(2)<123,ih=(n,e,t,a,s,l,o,i,c)=>{const r=s==="svg";e==="class"?Jd(n,a,r):e==="style"?Xd(n,t,a):pa(e)?kl(e)||th(n,e,t,a,o):(e[0]==="."?(e=e.slice(1),!0):e[0]==="^"?(e=e.slice(1),!1):ch(n,e,a,r))?Zd(n,e,a,l,o,i,c):(e==="true-value"?n._trueValue=a:e==="false-value"&&(n._falseValue=a),Qd(n,e,a,r))};function ch(n,e,t,a){if(a)return!!(e==="innerHTML"||e==="textContent"||e in n&&zo(e)&&tn(t));if(e==="spellcheck"||e==="draggable"||e==="translate"||e==="form"||e==="list"&&n.tagName==="INPUT"||e==="type"&&n.tagName==="TEXTAREA")return!1;if(e==="width"||e==="height"){const s=n.tagName;if(s==="IMG"||s==="VIDEO"||s==="CANVAS"||s==="SOURCE")return!1}return zo(e)&&Dn(t)?!1:e in n}const Di=new WeakMap,Ri=new WeakMap,Za=Symbol("_moveCb"),Uo=Symbol("_enterCb"),Fi={name:"TransitionGroup",props:On({},$d,{tag:String,moveClass:String}),setup(n,{slots:e}){const t=ha(),a=ri();let s,l;return ki(()=>{if(!s.length)return;const o=n.moveClass||`${n.name||"v"}-move`;if(!gh(s[0].el,t.vnode.el,o))return;s.forEach(dh),s.forEach(hh);const i=s.filter(kh);ji(),i.forEach(c=>{const r=c.el,u=r.style;ye(r,o),u.transform=u.webkitTransform=u.transitionDuration="";const d=r[Za]=k=>{k&&k.target!==r||(!k||/transform$/.test(k.propertyName))&&(r.removeEventListener("transitionend",d),r[Za]=null,je(r,o))};r.addEventListener("transitionend",d)})}),()=>{const o=on(n),i=Ii(o);let c=o.tag||Kn;s=l,l=e.default?Ol(e.default()):[];for(let r=0;rdelete n.mode;Fi.props;const uh=Fi;function dh(n){const e=n.el;e[Za]&&e[Za](),e[Uo]&&e[Uo]()}function hh(n){Ri.set(n,n.el.getBoundingClientRect())}function kh(n){const e=Di.get(n),t=Ri.get(n),a=e.left-t.left,s=e.top-t.top;if(a||s){const l=n.el.style;return l.transform=l.webkitTransform=`translate(${a}px,${s}px)`,l.transitionDuration="0s",n}}function gh(n,e,t){const a=n.cloneNode(),s=n[xt];s&&s.forEach(i=>{i.split(/\s+/).forEach(c=>c&&a.classList.remove(c))}),t.split(/\s+/).forEach(i=>i&&a.classList.add(i)),a.style.display="none";const l=e.nodeType===1?e:e.parentNode;l.appendChild(a);const{hasTransform:o}=Pi(a);return l.removeChild(a),o}const mh=On({patchProp:ih},Hd);let Ts,Jo=!1;function vh(){return Ts=Jo?Ts:bd(mh),Jo=!0,Ts}const fh=(...n)=>{const e=vh().createApp(...n),{mount:t}=e;return e.mount=a=>{const s=yh(a);if(s)return t(s,!0,_h(s))},e};function _h(n){if(n instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&n instanceof MathMLElement)return"mathml"}function yh(n){return Dn(n)?document.querySelector(n):n}var bh=["link","meta","script","style","noscript","template"],wh=["title","base"],Eh=([n,e,t])=>wh.includes(n)?n:bh.includes(n)?n==="meta"&&e.name?`${n}.${e.name}`:n==="template"&&e.id?`${n}.${e.id}`:JSON.stringify([n,Object.entries(e).map(([a,s])=>typeof s=="boolean"?s?[a,""]:null:[a,s]).filter(a=>a!=null).sort(([a],[s])=>a.localeCompare(s)),t]):null,xh=n=>{const e=new Set,t=[];return n.forEach(a=>{const s=Eh(a);s&&!e.has(s)&&(e.add(s),t.push(a))}),t},Ah=n=>n[0]==="/"?n:`/${n}`,Bi=n=>n[n.length-1]==="/"||n.endsWith(".html")?n:`${n}/`,Je=n=>/^(https?:)?\/\//.test(n),Lh=/.md((\?|#).*)?$/,Zt=(n,e="/")=>!!(Je(n)||n.startsWith("/")&&!n.startsWith(e)&&!Lh.test(n)),Mi=n=>/^[a-z][a-z0-9+.-]*:/.test(n),ma=n=>Object.prototype.toString.call(n)==="[object Object]",Sh=n=>{const[e,...t]=n.split(/(\?|#)/);if(!e||e.endsWith("/"))return n;let a=e.replace(/(^|\/)README.md$/i,"$1index.html");return a.endsWith(".md")?a=a.substring(0,a.length-3)+".html":a.endsWith(".html")||(a=a+".html"),a.endsWith("/index.html")&&(a=a.substring(0,a.length-10)),a+t.join("")},Fl=n=>n[n.length-1]==="/"?n.slice(0,-1):n,Vi=n=>n[0]==="/"?n.slice(1):n,Ch=(n,e)=>{const t=Object.keys(n).sort((a,s)=>{const l=s.split("/").length-a.split("/").length;return l!==0?l:s.length-a.length});for(const a of t)if(e.startsWith(a))return a;return"/"},An=n=>typeof n=="string";const Th="modulepreload",Oh=function(n){return"/"+n},Go={},h=function(e,t,a){let s=Promise.resolve();return t&&t.length>0&&(document.getElementsByTagName("link"),s=Promise.all(t.map(l=>{if(l=Oh(l),l in Go)return;Go[l]=!0;const o=l.endsWith(".css"),i=o?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${l}"]${i}`))return;const c=document.createElement("link");if(c.rel=o?"stylesheet":Th,o||(c.as="script",c.crossOrigin=""),c.href=l,document.head.appendChild(c),o)return new Promise((r,u)=>{c.addEventListener("load",r),c.addEventListener("error",()=>u(new Error(`Unable to preload CSS for ${l}`)))})}))),s.then(()=>e()).catch(l=>{const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=l,window.dispatchEvent(o),!o.defaultPrevented)throw l})},Ih=JSON.parse("{}"),Ph=Object.fromEntries([["/",{loader:()=>h(()=>import("./index.html-Di3gSe-7.js"),__vite__mapDeps([0,1])),meta:{y:"h",t:"ChenSino",i:"home"}}],["/home.html",{loader:()=>h(()=>import("./home.html-Ds_ny-Cd.js"),__vite__mapDeps([2,1])),meta:{y:"h",t:"Home",i:"home"}}],["/slide.html",{loader:()=>h(()=>import("./slide.html-gu2cHfI1.js"),__vite__mapDeps([3,1])),meta:{d:1659087823e3,e:`
                     

                    @slidestart

                    幻灯片演示

                    @@ -32,9 +32,10 @@ function __vite__mapDeps(indexes) {

                    标注幻灯片

                    -`,r:{minutes:4.5,words:1349},y:"s",t:"幻灯片页",i:"slides"}}],["/designpattern/",{loader:()=>h(()=>import("./index.html-DYLEdXjZ.js"),__vite__mapDeps([4,1])),meta:{d:16838496e5,l:"2023年5月12日",o:!0,e:`

                    说明

                    +`,r:{minutes:4.5,words:1349},y:"s",t:"幻灯片页",i:"slides"}}],["/frontweb/",{loader:()=>h(()=>import("./index.html-DyGScxDV.js"),__vite__mapDeps([4,1])),meta:{d:1527552e6,l:"2018年5月29日",e:`

                    基础打牢

                    +`,r:{minutes:.05,words:14},y:"a",t:"JavaScript成神之路"}}],["/designpattern/",{loader:()=>h(()=>import("./index.html-Zxb4dqDx.js"),__vite__mapDeps([5,1])),meta:{d:16838496e5,l:"2023年5月12日",o:!0,e:`

                    说明

                    java设计模式代码请参考开源项目java-design-patterns,本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。

                    -`,r:{minutes:.34,words:101},y:"a",t:"README"}}],["/designpattern/%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F.html",{loader:()=>h(()=>import("./建造者模式.html-BpLKsWf9.js"),__vite__mapDeps([5,1])),meta:{d:16838496e5,l:"2023年5月12日",c:["设计模式"],o:!0,e:`

                    建造者模式

                    +`,r:{minutes:.34,words:101},y:"a",t:"README"}}],["/designpattern/%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F.html",{loader:()=>h(()=>import("./建造者模式.html-CPvB1dQQ.js"),__vite__mapDeps([6,1])),meta:{d:16838496e5,l:"2023年5月12日",c:["设计模式"],o:!0,e:`

                    建造者模式

                    这种模式太常见了,开源框架中的各种builder都是

                    mybatis中

                    org.apache.ibatis.mapping.Environment

                    @@ -102,8 +103,8 @@ function __vite__mapDeps(indexes) { } -
                    `,r:{minutes:.61,words:183},y:"a",t:"Builder Pattern"}}],["/designpattern/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F.html",{loader:()=>h(()=>import("./模板方法模式.html-DLubeSKF.js"),__vite__mapDeps([6,1])),meta:{d:16854048e5,l:"2023年5月30日",c:["设计模式"],o:!0,e:`

                    定义

                    -

                    模板方法模式(Template Method Pattern)是一种行为型设计模式,定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。这种设计模式是一种控制反转的实现方式。因为高层代码不再确定(控制)算法的处理流程。模板方法模式多用在某些类别的算法中,实现了相同的方法,造成代码的重复。这个设计模式和策略模式很像,不同的是,模板方法会有一些通用的逻辑,而策略模式是整个方法重写。从类的继承结构也可以看出来,模板方法是提供一个抽象类,有一个通用的方法,不通用的逻辑放到子类去实现,而策略模式是子类直接继承自接口,要重写整个方法。

                    `,r:{minutes:1.87,words:562},y:"a",t:"Template Method"}}],["/designpattern/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F.html",{loader:()=>h(()=>import("./策略模式.html-BzaYdrRx.js"),__vite__mapDeps([7,1])),meta:{d:16838496e5,l:"2023年5月12日",c:["设计模式"],o:!0,e:`

                    策略模式【CHATGPT回答】

                    +
                    `,r:{minutes:.61,words:183},y:"a",t:"Builder Pattern"}}],["/designpattern/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F.html",{loader:()=>h(()=>import("./模板方法模式.html-D86ZbVNZ.js"),__vite__mapDeps([7,1])),meta:{d:16854048e5,l:"2023年5月30日",c:["设计模式"],o:!0,e:`

                    定义

                    +

                    模板方法模式(Template Method Pattern)是一种行为型设计模式,定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。这种设计模式是一种控制反转的实现方式。因为高层代码不再确定(控制)算法的处理流程。模板方法模式多用在某些类别的算法中,实现了相同的方法,造成代码的重复。这个设计模式和策略模式很像,不同的是,模板方法会有一些通用的逻辑,而策略模式是整个方法重写。从类的继承结构也可以看出来,模板方法是提供一个抽象类,有一个通用的方法,不通用的逻辑放到子类去实现,而策略模式是子类直接继承自接口,要重写整个方法。

                    `,r:{minutes:1.87,words:562},y:"a",t:"Template Method"}}],["/designpattern/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F.html",{loader:()=>h(()=>import("./策略模式.html-1pJjZLuA.js"),__vite__mapDeps([8,1])),meta:{d:16838496e5,l:"2023年5月12日",c:["设计模式"],o:!0,e:`

                    策略模式【CHATGPT回答】

                    策略模式(Strategy Pattern)是一种行为型设计模式,它将一组可相互替换的算法封装成独立的对象,并对外暴露相同的接口,从而使得它们可以根据需要动态地替换,以实现不同的行为。
                     
                     策略模式由三个角色组成,分别是:
                    @@ -117,11 +118,11 @@ function __vite__mapDeps(indexes) {
                     在策略模式中,不同的算法被封装在具体的策略中,这些策略可以根据上下文的需要进行灵活替换,从而实现不同的功能。因此,策略模式具有高度的可扩展性和灵活性,并且可以有效地减少代码冗余。
                     
                     例如,在订单系统中,可以使用策略模式来处理收费方式的问题。具体而言,可以定义一个ChargeStrategy接口,用于实现不同的收费算法。然后,定义多个具体的收费算法,分别实现ChargeStrategy接口。最后,在订单系统中,可以根据订单的不同情况,动态地设置不同的收费算法,以实现灵活的收费功能。
                    -
                    `,r:{minutes:2.41,words:722},y:"a",t:"Strategy Pattern"}}],["/designpattern/%E8%A3%85%E9%A5%B0%E6%A8%A1%E5%BC%8F.html",{loader:()=>h(()=>import("./装饰模式.html-NVA1JlXh.js"),__vite__mapDeps([8,1])),meta:{d:16859232e5,l:"2023年6月5日",c:["设计模式"],o:!0,e:`

                    定义

                    +
                    `,r:{minutes:2.41,words:722},y:"a",t:"Strategy Pattern"}}],["/designpattern/%E8%A3%85%E9%A5%B0%E6%A8%A1%E5%BC%8F.html",{loader:()=>h(()=>import("./装饰模式.html-Bq_Ohlm8.js"),__vite__mapDeps([9,1])),meta:{d:16859232e5,l:"2023年6月5日",c:["设计模式"],o:!0,e:`

                    定义

                    装饰者模式是一种常用的设计模式,它动态地给一个对象添加一些额外的职责1。装饰者模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为2。

                    框架中使用

                    mybatis中:

                    -

                    打开sqlSession时,会创建Executor,最终会进入org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

                    `,r:{minutes:.9,words:270},y:"a",t:"Decorator Design Pattern"}}],["/designpattern/%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F.html",{loader:()=>h(()=>import("./责任链模式.html-WcnUM2LR.js"),__vite__mapDeps([9,1])),meta:{d:16838496e5,l:"2023年5月12日",c:["设计模式"],o:!0,e:`

                    定义

                    +

                    打开sqlSession时,会创建Executor,最终会进入org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

                    `,r:{minutes:.9,words:270},y:"a",t:"Decorator Design Pattern"}}],["/designpattern/%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F.html",{loader:()=>h(()=>import("./责任链模式.html-C6zRijOG.js"),__vite__mapDeps([10,1])),meta:{d:16838496e5,l:"2023年5月12日",c:["设计模式"],o:!0,e:`

                    定义

                    责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它可以将多个对象组合成一条链,并按照事先规定的顺序依次处理请求。每个对象都可以选择处理请求,或者将请求传递给链中的下一个对象。这种模式将请求的发送者和接收者解耦,使得多个对象都有机会处理请求,从而提高了系统的灵活性和可扩展性。
                     
                     具体来说,责任链模式包含以下几个角色:
                    @@ -133,8 +134,7 @@ function __vite__mapDeps(indexes) {
                     3. Client:客户端,它向链中的第一个ConcreteHandler对象发起请求,然后等待链条响应请求。
                     
                     责任链模式的优点在于它可以简化对象之间的耦合关系,增加系统的灵活性。它对系统进行解耦,使得请求发送者不必关心请求的具体处理者,发起请求后,请求会在链条中依次被多个对象处理。同时,责任链模式还可以方便地进行动态的链式调整,即在运行时往链中添加或删除具体处理者,以便满足实时的业务需求。
                    -
                    `,r:{minutes:2.14,words:641},y:"a",t:"Chain of Responsibility Pattern"}}],["/frontweb/",{loader:()=>h(()=>import("./index.html-B-rzW1p0.js"),__vite__mapDeps([10,1])),meta:{d:1527552e6,l:"2018年5月29日",e:`

                    基础打牢

                    -`,r:{minutes:.05,words:14},y:"a",t:"JavaScript成神之路"}}],["/guide/",{loader:()=>h(()=>import("./index.html-BXBwg_XP.js"),__vite__mapDeps([11,1])),meta:{d:1659087823e3,c:["使用指南"],e:`

                    目录

                    +
                    `,r:{minutes:2.14,words:641},y:"a",t:"Chain of Responsibility Pattern"}}],["/guide/",{loader:()=>h(()=>import("./index.html-_Hk-UKaP.js"),__vite__mapDeps([11,1])),meta:{d:1659087823e3,c:["使用指南"],e:`

                    目录

                    `,r:{minutes:.13,words:40},y:"a",t:"主要功能与配置演示",i:"creative"}}],["/guide/disable.html",{loader:()=>h(()=>import("./disable.html-D2cQj4ic.js"),__vite__mapDeps([12,1])),meta:{d:1659087823e3,c:["使用指南"],g:["禁用"],e:`

                    你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。

                    -`,r:{minutes:.43,words:128},y:"a",t:"布局与功能禁用",i:"config",O:3}}],["/guide/encrypt.html",{loader:()=>h(()=>import("./encrypt.html-BEDsFZkv.js"),__vite__mapDeps([13,1])),meta:{d:1659087823e3,c:["使用指南"],g:["文章加密"],n:!0,r:{minutes:.52,words:156},y:"a",t:"密码加密的文章",i:"lock"}}],["/guide/markdown.html",{loader:()=>h(()=>import("./markdown.html-Ku2vuX_R.js"),__vite__mapDeps([14,1])),meta:{d:1659087823e3,c:["使用指南"],g:["Markdown"],e:`

                    VuePress 主要从 Markdown 文件生成页面。因此,你可以使用它轻松生成文档或博客站点。

                    +
    `,r:{minutes:.13,words:40},y:"a",t:"主要功能与配置演示",i:"creative"}}],["/guide/disable.html",{loader:()=>h(()=>import("./disable.html-D_vnyg33.js"),__vite__mapDeps([12,1])),meta:{d:1659087823e3,c:["使用指南"],g:["禁用"],e:`

    你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。

    +`,r:{minutes:.43,words:128},y:"a",t:"布局与功能禁用",i:"config",O:3}}],["/guide/encrypt.html",{loader:()=>h(()=>import("./encrypt.html-BVOYksPN.js"),__vite__mapDeps([13,1])),meta:{d:1659087823e3,c:["使用指南"],g:["文章加密"],n:!0,r:{minutes:.52,words:156},y:"a",t:"密码加密的文章",i:"lock"}}],["/guide/markdown.html",{loader:()=>h(()=>import("./markdown.html-DnotFn9F.js"),__vite__mapDeps([14,1])),meta:{d:1659087823e3,c:["使用指南"],g:["Markdown"],e:`

    VuePress 主要从 Markdown 文件生成页面。因此,你可以使用它轻松生成文档或博客站点。

    你应该创建和编写 Markdown 文件,以便 VuePress 可以根据文件结构将它们转换为不同的页面。

    -`,r:{minutes:3.58,words:1073},y:"a",t:"Markdown 展示",i:"markdown",O:2}}],["/guide/page.html",{loader:()=>h(()=>import("./page.html-CNLLwtGV.js"),__vite__mapDeps([15,1])),meta:{a:"Ms.Hope",d:15778368e5,l:"2020年1月1日",c:["使用指南"],g:["页面配置","使用指南"],u:!0,e:`

    more 注释之前的内容被视为文章摘要。

    -`,r:{minutes:1.46,words:438},y:"a",t:"页面配置",i:"page",O:1}}],["/java/",{loader:()=>h(()=>import("./index.html-iq2vqpLb.js"),__vite__mapDeps([16,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.04,words:11},y:"a",t:"Java入门到放弃"}}],["/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html",{loader:()=>h(()=>import("./Jellyfin搭建.html-CLPGwkSq.js"),__vite__mapDeps([17,1])),meta:{a:"chensino",d:17110656e5,l:"2024年3月22日",o:!0,e:`

    家庭影院

    -`,r:{minutes:.06,words:18},y:"a",t:"jellyfin搭建"}}],["/myserver/",{loader:()=>h(()=>import("./index.html-COH-CgE0.js"),__vite__mapDeps([18,1])),meta:{a:"chensino",d:17110656e5,l:"2024年3月22日",e:`

    个人服务器搭建

    +`,r:{minutes:3.58,words:1073},y:"a",t:"Markdown 展示",i:"markdown",O:2}}],["/guide/page.html",{loader:()=>h(()=>import("./page.html-CalA3aZV.js"),__vite__mapDeps([15,1])),meta:{a:"Ms.Hope",d:15778368e5,l:"2020年1月1日",c:["使用指南"],g:["页面配置","使用指南"],u:!0,e:`

    more 注释之前的内容被视为文章摘要。

    +`,r:{minutes:1.46,words:438},y:"a",t:"页面配置",i:"page",O:1}}],["/java/",{loader:()=>h(()=>import("./index.html-DLU057mi.js"),__vite__mapDeps([16,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.04,words:11},y:"a",t:"Java入门到放弃"}}],["/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html",{loader:()=>h(()=>import("./Jellyfin搭建.html-B4JCtBiz.js"),__vite__mapDeps([17,1])),meta:{a:"chensino",d:17110656e5,l:"2024年3月22日",o:!0,e:`

    家庭影院

    +`,r:{minutes:.06,words:18},y:"a",t:"jellyfin搭建"}}],["/myserver/",{loader:()=>h(()=>import("./index.html-raRbuKfh.js"),__vite__mapDeps([18,1])),meta:{a:"chensino",d:17110656e5,l:"2024年3月22日",e:`

    个人服务器搭建

    自建NAS

    自己搭建nas

    实现nas外网访问

    使用外部网络访问nas

    搭建家庭影院

    下载,刮削,展示

    -`,r:{minutes:.19,words:57},y:"a",t:"个人服务器"}}],["/myserver/x86_openwrt.html",{loader:()=>h(()=>import("./x86_openwrt.html-Rfp5hWIu.js"),__vite__mapDeps([19,1])),meta:{a:"chensino",d:17116704e5,l:"2024年3月29日",o:!0,e:`

    vmware中使用openwrt做旁路由网关

    +`,r:{minutes:.19,words:57},y:"a",t:"个人服务器"}}],["/myserver/x86_openwrt.html",{loader:()=>h(()=>import("./x86_openwrt.html-CASth0J6.js"),__vite__mapDeps([19,1])),meta:{a:"chensino",d:17116704e5,l:"2024年3月29日",o:!0,e:`

    vmware中使用openwrt做旁路由网关

    注意!!

    在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下: @@ -168,33 +168,33 @@ vmware中其他系统可以正常使用openwrt做旁路由网关,但是局域 解决方式: 要么不要用windows系统,要么把宿主机切换为有线连接

    -

    参考

    `,r:{minutes:.57,words:172},y:"a",t:"旁路由网关"}}],["/myserver/%E6%97%81%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3.html",{loader:()=>h(()=>import("./旁路由网关.html-2p0UA79e.js"),__vite__mapDeps([20,1])),meta:{a:"chensino",d:1712016e6,l:"2024年4月2日",o:!0,e:`

    问题1. 内网主机把网关指向旁路由无法通过主路由端口转发

    +

    参考

    `,r:{minutes:.57,words:172},y:"a",t:"旁路由网关"}}],["/myserver/%E6%97%81%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3.html",{loader:()=>h(()=>import("./旁路由网关.html-JyFgPLU6.js"),__vite__mapDeps([20,1])),meta:{a:"chensino",d:1712016e6,l:"2024年4月2日",o:!0,e:`

    问题1. 内网主机把网关指向旁路由无法通过主路由端口转发

        
     

    参考: 参考

    -`,r:{minutes:.16,words:49},y:"a",t:"旁路由网关"}}],["/myserver/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE.html",{loader:()=>h(()=>import("./网络设置.html-DbRAJ3uC.js"),__vite__mapDeps([21,1])),meta:{a:"chensino",d:17110656e5,l:"2024年3月22日",o:!0,e:`

    本篇介绍我的网络配置过程,如何获得公网ip,以及升级我的家庭路由,mesh组网,我的网络是湖北电信500m,光猫一开始是天翼网关3.0, +`,r:{minutes:.16,words:49},y:"a",t:"旁路由网关"}}],["/myserver/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE.html",{loader:()=>h(()=>import("./网络设置.html-CT2typrG.js"),__vite__mapDeps([21,1])),meta:{a:"chensino",d:17110656e5,l:"2024年3月22日",o:!0,e:`

    本篇介绍我的网络配置过程,如何获得公网ip,以及升级我的家庭路由,mesh组网,我的网络是湖北电信500m,光猫一开始是天翼网关3.0, 后来咸鱼买了一个二手的4.0的花了50,曾打电话给电信客服升级4.0的是不是免费,他说199元。 家庭组网章节适合所有用户阅读,公网部分适合程序员朋友参考。

    家庭组网

    为什么要组网

    我家套内93平,一个无线路由器无法覆盖整个房间,起初是在每个房间放一个路由器,每次从一个房间到另一房间都要切换网络,即使手机有
     自动切换网络功能,但是总是不丝滑,我想做到wifi切换无感知,所以就生出组网的想法。
    -
    `,r:{minutes:5.99,words:1797},y:"a",t:"网络设置"}}],["/myserver/%E8%87%AA%E5%BB%BAnas.html",{loader:()=>h(()=>import("./自建nas.html-BhQRnqm0.js"),__vite__mapDeps([22,1])),meta:{a:"chensino",d:17110656e5,l:"2024年3月22日",o:!0,e:`

    nas介绍

    +
    `,r:{minutes:5.99,words:1797},y:"a",t:"网络设置"}}],["/myserver/%E8%87%AA%E5%BB%BAnas.html",{loader:()=>h(()=>import("./自建nas.html-D-Ky5W6L.js"),__vite__mapDeps([22,1])),meta:{a:"chensino",d:17110656e5,l:"2024年3月22日",o:!0,e:`

    nas介绍

    nas是什么

    -`,r:{minutes:.07,words:21},y:"a",t:"自建nas"}}],["/other/",{loader:()=>h(()=>import("./index.html-BqSgUMIT.js"),__vite__mapDeps([23,1])),meta:{a:"chenkun",d:1659312e6,l:"2022年8月1日",r:{minutes:.03,words:9},y:"a",t:"其他"}}],["/cpp/other/1.html",{loader:()=>h(()=>import("./1.html-BsLOzPLz.js"),__vite__mapDeps([24,1])),meta:{d:1673568e6,l:"2023年1月13日",e:`

    文章来源:https://zhuanlan.zhihu.com/p/111110992

    +`,r:{minutes:.07,words:21},y:"a",t:"自建nas"}}],["/other/",{loader:()=>h(()=>import("./index.html-ZWa93oKC.js"),__vite__mapDeps([23,1])),meta:{a:"chenkun",d:1659312e6,l:"2022年8月1日",r:{minutes:.03,words:9},y:"a",t:"其他"}}],["/cpp/other/1.html",{loader:()=>h(()=>import("./1.html-CeguXEpG.js"),__vite__mapDeps([24,1])),meta:{d:1673568e6,l:"2023年1月13日",e:`

    文章来源:https://zhuanlan.zhihu.com/p/111110992

    gcc

    它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。

    我们的程序只有一个源文件时,直接就可以用gcc命令编译它。

    可是,如果我们的程序包含很多个源文件时,该咋整?用gcc命令逐个去编译时,就发现很容易混乱而且工作量大,所以出现了下面make工具。

    gcc命令在链接时默认使用C的库,只有添加了-lstdc++选项才会使用 C++ 的库。 -不过 GCC 中还有一个g++命令,它专门用来编译 C++ 程序,广大 C++ 开发人员也都使用这个命令。g++命令和gcc命令的用法是一样的。

    `,r:{minutes:3.17,words:951},y:"a",t:"c++中使用的编译工具介绍"}}],["/cpp/other/2.html",{loader:()=>h(()=>import("./2.html-CDG7SSJK.js"),__vite__mapDeps([25,1])),meta:{d:1673568e6,l:"2023年1月13日",e:`

    占坑

    +不过 GCC 中还有一个g++命令,它专门用来编译 C++ 程序,广大 C++ 开发人员也都使用这个命令。g++命令和gcc命令的用法是一样的。

    `,r:{minutes:3.17,words:951},y:"a",t:"c++中使用的编译工具介绍"}}],["/cpp/other/2.html",{loader:()=>h(()=>import("./2.html-Dp6E9lgb.js"),__vite__mapDeps([25,1])),meta:{d:1673568e6,l:"2023年1月13日",e:`

    占坑

    https://juejin.cn/post/6969389200416178213#heading-11

    https://blog.jetbrains.com/clion/2020/03/openjdk-with-clion/

    -`,r:{minutes:.09,words:27},y:"a",t:"使用Clion搭建jdk源码调试环境"}}],["/cpp/study/",{loader:()=>h(()=>import("./index.html-09kdO8uk.js"),__vite__mapDeps([26,1])),meta:{d:1673568e6,l:"2023年1月13日",e:`

    c++基础学习

    +`,r:{minutes:.09,words:27},y:"a",t:"使用Clion搭建jdk源码调试环境"}}],["/cpp/study/",{loader:()=>h(()=>import("./index.html-UPH_9_-C.js"),__vite__mapDeps([26,1])),meta:{d:1673568e6,l:"2023年1月13日",e:`

    c++基础学习

    c++的进阶之路

    -`,r:{minutes:.06,words:17},y:"a",t:"index"}}],["/frontweb/es5/",{loader:()=>h(()=>import("./index.html-BJvieq0-.js"),__vite__mapDeps([27,1])),meta:{a:"Zxf",d:16508448e5,l:"2022年4月25日",g:["你所不了解的JavaScript"],e:`

    目录

    +`,r:{minutes:.06,words:17},y:"a",t:"index"}}],["/frontweb/es5/",{loader:()=>h(()=>import("./index.html-DESeP-r7.js"),__vite__mapDeps([27,1])),meta:{a:"Zxf",d:16508448e5,l:"2022年4月25日",g:["你所不了解的JavaScript"],e:`

    目录

    `,r:{minutes:.23,words:69},y:"a",t:"ECMAScript 5"}}],["/frontweb/es5/aboutAsync.html",{loader:()=>h(()=>import("./aboutAsync.html-CVw3VyIR.js"),__vite__mapDeps([28,1])),meta:{a:"qianxun",d:16137792e5,l:"2021年2月20日",c:["vue知识点"],g:["必会"],e:`

    一,async函数的定义

    +
`,r:{minutes:.23,words:69},y:"a",t:"ECMAScript 5"}}],["/frontweb/es5/aboutAsync.html",{loader:()=>h(()=>import("./aboutAsync.html-CukGl2Bf.js"),__vite__mapDeps([28,1])),meta:{a:"qianxun",d:16137792e5,l:"2021年2月20日",c:["vue知识点"],g:["必会"],e:`

一,async函数的定义

async函数是使用async关键字声明的函数。 并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise异步行为,而无需刻意地链式调用promise。

备注:async/await的目的为了简化使用基于 promise 的 API 时所需的语法。async/await的行为就好像搭配使用了生成器和 promise。

-
`,r:{minutes:4.97,words:1490},y:"a",t:"异步async函数"}}],["/frontweb/es5/aboutEvent.html",{loader:()=>h(()=>import("./aboutEvent.html-1fGRZu30.js"),__vite__mapDeps([29,1])),meta:{a:"qianxun",d:16578432e5,l:"2022年7月15日",c:["js基础"],g:["必会"],e:`

一,事件注册的三种方式

+`,r:{minutes:4.97,words:1490},y:"a",t:"异步async函数"}}],["/frontweb/es5/aboutEvent.html",{loader:()=>h(()=>import("./aboutEvent.html-CvhiVca8.js"),__vite__mapDeps([29,1])),meta:{a:"qianxun",d:16578432e5,l:"2022年7月15日",c:["js基础"],g:["必会"],e:`

一,事件注册的三种方式

1,通过 HTML 元素指定事件属性来绑定

<button onclick ="clickFu()">点我吧</button>
 <script>
@@ -215,7 +215,7 @@ vmware中其他系统可以正常使用openwrt做旁路由网关,但是局域
        alert(3333)
    }
 </script>
-
`,r:{minutes:5.17,words:1550},y:"a",t:"JS原生事件"}}],["/frontweb/es5/aboutThis.html",{loader:()=>h(()=>import("./aboutThis.html-DlclfKVB.js"),__vite__mapDeps([30,1])),meta:{a:"qianxun",d:16508448e5,l:"2022年4月25日",c:["js基础"],g:["必会"],e:`

一,函数内部的this指向

+
`,r:{minutes:5.17,words:1550},y:"a",t:"JS原生事件"}}],["/frontweb/es5/aboutThis.html",{loader:()=>h(()=>import("./aboutThis.html-B-2Ir7Kw.js"),__vite__mapDeps([30,1])),meta:{a:"qianxun",d:16508448e5,l:"2022年4月25日",c:["js基础"],g:["必会"],e:`

一,函数内部的this指向

函数内this指向,是当我们调用函数的时候才能确定。调用方式的不同决定this的指向不同。
 this的指向,是在调用函数时根据执行上下文所动态确定的。
 
@@ -252,7 +252,7 @@ this的指向,是在调用函数时根据执行上下文所动态确定的。 window -`,r:{minutes:2.38,words:714},y:"a",t:"this指向问题"}}],["/frontweb/es5/asyncError.html",{loader:()=>h(()=>import("./asyncError.html-WxV4KJUL.js"),__vite__mapDeps([31,1])),meta:{a:"qianxun",d:16680384e5,l:"2022年11月10日",c:["vue知识点"],g:["必会"],e:`

一,为什么要捕获异常

+`,r:{minutes:2.38,words:714},y:"a",t:"this指向问题"}}],["/frontweb/es5/asyncError.html",{loader:()=>h(()=>import("./asyncError.html-npKDZnPq.js"),__vite__mapDeps([31,1])),meta:{a:"qianxun",d:16680384e5,l:"2022年11月10日",c:["vue知识点"],g:["必会"],e:`

一,为什么要捕获异常

//确认提交
 const submitWorkloadSure = async () => {
   
@@ -260,10 +260,10 @@ this的指向,是在调用函数时根据执行上下文所动态确定的。
      console.log(555)
 };
 
-
`,r:{minutes:2.56,words:769},y:"a",t:"关于async/await的异常捕获"}}],["/frontweb/es5/crossDomain.html",{loader:()=>h(()=>import("./crossDomain.html-DCiVSBRz.js"),__vite__mapDeps([32,1])),meta:{a:"qianxun",d:16508448e5,l:"2022年4月25日",c:["vue知识点"],g:["必会"],e:`

一,同源策略

+
`,r:{minutes:2.56,words:769},y:"a",t:"关于async/await的异常捕获"}}],["/frontweb/es5/crossDomain.html",{loader:()=>h(()=>import("./crossDomain.html-CSDExzG7.js"),__vite__mapDeps([32,1])),meta:{a:"qianxun",d:16508448e5,l:"2022年4月25日",c:["vue知识点"],g:["必会"],e:`

一,同源策略

所谓同源是指:当浏览器向后端发送请求时其请求的协议、域名、端口要和当前服务完全一致。比如前端项目的服务位于http://localhost:8080,则其发送的所有请求必须是http://localhost:8080/xxx/xxx这种格式,否则就会被同源策略拦截。

http://www.test.com:8000/  协议(http)、主域名(test)、子域名(www)、端口号(8000)
-
`,r:{minutes:6.18,words:1854},y:"a",t:"前端跨域(一)之proxy配置"}}],["/frontweb/es5/crossDomain2.html",{loader:()=>h(()=>import("./crossDomain2.html-DTphraX3.js"),__vite__mapDeps([33,1])),meta:{a:"qianxun",d:16508448e5,l:"2022年4月25日",c:["vue知识点"],g:["必会"],e:`

一,什么是JSONP

+
`,r:{minutes:6.18,words:1854},y:"a",t:"前端跨域(一)之proxy配置"}}],["/frontweb/es5/crossDomain2.html",{loader:()=>h(()=>import("./crossDomain2.html-CA7WfkLl.js"),__vite__mapDeps([33,1])),meta:{a:"qianxun",d:16508448e5,l:"2022年4月25日",c:["vue知识点"],g:["必会"],e:`

一,什么是JSONP

Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

@@ -271,8 +271,8 @@ this的指向,是在调用函数时根据执行上下文所动态确定的。 同源策略,它是由 Netscape 提出的一个著名的安全策略,现在所有支持 JavaScript 的浏览器都会使用这个策略。

二,JSONP的原理

我们知道,在页面上有三种资源是可以与页面本身不同源的。它们是:js脚本,css样式文件,图片。像taobao等大型网站,很定会将这些静态资源放入cdn中,然后在页面上链接。而jsonp就是利用了script标签可以链接到不同源的js脚本,来到达跨域目的。 -于是可以判断,当前阶段如果想通过纯web端(ActiveX控件、服务端代理、Web socket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理;

`,r:{minutes:2.97,words:891},y:"a",t:"前端跨域(二)之JSONP跨域"}}],["/frontweb/es5/lazyLoad.html",{loader:()=>h(()=>import("./lazyLoad.html-Cdfyboon.js"),__vite__mapDeps([34,1])),meta:{a:"qianxun",d:16508448e5,l:"2022年4月25日",c:["js基础"],g:["必会"],e:`

一,为什么需要图片懒加载

-

在老版本的Chrome中,图片的加载其实是会阻塞DOM渲染的。在我们现代浏览器中,这一点基本不用担心了,也就是说现在的图片加载不会阻塞DOM渲染,但是每一个图片都会对应一个HTTP请求而浏览器允许同时并发请求的数量是有限的(数量为6),假设你的网站有大量的图片,那么加载的过程是很耗时的,尤其像那些电商类需要大量图片的网站,可想而知,网站的初始加载时间会很长,再加上网络等其它影响,用户体验会很差。为了解决这个问题,提高用户体验,所以就出现了懒加载这种方式来减轻服务器的压力,优先加载可视区域的内容,其他部分等进入了可视区域再加载,从而提高性能。

`,r:{minutes:3.16,words:949},y:"a",t:"图片懒加载"}}],["/frontweb/es5/throttle.html",{loader:()=>h(()=>import("./throttle.html-BVURcdFS.js"),__vite__mapDeps([35,1])),meta:{a:"qianxun",d:16481664e5,l:"2022年3月25日",c:["js基础"],g:["必会"],e:`

一,节流概念(Throttle)

+于是可以判断,当前阶段如果想通过纯web端(ActiveX控件、服务端代理、Web socket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理;

`,r:{minutes:2.97,words:891},y:"a",t:"前端跨域(二)之JSONP跨域"}}],["/frontweb/es5/lazyLoad.html",{loader:()=>h(()=>import("./lazyLoad.html-B0wYE_Yw.js"),__vite__mapDeps([34,1])),meta:{a:"qianxun",d:16508448e5,l:"2022年4月25日",c:["js基础"],g:["必会"],e:`

一,为什么需要图片懒加载

+

在老版本的Chrome中,图片的加载其实是会阻塞DOM渲染的。在我们现代浏览器中,这一点基本不用担心了,也就是说现在的图片加载不会阻塞DOM渲染,但是每一个图片都会对应一个HTTP请求而浏览器允许同时并发请求的数量是有限的(数量为6),假设你的网站有大量的图片,那么加载的过程是很耗时的,尤其像那些电商类需要大量图片的网站,可想而知,网站的初始加载时间会很长,再加上网络等其它影响,用户体验会很差。为了解决这个问题,提高用户体验,所以就出现了懒加载这种方式来减轻服务器的压力,优先加载可视区域的内容,其他部分等进入了可视区域再加载,从而提高性能。

`,r:{minutes:3.16,words:949},y:"a",t:"图片懒加载"}}],["/frontweb/es5/throttle.html",{loader:()=>h(()=>import("./throttle.html-CDssB4Vw.js"),__vite__mapDeps([35,1])),meta:{a:"qianxun",d:16481664e5,l:"2022年3月25日",c:["js基础"],g:["必会"],e:`

一,节流概念(Throttle)

规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

@@ -281,12 +281,12 @@ this的指向,是在调用函数时根据执行上下文所动态确定的。
  • 鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;
  • 在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据;
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断;
  • -`,r:{minutes:2.53,words:759},y:"a",t:"节流与防抖"}}],["/frontweb/es6/",{loader:()=>h(()=>import("./index.html-BtjOnGEp.js"),__vite__mapDeps([36,1])),meta:{a:"qianxun",d:16137792e5,l:"2021年2月20日",r:{minutes:.03,words:9},y:"a",t:"ECMAScript 6"}}],["/frontweb/es6/js%E4%B8%AD%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.html",{loader:()=>h(()=>import("./js中整数的最大值.html-BzW3aeox.js"),__vite__mapDeps([37,1])),meta:{d:16898976e5,l:"2023年7月21日",o:!0,e:`

    背景

    +`,r:{minutes:2.53,words:759},y:"a",t:"节流与防抖"}}],["/frontweb/es6/",{loader:()=>h(()=>import("./index.html-C6kiZ_HF.js"),__vite__mapDeps([36,1])),meta:{a:"qianxun",d:16137792e5,l:"2021年2月20日",r:{minutes:.03,words:9},y:"a",t:"ECMAScript 6"}}],["/frontweb/es6/js%E4%B8%AD%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.html",{loader:()=>h(()=>import("./js中整数的最大值.html-BiKvCKWl.js"),__vite__mapDeps([37,1])),meta:{d:16898976e5,l:"2023年7月21日",o:!0,e:`

    背景

    今天用Mybatis-plus插入数据,自动产生了一个19位的主键id,后来才发现这是Mybatis-plus3.0后更新的功能,默认使用雪花算法最终会产生一个19位数字。

    然后我在前端项目查询后端接口,后端接口返回的1682195717382606849,而到了前端一直打印的是另一个数字。

    原因,参考gpt回答

    问题1:数据库19位id,在浏览器控制台显示错误?

    -

    如果你的数据库使用的是19位的ID,而浏览器控制台显示错误,可能是因为浏览器默认将超过16位的整数值视为科学计数法表示。在科学计数法表示下,可能会丢失精度或显示不正确。

    `,r:{minutes:2.48,words:744},y:"a",t:"js能识别最大的正整数"}}],["/frontweb/es6/promise.html",{loader:()=>h(()=>import("./promise.html-BUt4IEbi.js"),__vite__mapDeps([38,1])),meta:{d:16945632e5,l:"2023年9月13日",o:!0,e:`

    1. Promise介绍

    +

    如果你的数据库使用的是19位的ID,而浏览器控制台显示错误,可能是因为浏览器默认将超过16位的整数值视为科学计数法表示。在科学计数法表示下,可能会丢失精度或显示不正确。

    `,r:{minutes:2.48,words:744},y:"a",t:"js能识别最大的正整数"}}],["/frontweb/es6/promise.html",{loader:()=>h(()=>import("./promise.html-DlbJ5Sxz.js"),__vite__mapDeps([38,1])),meta:{d:16945632e5,l:"2023年9月13日",o:!0,e:`

    1. Promise介绍

    romise 是 JavaScript 中用于异步编程的一种解决方案。Promise 可以将异步操作进行封装,并提供了更加灵活和强大的处理方式。

    Promise 有三种状态:

      @@ -294,7 +294,7 @@ this的指向,是在调用函数时根据执行上下文所动态确定的。
    • fulfilled(成功):意味着操作成功完成,Promise 实例的最终值可通过 then 方法获取到。
    • rejected(失败):意味着操作失败,Promise 实例的最终值可通过 catch 方法获取到。
    -

    Promise 实例可以使用 then、catch 和 finally 方法实现异步操作的链式调用:

    `,r:{minutes:4.59,words:1377},y:"a",t:"Promise介绍"}}],["/frontweb/es6/useModule.html",{loader:()=>h(()=>import("./useModule.html-C54V2441.js"),__vite__mapDeps([39,1])),meta:{a:"qianxun",d:1666717021e3,l:"2022年10月25日",c:["vue知识点"],g:["必会"],e:`

    一,什么是前端模块化

    +

    Promise 实例可以使用 then、catch 和 finally 方法实现异步操作的链式调用:

    `,r:{minutes:4.59,words:1377},y:"a",t:"Promise介绍"}}],["/frontweb/es6/useModule.html",{loader:()=>h(()=>import("./useModule.html-BHtXy4j3.js"),__vite__mapDeps([39,1])),meta:{a:"qianxun",d:1666717021e3,l:"2022年10月25日",c:["vue知识点"],g:["必会"],e:`

    一,什么是前端模块化

    模块化就是将一个复杂的应用程序,按照规范拆分成几个相互独立的文件,这些文件里面完成共同的或者类似的逻辑,通过对外暴露一些数据或者调用方法,与外部整合。

    这样每个文件彼此独立,我们开发者更容易开发和维护代码,特别是当开发的项目越来越大,代码复杂性也不断增加,这对于模块化的需求也会越来越大。

    模块化主要特点是:可复用性、可组合性、独立性、中心化。

    @@ -305,12 +305,12 @@ this的指向,是在调用函数时根据执行上下文所动态确定的。
  • 性能优化:异步加载模块对页面性能会非常好
  • 模块的版本管理:通过别名等配置,配合构建工具,可以实现模块的版本管理
  • 跨环境共享模块:通过 Sea.jsNodeJS版本,可以实现模块的跨服务器和浏览器共享
  • -`,r:{minutes:9.33,words:2800},y:"a",t:"前端模块化"}}],["/frontweb/es6/useNpm.html",{loader:()=>h(()=>import("./useNpm.html-DJJdqwXN.js"),__vite__mapDeps([40,1])),meta:{a:"qianxun",d:1665766621e3,l:"2022年10月14日",c:["vue知识点"],g:["必会"],e:`

    一,什么是npm

    +`,r:{minutes:9.33,words:2800},y:"a",t:"前端模块化"}}],["/frontweb/es6/useNpm.html",{loader:()=>h(()=>import("./useNpm.html-CKLfIIZn.js"),__vite__mapDeps([40,1])),meta:{a:"qianxun",d:1665766621e3,l:"2022年10月14日",c:["vue知识点"],g:["必会"],e:`

    一,什么是npm

    npm是node官方的包管理工具 cnpm 是淘宝NPM镜像官网,来自淘宝NPM镜像官网的说明:

    淘宝为我们搭建了一个国内的npm服务器,它目前是每隔10分钟将国外npm仓库的所有内容“搬运”回国内的服务器上,这样我们直接访问淘宝的国内服务器就可以了,它的地址是:https://registry.npm.taobao.org

    -
    `,r:{minutes:1.5,words:449},y:"a",t:"搞懂npm与cnpm"}}],["/frontweb/es6/usePnpm.html",{loader:()=>h(()=>import("./usePnpm.html-mIezI9dx.js"),__vite__mapDeps([41,1])),meta:{a:"qianxun",d:1665766621e3,l:"2022年10月14日",c:["npm知识点"],g:["必会"],e:`

    一,什么是pnpm

    +`,r:{minutes:1.5,words:449},y:"a",t:"搞懂npm与cnpm"}}],["/frontweb/es6/usePnpm.html",{loader:()=>h(()=>import("./usePnpm.html-C3QlcelL.js"),__vite__mapDeps([41,1])),meta:{a:"qianxun",d:1665766621e3,l:"2022年10月14日",c:["npm知识点"],g:["必会"],e:`

    一,什么是pnpm

    performant npm ,意味“高性能的 npm”。pnpm由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”

    二,特点

      @@ -322,7 +322,7 @@ cnpm 是淘宝NPM镜像官网,来自h(()=>import("./useYarn.html-DUPdL_R5.js"),__vite__mapDeps([42,1])),meta:{a:"qianxun",d:1665766621e3,l:"2022年10月14日",c:["vue知识点"],g:["必会"],e:`

      一,yarn简介

      +
    `,r:{minutes:2.27,words:682},y:"a",t:"搞懂npm与pnpm"}}],["/frontweb/es6/useYarn.html",{loader:()=>h(()=>import("./useYarn.html-BrgPHvAk.js"),__vite__mapDeps([42,1])),meta:{a:"qianxun",d:1665766621e3,l:"2022年10月14日",c:["vue知识点"],g:["必会"],e:`

    一,yarn简介

    yarn 是由 Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具,yarn 是为了弥补 npm 的一些缺陷而出现的。

    二,npm的缺陷

    `,r:{minutes:1.06,words:319},y:"a",t:"npm与yarn的区别"}}],["/frontweb/nodejs/",{loader:()=>h(()=>import("./index.html-D5egMLMa.js"),__vite__mapDeps([43,1])),meta:{a:"chenkun",d:17235936e5,l:"2024年8月14日",c:["问题定位"],e:`

    +`,r:{minutes:.05,words:16},y:"a",t:"NodeJS"}}],["/frontweb/nodejs/%E5%AE%89%E8%A3%85%E9%97%AE%E9%A2%98.html",{loader:()=>h(()=>import("./安装问题.html-B-h8Zfob.js"),__vite__mapDeps([44,1])),meta:{a:"chenkun",d:17235936e5,l:"2024年8月14日",e:`

    背景

    从nodejs18开始就不支持Centos7了,这是因为centos7的gilbc版本比较低,因此需要安装非官方构建的版本。

    Note:如果npm安装的包依赖于glibc,那得改用docker或者换操作系统了。

    @@ -351,9 +351,9 @@ cnpm 是淘宝NPM镜像官网,来自
    tar -zxvf node-v18.19.0-linux-x64-glibc-217.tar.gz
     mv node-v18.19.0-linux-x64-glibc-217  node-v18
     
    -`,r:{minutes:.39,words:117},y:"a",t:"CentOS7安装node18"}}],["/frontweb/typeScript/",{loader:()=>h(()=>import("./index.html-DQDf-iB8.js"),__vite__mapDeps([45,1])),meta:{a:"qianxun",d:1659718621e3,l:"2022年8月5日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    学习typeScript

    +`,r:{minutes:.39,words:117},y:"a",t:"CentOS7安装node18"}}],["/frontweb/typeScript/",{loader:()=>h(()=>import("./index.html-CJQlCK8P.js"),__vite__mapDeps([45,1])),meta:{a:"qianxun",d:1659718621e3,l:"2022年8月5日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    学习typeScript

    (1)如何进阶TypeScript功底?一文带你理解TS中各种高级语法

    -

    (2) TypeScript学习(十)——缩小类型限制范围

    `,r:{minutes:.37,words:112},y:"a",t:"typeScript学习资料"}}],["/frontweb/typeScript/action-usage.html",{loader:()=>h(()=>import("./action-usage.html-xDUlIzb_.js"),__vite__mapDeps([46,1])),meta:{a:"qianxun",d:1666285021e3,l:"2022年10月20日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    一,利用typeScript实现新增,删除一行数据

    +

    (2) TypeScript学习(十)——缩小类型限制范围

    `,r:{minutes:.37,words:112},y:"a",t:"typeScript学习资料"}}],["/frontweb/typeScript/action-usage.html",{loader:()=>h(()=>import("./action-usage.html-DFNFrJyT.js"),__vite__mapDeps([46,1])),meta:{a:"qianxun",d:1666285021e3,l:"2022年10月20日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    一,利用typeScript实现新增,删除一行数据

    这里基本涵盖了typeScript在项目中的实战用法

    1,html部分

    <!DOCTYPE html>
    @@ -391,7 +391,7 @@ cnpm 是淘宝NPM镜像官网,来自</body>
     </html>
     
    -
    `,r:{minutes:1.33,words:399},y:"a",t:"typeScript项目实战"}}],["/frontweb/typeScript/axios.html",{loader:()=>h(()=>import("./axios.html-DIiEtqYW.js"),__vite__mapDeps([47,1])),meta:{a:"qianxun",d:1667926621e3,l:"2022年11月8日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    项目一

    +
    `,r:{minutes:1.33,words:399},y:"a",t:"typeScript项目实战"}}],["/frontweb/typeScript/axios.html",{loader:()=>h(()=>import("./axios.html-BXoNbwVV.js"),__vite__mapDeps([47,1])),meta:{a:"qianxun",d:1667926621e3,l:"2022年11月8日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    项目一

    根据名称获取动漫列表

    1,定义api.type.ts (定义后台接口返回的类型)

    `,r:{minutes:1.4,words:421},y:"a",t:"typeScript中使用axios"}}],["/frontweb/typeScript/basic-usage.html",{loader:()=>h(()=>import("./basic-usage.html-CEUsK9ry.js"),__vite__mapDeps([48,1])),meta:{a:"qianxun",d:1659718621e3,l:"2022年8月5日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    一,模板中的 TypeScript

    +
    `,r:{minutes:1.4,words:421},y:"a",t:"typeScript中使用axios"}}],["/frontweb/typeScript/basic-usage.html",{loader:()=>h(()=>import("./basic-usage.html-CBqhMzrq.js"),__vite__mapDeps([48,1])),meta:{a:"qianxun",d:1659718621e3,l:"2022年8月5日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    一,模板中的 TypeScript

    在使用了 <script lang="ts"> 或 <script setup lang="ts"> 后,<template> 在绑定表达式中也支持 TypeScript。

    `,r:{minutes:1.94,words:582},y:"a",t:"typeScript在vue项目中常见用法"}}],["/frontweb/typeScript/fanType.html",{loader:()=>h(()=>import("./fanType.html-DHpjy03Z.js"),__vite__mapDeps([49,1])),meta:{a:"qianxun",d:1659718621e3,l:"2022年8月5日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    一,泛型

    +
    `,r:{minutes:1.94,words:582},y:"a",t:"typeScript在vue项目中常见用法"}}],["/frontweb/typeScript/fanType.html",{loader:()=>h(()=>import("./fanType.html-34EbXHhU.js"),__vite__mapDeps([49,1])),meta:{a:"qianxun",d:1659718621e3,l:"2022年8月5日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    一,泛型

    泛型就是通过给类型传参,得到一个更加通用的类型,就像给函数传参一样。 如下我们得到一个通用的泛型类型 T1,通过传参,可以得到 T2 类型 string[]、T3 类型 number[]; T 是变量,我们可以用任意其他变量名代替他。

    `,r:{minutes:2.13,words:640},y:"a",t:"typeScript中的泛型"}}],["/frontweb/typeScript/tsAndvue3.html",{loader:()=>h(()=>import("./tsAndvue3.html-DuJXkf-s.js"),__vite__mapDeps([50,1])),meta:{a:"qianxun",d:1659718621e3,l:"2022年8月5日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    ts在表单中的应用

    +
    `,r:{minutes:2.13,words:640},y:"a",t:"typeScript中的泛型"}}],["/frontweb/typeScript/tsAndvue3.html",{loader:()=>h(()=>import("./tsAndvue3.html-8xWV3_Wf.js"),__vite__mapDeps([50,1])),meta:{a:"qianxun",d:1659718621e3,l:"2022年8月5日",c:["vue知识点"],g:["必会","vue中的 TypeScript"],e:`

    ts在表单中的应用

    `,r:{minutes:1.81,words:543},y:"a",t:"typeScript在vue3中的实战"}}],["/frontweb/vite/",{loader:()=>h(()=>import("./index.html-CHghYPfg.js"),__vite__mapDeps([51,1])),meta:{a:"qianxun",d:16137792e5,l:"2021年2月20日",c:["vite"],g:["vite"],r:{minutes:.05,words:14},y:"a",t:"Vite"}}],["/frontweb/vue/",{loader:()=>h(()=>import("./index.html-CToWg7cr.js"),__vite__mapDeps([52,1])),meta:{a:"Zxf",d:164808e7,l:"2022年3月24日",g:["你所不了解的JavaScript"],r:{minutes:.05,words:16},y:"a",t:"Vue"}}],["/frontweb/vue/elementui%E8%A1%A8%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C.html",{loader:()=>h(()=>import("./elementui表单自定义校验.html-Bl7vnRpA.js"),__vite__mapDeps([53,1])),meta:{a:"chensino",d:17199648e5,l:"2024年7月3日",o:!0,e:`

    常规

    +
    `,r:{minutes:1.81,words:543},y:"a",t:"typeScript在vue3中的实战"}}],["/frontweb/vite/",{loader:()=>h(()=>import("./index.html-DAGR5a1Q.js"),__vite__mapDeps([51,1])),meta:{a:"qianxun",d:16137792e5,l:"2021年2月20日",c:["vite"],g:["vite"],r:{minutes:.05,words:14},y:"a",t:"Vite"}}],["/frontweb/vue/",{loader:()=>h(()=>import("./index.html-DHWvYkOD.js"),__vite__mapDeps([52,1])),meta:{a:"Zxf",d:164808e7,l:"2022年3月24日",g:["你所不了解的JavaScript"],r:{minutes:.05,words:16},y:"a",t:"Vue"}}],["/frontweb/vue/elementui%E8%A1%A8%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C.html",{loader:()=>h(()=>import("./elementui表单自定义校验.html-BDHhWiHk.js"),__vite__mapDeps([53,1])),meta:{a:"chensino",d:17199648e5,l:"2024年7月3日",o:!0,e:`

    常规

    `,r:{minutes:1.18,words:354},y:"a",t:"ElementUI表单自定义校验"}}],["/frontweb/vue/eventBus.html",{loader:()=>h(()=>import("./eventBus.html-DxETLIkG.js"),__vite__mapDeps([54,1])),meta:{a:"qianxun",d:1646845021e3,l:"2022年3月9日",c:["vue知识点"],g:["必会"],e:`

    1,安装mitt库

    +
    `,r:{minutes:1.18,words:354},y:"a",t:"ElementUI表单自定义校验"}}],["/frontweb/vue/eventBus.html",{loader:()=>h(()=>import("./eventBus.html-B0dTFe1w.js"),__vite__mapDeps([54,1])),meta:{a:"qianxun",d:1646845021e3,l:"2022年3月9日",c:["vue知识点"],g:["必会"],e:`

    1,安装mitt库

    npm install mitt
     

    2,在main.ts中引入mitt库

    import mitt from 'mitt'
     app.config.globalProperties.mittBus = mitt()
    -
    `,r:{minutes:.45,words:136},y:"a",t:"事件总线Mitt"}}],["/frontweb/vue/vue-Direactive.html",{loader:()=>h(()=>import("./vue-Direactive.html-CrUvqxXL.js"),__vite__mapDeps([55,1])),meta:{a:"qianxun",d:16576704e5,l:"2022年7月13日",c:["vue知识点"],g:["必会"],e:` +
    `,r:{minutes:.45,words:136},y:"a",t:"事件总线Mitt"}}],["/frontweb/vue/vue-Direactive.html",{loader:()=>h(()=>import("./vue-Direactive.html-CwIH1XaN.js"),__vite__mapDeps([55,1])),meta:{a:"qianxun",d:16576704e5,l:"2022年7月13日",c:["vue知识点"],g:["必会"],e:`

    一,自定义指令介绍

    除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。

    vue中重用代码,3种方式:

    @@ -513,7 +513,7 @@ export default { } <input v-focus /> -
    `,r:{minutes:2.99,words:898},y:"a",t:"vue自定义指令控制按钮级别权限"}}],["/frontweb/vue/vue-authority.html",{loader:()=>h(()=>import("./vue-authority.html-hGxiTJj0.js"),__vite__mapDeps([56,1])),meta:{a:"qianxun",d:16570656e5,l:"2022年7月6日",c:["vue知识点"],g:["必会"],e:`

    前端主要有哪些权限控制?

    +
    `,r:{minutes:2.99,words:898},y:"a",t:"vue自定义指令控制按钮级别权限"}}],["/frontweb/vue/vue-authority.html",{loader:()=>h(()=>import("./vue-authority.html-HH15bglq.js"),__vite__mapDeps([56,1])),meta:{a:"qianxun",d:16570656e5,l:"2022年7月6日",c:["vue知识点"],g:["必会"],e:`

    前端主要有哪些权限控制?

    一,接口访问权限

    接口权限目前一般采用通用的形式来验证(用户是否登录系统),没有的话一般返回401,跳转到登录页面重新进行登录 ,登录成功后拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token

    
    @@ -527,11 +527,11 @@ axios.interceptors}
     })
     
    -
    `,r:{minutes:2.54,words:761},y:"a",t:"vue中权限相关的问题"}}],["/frontweb/vue/vue-in-action.html",{loader:()=>h(()=>import("./vue-in-action.html-6hfWli8S.js"),__vite__mapDeps([57,1])),meta:{a:"qianxun",d:16570656e5,l:"2022年7月6日",c:["vue知识点"],g:["必会"],e:`

    一、创建一个新项目

    +
    `,r:{minutes:2.54,words:761},y:"a",t:"vue中权限相关的问题"}}],["/frontweb/vue/vue-in-action.html",{loader:()=>h(()=>import("./vue-in-action.html-DaPOjPap.js"),__vite__mapDeps([57,1])),meta:{a:"qianxun",d:16570656e5,l:"2022年7月6日",c:["vue知识点"],g:["必会"],e:`

    一、创建一个新项目

    vue create vue2-admin
     
     

    这里搭建好的脚手架是采用vue2+webpack,并且预装了vueRoutervuexscsseslint.

    -

    image-20220809105240533

    `,r:{minutes:1.15,words:346},y:"a",t:"利用vue-cli搭建项目"}}],["/frontweb/vue/vue-nextTick.html",{loader:()=>h(()=>import("./vue-nextTick.html-D03AWrxQ.js"),__vite__mapDeps([58,1])),meta:{a:"qianxun",d:1646784e6,l:"2022年3月9日",c:["vue知识点"],g:["必会"],e:` +

    image-20220809105240533

    `,r:{minutes:1.15,words:346},y:"a",t:"利用vue-cli搭建项目"}}],["/frontweb/vue/vue-nextTick.html",{loader:()=>h(()=>import("./vue-nextTick.html-BAWrVlUH.js"),__vite__mapDeps([58,1])),meta:{a:"qianxun",d:1646784e6,l:"2022年3月9日",c:["vue知识点"],g:["必会"],e:`

    一,什么是nextTick

    定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

    @@ -539,12 +539,12 @@ axios.interceptorsh(()=>import("./vue-pic.html-CpOJwwqn.js"),__vite__mapDeps([59,1])),meta:{a:"qianxun",d:1646845021e3,l:"2022年3月9日",c:["vue知识点"],g:["必会"],e:`

    一,在vue中静态导入相对路径

    +

    理解:nextTick(),是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:当你通过数据更新了页面后,想获 取更新后的DOM,就需要使用到nextTick.

    `,r:{minutes:2.82,words:846},y:"a",t:"关于vue-nextTick"}}],["/frontweb/vue/vue-pic.html",{loader:()=>h(()=>import("./vue-pic.html-BGsMqmKX.js"),__vite__mapDeps([59,1])),meta:{a:"qianxun",d:1646845021e3,l:"2022年3月9日",c:["vue知识点"],g:["必会"],e:`

    一,在vue中静态导入相对路径

    <img src="../../assets/1.png" />
     <!-- 或者如下 -->
     <img src="@/assets/1.png" />
     
    -
    `,r:{minutes:1.47,words:441},y:"a",t:"vue图片路径问题"}}],["/frontweb/vue/vue-router1.html",{loader:()=>h(()=>import("./vue-router1.html-DrI04ly7.js"),__vite__mapDeps([60,1])),meta:{a:"qianxun",d:16651872e5,l:"2022年10月8日",c:["vue知识点"],g:["必会"],e:` +
    `,r:{minutes:1.47,words:441},y:"a",t:"vue图片路径问题"}}],["/frontweb/vue/vue-router1.html",{loader:()=>h(()=>import("./vue-router1.html-DZlcHgjO.js"),__vite__mapDeps([60,1])),meta:{a:"qianxun",d:16651872e5,l:"2022年10月8日",c:["vue知识点"],g:["必会"],e:`

    一,安装

    npm install vue-router@4
     

    二,基本用法

    @@ -575,7 +575,7 @@ axios.interceptorsexport default router -`,r:{minutes:1.34,words:402},y:"a",t:"vue-router4.0的基本使用"}}],["/frontweb/vue/vue-router2.html",{loader:()=>h(()=>import("./vue-router2.html-C4QGqnFR.js"),__vite__mapDeps([61,1])),meta:{a:"qianxun",d:16651872e5,l:"2022年10月8日",c:["vue知识点"],g:["必会"],e:"\n

    一, 路由的本质

    \n

    简单来说,浏览器端路由其实并不是真实的网页跳转(和服务器没有任何交互),而是纯粹在浏览器端发生的一系列行为,本质上来说前端路由就是:

    \n

    对 url 进行改变和监听,来让某个 dom 节点显示对应的视图

    \n

    二, 路由的区别

    \n

    一般来说,浏览器端的路由分为两种:

    \n
    1. hash 路由,特征是` url` 后面会有 # 号,如` baidu.com/#foo/bar/baz`。\n2. history路由,`url` 和普通路径没有差异。如 `baidu.com/foo/bar/baz`。\n
    ",r:{minutes:13.85,words:4154},y:"a",t:"vue-router源码浅析"}}],["/frontweb/vue/vue3Emit.html",{loader:()=>h(()=>import("./vue3Emit.html-DJy1ivmL.js"),__vite__mapDeps([62,1])),meta:{d:16812576e5,l:"2023年4月12日",g:["ts","vue3"],o:!0,e:`

    vue3 使用组合式api时如何进行父子组件通信

    +`,r:{minutes:1.34,words:402},y:"a",t:"vue-router4.0的基本使用"}}],["/frontweb/vue/vue-router2.html",{loader:()=>h(()=>import("./vue-router2.html-DNXG8jNK.js"),__vite__mapDeps([61,1])),meta:{a:"qianxun",d:16651872e5,l:"2022年10月8日",c:["vue知识点"],g:["必会"],e:"\n

    一, 路由的本质

    \n

    简单来说,浏览器端路由其实并不是真实的网页跳转(和服务器没有任何交互),而是纯粹在浏览器端发生的一系列行为,本质上来说前端路由就是:

    \n

    对 url 进行改变和监听,来让某个 dom 节点显示对应的视图

    \n

    二, 路由的区别

    \n

    一般来说,浏览器端的路由分为两种:

    \n
    1. hash 路由,特征是` url` 后面会有 # 号,如` baidu.com/#foo/bar/baz`。\n2. history路由,`url` 和普通路径没有差异。如 `baidu.com/foo/bar/baz`。\n
    ",r:{minutes:13.85,words:4154},y:"a",t:"vue-router源码浅析"}}],["/frontweb/vue/vue3Emit.html",{loader:()=>h(()=>import("./vue3Emit.html-C3GB9oZp.js"),__vite__mapDeps([62,1])),meta:{d:16812576e5,l:"2023年4月12日",g:["ts","vue3"],o:!0,e:`

    vue3 使用组合式api时如何进行父子组件通信

    // 1.在vue组件中定义事件
     const emit = defineEmits(['update:hospitalDept'])
     //2. 调用userXXXControl时,把emit当做参数传递进去,useCreateHospitalDeptControl(emit)实际也就类似一个函数
    @@ -607,7 +607,7 @@ axios.interceptors="createHospitalDeptRef"
           @update:hospital-dept="updateHospitalDeptList"
         />
    -
    `,r:{minutes:.55,words:164},y:"a",t:"vue3使用emit进行父子组件传值"}}],["/frontweb/vue/vue3LifeTime.html",{loader:()=>h(()=>import("./vue3LifeTime.html-CgbHBDaz.js"),__vite__mapDeps([63,1])),meta:{a:"qianxun",d:16570656e5,l:"2022年7月6日",c:["vue知识点"],g:["必会"],e:`

    一,vue生命周期钩子函数

    +`,r:{minutes:.55,words:164},y:"a",t:"vue3使用emit进行父子组件传值"}}],["/frontweb/vue/vue3LifeTime.html",{loader:()=>h(()=>import("./vue3LifeTime.html-L4zQOdiA.js"),__vite__mapDeps([63,1])),meta:{a:"qianxun",d:16570656e5,l:"2022年7月6日",c:["vue知识点"],g:["必会"],e:`

    一,vue生命周期钩子函数

    d0c8a786c9177f3e668177cd4bfcf9c19e3d5676

    1.1 beforeCreate

    `,r:{minutes:5.35,words:1604},y:"a",t:"vue中组件的生命周期"}}],["/frontweb/vue/vueExtend.html",{loader:()=>h(()=>import("./vueExtend.html-DnRRc5Za.js"),__vite__mapDeps([64,1])),meta:{a:"qianxun",d:16570656e5,l:"2022年7月6日",c:["vue知识点"],g:["必会"],e:`

    一,vue.extend基本概念和用法

    使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

    data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数。

    <div id="mount-point"></div>
    -
    `,r:{minutes:2.69,words:807},y:"a",t:"利用Vue.extend定义全局组件"}}],["/frontweb/vue/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4%E7%9A%84%E5%AE%9E%E8%B7%B5.html",{loader:()=>h(()=>import("./自定义指令的实践.html-C1dSRMpn.js"),__vite__mapDeps([65,1])),meta:{a:"chensino",d:17201376e5,l:"2024年7月5日",o:!0,e:`

    需求

    +`,r:{minutes:2.69,words:807},y:"a",t:"利用Vue.extend定义全局组件"}}],["/frontweb/vue/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4%E7%9A%84%E5%AE%9E%E8%B7%B5.html",{loader:()=>h(()=>import("./自定义指令的实践.html-dDt_1gUV.js"),__vite__mapDeps([65,1])),meta:{a:"chensino",d:17201376e5,l:"2024年7月5日",o:!0,e:`

    需求

    需求是: 在前端页面有很多文本,要求提取文本,根据规则判断文本是否和规则互斥,比如“女”和“前列腺”就是互斥的。当互斥时,需要高亮,同时当鼠标 指在文本上,弹出提示框,提示“女和前列腺规则互斥” 在页面中,通过自定义指令,实现对文本的局部高亮,其中高亮内容是通过接口动态获取。

    @@ -683,7 +683,7 @@ axios.interceptors} }, } -`,r:{minutes:1.94,words:581},y:"a",t:"使用自定义指令实现高亮"}}],["/java/advance/Arthas.html",{loader:()=>h(()=>import("./Arthas.html-D1k7ux4i.js"),__vite__mapDeps([66,1])),meta:{a:"chenkun",d:1651104e6,l:"2022年4月28日",c:["问题定位"],e:` +`,r:{minutes:1.94,words:581},y:"a",t:"使用自定义指令实现高亮"}}],["/java/advance/Arthas.html",{loader:()=>h(()=>import("./Arthas.html-BDHUU6lY.js"),__vite__mapDeps([66,1])),meta:{a:"chenkun",d:1651104e6,l:"2022年4月28日",c:["问题定位"],e:`

    背景:有一个导出PDF的功能,在本地运行正常,在线上异常,抛出的异常无法直接定位到问题。

    @@ -701,13 +701,13 @@ Found existing java process, please choose o [2]: 47802 ccs-data-biz.jar 2 -`,r:{minutes:1.04,words:313},y:"a",t:"使用Arthas定位线上问题"}}],["/java/advance/Collection.html",{loader:()=>h(()=>import("./Collection.html-CeAHa-2T.js"),__vite__mapDeps([67,1])),meta:{d:15537312e5,l:"2019年3月28日",c:["集合"],g:["集合"],e:`

    分析集合数据结构,HashMap、ArrayList、LinkedList扩容原理等

    +`,r:{minutes:1.04,words:313},y:"a",t:"使用Arthas定位线上问题"}}],["/java/advance/Collection.html",{loader:()=>h(()=>import("./Collection.html-D-nzOS-a.js"),__vite__mapDeps([67,1])),meta:{d:15537312e5,l:"2019年3月28日",c:["集合"],g:["集合"],e:`

    分析集合数据结构,HashMap、ArrayList、LinkedList扩容原理等

    1、Collection

    -`,r:{minutes:.12,words:36},y:"a",t:"java集合"}}],["/java/advance/CompileJdk11.html",{loader:()=>h(()=>import("./CompileJdk11.html-DQ_9XJev.js"),__vite__mapDeps([68,1])),meta:{a:"chenkun",d:1673568e6,l:"2023年1月13日",c:["jdk"],e:` +`,r:{minutes:.12,words:36},y:"a",t:"java集合"}}],["/java/advance/CompileJdk11.html",{loader:()=>h(()=>import("./CompileJdk11.html-k8rk73iW.js"),__vite__mapDeps([68,1])),meta:{a:"chenkun",d:1673568e6,l:"2023年1月13日",c:["jdk"],e:`

    1 下载源码

    网上根据关键词查找jdk源码,查找出来很多可以下载源码的链接,这里我们使用github去官方仓库,openjdk是托管在github的OpenJDK组织下,该组织下有各个版本的openjdk源码,不要直接使用jdk仓库,这个仓库存放的是当前正在开发的最新版本代码,我们要用的是jdk11,因此我们搜索jdk11仓库,我这里选择的是jdk11u这个库。

    -

    20230113095229

    `,r:{minutes:4.54,words:1363},y:"a",t:"在Manjaro中编译JDK11"}}],["/java/advance/Concurrent.html",{loader:()=>h(()=>import("./Concurrent.html-ClwuWlCD.js"),__vite__mapDeps([69,1])),meta:{d:1676592e6,l:"2023年2月17日",g:"-- 并发",o:!0,e:`

    1、背景

    +

    20230113095229

    `,r:{minutes:4.54,words:1363},y:"a",t:"在Manjaro中编译JDK11"}}],["/java/advance/Concurrent.html",{loader:()=>h(()=>import("./Concurrent.html-DQRS-IpH.js"),__vite__mapDeps([69,1])),meta:{d:1676592e6,l:"2023年2月17日",g:"-- 并发",o:!0,e:`

    1、背景

    因没做过大项目,并发经验匮乏,所以很多时候考虑不到并发问题,今天做接口的压力测试,无意间发现一个并发的问题,就是判断数据库是否存在某个记录时,如果没做并发处理,就会出现并发的问题,比如以下代码,给某个医院添加科室,当医院已经有这个科室则不允许重复添加重名的科室

    public void addHospitalDept(SysHospitalDept sysHospitalDept) {
             //根据医院id,科室名,校验科室唯一性
    @@ -721,27 +721,27 @@ Found existing java process, please choose o
     		sysHospitalDept.setCreateTime(LocalDateTime.now());
     		this.save(sysHospitalDept);
     	}
    -
    `,r:{minutes:1.57,words:471},y:"a",t:"并发问题"}}],["/java/advance/Future.html",{loader:()=>h(()=>import("./Future.html-C-IGHrDE.js"),__vite__mapDeps([70,1])),meta:{a:"chenkun",d:16492032e5,l:"2022年4月6日",c:["线程池","多线程"],g:["多线程","线程池"],e:` +`,r:{minutes:1.57,words:471},y:"a",t:"并发问题"}}],["/java/advance/Future.html",{loader:()=>h(()=>import("./Future.html-CI2mkHT2.js"),__vite__mapDeps([70,1])),meta:{a:"chenkun",d:16492032e5,l:"2022年4月6日",c:["线程池","多线程"],g:["多线程","线程池"],e:`

    1、Future的作用

    Future可以用来获取一个异步执行的结果,可以使用isDone方法检查异步任务是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。

    -`,r:{minutes:.36,words:107},y:"a",t:"多线程中的Future"}}],["/java/advance/IO-Model.html",{loader:()=>h(()=>import("./IO-Model.html-BPdWwFrU.js"),__vite__mapDeps([71,1])),meta:{a:"勤劳的小手",d:16720992e5,l:"2022年12月27日",e:`

    本文是转载于知乎100%弄明白5种IO模型,原作者勤劳的小手,在原博客基础上整理了其他 +`,r:{minutes:.36,words:107},y:"a",t:"多线程中的Future"}}],["/java/advance/IO-Model.html",{loader:()=>h(()=>import("./IO-Model.html-O-6ElLZv.js"),__vite__mapDeps([71,1])),meta:{a:"勤劳的小手",d:16720992e5,l:"2022年12月27日",e:`

    本文是转载于知乎100%弄明白5种IO模型,原作者勤劳的小手,在原博客基础上整理了其他 与IO模型相关的优质内容。

    1、从TCP发送数据的流程说起

    所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。
     
     需要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在”干活”,而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。
    -
    `,r:{minutes:22.98,words:6893},y:"a",t:"100%搞懂5中I/O模型"}}],["/java/advance/IO-model1.html",{loader:()=>h(()=>import("./IO-model1.html-DbFAWkDL.js"),__vite__mapDeps([72,1])),meta:{d:16759008e5,l:"2023年2月9日",e:`

    1 BIO

    +`,r:{minutes:22.98,words:6893},y:"a",t:"100%搞懂5中I/O模型"}}],["/java/advance/IO-model1.html",{loader:()=>h(()=>import("./IO-model1.html-Bko-tL29.js"),__vite__mapDeps([72,1])),meta:{d:16759008e5,l:"2023年2月9日",e:`

    1 BIO

    当给BIO的accept和read方法加上超时机制后,可以在代码层面解决阻塞问题,但这不是真正的非阻塞,通常我们说的非阻塞是指的操作系统层面的非阻塞,就是当accept通过jni调用native方法后,最终系统不会一直被阻塞。真正的非阻塞是操作系统增加非阻塞功能后,java同步添加java.nio出现以后才有的,因此通过java实现非阻塞,需要调用nio中的类。

    2 NIO

    3 IO多路复用

    4 异步IO

    5 事件驱动的io

    6 reactor线程模型

    -

    reactor线程模型可参考Scalable IO in java,该书作者也是java.nio的作者

    `,r:{minutes:2.64,words:791},y:"a",t:"I/O模型总结"}}],["/java/advance/ImplementSameInterface.html",{loader:()=>h(()=>import("./ImplementSameInterface.html-8aOTv4So.js"),__vite__mapDeps([73,1])),meta:{d:1672704e6,l:"2023年1月3日",g:["oauth","sso"],e:`

    1、背景

    +

    reactor线程模型可参考Scalable IO in java,该书作者也是java.nio的作者

    `,r:{minutes:2.64,words:791},y:"a",t:"I/O模型总结"}}],["/java/advance/ImplementSameInterface.html",{loader:()=>h(()=>import("./ImplementSameInterface.html-iv1dGOUt.js"),__vite__mapDeps([73,1])),meta:{d:1672704e6,l:"2023年1月3日",g:["oauth","sso"],e:`

    1、背景

    今天看Securfity的源码,其中org.springframework.security.config.annotation.web.builders.HttpSecurity类的UML看着很奇怪,如下图所示,命名其父类和父接口都实现过SecurityBuilder,为什么自己要再次实现呢?

    20230105160622

    2、探索

    -`,r:{minutes:1.69,words:506},y:"a",t:"子类和父类(或者父接口)实现同一个接口"}}],["/java/advance/MysqlMasterSlave.html",{loader:()=>h(()=>import("./MysqlMasterSlave.html-CfozUk_D.js"),__vite__mapDeps([74,1])),meta:{d:15661728e5,l:"2019年8月19日",c:["java"],g:["部署搭建"],e:`

    1、mysql主从复制

    +`,r:{minutes:1.69,words:506},y:"a",t:"子类和父类(或者父接口)实现同一个接口"}}],["/java/advance/MysqlMasterSlave.html",{loader:()=>h(()=>import("./MysqlMasterSlave.html-CPoF9taw.js"),__vite__mapDeps([74,1])),meta:{d:15661728e5,l:"2019年8月19日",c:["java"],g:["部署搭建"],e:`

    1、mysql主从复制

    1.1 搭建主从复制目的?

    为了实现读写分离,解决数据库性能问题,读写分离中,“读”的数据是从哪里来呢?其实他是从“写”库copy过来的

    @@ -851,7 +851,7 @@ mysql> change master to h(()=>import("./NativeMethod.html-BICH8rB4.js"),__vite__mapDeps([75,1])),meta:{d:16733952e5,l:"2023年1月11日",e:`

    使用c++实现一个native方法供java调用

    +`,r:{minutes:6.67,words:2e3},y:"a",t:"mysql8搭建主从复制"}}],["/java/advance/NativeMethod.html",{loader:()=>h(()=>import("./NativeMethod.html-CW2rOE3c.js"),__vite__mapDeps([75,1])),meta:{d:16733952e5,l:"2023年1月11日",e:`

    使用c++实现一个native方法供java调用

    环境

    @@ -861,7 +861,7 @@ mysql> change master to
    用c++实现一个动态链接库,使用java调用链接库中的方法
    -
    `,r:{minutes:2.44,words:733},y:"a",t:"自定义native方法"}}],["/java/advance/ParentDelegationClassLoader.html",{loader:()=>h(()=>import("./ParentDelegationClassLoader.html-lWjNd7F6.js"),__vite__mapDeps([76,1])),meta:{a:"chenkun",d:16485984e5,l:"2022年3月30日",u:1,e:`

    为什么说spi打破了双亲委派机制?

    +`,r:{minutes:2.44,words:733},y:"a",t:"自定义native方法"}}],["/java/advance/ParentDelegationClassLoader.html",{loader:()=>h(()=>import("./ParentDelegationClassLoader.html-BE03CURY.js"),__vite__mapDeps([76,1])),meta:{a:"chenkun",d:16485984e5,l:"2022年3月30日",u:1,e:`

    为什么说spi打破了双亲委派机制?

    1、什么是双亲委派?

    image-20220330170731913

    @@ -919,9 +919,9 @@ ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系 类,将会发现它可以正常编译,但永远无法被加载运行[2]。 ​双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现却异常简单,用以实现双亲委 派的代码只有短短十余行,全部集中在java.lang.ClassLoader的loadClass()方法之中。

    -
    `,r:{minutes:12.88,words:3864},y:"a",t:"证明SPI打破双亲委派模式"}}],["/java/advance/ProxyInJava.html",{loader:()=>h(()=>import("./ProxyInJava.html-C0ibZtz7.js"),__vite__mapDeps([77,1])),meta:{d:1647216e6,l:"2022年3月14日",c:["java"],u:1,e:`

    是兄弟就来看我的博客

    -`,r:{minutes:8.46,words:2539},y:"a",t:"彻底理清Java中的几种代理"}}],["/java/advance/",{loader:()=>h(()=>import("./index.html-DaPOtDhI.js"),__vite__mapDeps([78,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.03,words:8},y:"a",t:"Java进阶"}}],["/java/advance/Synchronized.html",{loader:()=>h(()=>import("./Synchronized.html-Dhr9EviB.js"),__vite__mapDeps([79,1])),meta:{d:16751232e5,l:"2023年1月31日",c:["对象锁"],e:`

    1、

    -`,r:{minutes:.05,words:15},y:"a",t:"synchronized实现 原理"}}],["/java/advance/ThreadLocal.html",{loader:()=>h(()=>import("./ThreadLocal.html-CHq5iQUn.js"),__vite__mapDeps([80,1])),meta:{a:"chenkun",d:16590528e5,l:"2022年7月29日",e:`

    1、介绍

    +`,r:{minutes:12.88,words:3864},y:"a",t:"证明SPI打破双亲委派模式"}}],["/java/advance/ProxyInJava.html",{loader:()=>h(()=>import("./ProxyInJava.html-Craly_CB.js"),__vite__mapDeps([77,1])),meta:{d:1647216e6,l:"2022年3月14日",c:["java"],u:1,e:`

    是兄弟就来看我的博客

    +`,r:{minutes:8.46,words:2539},y:"a",t:"彻底理清Java中的几种代理"}}],["/java/advance/",{loader:()=>h(()=>import("./index.html-BLiK3lZA.js"),__vite__mapDeps([78,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.03,words:8},y:"a",t:"Java进阶"}}],["/java/advance/Synchronized.html",{loader:()=>h(()=>import("./Synchronized.html-Bz2_U7rD.js"),__vite__mapDeps([79,1])),meta:{d:16751232e5,l:"2023年1月31日",c:["对象锁"],e:`

    1、

    +`,r:{minutes:.05,words:15},y:"a",t:"synchronized实现 原理"}}],["/java/advance/ThreadLocal.html",{loader:()=>h(()=>import("./ThreadLocal.html-YdsPoeQ1.js"),__vite__mapDeps([80,1])),meta:{a:"chenkun",d:16590528e5,l:"2022年7月29日",e:`

    1、介绍

    2、使用

    3、 使用反射在当前线程获取所有的ThreadLocal

    @@ -996,7 +996,7 @@ ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系 } } } -`,r:{minutes:.77,words:232},y:"a",t:"ThreadLocal"}}],["/java/advance/ThreadPool.html",{loader:()=>h(()=>import("./ThreadPool.html-B4bZt9rf.js"),__vite__mapDeps([81,1])),meta:{a:"chenkun",d:16484256e5,l:"2022年3月28日",c:["线程池","多线程"],g:["多线程","线程池"],e:`

    线程池

    +`,r:{minutes:.77,words:232},y:"a",t:"ThreadLocal"}}],["/java/advance/ThreadPool.html",{loader:()=>h(()=>import("./ThreadPool.html-D9vbBMnV.js"),__vite__mapDeps([81,1])),meta:{a:"chenkun",d:16484256e5,l:"2022年3月28日",c:["线程池","多线程"],g:["多线程","线程池"],e:`

    线程池

    前言:

    @@ -1016,7 +1016,7 @@ ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系 Thread.sleep(2000000); } -`,r:{minutes:11.83,words:3548},y:"a",t:"线程池总结"}}],["/java/advance/io.html",{loader:()=>h(()=>import("./io.html-DYLJUoUT.js"),__vite__mapDeps([82,1])),meta:{d:16598304e5,l:"2022年8月7日",e:`

    1、参考

    +`,r:{minutes:11.83,words:3548},y:"a",t:"线程池总结"}}],["/java/advance/io.html",{loader:()=>h(()=>import("./io.html-C1zkJgLS.js"),__vite__mapDeps([82,1])),meta:{d:16598304e5,l:"2022年8月7日",e:`

    1、参考

    `,r:{minutes:5.79,words:1737},y:"a",t:"I/O模型"}}],["/java/base/ConstantPool.html",{loader:()=>h(()=>import("./ConstantPool.html-BlmpO7p_.js"),__vite__mapDeps([83,1])),meta:{d:1646784e6,l:"2022年3月9日",c:["java基础"],g:["必会"],e:`

    1. Integer常量池默认的范围

    +`,r:{minutes:5.79,words:1737},y:"a",t:"I/O模型"}}],["/java/base/ConstantPool.html",{loader:()=>h(()=>import("./ConstantPool.html-C5zSxd7l.js"),__vite__mapDeps([83,1])),meta:{d:1646784e6,l:"2022年3月9日",c:["java基础"],g:["必会"],e:`

    1. Integer常量池默认的范围

    范围:[-128,127],Integer内部有个缓存池,最小值-128是固定的,最大的值127是可以调整的,看源码知道, 最大值是和integerCacheHighPropValue有关,这个值是可以通过~java.lang.Integer.IntegerCache.high~ 属性指定,实际测试~~~System.setProperty("java.lang.Integer.IntegerCache.high","300")~~~不生效, 因为他是jvm参数应该在启动时设置vm参数,-Djava.lang.Integer.IntegerCache.high=300 -使用-XX:AutoBoxCacheMax=300也可以。

    `,r:{minutes:.99,words:298},y:"a",t:"ConstantPool"}}],["/java/base/CustomLRU.html",{loader:()=>h(()=>import("./CustomLRU.html-CX9zw1r2.js"),__vite__mapDeps([84,1])),meta:{d:16479072e5,l:"2022年3月22日",c:["java基础"],g:["必会"],e:` +使用-XX:AutoBoxCacheMax=300也可以。

    `,r:{minutes:.99,words:298},y:"a",t:"ConstantPool"}}],["/java/base/CustomLRU.html",{loader:()=>h(()=>import("./CustomLRU.html-DHBi8rnr.js"),__vite__mapDeps([84,1])),meta:{d:16479072e5,l:"2022年3月22日",c:["java基础"],g:["必会"],e:`

    LRU介绍

    lru(latest recently used)最近最少使用,在缓存中可以使用LRU算法移除最近最少使用的

    自定义lru算法

    @@ -1058,7 +1058,7 @@ ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系 } } -`,r:{minutes:1.42,words:426},y:"a",t:"自定义LRU实现"}}],["/java/base/IntegerConstantPool.html",{loader:()=>h(()=>import("./IntegerConstantPool.html-BgqTmJIN.js"),__vite__mapDeps([85,1])),meta:{d:15652224e5,l:"2019年8月8日",c:["java基础"],e:`

    1. Integer常量池默认的范围

    +`,r:{minutes:1.42,words:426},y:"a",t:"自定义LRU实现"}}],["/java/base/IntegerConstantPool.html",{loader:()=>h(()=>import("./IntegerConstantPool.html-B-ijklmd.js"),__vite__mapDeps([85,1])),meta:{d:15652224e5,l:"2019年8月8日",c:["java基础"],e:`

    1. Integer常量池默认的范围

    范围:[-128,127],Integer内部有个缓存池,最小值-128是固定的,最大的值127是可以调整的,看源码知道,最大值是和integerCacheHighPropValue有关,这个值是可以通过~java.lang.Integer.IntegerCache.high~属性指定,实际测试~~~System.setProperty("java.lang.Integer.IntegerCache.high","300")~~~不生效,使用-XX:AutoBoxCacheMax=300可以。

    private static class IntegerCache {
             static final int low = -128;
    @@ -1093,7 +1093,7 @@ ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系
     
             private IntegerCache() {}
         }
    -
    `,r:{minutes:.92,words:277},y:"a",t:"Integer常量池"}}],["/java/base/",{loader:()=>h(()=>import("./index.html-hsfyfYXC.js"),__vite__mapDeps([86,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.03,words:8},y:"a",t:"Java基础"}}],["/java/base/Serialization.html",{loader:()=>h(()=>import("./Serialization.html-BPyqpG-l.js"),__vite__mapDeps([87,1])),meta:{a:"ChenSino",d:15652224e5,l:"2019年8月8日",c:["java基础"],e:`

    1、序列化、反序列化是什么?

    +`,r:{minutes:.92,words:277},y:"a",t:"Integer常量池"}}],["/java/base/",{loader:()=>h(()=>import("./index.html-yLnx4YcF.js"),__vite__mapDeps([86,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.03,words:8},y:"a",t:"Java基础"}}],["/java/base/Serialization.html",{loader:()=>h(()=>import("./Serialization.html-BJVNALhI.js"),__vite__mapDeps([87,1])),meta:{a:"ChenSino",d:15652224e5,l:"2019年8月8日",c:["java基础"],e:`

    1、序列化、反序列化是什么?

    `,r:{minutes:3.98,words:1195},y:"a",t:"给对象设置null的意义"}}],["/java/jvm/StringAdd.html",{loader:()=>h(()=>import("./StringAdd.html-BihzcLHW.js"),__vite__mapDeps([95,1])),meta:{d:1647216e6,l:"2022年3月14日",c:["java","jvm"],g:["字节码","反汇编"],u:2,e:`

    1、先看问题,以下结果是什么?

    +`,r:{minutes:3.98,words:1195},y:"a",t:"给对象设置null的意义"}}],["/java/jvm/StringAdd.html",{loader:()=>h(()=>import("./StringAdd.html-fGViknMr.js"),__vite__mapDeps([95,1])),meta:{d:1647216e6,l:"2022年3月14日",c:["java","jvm"],g:["字节码","反汇编"],u:2,e:`

    1、先看问题,以下结果是什么?

    String s1 = "Hello";
     String s2 = "Hello";
     String s3 = "Hel" + "lo";
    @@ -1342,7 +1342,7 @@ SourceFile: "JavaBase.java"
     System.out.println(s1 == s9);  
     System.out.println(s4 == s5); 
     System.out.println(s1 == s6); 
    -
    `,r:{minutes:3.06,words:917},y:"a",t:"通过反汇编来看String的拼接"}}],["/java/jvm/volatile.html",{loader:()=>h(()=>import("./volatile.html-ChArXn5X.js"),__vite__mapDeps([96,1])),meta:{d:16752096e5,l:"2023年2月1日",e:`

    1、双重检查的单例模式是否真的线程安全?

    +`,r:{minutes:3.06,words:917},y:"a",t:"通过反汇编来看String的拼接"}}],["/java/jvm/volatile.html",{loader:()=>h(()=>import("./volatile.html-B-u5KWIK.js"),__vite__mapDeps([96,1])),meta:{d:16752096e5,l:"2023年2月1日",e:`

    1、双重检查的单例模式是否真的线程安全?

    public class Single{
         public static Single single;
     
    @@ -1361,7 +1361,7 @@ SourceFile: "JavaBase.java"
             return single;
         }
     }
    -
    `,r:{minutes:4.63,words:1390},y:"a",t:"volatile关键字"}}],["/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html",{loader:()=>h(()=>import("./名词解释.html-D6DWrpIk.js"),__vite__mapDeps([97,1])),meta:{a:"chenkun",d:1724891306e3,e:`

    vm中说的字面量和符号引用是什么

    +`,r:{minutes:4.63,words:1390},y:"a",t:"volatile关键字"}}],["/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html",{loader:()=>h(()=>import("./名词解释.html-B5GJmRJ1.js"),__vite__mapDeps([97,1])),meta:{a:"chenkun",d:1724891306e3,e:`

    vm中说的字面量和符号引用是什么

    在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。

    字面量(Literal)

    字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。

    @@ -1370,7 +1370,7 @@ SourceFile: "JavaBase.java"
  • 浮点数字面量: 3.14, -0.001
  • 字符字面量: 'A', 'z'
  • 字符串字面量: "Hello, World!", "Java"
  • -`,r:{minutes:1.67,words:502},y:"a",t:"名词解释"}}],["/java/other/JdkVersion.html",{loader:()=>h(()=>import("./JdkVersion.html-BvKsTRiv.js"),__vite__mapDeps([98,1])),meta:{a:"chenkun",d:16606944e5,l:"2022年8月17日",e:`
    +`,r:{minutes:1.67,words:502},y:"a",t:"名词解释"}}],["/java/other/JdkVersion.html",{loader:()=>h(()=>import("./JdkVersion.html-CS9P09_u.js"),__vite__mapDeps([98,1])),meta:{a:"chenkun",d:16606944e5,l:"2022年8月17日",e:`

    开发者注意

    Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。

    @@ -1405,16 +1405,16 @@ List、Set、Map 接口中,提供新的静态工厂方法直接创建不可变

    引入响应式流 API Java 9 引入了新的响应式流 API。

    -`,r:{minutes:1.54,words:463},y:"a",t:"Jdk版本"}}],["/java/other/",{loader:()=>h(()=>import("./index.html-DAKYBthR.js"),__vite__mapDeps([99,1])),meta:{a:"chenkun",d:16599168e5,l:"2022年8月8日",e:`

    其他分类

    -`,r:{minutes:.04,words:13},y:"a",t:"其他"}}],["/other/books/",{loader:()=>h(()=>import("./index.html-C3kmdsDq.js"),__vite__mapDeps([100,1])),meta:{d:16592256e5,l:"2022年7月31日",e:`

    目录

    +`,r:{minutes:1.54,words:463},y:"a",t:"Jdk版本"}}],["/java/other/",{loader:()=>h(()=>import("./index.html-Cjv6EKnU.js"),__vite__mapDeps([99,1])),meta:{a:"chenkun",d:16599168e5,l:"2022年8月8日",e:`

    其他分类

    +`,r:{minutes:.04,words:13},y:"a",t:"其他"}}],["/other/books/",{loader:()=>h(()=>import("./index.html-DDdnWKF-.js"),__vite__mapDeps([100,1])),meta:{d:16592256e5,l:"2022年7月31日",e:`

    目录

    -`,r:{minutes:.07,words:20},y:"a",t:"电子书资源"}}],["/other/books/ebooks.html",{loader:()=>h(()=>import("./ebooks.html-D9FixpLr.js"),__vite__mapDeps([101,1])),meta:{d:16587072e5,l:"2022年7月25日",c:["电子书"],e:`

    1、springboot

    +`,r:{minutes:.07,words:20},y:"a",t:"电子书资源"}}],["/other/books/ebooks.html",{loader:()=>h(()=>import("./ebooks.html-BgNEqnIF.js"),__vite__mapDeps([101,1])),meta:{d:16587072e5,l:"2022年7月25日",c:["电子书"],e:`

    1、springboot

    SpringBoot基本原理-黑马程序员

    2、Nacos原理

    -

    阿里巴巴官方出品

    `,r:{minutes:.16,words:47},y:"a",t:"电子书资源汇总"}}],["/other/computerprinciple/TCP-IP.html",{loader:()=>h(()=>import("./TCP-IP.html-DSyGo702.js"),__vite__mapDeps([102,1])),meta:{d:16750368e5,l:"2023年1月30日",e:`

    1、TCP/IP与OSI的关系

    -

    网上学习网络模型时一会是七层osi模型,一会又五层,一会又是四层模型,着实五花八门让人费解,实际上七层模型是osi的标准,目前大家所讲的tcp/ip也是符合osi的标准的,不过是把其中几层合并到一层了而已,点击查看tcp/ip和osi的关系。TCP/IP实际上是一个协议簇,包含很多协议,比如TCP、IP、UDP、ICMP、RIP、TELNETFTP、SMTP、ARP、TFTP等,这些协议一起称为TCP/IP协议。常见协议简单介绍:

    `,r:{minutes:7.86,words:2359},y:"a",t:"深入理解TCP/IP"}}],["/other/database/CPUOverLoad.html",{loader:()=>h(()=>import("./CPUOverLoad.html-Cl4MUidN.js"),__vite__mapDeps([103,1])),meta:{a:"chenkun",d:16582752e5,l:"2022年7月20日",c:["数据库"],e:` +

    阿里巴巴官方出品

    `,r:{minutes:.16,words:47},y:"a",t:"电子书资源汇总"}}],["/other/computerprinciple/TCP-IP.html",{loader:()=>h(()=>import("./TCP-IP.html-obsJBTgk.js"),__vite__mapDeps([102,1])),meta:{d:16750368e5,l:"2023年1月30日",e:`

    1、TCP/IP与OSI的关系

    +

    网上学习网络模型时一会是七层osi模型,一会又五层,一会又是四层模型,着实五花八门让人费解,实际上七层模型是osi的标准,目前大家所讲的tcp/ip也是符合osi的标准的,不过是把其中几层合并到一层了而已,点击查看tcp/ip和osi的关系。TCP/IP实际上是一个协议簇,包含很多协议,比如TCP、IP、UDP、ICMP、RIP、TELNETFTP、SMTP、ARP、TFTP等,这些协议一起称为TCP/IP协议。常见协议简单介绍:

    `,r:{minutes:7.86,words:2359},y:"a",t:"深入理解TCP/IP"}}],["/other/database/CPUOverLoad.html",{loader:()=>h(()=>import("./CPUOverLoad.html-B_JZj2Bd.js"),__vite__mapDeps([103,1])),meta:{a:"chenkun",d:16582752e5,l:"2022年7月20日",c:["数据库"],e:`

    问题

    某天突然收到预警邮件,服务器CPU超过阈值,并且一直持续居高不下

    @@ -1423,7 +1423,7 @@ Java 9 引入了新的响应式流 API。

    1. 使用htop查看资源消耗,按照CPU使用率降序排列,发现都是mysqld进程占用CPU很高
    2. 进入mysql命令行使用show processlist;查看当前正在执行的命令,经过多次执行show processlist发现有几条固定的sql一直在执行,并且每次传递的参数害不一样
    3. -
    `,r:{minutes:2.12,words:636},y:"a",t:"Mysql CPU负载过高"}}],["/other/database/MysqlCollate.html",{loader:()=>h(()=>import("./MysqlCollate.html-D6Cxtc4N.js"),__vite__mapDeps([104,1])),meta:{d:1676592e6,l:"2023年2月17日",g:["mysql"],o:!0,e:`

    本文转载自此处

    +`,r:{minutes:2.12,words:636},y:"a",t:"Mysql CPU负载过高"}}],["/other/database/MysqlCollate.html",{loader:()=>h(()=>import("./MysqlCollate.html-BXuIpRKi.js"),__vite__mapDeps([104,1])),meta:{d:1676592e6,l:"2023年2月17日",g:["mysql"],o:!0,e:`

    本文转载自此处

    在mysql中执行show create table &lt tablename>指令,可以看到一张表的建表语句,example如下:

    CREATE TABLE \`table1\` (
         \`id\` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    @@ -1431,13 +1431,13 @@ Java 9 引入了新的响应式流 API。

    \`field2\` varchar(128) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT '字段2', PRIMARY KEY (\`id\`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8_unicode_ci; -
    `,r:{minutes:5.68,words:1704},y:"a",t:"Mysql中的Collate和charset"}}],["/other/database/MysqlNote.html",{loader:()=>h(()=>import("./MysqlNote.html-C-U71JLc.js"),__vite__mapDeps([105,1])),meta:{a:"chenkun",d:165456e7,l:"2022年6月7日",e:` +
    `,r:{minutes:5.68,words:1704},y:"a",t:"Mysql中的Collate和charset"}}],["/other/database/MysqlNote.html",{loader:()=>h(()=>import("./MysqlNote.html-Cy1AGfO1.js"),__vite__mapDeps([105,1])),meta:{a:"chenkun",d:165456e7,l:"2022年6月7日",e:`

    1、批量插入速度慢

    项目使用的MyBatis-plus批量插入效率很低,遂百度一下原因

    在jdbc的链接上加上rewriteBatchedStatements=true参数,可以解决此问题。

    -

    默认情况下rewriteBatchedStatements=false,jdbc批量插入会判断rewriteBatchedStatements,当为true才会执行批量语句,以下从源码(以下jdbc驱动源码版本为8.0.20)角度分析:

    `,r:{minutes:1.56,words:468},y:"a",t:"Mysql知识点记录"}}],["/other/database/MysqlRemoteConnect.html",{loader:()=>h(()=>import("./MysqlRemoteConnect.html-DGYiJFHo.js"),__vite__mapDeps([106,1])),meta:{a:"chenkun",d:16521408e5,l:"2022年5月10日",e:` +

    默认情况下rewriteBatchedStatements=false,jdbc批量插入会判断rewriteBatchedStatements,当为true才会执行批量语句,以下从源码(以下jdbc驱动源码版本为8.0.20)角度分析:

    `,r:{minutes:1.56,words:468},y:"a",t:"Mysql知识点记录"}}],["/other/database/MysqlRemoteConnect.html",{loader:()=>h(()=>import("./MysqlRemoteConnect.html-B6Rz_Dfl.js"),__vite__mapDeps([106,1])),meta:{a:"chenkun",d:16521408e5,l:"2022年5月10日",e:`

    默认情况下,mysql只允许本地登录,如果要开启远程连接,则需要修改/etc/mysql/my.conf文件。Mariadb可以去/etc/my.cnf看看引用的目录配置

    @@ -1449,9 +1449,9 @@ Java 9 引入了新的响应式流 API。

    grant all on . to admin@'%' identified by '123456' with grant option; flush privileges; 允许任何ip地址(%表示允许任何ip地址)的电脑用admin帐户和密码(123456)来访问这个mysql server。 -注意admin账户不一定要存在。

    `,r:{minutes:.76,words:227},y:"a",t:"Mysql开启远程连接权限"}}],["/other/database/",{loader:()=>h(()=>import("./index.html-oz23N8Lk.js"),__vite__mapDeps([107,1])),meta:{a:"chenkun",d:1651104e6,l:"2022年4月28日",e:`

    学习数据库知识

    +注意admin账户不一定要存在。

    `,r:{minutes:.76,words:227},y:"a",t:"Mysql开启远程连接权限"}}],["/other/database/",{loader:()=>h(()=>import("./index.html-D5Bas5eh.js"),__vite__mapDeps([107,1])),meta:{a:"chenkun",d:1651104e6,l:"2022年4月28日",e:`

    学习数据库知识

    -`,r:{minutes:.07,words:21},y:"a",t:"数据库"}}],["/other/database/Recurse.html",{loader:()=>h(()=>import("./Recurse.html-6jai2Fmm.js"),__vite__mapDeps([108,1])),meta:{d:16807392e5,l:"2023年4月6日",o:!0,e:`
    +`,r:{minutes:.07,words:21},y:"a",t:"数据库"}}],["/other/database/Recurse.html",{loader:()=>h(()=>import("./Recurse.html-CzvVNt5P.js"),__vite__mapDeps([108,1])),meta:{d:16807392e5,l:"2023年4月6日",o:!0,e:`

    注意!!!

    只有在mysql8.0之后才有递归,5.7及之前是不支持的

    @@ -1470,11 +1470,11 @@ flush privileges; JOIN cte ON t.parent_id = cte.id ) SELECT * FROM cte; -
    `,r:{minutes:1.2,words:360},y:"a",t:"递归下钻"}}],["/other/database/SQLOptimization.html",{loader:()=>h(()=>import("./SQLOptimization.html-CzzwcOPq.js"),__vite__mapDeps([109,1])),meta:{a:"chenkun",d:1651104e6,l:"2022年4月28日",c:["数据库"],e:` +`,r:{minutes:1.2,words:360},y:"a",t:"递归下钻"}}],["/other/database/SQLOptimization.html",{loader:()=>h(()=>import("./SQLOptimization.html-DB9Wuz97.js"),__vite__mapDeps([109,1])),meta:{a:"chenkun",d:1651104e6,l:"2022年4月28日",c:["数据库"],e:`

    背景:

    最近在改一个老项目,其中使用到框架是mybatis,有一个业务表install_record,代表装机记录,一个accessory代表备件表,一个sys_file代表文件表,业务关系是一个install_record对应多个accessory、以及多个sys_file,在一开始使用的是mybatis的嵌套查询的方式,但此方式有N+1的问题,比如一个装机表对应10个accessory、20个sys_file,则就要查询1+10+20 = 31次数据库,效率是很低的,因此想改成嵌套查询的方式。

    -
    `,r:{minutes:1.01,words:303},y:"a",t:"联合查询sql优化"}}],["/other/database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD.html",{loader:()=>h(()=>import("./数据库备份.html-UG8-nnQB.js"),__vite__mapDeps([110,1])),meta:{a:"chensino",d:15389568e5,l:"2018年10月8日",o:!0,e:`

    1.备份脚本

    +
    `,r:{minutes:1.01,words:303},y:"a",t:"联合查询sql优化"}}],["/other/database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD.html",{loader:()=>h(()=>import("./数据库备份.html-rdxyp_Zo.js"),__vite__mapDeps([110,1])),meta:{a:"chensino",d:15389568e5,l:"2018年10月8日",o:!0,e:`

    1.备份脚本

    脚本文件: xxx.sh

    #!/bin/bash
     
    @@ -1523,23 +1523,23 @@ flush privileges;
       echo "【\`date +%Y%m%d-%H:%M:%S\`】delete $delfile" >> $backup_dir/log.txt
     fi
     
    -
    `,r:{minutes:.97,words:291},y:"a",t:"定时备份数据库"}}],["/other/distributeservice/DistributeLock.html",{loader:()=>h(()=>import("./DistributeLock.html-PlGmUQ21.js"),__vite__mapDeps([111,1])),meta:{d:14897952e5,l:"2017年3月18日",e:`

    1、写在前面

    +`,r:{minutes:.97,words:291},y:"a",t:"定时备份数据库"}}],["/other/distributeservice/DistributeLock.html",{loader:()=>h(()=>import("./DistributeLock.html-C1ihBiBe.js"),__vite__mapDeps([111,1])),meta:{d:14897952e5,l:"2017年3月18日",e:`

    1、写在前面

    以前一直没搞清楚分布式锁和分布式事务,对其概念以及使用场景很模糊,今天查查资料,好好总结一下分布式事务和分布式锁。另外提前说一句,使用redis来解决分布式、高并发问题存在一些困难,Redis 分布式锁只能作为一种缓解并发的手段,如果要完全解决并发问题,仍需要数据库的防并发手段。

    2、锁和分布式锁解决了什么问题?

    单机锁:解决单进程中多线程同时操作共有数据(比如java堆数据)带来的安全问题,强调的是单机服务中线程安全问题,这种场景很常见,比如单机多线程售票的例子。这种锁直接通过java的本地锁实现即可,可以使用java自带的synchroized和Lock

    -
    `,r:{minutes:9.8,words:2940},y:"a",t:"分布式锁"}}],["/other/distributeservice/Nacos.html",{loader:()=>h(()=>import("./Nacos.html-D6kbUFLh.js"),__vite__mapDeps([112,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",e:`

    1、配置管理

    +
    `,r:{minutes:9.8,words:2940},y:"a",t:"分布式锁"}}],["/other/distributeservice/Nacos.html",{loader:()=>h(()=>import("./Nacos.html-DC2XGbQ_.js"),__vite__mapDeps([112,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",e:`

    1、配置管理

    命名空间——>Group——>Data Id——>配置项

    命名空间:

    默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。

    如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM代表bom系统。

    -

    image-20220602103505994

    `,r:{minutes:.7,words:209},y:"a",t:"Nacos学习"}}],["/other/distributeservice/",{loader:()=>h(()=>import("./index.html-Beu4CvPJ.js"),__vite__mapDeps([113,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",e:` -`,r:{minutes:.06,words:17},y:"a",t:"分布式微服务"}}],["/other/docker/Docker.html",{loader:()=>h(()=>import("./Docker.html-BaiyWfkU.js"),__vite__mapDeps([114,1])),meta:{a:"chenkun",d:166536e7,l:"2022年10月10日",c:["docker"],e:`

    1 命令速查

    -`,r:{minutes:.07,words:22},y:"a",t:"Docker常用命令"}}],["/other/docker/ServiceInstall.html",{loader:()=>h(()=>import("./ServiceInstall.html-CCIiUtbe.js"),__vite__mapDeps([115,1])),meta:{d:16695936e5,l:"2022年11月28日",c:["docker"],e:`

    1. docker安装Redis

    +

    image-20220602103505994

    `,r:{minutes:.7,words:209},y:"a",t:"Nacos学习"}}],["/other/distributeservice/",{loader:()=>h(()=>import("./index.html-DaiGMbWY.js"),__vite__mapDeps([113,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",e:` +`,r:{minutes:.06,words:17},y:"a",t:"分布式微服务"}}],["/other/docker/Docker.html",{loader:()=>h(()=>import("./Docker.html-DzlBwcZ1.js"),__vite__mapDeps([114,1])),meta:{a:"chenkun",d:166536e7,l:"2022年10月10日",c:["docker"],e:`

    1 命令速查

    +`,r:{minutes:.07,words:22},y:"a",t:"Docker常用命令"}}],["/other/docker/ServiceInstall.html",{loader:()=>h(()=>import("./ServiceInstall.html-BaooIkHM.js"),__vite__mapDeps([115,1])),meta:{d:16695936e5,l:"2022年11月28日",c:["docker"],e:`

    1. docker安装Redis

    起初的需求是用docker启动一个redis,并且指定一个配置,死活不成功,主要是少设置了data目录的映射

    @@ -1547,7 +1547,7 @@ flush privileges;

    注意

    使用docker-compose安装redis时,若指定外置redis.conf配置文件,要切记同时设置data存储目录,默认docker中的数据存在/data下,所以 使用卷映射时需要映射到容器内的/data docker-compose.yml

    -`,r:{minutes:.7,words:210},y:"a",t:"使用docker安装常见服务"}}],["/other/essay/2022-04-12.html",{loader:()=>h(()=>import("./2022-04-12.html-BnI2oaJK.js"),__vite__mapDeps([116,1])),meta:{a:"chenkun",d:16488576e5,l:"2022年4月2日",c:["数据库"],g:["数据库"],e:` +`,r:{minutes:.7,words:210},y:"a",t:"使用docker安装常见服务"}}],["/other/essay/2022-04-12.html",{loader:()=>h(()=>import("./2022-04-12.html-omw7Q29H.js"),__vite__mapDeps([116,1])),meta:{a:"chenkun",d:16488576e5,l:"2022年4月2日",c:["数据库"],g:["数据库"],e:`

    背景:

    我有一个库A,这个库同时被两个服务使用(serviceA、serviceB),某天,因serviceA业务需要,必须更改数据库结构,导致serviceB无法使用,但是serviceB又不能停机,所以就考虑把数据库克隆一份给serviceB使用。在克隆使用的是mysqldump直接导出,然后再新建另一个库B,在库B导入sql,结果就悲剧了。

    @@ -1555,14 +1555,14 @@ flush privileges;

    1、库A导出

    # 在服务器把写了个定时备份脚本,这个databaseA是未更新前备份的库
     mysqldump -h localhost -uroot -p123456 databaseA>databaseA.sql
    -
    `,r:{minutes:1.18,words:355},y:"a",t:"2022-04-02"}}],["/other/essay/BTree.html",{loader:()=>h(()=>import("./BTree.html-CuSxbXvJ.js"),__vite__mapDeps([117,1])),meta:{d:16137792e5,l:"2021年2月20日",g:["数据结构","二叉树"],u:8,e:`

    二叉树进化图

    +`,r:{minutes:1.18,words:355},y:"a",t:"2022-04-02"}}],["/other/essay/BTree.html",{loader:()=>h(()=>import("./BTree.html-DJQoEL8B.js"),__vite__mapDeps([117,1])),meta:{d:16137792e5,l:"2021年2月20日",g:["数据结构","二叉树"],u:8,e:`

    二叉树进化图

    树结构大道

    -`,r:{minutes:.11,words:32},y:"a",t:"二叉树"}}],["/other/essay/CDN.html",{loader:()=>h(()=>import("./CDN.html-rIf3-Wbw.js"),__vite__mapDeps([118,1])),meta:{a:"John",d:15821568e5,l:"2020年2月20日",g:["运维"],u:8,e:`

    1、什么是CDN?

    +`,r:{minutes:.11,words:32},y:"a",t:"二叉树"}}],["/other/essay/CDN.html",{loader:()=>h(()=>import("./CDN.html-Cd7ildNJ.js"),__vite__mapDeps([118,1])),meta:{a:"John",d:15821568e5,l:"2020年2月20日",g:["运维"],u:8,e:`

    1、什么是CDN?

    CDN(Content Delivery Network,内容分发网络)是构建在现有互联网基础之上的一层智能虚拟网络,通过在网络各处部署节点服务器,实现将源站内容分发至所有CDN节点,使用户可以就近获得所需的内容。CDN服务缩短了用户查看内容的访问延迟,提高了用户访问网站的响应速度与网站的可用性,解决了网络带宽小、用户访问量大、网点分布不均等问题。
    -
    `,r:{minutes:7.17,words:2152},y:"a",t:"CDN静态资源加速"}}],["/other/essay/ChromeDevTools.html",{loader:()=>h(()=>import("./ChromeDevTools.html-BoMI5RzK.js"),__vite__mapDeps([119,1])),meta:{a:"陈老师",d:16556832e5,l:"2022年6月20日",g:["工具使用"],e:`

    学会ChromeDevtool,高效率定位问题

    +`,r:{minutes:7.17,words:2152},y:"a",t:"CDN静态资源加速"}}],["/other/essay/ChromeDevTools.html",{loader:()=>h(()=>import("./ChromeDevTools.html-jvUjLE8a.js"),__vite__mapDeps([119,1])),meta:{a:"陈老师",d:16556832e5,l:"2022年6月20日",g:["工具使用"],e:`

    学会ChromeDevtool,高效率定位问题

    https://developer.chrome.com/docs/devtools/evaluate-performance/

    -`,r:{minutes:.1,words:31},y:"a",t:"ChromeDevTools学习"}}],["/other/essay/CloudService.html",{loader:()=>h(()=>import("./CloudService.html-DjGDK1Z4.js"),__vite__mapDeps([120,1])),meta:{a:"陈老师",d:16556832e5,l:"2022年6月20日",g:["运维"],e:`

    1、我的云服务使用场景

    +`,r:{minutes:.1,words:31},y:"a",t:"ChromeDevTools学习"}}],["/other/essay/CloudService.html",{loader:()=>h(()=>import("./CloudService.html-DKHWbJgc.js"),__vite__mapDeps([120,1])),meta:{a:"陈老师",d:16556832e5,l:"2022年6月20日",g:["运维"],e:`

    1、我的云服务使用场景

    1. @@ -1578,27 +1578,27 @@ mysqldump -h localhost 一开始没有上cdn,用户反应慢,后来配置了cdn加速obs中的图片,这样第一次打开图片会把图片资源拉到就近的服务器,问题就是第一次打开依然慢;
    2. 导出PDF很慢,经常出现504Timeout
    -

    3、问题分析

    `,r:{minutes:7.92,words:2375},y:"a",t:"云服务问题分析及总结"}}],["/other/essay/DeployGithubPage.html",{loader:()=>h(()=>import("./DeployGithubPage.html-2HbSyCiP.js"),__vite__mapDeps([121,1])),meta:{a:"Sino",d:16473024e5,l:"2022年3月15日",g:["部署搭建"],u:5,e:`

    1、 GitHubPage介绍

    +

    3、问题分析

    `,r:{minutes:7.92,words:2375},y:"a",t:"云服务问题分析及总结"}}],["/other/essay/DeployGithubPage.html",{loader:()=>h(()=>import("./DeployGithubPage.html-COQNI509.js"),__vite__mapDeps([121,1])),meta:{a:"Sino",d:16473024e5,l:"2022年3月15日",g:["部署搭建"],u:5,e:`

    1、 GitHubPage介绍

    1.1 ok

    1.2 搭建个人githubpage

    个人page和项目page的区别就是个人page只有一个,所谓的个人Page说白了也是一个特殊的项目Page,无非就是它的仓库名字比较特殊,必须为<username>.github.io,比如java框架\`spring-cloud.github.io\`、\`facebook.github.io\`,注意个人page的仓库名一定要加上 \`.github.io\`才算个人Page,不加的话就是一个普通项目了。
     个人page有啥特殊之处呢?
     在访问页面时可以直接使用https://<username>.github.io,不用加仓库名,普通的项目page,访问时需要加仓库名,比如https://<username>.github.io/<reponame>
     
    -
    `,r:{minutes:2.08,words:625},y:"a",t:"如何在github部署静态网站"}}],["/other/essay/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B.html",{loader:()=>h(()=>import("./IM即时通信技术选型.html-DiDYCl7r.js"),__vite__mapDeps([122,1])),meta:{a:"chensino",d:17234208e5,l:"2024年8月12日",o:!0,e:`

    1. 需求

    +`,r:{minutes:2.08,words:625},y:"a",t:"如何在github部署静态网站"}}],["/other/essay/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B.html",{loader:()=>h(()=>import("./IM即时通信技术选型.html-B2_U3umv.js"),__vite__mapDeps([122,1])),meta:{a:"chensino",d:17234208e5,l:"2024年8月12日",o:!0,e:`

    1. 需求

    服务端开发语言:Java

    终端:Android、iOS、Web、小程序

    核心需求:发送文字、图片、文件、语音、视频、消息缓存、消息存储、消息未读、已读、撤回,离线消息、历史消息、单聊、群聊,多端同步,以及其他一些需求。

    用户管理:添加好友、查看好友列表、删除好友、查看好友信息、创建群聊、加入群聊、查看群成员信息、退出群聊、修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注以及其他用户相关的需求等。

    -

    权限管理: 针对群聊不同用户有不同的权限,比如群主、管理员、普通成员,普通成员可以发送消息、撤回消息、删除消息、修改消息、查看消息等,管理员可以管理群成员,修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注等。

    `,r:{minutes:19.44,words:5832},y:"a",t:"IM即时通信选型"}}],["/other/essay/Jenkins.html",{loader:()=>h(()=>import("./Jenkins.html-D5aTJlwz.js"),__vite__mapDeps([123,1])),meta:{a:"chenkun",d:16499808e5,l:"2022年4月15日",c:["运维"],e:` +

    权限管理: 针对群聊不同用户有不同的权限,比如群主、管理员、普通成员,普通成员可以发送消息、撤回消息、删除消息、修改消息、查看消息等,管理员可以管理群成员,修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注等。

    `,r:{minutes:19.44,words:5832},y:"a",t:"IM即时通信选型"}}],["/other/essay/Jenkins.html",{loader:()=>h(()=>import("./Jenkins.html-e7mi9H_K.js"),__vite__mapDeps([123,1])),meta:{a:"chenkun",d:16499808e5,l:"2022年4月15日",c:["运维"],e:`

    1、jenkins插件更新报错

    1.1 报错如下,ssl证书问题

    image-20220415144539319

    Jenkins(2020年及以后版本,2.260以上)安装后,插件下载时失败,网上找了各种解决方法,修改jenkins插件的下载源地址:

    -

    找到菜单Manage Jenkins → Manage Plugins → Advanced → Update Site,

    `,r:{minutes:5.09,words:1528},y:"a",t:"jenkins部署及使用"}}],["/other/essay/",{loader:()=>h(()=>import("./index.html-EDXVLWDR.js"),__vite__mapDeps([124,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",r:{minutes:.05,words:14},y:"a",t:"随笔分享"}}],["/other/essay/TyporaPicgo.html",{loader:()=>h(()=>import("./TyporaPicgo.html-4B31qBXz.js"),__vite__mapDeps([125,1])),meta:{d:1583712e6,l:"2020年3月9日",e:`

    前言

    +

    找到菜单Manage Jenkins → Manage Plugins → Advanced → Update Site,

    `,r:{minutes:5.09,words:1528},y:"a",t:"jenkins部署及使用"}}],["/other/essay/",{loader:()=>h(()=>import("./index.html-BBsSpY4m.js"),__vite__mapDeps([124,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",r:{minutes:.05,words:14},y:"a",t:"随笔分享"}}],["/other/essay/TyporaPicgo.html",{loader:()=>h(()=>import("./TyporaPicgo.html-DyxSZmxq.js"),__vite__mapDeps([125,1])),meta:{d:1583712e6,l:"2020年3月9日",e:`

    前言

    平时用MarkDown写博客少不了需要截图,我用的是Typora,刚开始截图是保存在本地,有时想把博客分享到网上,就发现各种图全挂了,需要手动一个一个再复制一下,着实麻烦,今天无意间发现有个叫PicGo的工具,此工具专门上传图到各大图床,着实方便。

    picgo配置

    -

    picgo-core

    `,r:{minutes:1.65,words:494},y:"a",t:"在Typora中使用Picgo"}}],["/other/essay/elasticSearch%E6%93%8D%E4%BD%9C.html",{loader:()=>h(()=>import("./elasticSearch操作.html-B7hezSq2.js"),__vite__mapDeps([126,1])),meta:{a:"chensino",d:1722816e6,l:"2024年8月5日",o:!0,e:`

    1.索引库操作

    +

    picgo-core

    `,r:{minutes:1.65,words:494},y:"a",t:"在Typora中使用Picgo"}}],["/other/essay/elasticSearch%E6%93%8D%E4%BD%9C.html",{loader:()=>h(()=>import("./elasticSearch操作.html-DivcwrqA.js"),__vite__mapDeps([126,1])),meta:{a:"chensino",d:1722816e6,l:"2024年8月5日",o:!0,e:`

    1.索引库操作

    索引库就类似数据库表,mapping映射就类似表的结构。

    我们要向es中存储数据,必须先创建“库”和“表”

    1.1 Mapping映射属性

    @@ -1624,7 +1624,7 @@ mysqldump -h localhost

    properties:该字段的子字段

    -`,r:{minutes:21.3,words:6391},y:"a",t:"elasticsearch操作"}}],["/other/essay/elk%E9%83%A8%E7%BD%B2.html",{loader:()=>h(()=>import("./elk部署.html-DF5G_BDL.js"),__vite__mapDeps([127,1])),meta:{a:"chensino",d:1722816e6,l:"2024年8月5日",o:!0,e:`

    1. 目录结构

    +`,r:{minutes:21.3,words:6391},y:"a",t:"elasticsearch操作"}}],["/other/essay/elk%E9%83%A8%E7%BD%B2.html",{loader:()=>h(()=>import("./elk部署.html-hYHFl7Qx.js"),__vite__mapDeps([127,1])),meta:{a:"chensino",d:1722816e6,l:"2024年8月5日",o:!0,e:`

    1. 目录结构

    elk
     ├── config
     │   ├── es
    @@ -1642,7 +1642,7 @@ mysqldump -h localhost 
    `,r:{minutes:3.3,words:990},y:"a",t:"ELK 部署"}}],["/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html",{loader:()=>h(()=>import("./im即时通信的需求.html-qDdBmZbJ.js"),__vite__mapDeps([128,1])),meta:{a:"chenkun",d:1724891306e3,o:!0,e:` +`,r:{minutes:3.3,words:990},y:"a",t:"ELK 部署"}}],["/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html",{loader:()=>h(()=>import("./im即时通信的需求.html-Disuf4MT.js"),__vite__mapDeps([128,1])),meta:{a:"chenkun",d:1724891306e3,o:!0,e:`

    1. 概述

    1.1 项目背景

    随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。

    @@ -1654,10 +1654,10 @@ mysqldump -h localhost 医生:科室主任、主治医师、住院医师等。
  • 护士:护士长、责任护士、实习护士等。
  • 行政人员:医院管理层、科室秘书、后勤人员等。
  • -`,r:{minutes:10.3,words:3091},y:"a",t:"即时通信软件需求"}}],["/other/essay/windows%E4%B8%8B%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C.html",{loader:()=>h(()=>import("./windows下服务注册.html-DuNb2TND.js"),__vite__mapDeps([129,1])),meta:{a:"chensino",d:17116704e5,l:"2024年3月29日",o:!0,e:`

    1.需求

    +`,r:{minutes:10.3,words:3091},y:"a",t:"即时通信软件需求"}}],["/other/essay/windows%E4%B8%8B%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C.html",{loader:()=>h(()=>import("./windows下服务注册.html-CH2QvxT3.js"),__vite__mapDeps([129,1])),meta:{a:"chensino",d:17116704e5,l:"2024年3月29日",o:!0,e:`

    1.需求

        1. windows下没有像linux的\`nohup xxx &\`的后台启动命令,springboot项目就无法后台运行
         2. 把任意一个命令注册为服务
    -
    `,r:{minutes:.99,words:297},y:"a",t:"windows下服务注册"}}],["/other/essay/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%BC%95%E5%AF%BC.html",{loader:()=>h(()=>import("./操作系统引导.html-DD7Rd6wj.js"),__vite__mapDeps([130,1])),meta:{a:"chensino",d:17121024e5,l:"2024年4月3日",o:!0,e:`

    对应关系

    +`,r:{minutes:.99,words:297},y:"a",t:"windows下服务注册"}}],["/other/essay/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%BC%95%E5%AF%BC.html",{loader:()=>h(()=>import("./操作系统引导.html-BKBwfgmF.js"),__vite__mapDeps([130,1])),meta:{a:"chensino",d:17121024e5,l:"2024年4月3日",o:!0,e:`

    对应关系

    @@ -1687,13 +1687,13 @@ mysqldump -h localhost -
    `,r:{minutes:5.29,words:1586},y:"a",t:"系统引导基本名词BIOS/EFI/MBR/GPT/GRUB"}}],["/other/git/BatchDeleteGitHubRepo.html",{loader:()=>h(()=>import("./BatchDeleteGitHubRepo.html-WNdAC_un.js"),__vite__mapDeps([131,1])),meta:{d:1677024e6,l:"2023年2月22日",o:!0,e:`

    背景

    +`,r:{minutes:5.29,words:1586},y:"a",t:"系统引导基本名词BIOS/EFI/MBR/GPT/GRUB"}}],["/other/git/BatchDeleteGitHubRepo.html",{loader:()=>h(()=>import("./BatchDeleteGitHubRepo.html-DtqMXGl3.js"),__vite__mapDeps([131,1])),meta:{d:1677024e6,l:"2023年2月22日",o:!0,e:`

    背景

    github上fork了很多仓库,但是平时又没看,所以索性删除,一个个删除又很慢,所以搞个脚本批量删除

    方法

    1. 到github配置一个token,https://github.com/settings/tokens
     2. 准备一个文件放置待删除的仓库,每行一个仓库名
     3. 用脚本批量删除
    -
    `,r:{minutes:.56,words:168},y:"a",t:"批量删除github仓库"}}],["/other/git/GitCommands.html",{loader:()=>h(()=>import("./GitCommands.html-_C8ZQnH_.js"),__vite__mapDeps([132,1])),meta:{a:"ChenSino",d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    一、善用手册

    +`,r:{minutes:.56,words:168},y:"a",t:"批量删除github仓库"}}],["/other/git/GitCommands.html",{loader:()=>h(()=>import("./GitCommands.html-BB60xrRT.js"),__vite__mapDeps([132,1])),meta:{a:"ChenSino",d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    一、善用手册

    $ git --help
     用法:git [--version] [--help] [-C <路径>] [-c <名称>=<取值>]
                [--exec-path[=<路径>]] [--html-path] [--man-path] [--info-path]
    @@ -1741,28 +1741,28 @@ mysqldump -h localhost 'git help git'。
     
    -
    `,r:{minutes:11.64,words:3491},y:"a",t:"git命令"}}],["/other/git/",{loader:()=>h(()=>import("./index.html-BMhOFPmp.js"),__vite__mapDeps([133,1])),meta:{d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],r:{minutes:.05,words:16},y:"a",t:"Git"}}],["/other/git/branch01.html",{loader:()=>h(()=>import("./branch01.html--1ri9Qlh.js"),__vite__mapDeps([134,1])),meta:{d:1659545821e3,l:"2022年8月3日",c:["git 操作"],g:["必会"],e:`

    1,查看远程分支

    +`,r:{minutes:11.64,words:3491},y:"a",t:"git命令"}}],["/other/git/",{loader:()=>h(()=>import("./index.html-BtN-4LZa.js"),__vite__mapDeps([133,1])),meta:{d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],r:{minutes:.05,words:16},y:"a",t:"Git"}}],["/other/git/branch01.html",{loader:()=>h(()=>import("./branch01.html-CEgmUaaW.js"),__vite__mapDeps([134,1])),meta:{d:1659545821e3,l:"2022年8月3日",c:["git 操作"],g:["必会"],e:`

    1,查看远程分支

    可以查看远程分支名,查看要切换的远程分支是否存在。

    git branch -a
     

    2, 查看本地分支

    -

    查看本地是否已经有了要切换的分支,如果有,可以直接git checkout 分支名来切换分支。

    `,r:{minutes:.49,words:147},y:"a",t:"git 拉取远程分支到本地"}}],["/other/git/branch02.html",{loader:()=>h(()=>import("./branch02.html-CScQPDBP.js"),__vite__mapDeps([135,1])),meta:{d:1659545821e3,l:"2022年8月3日",c:["git 操作"],g:["必会"],e:`

    一,推送本地分支到远程

    +

    查看本地是否已经有了要切换的分支,如果有,可以直接git checkout 分支名来切换分支。

    `,r:{minutes:.49,words:147},y:"a",t:"git 拉取远程分支到本地"}}],["/other/git/branch02.html",{loader:()=>h(()=>import("./branch02.html-CkbKOtQo.js"),__vite__mapDeps([135,1])),meta:{d:1659545821e3,l:"2022年8月3日",c:["git 操作"],g:["必会"],e:`

    一,推送本地分支到远程

    昨天我在自家电脑创建了一个分支search_dev.写搜索功能。

    1,在本地创建并切换到search_dev分支

    git checkout -b search_dev
    -
    `,r:{minutes:1.63,words:489},y:"a",t:"git分支操作"}}],["/other/git/fatal.html",{loader:()=>h(()=>import("./fatal.html-CLx7xyLl.js"),__vite__mapDeps([136,1])),meta:{a:"zxf",d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    如何解决 fatal: unable to access...的问题

    +`,r:{minutes:1.63,words:489},y:"a",t:"git分支操作"}}],["/other/git/fatal.html",{loader:()=>h(()=>import("./fatal.html-CuBowvDc.js"),__vite__mapDeps([136,1])),meta:{a:"zxf",d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    如何解决 fatal: unable to access...的问题

    现象

    浏览器可正常访问github,终端无法clone

    确定是否是因为代理问题

    ## 查看git所有配置,检查是否使用了代理
     git config --list
    -
    `,r:{minutes:.98,words:294},y:"a",t:"Git克隆出现连接错误"}}],["/other/git/gitConflict.html",{loader:()=>h(()=>import("./gitConflict.html-XWxwXqnJ.js"),__vite__mapDeps([137,1])),meta:{a:"zxf",d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    一,git冲突出现的原因及解决方案

    +`,r:{minutes:.98,words:294},y:"a",t:"Git克隆出现连接错误"}}],["/other/git/gitConflict.html",{loader:()=>h(()=>import("./gitConflict.html-CD4vs1Ct.js"),__vite__mapDeps([137,1])),meta:{a:"zxf",d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    一,git冲突出现的原因及解决方案

    简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。

    1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。

    • 修改component.html本地文件第11行,既没有add,也没有commit
    • 然后修改远程component.html远程文件第14行
    • 本地执行git pull,拉取代码提示,同时修改component.html,提示发生冲突
    • -
    `,r:{minutes:2.72,words:815},y:"a",t:"git冲突出现的原因"}}],["/other/git/gitRebase.html",{loader:()=>h(()=>import("./gitRebase.html-Bh0TFroo.js"),__vite__mapDeps([138,1])),meta:{a:"zxf",d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    一,用于合并当前分支的多个commit记录

    +`,r:{minutes:2.72,words:815},y:"a",t:"git冲突出现的原因"}}],["/other/git/gitRebase.html",{loader:()=>h(()=>import("./gitRebase.html-DF3bIliz.js"),__vite__mapDeps([138,1])),meta:{a:"zxf",d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    一,用于合并当前分支的多个commit记录

    应用场景,如下第2-4次提交是对同一功能的代码提交记录,完全可以合并成一次提交记录。这个时候rebase就很有用了。

    image-20220724180044950

      @@ -1775,31 +1775,31 @@ mysqldump -h localhost
    -`,r:{minutes:3.03,words:909},y:"a",t:"git rebase的使用"}}],["/other/git/gitwork.html",{loader:()=>h(()=>import("./gitwork.html-DCYwt7rE.js"),__vite__mapDeps([139,1])),meta:{a:"zxf",d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    一,基本概念

    +`,r:{minutes:3.03,words:909},y:"a",t:"git rebase的使用"}}],["/other/git/gitwork.html",{loader:()=>h(()=>import("./gitwork.html-BenGZ5gF.js"),__vite__mapDeps([139,1])),meta:{a:"zxf",d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    一,基本概念

    下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:

    -

    image-20220803144042656

    `,r:{minutes:2.15,words:645},y:"a",t:"git工作区、暂存区、和版本库"}}],["/other/git/mergeBranch.html",{loader:()=>h(()=>import("./mergeBranch.html-4sgMZ_ic.js"),__vite__mapDeps([140,1])),meta:{d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    一,问题

    +

    image-20220803144042656

    `,r:{minutes:2.15,words:645},y:"a",t:"git工作区、暂存区、和版本库"}}],["/other/git/mergeBranch.html",{loader:()=>h(()=>import("./mergeBranch.html-CCnBpahJ.js"),__vite__mapDeps([140,1])),meta:{d:1646845021e3,l:"2022年3月9日",c:["git 操作"],g:["必会"],e:`

    一,问题

    ​ 在git log中往往会发现在log中出现Merge branch 'master of .....'这种合并节点,造成日志的污染

    image-20220726115529360

    二,产生的原因

    当多人合作开发一个项目时,本地仓库落后于远程仓库是一个非常正常的事情,可参考下图 -在这里插入图片描述

    `,r:{minutes:2.67,words:800},y:"a",t:"git pull产生临时Merge branch的问题"}}],["/other/git/proxy.html",{loader:()=>h(()=>import("./proxy.html-DRPn2ULh.js"),__vite__mapDeps([141,1])),meta:{a:"chenkun",d:16606944e5,l:"2022年8月17日",e:`

    1、全局代理

    +在这里插入图片描述

    `,r:{minutes:2.67,words:800},y:"a",t:"git pull产生临时Merge branch的问题"}}],["/other/git/proxy.html",{loader:()=>h(()=>import("./proxy.html-CIpuLV1M.js"),__vite__mapDeps([141,1])),meta:{a:"chenkun",d:16606944e5,l:"2022年8月17日",e:`

    1、全局代理

    git config --global https.proxy http://127.0.0.1:1080
     git config --global https.proxy https://127.0.0.1:1080
     ##取消代理
     git config --global --unset http.proxy
     git config --global --unset https.proxy
    -
    `,r:{minutes:.27,words:80},y:"a",t:"git代理"}}],["/other/git/rebaseAndMerge.html",{loader:()=>h(()=>import("./rebaseAndMerge.html-LfOBbMMt.js"),__vite__mapDeps([142,1])),meta:{d:1658854621e3,l:"2022年7月26日",c:["git 操作"],g:["必会"],e:`

    在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的)

    +`,r:{minutes:.27,words:80},y:"a",t:"git代理"}}],["/other/git/rebaseAndMerge.html",{loader:()=>h(()=>import("./rebaseAndMerge.html-DZipbfd7.js"),__vite__mapDeps([142,1])),meta:{d:1658854621e3,l:"2022年7月26日",c:["git 操作"],g:["必会"],e:`

    在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的)

    img

    -

    如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetch),需要将本地远程分支代码更新到个人工作分支,这时有两种方法,rebase和merge。

    `,r:{minutes:1.28,words:383},y:"a",t:"git rebase与merge的区别"}}],["/other/git/reset.html",{loader:()=>h(()=>import("./reset.html-CjbSN2yZ.js"),__vite__mapDeps([143,1])),meta:{a:"chenkun",d:16590528e5,l:"2022年7月29日",e:`

    1、几种reset介绍

    +

    如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetch),需要将本地远程分支代码更新到个人工作分支,这时有两种方法,rebase和merge。

    `,r:{minutes:1.28,words:383},y:"a",t:"git rebase与merge的区别"}}],["/other/git/reset.html",{loader:()=>h(()=>import("./reset.html-JinN7PbC.js"),__vite__mapDeps([143,1])),meta:{a:"chenkun",d:16590528e5,l:"2022年7月29日",e:`

    1、几种reset介绍

    git reset 命令用于回退版本,可以指定退回某一次提交的版本。

    git reset 命令语法格式如下:

    git reset [--soft | --mixed | --hard] [HEAD]
    -
    `,r:{minutes:1.78,words:533},y:"a",t:"git reset命令使用"}}],["/other/git/stash.html",{loader:()=>h(()=>import("./stash.html-DJPmBi5v.js"),__vite__mapDeps([144,1])),meta:{d:1646845021e3,l:"2022年3月9日",c:["git 操作","必会"],e:`

    一,使用场景

    +`,r:{minutes:1.78,words:533},y:"a",t:"git reset命令使用"}}],["/other/git/stash.html",{loader:()=>h(()=>import("./stash.html-D1rEWs9W.js"),__vite__mapDeps([144,1])),meta:{d:1646845021e3,l:"2022年3月9日",c:["git 操作","必会"],e:`

    一,使用场景

    在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。

    二,stash的作用

    stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。

    @@ -1827,23 +1827,23 @@ mysqldump -h localhost

    git stash drop stash@{$num} :丢弃stash@{$num}存储,从列表中删除这个存储

    -`,r:{minutes:1.52,words:456},y:"a",t:"git stash 暂存"}}],["/other/hardware/CPU.html",{loader:()=>h(()=>import("./CPU.html-D5a-tI0p.js"),__vite__mapDeps([145,1])),meta:{d:16915392e5,l:"2023年8月9日",o:!0,e:`

    intel cpu型号

    +`,r:{minutes:1.52,words:456},y:"a",t:"git stash 暂存"}}],["/other/hardware/CPU.html",{loader:()=>h(()=>import("./CPU.html-BmK1dlqs.js"),__vite__mapDeps([145,1])),meta:{d:16915392e5,l:"2023年8月9日",o:!0,e:`

    intel cpu型号

    官方说明

    cpu型号命名

    20230809094135

    -

    20230809094205

    `,r:{minutes:1.46,words:438},y:"a",t:"cpu介绍"}}],["/other/linux/CentOS.html",{loader:()=>h(()=>import("./CentOS.html-C59_Q0zj.js"),__vite__mapDeps([146,1])),meta:{a:"chenkun",d:16600896e5,l:"2022年8月10日",e:`

    1、安装JDK11

    +

    20230809094205

    `,r:{minutes:1.46,words:438},y:"a",t:"cpu介绍"}}],["/other/linux/CentOS.html",{loader:()=>h(()=>import("./CentOS.html-CSukTNYu.js"),__vite__mapDeps([146,1])),meta:{a:"chenkun",d:16600896e5,l:"2022年8月10日",e:`

    1、安装JDK11

    sudo yum -y install java-11-openjdk java-11-openjdk-devel
    -
    `,r:{minutes:.18,words:55},y:"a",t:"RedHat系"}}],["/other/linux/CommonUsedCMD.html",{loader:()=>h(()=>import("./CommonUsedCMD.html-WpgS454f.js"),__vite__mapDeps([147,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",e:` +`,r:{minutes:.18,words:55},y:"a",t:"RedHat系"}}],["/other/linux/CommonUsedCMD.html",{loader:()=>h(()=>import("./CommonUsedCMD.html-CHwLkMH3.js"),__vite__mapDeps([147,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",e:`

    1、查找多个文件中是否包含字符串

    grep -r targetString targetDirectory
     # -r 表示递归查询
     # targetString  表示目标字符串
     # targetDirectory 表示目录
    -
    `,r:{minutes:3.29,words:986},y:"a",t:"常用命令"}}],["/other/linux/Curl.html",{loader:()=>h(()=>import("./Curl.html-DlrJSikw.js"),__vite__mapDeps([148,1])),meta:{a:"chenkun",d:16602624e5,l:"2022年8月12日",c:["linux"],g:["linux"],e:`

    1、使用CURL分析接口请求耗时

    +`,r:{minutes:3.29,words:986},y:"a",t:"常用命令"}}],["/other/linux/Curl.html",{loader:()=>h(()=>import("./Curl.html-BO77gaot.js"),__vite__mapDeps([148,1])),meta:{a:"chenkun",d:16602624e5,l:"2022年8月12日",c:["linux"],g:["linux"],e:`

    1、使用CURL分析接口请求耗时

    `,r:{minutes:4.79,words:1438},y:"a",t:"Curl命令"}}],["/other/linux/InstallMysqlWithDocker.html",{loader:()=>h(()=>import("./InstallMysqlWithDocker.html-cGZHFWnE.js"),__vite__mapDeps([149,1])),meta:{a:"chenkun",d:16651872e5,l:"2022年10月8日",e:`

    1. 不要用太新的版本

    +`,r:{minutes:4.79,words:1438},y:"a",t:"Curl命令"}}],["/other/linux/InstallMysqlWithDocker.html",{loader:()=>h(()=>import("./InstallMysqlWithDocker.html-Dlzu3km3.js"),__vite__mapDeps([149,1])),meta:{a:"chenkun",d:16651872e5,l:"2022年10月8日",e:`

    1. 不要用太新的版本

    安装一定要选对版本,刚开始我使用的8.0.30镜像,一直无法启动,日志报错大概意思是我映射的目录有问题,应该是新版本mysql的安装文件有变动, 我也懒得去深究,直接换了一个版本就好了了。

    2 安装

    @@ -1857,7 +1857,7 @@ mysqldump -h localhost -v /usr/local/docker/mysql/data:/var/lib/mysql \\ -e MYSQL_ROOT_PASSWORD=root \\ -d mysql:8.0.23 -`,r:{minutes:.75,words:224},y:"a",t:"docker安装mysql"}}],["/other/linux/Manjaro.html",{loader:()=>h(()=>import("./Manjaro.html-CTuhb7rc.js"),__vite__mapDeps([150,1])),meta:{a:"chenkun",d:16479072e5,l:"2022年3月22日",e:`

    1、降级软件包

    +`,r:{minutes:.75,words:224},y:"a",t:"docker安装mysql"}}],["/other/linux/Manjaro.html",{loader:()=>h(()=>import("./Manjaro.html-Dy2e0Hrp.js"),__vite__mapDeps([150,1])),meta:{a:"chenkun",d:16479072e5,l:"2022年3月22日",e:`

    1、降级软件包

    安装downgrade程序 sudo pacman -S downgrade 降级 @@ -1865,7 +1865,7 @@ mysqldump -h localhost -

    image-20220322171440300

    `,r:{minutes:11.52,words:3457},y:"a",t:"Manjaro问题搜集"}}],["/other/linux/MountDisk.html",{loader:()=>h(()=>import("./MountDisk.html-AWo0fohf.js"),__vite__mapDeps([151,1])),meta:{a:"chenkun",d:16747776e5,l:"2023年1月27日",e:`

    参考

    +

    image-20220322171440300

    `,r:{minutes:11.52,words:3457},y:"a",t:"Manjaro问题搜集"}}],["/other/linux/MountDisk.html",{loader:()=>h(()=>import("./MountDisk.html-BqspOYo5.js"),__vite__mapDeps([151,1])),meta:{a:"chenkun",d:16747776e5,l:"2023年1月27日",e:`

    参考

    初始化Linux数据盘(fdisk)

    挂载

    划分分区并挂载磁盘

    @@ -1873,7 +1873,7 @@ mysqldump -h localhost
  • fdisk -l 回显类似如下信息:
  • -`,r:{minutes:7.62,words:2285},y:"a",t:"系统挂载磁盘"}}],["/other/linux/MultiNetworkCard.html",{loader:()=>h(()=>import("./MultiNetworkCard.html-ZHqfhuQy.js"),__vite__mapDeps([152,1])),meta:{a:"chenkun",d:1661472e6,l:"2022年8月26日",e:`

    1.1 kde桌面双网卡内外网设置

    +`,r:{minutes:7.62,words:2285},y:"a",t:"系统挂载磁盘"}}],["/other/linux/MultiNetworkCard.html",{loader:()=>h(()=>import("./MultiNetworkCard.html-BdZVkWRH.js"),__vite__mapDeps([152,1])),meta:{a:"chenkun",d:1661472e6,l:"2022年8月26日",e:`

    1.1 kde桌面双网卡内外网设置

    环境如下:

    $ screenfetch   
     
    @@ -1892,9 +1892,9 @@ mysqldump -h localhost 4.9GHz [29.0°C]
      ████████  ████████  ████████     GPU: Mesa Intel(R) Graphics (ADL-S GT1)
                                                        RAM: 16498MiB / 23813MiB
    -
    `,r:{minutes:1.9,words:570},y:"a",t:"双网卡问题"}}],["/other/linux/",{loader:()=>h(()=>import("./index.html-B2inZhc8.js"),__vite__mapDeps([153,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",c:["运维"],e:`

    好记性不如烂笔头

    +`,r:{minutes:1.9,words:570},y:"a",t:"双网卡问题"}}],["/other/linux/",{loader:()=>h(()=>import("./index.html-BIoY637M.js"),__vite__mapDeps([153,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",c:["运维"],e:`

    好记性不如烂笔头

    -`,r:{minutes:.08,words:23},y:"a",t:"Linux"}}],["/other/linux/Samba.html",{loader:()=>h(()=>import("./Samba.html-DATE_iKU.js"),__vite__mapDeps([154,1])),meta:{a:"chenkun",d:1656288e6,l:"2022年6月27日",e:`

    1、安装过程省略

    +`,r:{minutes:.08,words:23},y:"a",t:"Linux"}}],["/other/linux/Samba.html",{loader:()=>h(()=>import("./Samba.html-D8HLrsJP.js"),__vite__mapDeps([154,1])),meta:{a:"chenkun",d:1656288e6,l:"2022年6月27日",e:`

    1、安装过程省略

    2、配置

    2.1 配置文件

    配置目录在 /etc/samba,修改smb.conf在最后加一组[test],同时修改[global]在里面加上ntlm auth = yes,最终加完如下

    @@ -1936,21 +1936,21 @@ mysqldump -h localhost `,r:{minutes:.93,words:280},y:"a",t:"部署Samba"}}],["/other/linux/ShareBetweenWindowsAndLinux.html",{loader:()=>h(()=>import("./ShareBetweenWindowsAndLinux.html-Vr4o6QxT.js"),__vite__mapDeps([155,1])),meta:{a:"chenkun",d:16982784e5,l:"2023年10月26日",e:`

    1、在windows设置共享目录

    +`,r:{minutes:.93,words:280},y:"a",t:"部署Samba"}}],["/other/linux/ShareBetweenWindowsAndLinux.html",{loader:()=>h(()=>import("./ShareBetweenWindowsAndLinux.html-CJ7U7icg.js"),__vite__mapDeps([155,1])),meta:{a:"chenkun",d:16982784e5,l:"2023年10月26日",e:`

    1、在windows设置共享目录

    设置过程省略……

    2、在linux下挂载

        ## 1. 创建空白目录
             mkdir /home/data/share
         ## 2. 修改/etc/fstab开机自动挂载
         //10.10.102.97/tempfile    /home/data/share  cifs    defaults,user=xxx,password=xxx,uid=1000,gid=1000  0 0
    -
    `,r:{minutes:.64,words:192},y:"a",t:"Linux挂载windows共享目录"}}],["/other/linux/TcpDump.html",{loader:()=>h(()=>import("./TcpDump.html-cL04DF4f.js"),__vite__mapDeps([156,1])),meta:{a:"chenkun",d:16518816e5,l:"2022年5月7日",e:`

    简要介绍Linux抓报

    +`,r:{minutes:.64,words:192},y:"a",t:"Linux挂载windows共享目录"}}],["/other/linux/TcpDump.html",{loader:()=>h(()=>import("./TcpDump.html-DFEN5LYg.js"),__vite__mapDeps([156,1])),meta:{a:"chenkun",d:16518816e5,l:"2022年5月7日",e:`

    简要介绍Linux抓报

    作为web开发我抓报主要是针对http请求,主要是看请求行,请求头,请求体,以及响应

    1、所用抓报命令

    sudo tcpdump tcp -i eth1 -t -s 0 -c 100 and dst port ! 22 and src net 192.168.1.0/24 -w ./target.cap
    -
    `,r:{minutes:.97,words:290},y:"a",t:"tcpdump抓包"}}],["/other/linux/Wifi.html",{loader:()=>h(()=>import("./Wifi.html-D7OcL7nb.js"),__vite__mapDeps([157,1])),meta:{d:16616448e5,l:"2022年8月28日",e:`

    1、需求场景

    +`,r:{minutes:.97,words:290},y:"a",t:"tcpdump抓包"}}],["/other/linux/Wifi.html",{loader:()=>h(()=>import("./Wifi.html-DCwxVnq6.js"),__vite__mapDeps([157,1])),meta:{d:16616448e5,l:"2022年8月28日",e:`

    1、需求场景

    场景1:

    有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡, @@ -1959,7 +1959,7 @@ mysqldump -h localhost 场景2:

    有一个笔记本电脑,自带的有wifi模块,但是此无线网卡不支持5G、WIFI6等,此时可以根据情况升级无线网卡;

    -
    `,r:{minutes:.95,words:284},y:"a",t:"Linux下加装wifi模块"}}],["/other/linux/firewall.html",{loader:()=>h(()=>import("./firewall.html-axsN7QwQ.js"),__vite__mapDeps([158,1])),meta:{d:16911072e5,l:"2023年8月4日",o:!0,e:`

    集中常用的防火墙配置工具

    +
    `,r:{minutes:.95,words:284},y:"a",t:"Linux下加装wifi模块"}}],["/other/linux/firewall.html",{loader:()=>h(()=>import("./firewall.html-Bcf4dLh0.js"),__vite__mapDeps([158,1])),meta:{d:16911072e5,l:"2023年8月4日",o:!0,e:`

    集中常用的防火墙配置工具

    1. netfilter
         集成在linux内核中
     2. iptables
    @@ -1972,7 +1972,7 @@ mysqldump -h localhost 5. firewalld
         底层也生成一个iptables表
    -
    `,r:{minutes:12.55,words:3764},y:"a",t:"linux中防火墙"}}],["/other/linux/wsl.html",{loader:()=>h(()=>import("./wsl.html-DoVf1_uE.js"),__vite__mapDeps([159,1])),meta:{a:"chenkun",d:1658448e6,l:"2022年7月22日",e:` +`,r:{minutes:12.55,words:3764},y:"a",t:"linux中防火墙"}}],["/other/linux/wsl.html",{loader:()=>h(()=>import("./wsl.html-DiCvzzYG.js"),__vite__mapDeps([159,1])),meta:{a:"chenkun",d:1658448e6,l:"2022年7月22日",e:`

    二、wsl使用

    2.1 更换国内镜像源

    2.2 从windows进入wsl

    @@ -1983,44 +1983,44 @@ mysqldump -h localhost 2.3 wsl域名解析慢的问题

    域名解析慢会导致apt命令无法在线安装软件,更新等

    -
    `,r:{minutes:.79,words:236},y:"a",t:"windows子系统wsl"}}],["/other/linux/%E6%88%AA%E5%9B%BE.html",{loader:()=>h(()=>import("./截图.html-BRM_4DWV.js"),__vite__mapDeps([160,1])),meta:{a:"chensino",d:1712016e6,l:"2024年4月2日",o:!0,e:`

    manjaro-kde为flameshot设置快捷截图

    +
    `,r:{minutes:.79,words:236},y:"a",t:"windows子系统wsl"}}],["/other/linux/%E6%88%AA%E5%9B%BE.html",{loader:()=>h(()=>import("./截图.html-DV7uCl6s.js"),__vite__mapDeps([160,1])),meta:{a:"chensino",d:1712016e6,l:"2024年4月2日",o:!0,e:`

    manjaro-kde为flameshot设置快捷截图

    1. 打开快捷键设置
     2. 添加命令
     3. 输入\`/usr/bin/flameshot gui\`
     4. 设置一个快捷键
     
    -![20240402111155](https://ddns.chensina.cn:29000/afatpig/blog/20240402111155.png)
    `,r:{minutes:.19,words:57},y:"a",t:"使用Flameshot截图"}}],["/other/markdown/",{loader:()=>h(()=>import("./index.html-DZIt25hg.js"),__vite__mapDeps([161,1])),meta:{a:"chenkun",d:16574976e5,l:"2022年7月11日",c:["markdown"],g:["markdown"],e:`

    Emoji

    +![20240402111155](https://ddns.chensina.cn:29000/afatpig/blog/20240402111155.png)`,r:{minutes:.19,words:57},y:"a",t:"使用Flameshot截图"}}],["/other/markdown/",{loader:()=>h(()=>import("./index.html-BgeMLVVF.js"),__vite__mapDeps([161,1])),meta:{a:"chenkun",d:16574976e5,l:"2022年7月11日",c:["markdown"],g:["markdown"],e:`

    Emoji

    Emoji

    vue-press-theme-hope Icon

    Icon

    -`,r:{minutes:.1,words:29},y:"a",t:"MarkDown资源"}}],["/other/oauth2/01.html",{loader:()=>h(()=>import("./01.html-BpsuZlEh.js"),__vite__mapDeps([162,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    你好,我是王新栋。 +`,r:{minutes:.1,words:29},y:"a",t:"MarkDown资源"}}],["/other/oauth2/01.html",{loader:()=>h(()=>import("./01.html-jkQdEnIh.js"),__vite__mapDeps([162,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    你好,我是王新栋。 在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。

    到这里,我估计你会问,这是怎么实现的?微信把我的个人信息给了极客时间,它又是怎么保证我的数据安全的呢?

    -

    其实,微信这一系列授权背后的原理都可以归到一个词上,那就是 OAuth 2.0。今天这节课,我们就来看看 OAuth 2.0 到底是什么、能干什么以及它是怎么干的。

    `,r:{minutes:10.36,words:3109},y:"a",t:"01 | OAuth 2.0是要通过什么方式解决什么问题?"}}],["/other/oauth2/02.html",{loader:()=>h(()=>import("./02.html-9y4kTt4c.js"),__vite__mapDeps([163,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    你好,我是王新栋。

    +

    其实,微信这一系列授权背后的原理都可以归到一个词上,那就是 OAuth 2.0。今天这节课,我们就来看看 OAuth 2.0 到底是什么、能干什么以及它是怎么干的。

    `,r:{minutes:10.36,words:3109},y:"a",t:"01 | OAuth 2.0是要通过什么方式解决什么问题?"}}],["/other/oauth2/02.html",{loader:()=>h(()=>import("./02.html-B3UlmDxL.js"),__vite__mapDeps([163,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    你好,我是王新栋。

    在上一讲,我提到了 OAuth 2.0 的授权码许可类型,在小兔打单软件的例子里面,小兔最终是通过访问令牌请求到小明的店铺里的订单数据。同时呢,我还提到了,这个访问令牌是通过授权码换来的。到这里估计你会问了,为什么要用授权码来换令牌?为什么不能直接颁发访问令牌呢?

    你可以先停下来想想这个问题。今天咱们这节课,我会带着你深入探究下其中的逻辑。

    为什么需要授权码?

    -

    在讲这个问题之前,我先要和你同步下,在 OAuth 2.0 的体系里面有 4 种角色,按照官方的称呼它们分别是资源拥有者、客户端、授权服务和受保护资源。不过,这里的客户端,我更愿意称其为第三方软件,而且在咱们这个课程中,都是以第三方软件在举例子。所以,在后续的讲解中我统一把它称为第三方软件。

    `,r:{minutes:12.79,words:3838},y:"a",t:"02 | 授权码许可类型中,为什么一定要有授权码?"}}],["/other/oauth2/03.html",{loader:()=>h(()=>import("./03.html-_Fpqszha.js"),__vite__mapDeps([164,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    你好,我是王新栋。

    +

    在讲这个问题之前,我先要和你同步下,在 OAuth 2.0 的体系里面有 4 种角色,按照官方的称呼它们分别是资源拥有者、客户端、授权服务和受保护资源。不过,这里的客户端,我更愿意称其为第三方软件,而且在咱们这个课程中,都是以第三方软件在举例子。所以,在后续的讲解中我统一把它称为第三方软件。

    `,r:{minutes:12.79,words:3838},y:"a",t:"02 | 授权码许可类型中,为什么一定要有授权码?"}}],["/other/oauth2/03.html",{loader:()=>h(()=>import("./03.html-CK1TN99Q.js"),__vite__mapDeps([164,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    你好,我是王新栋。

    在上一讲,我从为什么需要授权码这个问题开始,为你串了一遍授权码许可流程整体的通信过程。在接下来的三讲中,我会着重为你讲解关于授权服务的工作流程、授权过程中的令牌,以及如何接入 OAuth 2.0。这样一来,你就可以吃透授权码许可这一最经典、最完备、最常用的授权流程了,以后再处理授权相关的逻辑就更得心应手了。现在呢,让我们开始这一讲。

    在介绍授权码许可类型时,我提到了很多次 “授权服务”。一句话概括,授权服务就是负责颁发访问令牌的服务。更进一步地讲,OAuth 2.0 的核心是授权服务,而授权服务的核心

    -

    为什么这么说呢?当第三方软件比如小兔,要想获取小明在京东店铺的订单,就必须先从京东商家开放平台的授权服务那里获取访问令牌,进而通过访问令牌来 “代表” 小明去请求小明的订单数据。这不恰恰就是整个 OAuth 2.0 授权体系的核心吗?

    `,r:{minutes:18.59,words:5577},y:"a",t:"03 | 授权服务:授权码和访问令牌的颁发流程是怎样"}}],["/other/oauth2/04.html",{loader:()=>h(()=>import("./04.html-BA8_N2LW.js"),__vite__mapDeps([165,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    在上一讲,我们讲到了授权服务的核心就是颁发访问令牌,而 OAuth 2.0 规范并没有约束访问令牌内容的生成规则,只要符合唯一性、不连续性、不可猜性就够了。这就意味着,我们可以灵活选择令牌的形式,既可以是没有内部结构且不包含任何信息含义的随机字符串,也可以是具有内部结构且包含有信息含义的字符串。

    +

    为什么这么说呢?当第三方软件比如小兔,要想获取小明在京东店铺的订单,就必须先从京东商家开放平台的授权服务那里获取访问令牌,进而通过访问令牌来 “代表” 小明去请求小明的订单数据。这不恰恰就是整个 OAuth 2.0 授权体系的核心吗?

    `,r:{minutes:18.59,words:5577},y:"a",t:"03 | 授权服务:授权码和访问令牌的颁发流程是怎样"}}],["/other/oauth2/04.html",{loader:()=>h(()=>import("./04.html-CeKJQimw.js"),__vite__mapDeps([165,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    在上一讲,我们讲到了授权服务的核心就是颁发访问令牌,而 OAuth 2.0 规范并没有约束访问令牌内容的生成规则,只要符合唯一性、不连续性、不可猜性就够了。这就意味着,我们可以灵活选择令牌的形式,既可以是没有内部结构且不包含任何信息含义的随机字符串,也可以是具有内部结构且包含有信息含义的字符串。

    随机字符串这样的方式我就不再介绍了,之前课程中我们生成令牌的方式都是默认一个随机字符串。而在结构化令牌这方面,目前用得最多的就是 JWT 令牌了。

    -

    接下来,我就要和你详细讲讲,JWT 是什么、原理是怎样的、优势是什么,以及怎么使用,同时我还会讲到令牌生命周期的问题。

    `,r:{minutes:14.53,words:4359},y:"a",t:"04 | 在OAuth 2.0中,如何使用JWT结构化令牌?"}}],["/other/oauth2/05.html",{loader:()=>h(()=>import("./05.html-C12aFhzW.js"),__vite__mapDeps([166,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    第 3 讲,我已经讲了授权服务的流程,如果你还记得的话,当时我特意强调了一点,就是授权服务将 OAuth 2.0 的复杂性都揽在了自己身上,这也是授权服务为什么是 OAuth 2.0 体系的核心的原因之一。

    -

    虽然授权服务做了大部分工作,但是呢,在 OAuth 2.0 的体系里面,除了资源拥有者是作为用户参与,还有另外两个系统角色,也就是第三方软件和受保护资源服务。那么今天这一讲,我们就站在这两个角色的角度,看看它们应该做哪些工作,才能接入到 OAuth 2.0 的体系里面呢?

    `,r:{minutes:14.76,words:4428},y:"a",t:"05 | 如何安全、快速地接入OAuth 2.0?"}}],["/other/oauth2/06.html",{loader:()=>h(()=>import("./06.html-fv-ofJNx.js"),__vite__mapDeps([167,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    在前面几讲学习授权码许可类型的原理与工作流程时,不知道你是不是一直有这样一个疑问:授权码许可的流程最完备、最安全没错儿,但它适合所有的授权场景吗?在有些场景下使用授权码许可授权,是不是过于复杂了,是不是根本就没必要这样?

    +

    接下来,我就要和你详细讲讲,JWT 是什么、原理是怎样的、优势是什么,以及怎么使用,同时我还会讲到令牌生命周期的问题。

    `,r:{minutes:14.53,words:4359},y:"a",t:"04 | 在OAuth 2.0中,如何使用JWT结构化令牌?"}}],["/other/oauth2/05.html",{loader:()=>h(()=>import("./05.html-CMuhT-M0.js"),__vite__mapDeps([166,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    第 3 讲,我已经讲了授权服务的流程,如果你还记得的话,当时我特意强调了一点,就是授权服务将 OAuth 2.0 的复杂性都揽在了自己身上,这也是授权服务为什么是 OAuth 2.0 体系的核心的原因之一。

    +

    虽然授权服务做了大部分工作,但是呢,在 OAuth 2.0 的体系里面,除了资源拥有者是作为用户参与,还有另外两个系统角色,也就是第三方软件和受保护资源服务。那么今天这一讲,我们就站在这两个角色的角度,看看它们应该做哪些工作,才能接入到 OAuth 2.0 的体系里面呢?

    `,r:{minutes:14.76,words:4428},y:"a",t:"05 | 如何安全、快速地接入OAuth 2.0?"}}],["/other/oauth2/06.html",{loader:()=>h(()=>import("./06.html-BX6nV64C.js"),__vite__mapDeps([167,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    在前面几讲学习授权码许可类型的原理与工作流程时,不知道你是不是一直有这样一个疑问:授权码许可的流程最完备、最安全没错儿,但它适合所有的授权场景吗?在有些场景下使用授权码许可授权,是不是过于复杂了,是不是根本就没必要这样?

    比如,小兔打单软件是京东官方开发的一款软件,那么小明在使用小兔的时候,还需要小兔再走一遍授权码许可类型的流程吗?估计你也猜到答案了,肯定是不需要了。

    你还记得授权码许可流程的特点么?它通过授权码这种临时的中间值,让小明这样的用户参与进来,从而让小兔软件和京东之间建立联系,进而让小兔代表小明去访问他在京东店铺的订单数据。

    -

    现在小兔被“招安”了,是京东自家的了,是被京东充分信任的,没有“第三方软件”的概念了。同时,小明也是京东店铺的商家,也就是说软件和用户都是京东的资产。这时,显然没有必要再使用授权码许可类型进行授权了。但是呢,小兔依然要通过互联网访问订单数据的 Web API,来提供为小明打单的功能。

    `,r:{minutes:11.08,words:3325},y:"a",t:"06 | 除了授权码许可类型,OAuth 2.0还支持什么授权流程?"}}],["/other/oauth2/07.html",{loader:()=>h(()=>import("./07.html-C8FkfsLs.js"),__vite__mapDeps([168,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    在前面几讲中,我都是基于 Web 应用的场景来讲解的 OAuth 2.0。除了 Web 应用外,现实环境中还有非常多的移动 App。那么,在移动 App 中,能不能使用 OAuth 2.0 ,又该如何使用 OAuth 2.0 呢?

    +

    现在小兔被“招安”了,是京东自家的了,是被京东充分信任的,没有“第三方软件”的概念了。同时,小明也是京东店铺的商家,也就是说软件和用户都是京东的资产。这时,显然没有必要再使用授权码许可类型进行授权了。但是呢,小兔依然要通过互联网访问订单数据的 Web API,来提供为小明打单的功能。

    `,r:{minutes:11.08,words:3325},y:"a",t:"06 | 除了授权码许可类型,OAuth 2.0还支持什么授权流程?"}}],["/other/oauth2/07.html",{loader:()=>h(()=>import("./07.html-CCBD9WTC.js"),__vite__mapDeps([168,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    在前面几讲中,我都是基于 Web 应用的场景来讲解的 OAuth 2.0。除了 Web 应用外,现实环境中还有非常多的移动 App。那么,在移动 App 中,能不能使用 OAuth 2.0 ,又该如何使用 OAuth 2.0 呢?

    没错,OAuth 2.0 最初的应用场景确实是 Web 应用,但是它的伟大之处就在于,它把自己的核心协议定位成了一个框架而不是单个的协议。这样做的好处是,我们可以基于这个基本的框架协议,在一些特定的领域进行扩展。

    -

    因此,到了桌面或者移动的场景下,OAuth 2.0 的协议一样适用。考虑到授权码许可是最完备、最安全的许可类型,所以我在讲移动 App 如何使用 OAuth 2.0 的时候,依然会用授权码许可来讲解,毕竟“要用就用最好的”。

    `,r:{minutes:9.48,words:2845},y:"a",t:"07 | 如何在移动App中使用OAuth 2.0?"}}],["/other/oauth2/08.html",{loader:()=>h(()=>import("./08.html-DoM55lqI.js"),__vite__mapDeps([169,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    当知道这一讲的主题是 OAuth 2.0 的安全漏洞时,你可能要问了:“OAuth 2.0 不是一种安全协议吗,不是保护 Web API 的吗?为啥 OAuth 2.0 自己还有安全的问题了呢?”

    +

    因此,到了桌面或者移动的场景下,OAuth 2.0 的协议一样适用。考虑到授权码许可是最完备、最安全的许可类型,所以我在讲移动 App 如何使用 OAuth 2.0 的时候,依然会用授权码许可来讲解,毕竟“要用就用最好的”。

    `,r:{minutes:9.48,words:2845},y:"a",t:"07 | 如何在移动App中使用OAuth 2.0?"}}],["/other/oauth2/08.html",{loader:()=>h(()=>import("./08.html-BJMKXxmH.js"),__vite__mapDeps([169,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    当知道这一讲的主题是 OAuth 2.0 的安全漏洞时,你可能要问了:“OAuth 2.0 不是一种安全协议吗,不是保护 Web API 的吗?为啥 OAuth 2.0 自己还有安全的问题了呢?”

    首先,OAuth 2.0 的确是一种安全协议。这没啥问题,但是它有很多使用规范,比如授权码是一个临时凭据只能被使用一次,要对重定向 URI 做校验等。那么,如果使用的时候你没有按照这样的规范来实施,就会有安全漏洞了。

    -

    其次,OAuth 2.0 既然是“生长”在互联网这个大环境中,就一样会面对互联网上常见安全风险的攻击,比如跨站请求伪造(Cross-site request forgery,CSRF)、跨站脚本攻击(Cross Site Scripting,XSS)。最后,除了这些常见攻击类型外,OAuth 2.0 自身也有可被利用的安全漏洞,比如授权码失窃、重定向 URI 伪造。

    `,r:{minutes:15.51,words:4652},y:"a",t:"08 | 实践OAuth 2.0时,使用不当可能会导致哪些安全漏洞?"}}],["/other/oauth2/09.html",{loader:()=>h(()=>import("./09.html-Aa1WASXU.js"),__vite__mapDeps([170,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    如果你是一个第三方软件开发者,在实现用户登录的逻辑时,除了可以让用户新注册一个账号再登录外,还可以接入微信、微博等平台,让用户使用自己的微信、微博账号去登录。同时,如果你的应用下面又有多个子应用,还可以让用户只登录一次就能访问所有的子应用,来提升用户体验。这就是联合登录和单点登录了。再继续深究,它们其实都是 OpenID Connect(简称 OIDC)的应用场景的实现。那 OIDC 又是什么呢?今天,我们就来学习下 OIDC 和 OAuth 2.0 的关系,以及如何用 OAuth 2.0 来实现一个 OIDC 用户身份认证协议。

    +

    其次,OAuth 2.0 既然是“生长”在互联网这个大环境中,就一样会面对互联网上常见安全风险的攻击,比如跨站请求伪造(Cross-site request forgery,CSRF)、跨站脚本攻击(Cross Site Scripting,XSS)。最后,除了这些常见攻击类型外,OAuth 2.0 自身也有可被利用的安全漏洞,比如授权码失窃、重定向 URI 伪造。

    `,r:{minutes:15.51,words:4652},y:"a",t:"08 | 实践OAuth 2.0时,使用不当可能会导致哪些安全漏洞?"}}],["/other/oauth2/09.html",{loader:()=>h(()=>import("./09.html-7MXGKP1a.js"),__vite__mapDeps([170,1])),meta:{a:"王新栋",d:16678656e5,l:"2022年11月8日",e:`

    如果你是一个第三方软件开发者,在实现用户登录的逻辑时,除了可以让用户新注册一个账号再登录外,还可以接入微信、微博等平台,让用户使用自己的微信、微博账号去登录。同时,如果你的应用下面又有多个子应用,还可以让用户只登录一次就能访问所有的子应用,来提升用户体验。这就是联合登录和单点登录了。再继续深究,它们其实都是 OpenID Connect(简称 OIDC)的应用场景的实现。那 OIDC 又是什么呢?今天,我们就来学习下 OIDC 和 OAuth 2.0 的关系,以及如何用 OAuth 2.0 来实现一个 OIDC 用户身份认证协议。

    OIDC 是什么?

    -

    OIDC 其实就是一种用户身份认证的开放标准。使用微信账号登录极客时间的场景,就是这种开放标准的实践。

    `,r:{minutes:12.38,words:3713},y:"a",t:"09 | 实战:利用OAuth 2.0实现一个OpenID Connect用户身份认证协议"}}],["/other/oauth2/10.html",{loader:()=>h(()=>import("./10.html-D50UJxV3.js"),__vite__mapDeps([171,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。

    +

    OIDC 其实就是一种用户身份认证的开放标准。使用微信账号登录极客时间的场景,就是这种开放标准的实践。

    `,r:{minutes:12.38,words:3713},y:"a",t:"09 | 实战:利用OAuth 2.0实现一个OpenID Connect用户身份认证协议"}}],["/other/oauth2/10.html",{loader:()=>h(()=>import("./10.html-IqVcln-C.js"),__vite__mapDeps([171,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。

    当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。

    好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。

    OAuth 2.0 工作流程串讲

    -

    img

    `,r:{minutes:10.17,words:3052},y:"a",t:"10 | 串讲:OAuth 2.0的工作流程与安全问题"}}],["/other/oauth2/11.html",{loader:()=>h(()=>import("./11.html-DLQhthQQ.js"),__vite__mapDeps([172,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    你好,我朱晔,是《Java 业务开发常见错误 100 例》专栏课程的作者。

    -

    《OAuth 2.0 实战课》上线之后,我也第一时间关注了这门课。在开篇词中,我看到有一些同学留言问道:“如何使用 Spring Security 来实现 OAuth 2.0?”这时,我想到之前自己写过一篇相关的文章,于是就直接在开篇词下留了言。后面我很快收到了不少用户的点赞和肯定,紧接着极客时间编辑也邀请我从自己的角度为专栏写篇加餐。好吧,功不唐捐,于是我就将之前我写的那篇老文章再次迭代、整理为今天的这一讲内容,希望可以帮助你掌握 OAuth 2.0。

    `,r:{minutes:25.57,words:7670},y:"a",t:"11 | 实战案例:使用Spring Security搭建一套基于JWT的OAuth 2.0架构"}}],["/other/oauth2/12.html",{loader:()=>h(()=>import("./12.html-By4kPY-J.js"),__vite__mapDeps([173,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`
    +

    img

    `,r:{minutes:10.17,words:3052},y:"a",t:"10 | 串讲:OAuth 2.0的工作流程与安全问题"}}],["/other/oauth2/11.html",{loader:()=>h(()=>import("./11.html-kw9nHLna.js"),__vite__mapDeps([172,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    你好,我朱晔,是《Java 业务开发常见错误 100 例》专栏课程的作者。

    +

    《OAuth 2.0 实战课》上线之后,我也第一时间关注了这门课。在开篇词中,我看到有一些同学留言问道:“如何使用 Spring Security 来实现 OAuth 2.0?”这时,我想到之前自己写过一篇相关的文章,于是就直接在开篇词下留了言。后面我很快收到了不少用户的点赞和肯定,紧接着极客时间编辑也邀请我从自己的角度为专栏写篇加餐。好吧,功不唐捐,于是我就将之前我写的那篇老文章再次迭代、整理为今天的这一讲内容,希望可以帮助你掌握 OAuth 2.0。

    `,r:{minutes:25.57,words:7670},y:"a",t:"11 | 实战案例:使用Spring Security搭建一套基于JWT的OAuth 2.0架构"}}],["/other/oauth2/12.html",{loader:()=>h(()=>import("./12.html-DxvMj_JN.js"),__vite__mapDeps([173,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。

    @@ -2028,9 +2028,9 @@ mysqldump -h localhost

    其中,在携程工作期间,他负责过携程的 API 网关产品的研发工作,包括它和携程的令牌服务的集成;在拍拍贷工作期间,他负责过拍拍贷的令牌服务的研发和运维工作。这两家公司的令牌服务和 OAuth 2.0 类似,但要更简单些。

    -
    `,r:{minutes:19.48,words:5844},y:"a",t:"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构"}}],["/other/oauth2/13.html",{loader:()=>h(()=>import("./13.html-DbVAkfja.js"),__vite__mapDeps([174,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就

    +
    `,r:{minutes:19.48,words:5844},y:"a",t:"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构"}}],["/other/oauth2/13.html",{loader:()=>h(()=>import("./13.html-CBYdZLrV.js"),__vite__mapDeps([174,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就

    到这里,你会发现“开放平台的官方文档”会是一个关键点。不过呢,当你去各大开放平台上面看这些文

    -

    其中的原因也很简单,那就是开放平台为了让已经具备 OAuth 2.0 知识的研发人员去快速地对接平台上面的业务,把各类对接流程做了分类归档。比如,你会发现微信开放平台上有使用授权码获取授权信息的文档,也有获取令牌的文档,但并没有一份整体的、能够串起来的文档说明。从我的角度来看,这其实也就间接提高了使用门槛,因为如果你不懂 OAuth 2.0,基本是

    `,r:{minutes:11.53,words:3458},y:"a",t:"13 | 各大开放平台是如何使用OAuth 2.0的?"}}],["/other/oauth2/14.html",{loader:()=>h(()=>import("./14.html-DC_xS0f7.js"),__vite__mapDeps([175,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题:

    +

    其中的原因也很简单,那就是开放平台为了让已经具备 OAuth 2.0 知识的研发人员去快速地对接平台上面的业务,把各类对接流程做了分类归档。比如,你会发现微信开放平台上有使用授权码获取授权信息的文档,也有获取令牌的文档,但并没有一份整体的、能够串起来的文档说明。从我的角度来看,这其实也就间接提高了使用门槛,因为如果你不懂 OAuth 2.0,基本是

    `,r:{minutes:11.53,words:3458},y:"a",t:"13 | 各大开放平台是如何使用OAuth 2.0的?"}}],["/other/oauth2/14.html",{loader:()=>h(()=>import("./14.html-B-d6pKTK.js"),__vite__mapDeps([175,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题:

    1. 发明 OAuth 的目的到底是什么?

      @@ -2050,15 +2050,15 @@ mysqldump -h localhost

      PKCE 协议到底解决的是什么问题?

    2. -
    `,r:{minutes:10.29,words:3086},y:"a",t:"14 | 查漏补缺:OAuth 2.0 常见问题答疑"}}],["/other/oauth2/",{loader:()=>h(()=>import("./index.html-CrtTLfwo.js"),__vite__mapDeps([176,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`
    +`,r:{minutes:10.29,words:3086},y:"a",t:"14 | 查漏补缺:OAuth 2.0 常见问题答疑"}}],["/other/oauth2/",{loader:()=>h(()=>import("./index.html-0gfJBSgv.js"),__vite__mapDeps([176,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

    申明

    oauth2相关博客转载于极客时间

    你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。

    -

    我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,毕竟网关要承载整个开放平台的调用量,同时还要有足够的系统容错能力。

    `,r:{minutes:7.87,words:2360},y:"a",t:"开篇词 | 为什么要学OAuth 2.0?"}}],["/other/pve/",{loader:()=>h(()=>import("./index.html-D5JX-dxI.js"),__vite__mapDeps([177,1])),meta:{a:"chensino",d:17283456e5,l:"2024年10月8日",o:!0,e:`

    pve

    +

    我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,毕竟网关要承载整个开放平台的调用量,同时还要有足够的系统容错能力。

    `,r:{minutes:7.87,words:2360},y:"a",t:"开篇词 | 为什么要学OAuth 2.0?"}}],["/other/pve/",{loader:()=>h(()=>import("./index.html-DEubCK6b.js"),__vite__mapDeps([177,1])),meta:{a:"chensino",d:17283456e5,l:"2024年10月8日",o:!0,e:`

    pve

    这是一个好玩的工具

    -`,r:{minutes:.09,words:27},y:"a",t:"玩转PVE虚拟机"}}],["/other/pve/ddns%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6.html",{loader:()=>h(()=>import("./ddns申请证书.html-D3MLskiS.js"),__vite__mapDeps([178,1])),meta:{a:"chensino",d:17276544e5,l:"2024年9月30日",o:!0,e:`

    acme官方文档:https://github.com/acmesh-official/acme.sh -pve官方文档:https://pve-doc-cn.readthedocs.io/zh-cn/latest/chapter_system_administration/certmgr.html

    `,r:{minutes:2.15,words:645},y:"a",t:"ddns证书"}}],["/other/pve/firewall.html",{loader:()=>h(()=>import("./firewall.html-CDBaOBQ8.js"),__vite__mapDeps([179,1])),meta:{a:"chensino",d:1727568e6,l:"2024年9月29日",o:!0,e:`

    参考:https://www.xh86.me/?p=11324

    +`,r:{minutes:.09,words:27},y:"a",t:"玩转PVE虚拟机"}}],["/other/pve/ddns%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6.html",{loader:()=>h(()=>import("./ddns申请证书.html-ykZMVUjH.js"),__vite__mapDeps([178,1])),meta:{a:"chensino",d:17276544e5,l:"2024年9月30日",o:!0,e:`

    acme官方文档:https://github.com/acmesh-official/acme.sh +pve官方文档:https://pve-doc-cn.readthedocs.io/zh-cn/latest/chapter_system_administration/certmgr.html

    `,r:{minutes:2.15,words:645},y:"a",t:"ddns证书"}}],["/other/pve/firewall.html",{loader:()=>h(()=>import("./firewall.html-CnVk-61G.js"),__vite__mapDeps([179,1])),meta:{a:"chensino",d:1727568e6,l:"2024年9月29日",o:!0,e:`

    参考:https://www.xh86.me/?p=11324

    一、防火墙简介

    pve有3个层级防火墙:

      @@ -2066,10 +2066,10 @@ pve官方文档:

      `,r:{minutes:2.2,words:660},y:"a",t:"pve防火墙"}}],["/other/rabbitmq/",{loader:()=>h(()=>import("./index.html-CTOJOPSY.js"),__vite__mapDeps([180,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

      从入门到放弃

      +

      20240929173211

      `,r:{minutes:2.2,words:660},y:"a",t:"pve防火墙"}}],["/other/rabbitmq/",{loader:()=>h(()=>import("./index.html-CuULl194.js"),__vite__mapDeps([180,1])),meta:{a:"ChenSino",d:16678656e5,l:"2022年11月8日",e:`

      从入门到放弃

      官网

      中文文档

      -

      在线 动态演示工具

      `,r:{minutes:.12,words:37},y:"a",t:"rabbitmq"}}],["/other/software/%E7%A0%B4%E8%A7%A3%E8%BD%AF%E4%BB%B6.html",{loader:()=>h(()=>import("./破解软件.html-CYd3SkT3.js"),__vite__mapDeps([181,1])),meta:{a:"chensino",d:1698624e6,l:"2023年10月30日",e:`

      smartsvn

      +

      在线 动态演示工具

      `,r:{minutes:.12,words:37},y:"a",t:"rabbitmq"}}],["/other/software/%E7%A0%B4%E8%A7%A3%E8%BD%AF%E4%BB%B6.html",{loader:()=>h(()=>import("./破解软件.html-DO8LI6dF.js"),__vite__mapDeps([181,1])),meta:{a:"chensino",d:1698624e6,l:"2023年10月30日",e:`

      smartsvn

      安装过程略......

      创建smartsvn.license,把以下内容复制进去,激活时选择smartsvn.license就可以了

      Name=csdn  
      @@ -2081,14 +2081,14 @@ Addon-xMerge=true
       Addon-API=true  
       Enterprise=true  
       Key=4kl-<Zqcm-iUF7I-IVmYG-XAyvv-KYRoC-xlgsv-sSBds-VAnP6
      -
      `,r:{minutes:.25,words:75},y:"a",t:"软件激活"}}],["/other/sono/SUC.html",{loader:()=>h(()=>import("./SUC.html-BJ48MGxE.js"),__vite__mapDeps([182,1])),meta:{d:1675056506e3,c:["公司业务"],e:`

      1、基础环境搭建

      +
    `,r:{minutes:.25,words:75},y:"a",t:"软件激活"}}],["/other/sono/SUC.html",{loader:()=>h(()=>import("./SUC.html-DtVpWo_q.js"),__vite__mapDeps([182,1])),meta:{d:1675056506e3,c:["公司业务"],e:`

    1、基础环境搭建

    1.1 引入依赖

           <dependency>
                 <groupId>org.springframework.security.oauth.boot</groupId>
                 <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                 <version>2.1.2.RELEASE</version>
             </dependency>
    -
    `,r:{minutes:1.34,words:403},y:"a",t:"客户端对接用户中心"}}],["/other/tools/Idea.html",{loader:()=>h(()=>import("./Idea.html-N3gMCtcp.js"),__vite__mapDeps([183,1])),meta:{a:"chenkun",d:16590528e5,l:"2022年7月29日",e:`

    1、多线程debug遇到的问题

    +`,r:{minutes:1.34,words:403},y:"a",t:"客户端对接用户中心"}}],["/other/tools/Idea.html",{loader:()=>h(()=>import("./Idea.html-DFw9lvM7.js"),__vite__mapDeps([183,1])),meta:{a:"chenkun",d:16590528e5,l:"2022年7月29日",e:`

    1、多线程debug遇到的问题

    多线程调试需要把Thread选上,至于Thread和All的区别请查看官方文档

    `,r:{minutes:2.72,words:817},y:"a",t:"Idea"}}],["/other/tools/",{loader:()=>h(()=>import("./index.html-DxrpWTG5.js"),__vite__mapDeps([184,1])),meta:{a:"chenkun",d:16477344e5,l:"2022年3月20日",e:` -`,r:{minutes:.05,words:15},y:"a",t:"软件工具"}}],["/other/tools/SoftWare.html",{loader:()=>h(()=>import("./SoftWare.html-DwRE_esc.js"),__vite__mapDeps([185,1])),meta:{a:"chenkun",d:1658448e6,l:"2022年7月22日",e:`

    1、Beyond Compare3

    +`,r:{minutes:2.72,words:817},y:"a",t:"Idea"}}],["/other/tools/",{loader:()=>h(()=>import("./index.html--jCoRugf.js"),__vite__mapDeps([184,1])),meta:{a:"chenkun",d:16477344e5,l:"2022年3月20日",e:` +`,r:{minutes:.05,words:15},y:"a",t:"软件工具"}}],["/other/tools/SoftWare.html",{loader:()=>h(()=>import("./SoftWare.html-6EROxq9x.js"),__vite__mapDeps([185,1])),meta:{a:"chenkun",d:1658448e6,l:"2022年7月22日",e:`

    1、Beyond Compare3

    1.1、下载

    资源地址

    1.2 、集成到git 对比

    -

    参考此处配置,主要有两步,

    `,r:{minutes:.3,words:89},y:"a",t:"软件分享"}}],["/other/tools/VsCode.html",{loader:()=>h(()=>import("./VsCode.html-o-J8uPWj.js"),__vite__mapDeps([186,1])),meta:{a:"chenkun",d:16578432e5,l:"2022年7月15日",e:` +

    参考此处配置,主要有两步,

    `,r:{minutes:.3,words:89},y:"a",t:"软件分享"}}],["/other/tools/VsCode.html",{loader:()=>h(()=>import("./VsCode.html-BCn8Egxl.js"),__vite__mapDeps([186,1])),meta:{a:"chenkun",d:16578432e5,l:"2022年7月15日",e:`

    版本: 1.69.1 (user setup) 提交: b06ae3b2d2dbfe28bca3134cc6be65935cdfea6a @@ -2117,7 +2117,7 @@ OS: Windows_NT x64 6.1.7601

    现象,使用vscod打开终端一直卡着。

    解决方法是禁用GPU加速,猜测是因为公司电脑没有独显的原因

    -
    `,r:{minutes:1.18,words:353},y:"a",t:"vscode配置"}}],["/other/training/CloudServiceTraining.html",{loader:()=>h(()=>import("./CloudServiceTraining.html-C8NinniP.js"),__vite__mapDeps([187,1])),meta:{a:"chenkun",d:1657584e6,l:"2022年7月12日",c:["小组分享"],e:` +
    `,r:{minutes:1.18,words:353},y:"a",t:"vscode配置"}}],["/other/training/CloudServiceTraining.html",{loader:()=>h(()=>import("./CloudServiceTraining.html-CsW1-SEa.js"),__vite__mapDeps([187,1])),meta:{a:"chenkun",d:1657584e6,l:"2022年7月12日",c:["小组分享"],e:`

    [TOC]

    问题:

    @@ -2131,11 +2131,11 @@ OS: Windows_NT x64 6.1.7601

    本章节参考DNS 原理入门——阮一峰

    1.1 什么是DNS

    DNS (Domain Name System 的缩写)的作用非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。 -举例来说,如果你要访问域名math.stackexchange.com,首先要通过DNS查出它的IP地址是151.101.129.69。

    `,r:{minutes:9.66,words:2899},y:"a",t:"小组分享-云服务"}}],["/other/training/",{loader:()=>h(()=>import("./index.html-DKR14bqO.js"),__vite__mapDeps([188,1])),meta:{a:"chenkun",d:1657584e6,l:"2022年7月12日",e:"",r:{minutes:.05,words:15},y:"a",t:"小组分享"}}],["/other/training/SSO.html",{loader:()=>h(()=>import("./SSO.html-DgwliJOO.js"),__vite__mapDeps([189,1])),meta:{a:"chenkun",d:16652736e5,l:"2022年10月9日",c:["小组分享"],e:`

    1、SSO

    +举例来说,如果你要访问域名math.stackexchange.com,首先要通过DNS查出它的IP地址是151.101.129.69。

    `,r:{minutes:9.66,words:2899},y:"a",t:"小组分享-云服务"}}],["/other/training/",{loader:()=>h(()=>import("./index.html-Bm2QW3Yx.js"),__vite__mapDeps([188,1])),meta:{a:"chenkun",d:1657584e6,l:"2022年7月12日",e:"",r:{minutes:.05,words:15},y:"a",t:"小组分享"}}],["/other/training/SSO.html",{loader:()=>h(()=>import("./SSO.html-DB2D0akT.js"),__vite__mapDeps([189,1])),meta:{a:"chenkun",d:16652736e5,l:"2022年10月9日",c:["小组分享"],e:`

    1、SSO

    1.1 SSO介绍

    单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    1.2 SSO使用场景(解决了什么问题)

    -

    很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都要进行注册登录,退出的时候又要一个个退出。用户体验很不好!你可以想象一下,上豆瓣 要登录豆瓣FM、豆瓣读书、豆瓣电影豆瓣日记......真的会让人崩溃的。我们想要另一种登录体验:一家企业下的服务只要一次注册,登录的时候只要一次登录,退出的时候只要一次退出。怎么做?

    `,r:{minutes:7.15,words:2144},y:"a",t:"OAuth2分享"}}],["/other/training/draw.html",{loader:()=>h(()=>import("./draw.html-3iC56TfH.js"),__vite__mapDeps([190,1])),meta:{a:"chenkun",d:16690752e5,l:"2022年11月22日",c:["小组分享"],e:`

    1 示例

    +

    很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都要进行注册登录,退出的时候又要一个个退出。用户体验很不好!你可以想象一下,上豆瓣 要登录豆瓣FM、豆瓣读书、豆瓣电影豆瓣日记......真的会让人崩溃的。我们想要另一种登录体验:一家企业下的服务只要一次注册,登录的时候只要一次登录,退出的时候只要一次退出。怎么做?

    `,r:{minutes:7.15,words:2144},y:"a",t:"OAuth2分享"}}],["/other/training/draw.html",{loader:()=>h(()=>import("./draw.html-DWXvV2Rt.js"),__vite__mapDeps([190,1])),meta:{a:"chenkun",d:16690752e5,l:"2022年11月22日",c:["小组分享"],e:`

    1 示例

    https://paper.pigx.vip/

    2 制图工具

    @@ -2179,12 +2179,12 @@ OS: Windows_NT x64 6.1.7601

    -
    ★★★
    `,r:{minutes:1.18,words:354},y:"a",t:"画图工具"}}],["/other/web/BuildWebProject.html",{loader:()=>h(()=>import("./BuildWebProject.html-D1F1nzup.js"),__vite__mapDeps([191,1])),meta:{a:"chenkun",d:16577568e5,l:"2022年7月14日",c:["web"],g:["web","springboot"],e:`
    +`,r:{minutes:1.18,words:354},y:"a",t:"画图工具"}}],["/other/web/BuildWebProject.html",{loader:()=>h(()=>import("./BuildWebProject.html-D4yumRMO.js"),__vite__mapDeps([191,1])),meta:{a:"chenkun",d:16577568e5,l:"2022年7月14日",c:["web"],g:["web","springboot"],e:`

    温馨提示

    项目地址,每个节点的代码使用commitId作为区分,想看某个节点代码,直接还原到对应commitid即可,执行git reset --hard commitId

    1、后端篇

    -

    1.1 初始化springboot项目

    `,r:{minutes:11.48,words:3445},y:"a",t:"前后分离项目搭建"}}],["/other/web/Cookie.html",{loader:()=>h(()=>import("./Cookie.html-ffctXEz0.js"),__vite__mapDeps([192,1])),meta:{a:"qianxun",d:1666717021e3,l:"2022年10月25日",g:["必会"],e:`

    Cookie的作用域domain

    +

    1.1 初始化springboot项目

    `,r:{minutes:11.48,words:3445},y:"a",t:"前后分离项目搭建"}}],["/other/web/Cookie.html",{loader:()=>h(()=>import("./Cookie.html-BobniOCU.js"),__vite__mapDeps([192,1])),meta:{a:"qianxun",d:1666717021e3,l:"2022年10月25日",g:["必会"],e:`

    Cookie的作用域domain

    一级域名:aaa.com 二级域名:bbb.aaa.com 三级域名:ccc.bbb.aaa.com

    @@ -2195,17 +2195,17 @@ aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.b

    划重点

    在当前域名下,只能设置当前域以及父域的cookie,不能设置子域下的cookie。例如我在浏览器访问后端服务的域名为bbb.aaa.com时,我在后端就只能把cookie的 域设置为当前域(缺省状态就是当前)或者设置为其父域名aaa.com,而不能设置为其子域名ccc.bbb.aaa.com,设置子域名前端SetCookie或有警告

    -
    `,r:{minutes:1.81,words:542},y:"a",t:"Cookie"}}],["/other/web/Http.html",{loader:()=>h(()=>import("./Http.html-C3TqYNG0.js"),__vite__mapDeps([193,1])),meta:{a:"chensino",d:16672608e5,l:"2022年11月1日",g:["必会"],e:`
    +
    `,r:{minutes:1.81,words:542},y:"a",t:"Cookie"}}],["/other/web/Http.html",{loader:()=>h(()=>import("./Http.html-Cx1NIixk.js"),__vite__mapDeps([193,1])),meta:{a:"chensino",d:16672608e5,l:"2022年11月1日",g:["必会"],e:`

    必看手册

    https://developer.mozilla.org/zh-CN/docs/Web/HTTP

    一、状态码

    1.1 3xx

    1.1.1 304

    -

    HTTP 304 Not Modified 说明无需再次传输请求的内容,也就是说可以使用缓存的内容。这通常是在一些安全的方法(safe),例如GET 或HEAD 或在请求中附带了头部信息: If-None-Match 或If-Modified-Since。

    `,r:{minutes:2.11,words:632},y:"a",t:"Http"}}],["/other/web/Jwt.html",{loader:()=>h(()=>import("./Jwt.html-BI9W3_p_.js"),__vite__mapDeps([194,1])),meta:{a:"chenkun",d:1666687039e3,c:["web"],g:["web"],e:`

    1、jwt在服务端如何校验的?

    +

    HTTP 304 Not Modified 说明无需再次传输请求的内容,也就是说可以使用缓存的内容。这通常是在一些安全的方法(safe),例如GET 或HEAD 或在请求中附带了头部信息: If-None-Match 或If-Modified-Since。

    `,r:{minutes:2.11,words:632},y:"a",t:"Http"}}],["/other/web/Jwt.html",{loader:()=>h(()=>import("./Jwt.html-CP1cMj9m.js"),__vite__mapDeps([194,1])),meta:{a:"chenkun",d:1666687039e3,c:["web"],g:["web"],e:`

    1、jwt在服务端如何校验的?

    之前一直用jwt但是仅仅了解他的基本原理没有去思考一个问题——服务端是如何校验jwt的?

    了解过jwt原理的同学都知道jwt是可以自校验的,token里面有header,payload,我有个想法就是如果用户随便生成一个token,那后端是如何知道这个token不能用?或者我把开发环境的token拿到生产环境使用 是否可行?

    -

    jwt用户认证流程如下,因jwt的token是无状态的,所以每次请求都要经过过滤器进行校验,第一次登陆后把生成的token缓存到redis,当校验时如果找到对应token则继续,解析head中userid信息,根据userid查用户角色权限等信息,然后再设置到security的context中,具体代码如下

    `,r:{minutes:3,words:899},y:"a",t:"jwt"}}],["/other/web/OAUTH_LOGIN.html",{loader:()=>h(()=>import("./OAUTH_LOGIN.html-Dt5KbJsg.js"),__vite__mapDeps([195,1])),meta:{d:16825536e5,l:"2023年4月27日",c:["oauth"],g:["oauth"],o:!0,e:`
    +

    jwt用户认证流程如下,因jwt的token是无状态的,所以每次请求都要经过过滤器进行校验,第一次登陆后把生成的token缓存到redis,当校验时如果找到对应token则继续,解析head中userid信息,根据userid查用户角色权限等信息,然后再设置到security的context中,具体代码如下

    `,r:{minutes:3,words:899},y:"a",t:"jwt"}}],["/other/web/OAUTH_LOGIN.html",{loader:()=>h(()=>import("./OAUTH_LOGIN.html-DZ9PB-Wb.js"),__vite__mapDeps([195,1])),meta:{d:16825536e5,l:"2023年4月27日",c:["oauth"],g:["oauth"],o:!0,e:`

    本博客介绍前后端分离项目的完整接入oauth的流程,本博客使用github来示范,因为github注册oauth应用无须审核,微信审核特别麻烦,并且个人用户无法注册

    @@ -2214,19 +2214,19 @@ aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.b
        1. 社交帐号登录应用,比如使用微信、微博登录其它应用
         2. 从第三方获取用户资料比如:手机号、邮箱、头像等
         3. 从第三方获取业务数据,比如:通过自己的系统想从京东获取订单
    -
    `,r:{minutes:5.84,words:1752},y:"a",t:"oauth第三方登录"}}],["/other/web/",{loader:()=>h(()=>import("./index.html-7hihg5cE.js"),__vite__mapDeps([196,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",c:["web"],g:["web"],e:`
    +
    `,r:{minutes:5.84,words:1752},y:"a",t:"oauth第三方登录"}}],["/other/web/",{loader:()=>h(()=>import("./index.html-DZ7gwFx0.js"),__vite__mapDeps([196,1])),meta:{a:"chenkun",d:16474752e5,l:"2022年3月17日",c:["web"],g:["web"],e:`

    此分类记录web开发通用知识

    -`,r:{minutes:.11,words:33},y:"a",t:"web开发通用知识"}}],["/other/web/RefreshToken.html",{loader:()=>h(()=>import("./RefreshToken.html-CFwkbthk.js"),__vite__mapDeps([197,1])),meta:{d:1683504e6,l:"2023年5月8日",o:!0,e:`

    1、 refresh_token介绍

    -

    ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_token和refresh_token,并在access_token过期时使用refresh_token申请新的access_token。refresh_token的安全性非常重要,授权服务器会根据设定的有效期对refresh_token进行管理,以确保对用户的信息和资源进行保护。同时,在使用refresh_token时,也需要进行国家或行业标准的数据保护和安全性认证,以避免出现不必要的风险和隐私泄漏。

    `,r:{minutes:2.1,words:631},y:"a",t:"refresh_token"}}],["/other/web/Restful.html",{loader:()=>h(()=>import("./Restful.html-X9thQzel.js"),__vite__mapDeps([198,1])),meta:{a:"chenkun",d:16574976e5,l:"2022年7月11日",c:["web"],g:["web"],e:`

    一、参考

    +`,r:{minutes:.11,words:33},y:"a",t:"web开发通用知识"}}],["/other/web/RefreshToken.html",{loader:()=>h(()=>import("./RefreshToken.html-B7uT6L3n.js"),__vite__mapDeps([197,1])),meta:{d:1683504e6,l:"2023年5月8日",o:!0,e:`

    1、 refresh_token介绍

    +

    ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_token和refresh_token,并在access_token过期时使用refresh_token申请新的access_token。refresh_token的安全性非常重要,授权服务器会根据设定的有效期对refresh_token进行管理,以确保对用户的信息和资源进行保护。同时,在使用refresh_token时,也需要进行国家或行业标准的数据保护和安全性认证,以避免出现不必要的风险和隐私泄漏。

    `,r:{minutes:2.1,words:631},y:"a",t:"refresh_token"}}],["/other/web/Restful.html",{loader:()=>h(()=>import("./Restful.html-BeZrEgn9.js"),__vite__mapDeps([198,1])),meta:{a:"chenkun",d:16574976e5,l:"2022年7月11日",c:["web"],g:["web"],e:`

    一、参考


    RESTful架构

    RESTful API 设计

    -`,r:{minutes:.1,words:30},y:"a",t:"HTTP Restful"}}],["/other/windows/WSL.html",{loader:()=>h(()=>import("./WSL.html-irNHs4ez.js"),__vite__mapDeps([199,1])),meta:{a:"chensino",d:17270496e5,l:"2024年9月23日",o:!0,e:`

    1. wsl系统设置桥接网络

    +`,r:{minutes:.1,words:30},y:"a",t:"HTTP Restful"}}],["/other/windows/WSL.html",{loader:()=>h(()=>import("./WSL.html-BIz7aujd.js"),__vite__mapDeps([199,1])),meta:{a:"chensino",d:17270496e5,l:"2024年9月23日",o:!0,e:`

    1. wsl系统设置桥接网络

    参考:https://www.cnblogs.com/cheyunhua/p/17577895.html

    1.1 开启hyper-v

    桥接功能需要windows的hyper-v组件支持,但是win10/11家庭版是不包含hyper-v的,专业版才包含。网上也有文章提到家庭版安装hyper-v的方法,但是我没有测试,以下内容都是在win11专业版上进行的测试,win10专业版应该也是一样的。 -首先,进入控制面板—程序—启用或关闭windows功能,勾选hyper-v,确认后重启电脑。

    `,r:{minutes:2.15,words:645},y:"a",t:"wsl问题"}}],["/java/framework/mybatis/MybatisPlusDataSource.html",{loader:()=>h(()=>import("./MybatisPlusDataSource.html-2C9lgUhC.js"),__vite__mapDeps([200,1])),meta:{a:"chenkun",d:16590528e5,l:"2022年7月29日",e:`

    1、问题的背景

    +首先,进入控制面板—程序—启用或关闭windows功能,勾选hyper-v,确认后重启电脑。

    `,r:{minutes:2.15,words:645},y:"a",t:"wsl问题"}}],["/java/framework/mybatis/MybatisPlusDataSource.html",{loader:()=>h(()=>import("./MybatisPlusDataSource.html-J7-G54ls.js"),__vite__mapDeps([200,1])),meta:{a:"chenkun",d:16590528e5,l:"2022年7月29日",e:`

    1、问题的背景

    有两个库,ccsx_data、ccsx_weibao,默认库是ccsx_data,我在代码中使用了>mybatis-pulus的@DS()注解,想切换到ccsx_weibao这个库,但是切换一直失败,代码如下:

    @@ -2243,15 +2243,15 @@ aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.b } return installRecordVOIPage; } -`,r:{minutes:2.32,words:696},y:"a",t:"MybatisPlus多线程数据源切换问题"}}],["/java/framework/mybatis/",{loader:()=>h(()=>import("./index.html-CeTjFoZy.js"),__vite__mapDeps([201,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.02,words:6},y:"a",t:"Mybatis"}}],["/java/framework/mybatis/mybatis.html",{loader:()=>h(()=>import("./mybatis.html-ZRAnLlvk.js"),__vite__mapDeps([202,1])),meta:{a:"chenkun",d:15717024e5,l:"2019年10月22日",c:["框架"],g:["框架"],e:`

    mybatis缓存

    +`,r:{minutes:2.32,words:696},y:"a",t:"MybatisPlus多线程数据源切换问题"}}],["/java/framework/mybatis/",{loader:()=>h(()=>import("./index.html-CntKYRgb.js"),__vite__mapDeps([201,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.02,words:6},y:"a",t:"Mybatis"}}],["/java/framework/mybatis/mybatis.html",{loader:()=>h(()=>import("./mybatis.html-CU1fMlIQ.js"),__vite__mapDeps([202,1])),meta:{a:"chenkun",d:15717024e5,l:"2019年10月22日",c:["框架"],g:["框架"],e:`

    mybatis缓存

    缓存介绍

    一级缓存存在的问题

    1、 一级缓存

    -

    mybatis缓存有一级缓存也叫SelSession缓存,是强制打开的,也就是说Mybatis没有提供关闭一级缓存的方式。一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其查询结果放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。一级缓存的存储位置是在JVM虚拟机内存,它放在一个Map中

    `,r:{minutes:11.71,words:3513},y:"a",t:"Mybatis使用"}}],["/java/framework/security/Authorization.html",{loader:()=>h(()=>import("./Authorization.html-BqA7v7f3.js"),__vite__mapDeps([203,1])),meta:{d:16714944e5,l:"2022年12月20日",c:["security"],e:`

    1、权限说明

    +

    mybatis缓存有一级缓存也叫SelSession缓存,是强制打开的,也就是说Mybatis没有提供关闭一级缓存的方式。一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其查询结果放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。一级缓存的存储位置是在JVM虚拟机内存,它放在一个Map中

    `,r:{minutes:11.71,words:3513},y:"a",t:"Mybatis使用"}}],["/java/framework/security/Authorization.html",{loader:()=>h(()=>import("./Authorization.html-DYO-IVFw.js"),__vite__mapDeps([203,1])),meta:{d:16714944e5,l:"2022年12月20日",c:["security"],e:`

    1、权限说明

    认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401
    授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403

    2、Security中负责权限校验的类结构图

    -

    Security中权限类

    `,r:{minutes:1.07,words:322},y:"a",t:"权限校验原理"}}],["/java/framework/security/CustomAuthenticationProvider.html",{loader:()=>h(()=>import("./CustomAuthenticationProvider.html-D7qaxkkr.js"),__vite__mapDeps([204,1])),meta:{a:"chensino",d:16716672e5,l:"2022年12月22日",c:["Security"],e:`

    1、需求

    +

    Security中权限类

    `,r:{minutes:1.07,words:322},y:"a",t:"权限校验原理"}}],["/java/framework/security/CustomAuthenticationProvider.html",{loader:()=>h(()=>import("./CustomAuthenticationProvider.html-C05ZFsDP.js"),__vite__mapDeps([204,1])),meta:{a:"chensino",d:16716672e5,l:"2022年12月22日",c:["Security"],e:`

    1、需求

    前后分离项目使用不同登录方式进行登录
         1. 使用帐号/密码登录
         2. 使用手机号/验证码登录
    @@ -2260,7 +2260,7 @@ aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.b
     

    Security是一个扩展性很强的框架,预留了各种端点进行扩展,多种方式登录需要扩展AuthenticationProvider,进行自定义实现。默认情况 Security使用的是DAOAuthenticationProvider,就是从数据库中读取用户名/密码进行校验。

    -
    `,r:{minutes:3.64,words:1092},y:"a",t:"Security扩展自定义登录方式"}}],["/java/framework/security/CustomLoginPage.html",{loader:()=>h(()=>import("./CustomLoginPage.html-DheOzz7K.js"),__vite__mapDeps([205,1])),meta:{a:"chenkun",d:16674336e5,l:"2022年11月3日",e:`
    +
    `,r:{minutes:3.64,words:1092},y:"a",t:"Security扩展自定义登录方式"}}],["/java/framework/security/CustomLoginPage.html",{loader:()=>h(()=>import("./CustomLoginPage.html-R1ammlY9.js"),__vite__mapDeps([205,1])),meta:{a:"chenkun",d:16674336e5,l:"2022年11月3日",e:`

    注意

    本篇博客是用来配置前后不分离的项目,正常情况下现在的项目都是前后分离了,因此本篇内容 并没有太多学习价值,但是网上大多数教程都特别喜欢讲这一部分内容,就我目前了解到的内容, @@ -2270,7 +2270,7 @@ Security使用的是DAOAuthenticationProvider,就是从数据库中读取用

    官方文档

    https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html

    -
    `,r:{minutes:2.47,words:741},y:"a",t:"自定义登陆页面"}}],["/java/framework/security/CustomTokenAuthentication.html",{loader:()=>h(()=>import("./CustomTokenAuthentication.html-BAwukKcd.js"),__vite__mapDeps([206,1])),meta:{a:"chensino",d:16716672e5,l:"2022年12月22日",e:`
    +
    `,r:{minutes:2.47,words:741},y:"a",t:"自定义登陆页面"}}],["/java/framework/security/CustomTokenAuthentication.html",{loader:()=>h(()=>import("./CustomTokenAuthentication.html-DcEKKC-z.js"),__vite__mapDeps([206,1])),meta:{a:"chensino",d:16716672e5,l:"2022年12月22日",e:`

    注意

    网上很难精准找到一个前后端分离项目自定义Token认证的教程,找了很久终于找到,特此记录

    @@ -2281,7 +2281,7 @@ Security使用的是DAOAuthenticationProvider,就是从数据库中读取用 后不分离在Security中是大大的不一样!!大家学习的时候关键词检索要记得加上前后分离!! 2. 配置自定义过滤器,自定义过滤器用来处理请求,识别请求中是否有我们自定义的合法token,当然合不合法就要在过滤器的doFilter中自己写了。 -
    `,r:{minutes:2.32,words:696},y:"a",t:"前后分离项目自定义token认证"}}],["/java/framework/security/DelegatingFilterProxy.html",{loader:()=>h(()=>import("./DelegatingFilterProxy.html-BfP7rcad.js"),__vite__mapDeps([207,1])),meta:{a:"chensino",d:17267904e5,l:"2024年9月20日",o:!0,e:`

    作用

    +`,r:{minutes:2.32,words:696},y:"a",t:"前后分离项目自定义token认证"}}],["/java/framework/security/DelegatingFilterProxy.html",{loader:()=>h(()=>import("./DelegatingFilterProxy.html-CK8Yiklp.js"),__vite__mapDeps([207,1])),meta:{a:"chensino",d:17267904e5,l:"2024年9月20日",o:!0,e:`

    作用

    在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。

    具体解析:

      @@ -2290,7 +2290,7 @@ Security使用的是DAOAuthenticationProvider,就是从数据库中读取用
      • ContextLoaderListener 是 Spring 中常见的监听器,负责在应用启动时加载 Spring 的 ApplicationContext,从而初始化应用中的所有 Spring Bean。这通常是在应用启动的较晚阶段完成的。
      • ContextLoaderListener 会监听 Web 应用的启动事件,并在合适的时机加载和管理 Spring 上下文。
      • -
      `,r:{minutes:2.4,words:721},y:"a",t:"DelegatingFilterProxy介绍"}}],["/java/framework/security/OAuth2Authentication.html",{loader:()=>h(()=>import("./OAuth2Authentication.html-BPOz3SI3.js"),__vite__mapDeps([208,1])),meta:{a:"chenkun",d:1672704e6,l:"2023年1月3日",c:["Security","OAuth"],e:`

      1、时序图

      +`,r:{minutes:2.4,words:721},y:"a",t:"DelegatingFilterProxy介绍"}}],["/java/framework/security/OAuth2Authentication.html",{loader:()=>h(()=>import("./OAuth2Authentication.html-ChmdjSKd.js"),__vite__mapDeps([208,1])),meta:{a:"chenkun",d:1672704e6,l:"2023年1月3日",c:["Security","OAuth"],e:`

      1、时序图

      时序图

      2、流程解析

      @@ -2298,7 +2298,7 @@ Security使用的是DAOAuthenticationProvider,就是从数据库中读取用

      2.1 第1步

      用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页
      -20230104105625

      `,r:{minutes:2.4,words:720},y:"a",t:"Ruoyi使用oauth对接pig"}}],["/java/framework/security/OncePerRequestFilter.html",{loader:()=>h(()=>import("./OncePerRequestFilter.html-BEOxLqvE.js"),__vite__mapDeps([209,1])),meta:{a:"chenkun",d:16043616e5,l:"2020年11月3日",c:["Security"],e:`

      1、OnecePerRequestFilter初识

      +20230104105625

      `,r:{minutes:2.4,words:720},y:"a",t:"Ruoyi使用oauth对接pig"}}],["/java/framework/security/OncePerRequestFilter.html",{loader:()=>h(()=>import("./OncePerRequestFilter.html-DIikqGft.js"),__vite__mapDeps([209,1])),meta:{a:"chenkun",d:16043616e5,l:"2020年11月3日",c:["Security"],e:`

      1、OnecePerRequestFilter初识

      第一次接触这个类,在SpringSecurity中,大概百度了一下,知道此类是限制一次请求只
       走一次过滤器,但是我不明白为啥要做这个限制,或者说难道还有一次请求会走两次过滤器?
       

      1.1 源码doc

      @@ -2306,11 +2306,11 @@ Security使用的是DAOAuthenticationProvider,就是从数据库中读取用 一个请求在一个过滤器只执行一次doFilter,因在在不同版本的Servlet容器中是存在多次执 行doFilter的可能的,比如一个request forward到另一个request,在servlet2.0和3.0 表现可能都不一样,在Tomcat和weblogic容器中可能表现也不一样,为了统一此行为,所以 -Spring官方提供了此类。

      `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePerRequestFilter"}}],["/java/framework/security/PreAuthorize.html",{loader:()=>h(()=>import("./PreAuthorize.html-B_BWAtGw.js"),__vite__mapDeps([210,1])),meta:{d:16847136e5,l:"2023年5月22日",o:!0,e:`

      1、使用方式

      +Spring官方提供了此类。

      `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePerRequestFilter"}}],["/java/framework/security/PreAuthorize.html",{loader:()=>h(()=>import("./PreAuthorize.html-DFU2o_nN.js"),__vite__mapDeps([210,1])),meta:{d:16847136e5,l:"2023年5月22日",o:!0,e:`

      1、使用方式

      比较简单,省略.....

      2、@PreAuthorize注解是何时生效的?

      2.1

      -`,r:{minutes:.11,words:33},y:"a",t:"PreAuthorize注解"}}],["/java/framework/security/",{loader:()=>h(()=>import("./index.html-SQqXSiBs.js"),__vite__mapDeps([211,1])),meta:{a:"chensino",d:16674336e5,l:"2022年11月3日",r:{minutes:.04,words:11},y:"a",t:"Security学习"}}],["/java/framework/security/SSO.html",{loader:()=>h(()=>import("./SSO.html-DBoqluIC.js"),__vite__mapDeps([212,1])),meta:{d:1672704e6,l:"2023年1月3日",g:["oauth","sso"],e:`

      1、写在前面

      +`,r:{minutes:.11,words:33},y:"a",t:"PreAuthorize注解"}}],["/java/framework/security/",{loader:()=>h(()=>import("./index.html-52PFUvm3.js"),__vite__mapDeps([211,1])),meta:{a:"chensino",d:16674336e5,l:"2022年11月3日",r:{minutes:.04,words:11},y:"a",t:"Security学习"}}],["/java/framework/security/SSO.html",{loader:()=>h(()=>import("./SSO.html-DKOv-tJ8.js"),__vite__mapDeps([212,1])),meta:{d:1672704e6,l:"2023年1月3日",g:["oauth","sso"],e:`

      1、写在前面

      一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。

      • 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录
      • @@ -2318,27 +2318,27 @@ Spring官方提供了此类。

        `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe

      也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源时,系统会校验其拥有的权限是否可以访问这个资源。

      2、SSO和OAUTH2介绍

      -

      SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。

      `,r:{minutes:3.65,words:1094},y:"a",t:"SSO协议"}}],["/java/framework/security/SecurityFilterChain.html",{loader:()=>h(()=>import("./SecurityFilterChain.html-D0yc0vvl.js"),__vite__mapDeps([213,1])),meta:{a:"chensino",d:16358976e5,l:"2021年11月3日",c:["security"],e:`

      1、配置类

      +

      SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。

      `,r:{minutes:3.65,words:1094},y:"a",t:"SSO协议"}}],["/java/framework/security/SecurityFilterChain.html",{loader:()=>h(()=>import("./SecurityFilterChain.html-j_4ZmVEf.js"),__vite__mapDeps([213,1])),meta:{a:"chensino",d:16358976e5,l:"2021年11月3日",c:["security"],e:`

      1、配置类

      1. 5.7版本后Security把WebSecurityConfigurerAdapter标记为废弃,鼓励程序员使用SecurityFilterChain进行配置,如果看过官网Security的架构图对SecurityFilterChain一定不会陌生,此类是Security过滤器的核心,所以用它来配置寓意更为明显。
      -
      `,r:{minutes:4.81,words:1444},y:"a",t:"Security配置类"}}],["/java/framework/security/Session.html",{loader:()=>h(()=>import("./Session.html-PVXvBLSr.js"),__vite__mapDeps([214,1])),meta:{a:"chenkun",d:1672704e6,l:"2023年1月3日",c:["Security"],e:`

      1、背景(问题复现)

      +`,r:{minutes:4.81,words:1444},y:"a",t:"Security配置类"}}],["/java/framework/security/Session.html",{loader:()=>h(()=>import("./Session.html-CKk3uN_p.js"),__vite__mapDeps([214,1])),meta:{a:"chenkun",d:1672704e6,l:"2023年1月3日",c:["Security"],e:`

      1、背景(问题复现)

      
       1. 使用idea新建一个springboot项目,引入web和security框架,不做任何配置直接启动web项目
       2. 随便访问一个接口比如:http://localhost:8080/hello/aa,此时由于接口被security默认保护,会重定向到登录页面(如图一),此时查看sessionid(也就是name为JSESSIONID的cookie)是91E9629F748637154F86CCB44FB2B23D
       3. 然后输入用户名:user,密码:控制台随机生成的,登录后会重定向到之前访问的接口,但此时
          查看JSESSIONID发现变了,变成87957B71A3CEA4FA375CFFACA6AD425D
      -
      `,r:{minutes:1.69,words:507},y:"a",t:"Security中的Session"}}],["/java/framework/security/note.html",{loader:()=>h(()=>import("./note.html-CoTL_cZp.js"),__vite__mapDeps([215,1])),meta:{a:"chenkun",d:1662336e6,l:"2022年9月5日",e:`

      1、加密解密流程

      -

      在项目中引入security依赖后,项目启动会自动生成一个账号密码,账号固定为user,密码为控制台随机生成的。账号密码可以自定义加解密方式,一般情况我们的账号密码都是放在数据库中,一个正常的业务流程是,先定义好密码加解密方式,加密方式需要实现PasswordEncoder接口,比如这里我用默认的加解密,然后在新增用户插入到数据库时,需要调用encode方法来加密密码。在请求后端接口时如果需要登陆权限,则会自动跳转到登陆页面,登陆接口会自动调用PasswordEncodermatches方法拿明文密码加密后和密文密码(根据用户名获取已经存到系统的密文)进行匹配,匹配成功则登陆通过。

      `,r:{minutes:3.75,words:1125},y:"a",t:"Security入门笔记"}}],["/java/framework/security/spring-security-oauth2-authorization-server.html",{loader:()=>h(()=>import("./spring-security-oauth2-authorization-server.html-BU0wlxJK.js"),__vite__mapDeps([216,1])),meta:{d:16835904e5,l:"2023年5月9日",o:!0,e:`

      介绍

      +`,r:{minutes:1.69,words:507},y:"a",t:"Security中的Session"}}],["/java/framework/security/note.html",{loader:()=>h(()=>import("./note.html-CZMFgVaM.js"),__vite__mapDeps([215,1])),meta:{a:"chenkun",d:1662336e6,l:"2022年9月5日",e:`

      1、加密解密流程

      +

      在项目中引入security依赖后,项目启动会自动生成一个账号密码,账号固定为user,密码为控制台随机生成的。账号密码可以自定义加解密方式,一般情况我们的账号密码都是放在数据库中,一个正常的业务流程是,先定义好密码加解密方式,加密方式需要实现PasswordEncoder接口,比如这里我用默认的加解密,然后在新增用户插入到数据库时,需要调用encode方法来加密密码。在请求后端接口时如果需要登陆权限,则会自动跳转到登陆页面,登陆接口会自动调用PasswordEncodermatches方法拿明文密码加密后和密文密码(根据用户名获取已经存到系统的密文)进行匹配,匹配成功则登陆通过。

      `,r:{minutes:3.75,words:1125},y:"a",t:"Security入门笔记"}}],["/java/framework/security/spring-security-oauth2-authorization-server.html",{loader:()=>h(()=>import("./spring-security-oauth2-authorization-server.html-o1Bz8Yim.js"),__vite__mapDeps([216,1])),meta:{d:16835904e5,l:"2023年5月9日",o:!0,e:`

      介绍

      spring-security-oauth2已经被废弃,采用Security5.7 之后就用spring-security-oauth2-authorization-server,

      /oauth2/token

      在之前的老版本中请求token的端点是在一个叫做TokenEndpoint的类中,此类可以处理/oauth/token请求,这个类可看成是一个Controller,而在新版本中已经没有这个类,新版本中请求token的端点是/oauth2/token,我看源码没有找到对应的controller,直到我debug源码,才发现根本没有类似之前的专门处理/oauth/token的controller,其实新版本在Filter(实际是OAuth2TokenEndpointFilter)中就已经直接响应给客户端了, -20230509201806

      `,r:{minutes:1.02,words:305},y:"a",t:"spring-security-oauth2-authorization-server"}}],["/java/framework/spring/Annotation.html",{loader:()=>h(()=>import("./Annotation.html-Bu-Jo73Z.js"),__vite__mapDeps([217,1])),meta:{d:16715808e5,l:"2022年12月21日",c:["Spring"],e:`

      1、依赖注入

      +20230509201806

      `,r:{minutes:1.02,words:305},y:"a",t:"spring-security-oauth2-authorization-server"}}],["/java/framework/spring/Annotation.html",{loader:()=>h(()=>import("./Annotation.html-CN1Pgot9.js"),__vite__mapDeps([217,1])),meta:{d:16715808e5,l:"2022年12月21日",c:["Spring"],e:`

      1、依赖注入

       1. @Autowired
            是Spring中的注解,按照类型注入,此注解可以用于字段属性上、setter方法上、构造函数上,用在字段上则Spring底层会使用反射对字段进行赋值,用成员变量的在setter方法上,则会调用setter方法进行注入。从Spring4.3开始,如果只有一个有参的构造方法,则可以省略构造方法上的@Autowired
        2. @Autowired + @Qualifier 
           按照bean的名字注入
        3. @Resource
           是JavaEE规范中的注解在JSR250引入,默认是按照bean的名字注入,如果没指定名字则按照类型注入
      -
      `,r:{minutes:1.48,words:443},y:"a",t:"注解"}}],["/java/framework/spring/BeanPostProcessor.html",{loader:()=>h(()=>import("./BeanPostProcessor.html-2fGhuRBC.js"),__vite__mapDeps([218,1])),meta:{a:"chenkun",d:15717024e5,l:"2019年10月22日",e:` +`,r:{minutes:1.48,words:443},y:"a",t:"注解"}}],["/java/framework/spring/BeanPostProcessor.html",{loader:()=>h(()=>import("./BeanPostProcessor.html-CLtCpKMO.js"),__vite__mapDeps([218,1])),meta:{a:"chenkun",d:15717024e5,l:"2019年10月22日",e:`

      1. BeanPostProcessor介绍

      打开源码里面有两个方法,分别是postProcessBeforeInitialization和postProcessAfterInitialization。

      
      @@ -2354,7 +2354,7 @@ Spring官方提供了此类。

      `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe } } -
      `,r:{minutes:3.63,words:1089},y:"a",t:"BeanPostProcessor介绍"}}],["/java/framework/spring/CircularDependency.html",{loader:()=>h(()=>import("./CircularDependency.html-Cy-x1Wkb.js"),__vite__mapDeps([219,1])),meta:{a:"chensino",d:16717536e5,l:"2022年12月23日",c:["Spring"],e:`

      1、循环依赖的产生

      +`,r:{minutes:3.63,words:1089},y:"a",t:"BeanPostProcessor介绍"}}],["/java/framework/spring/CircularDependency.html",{loader:()=>h(()=>import("./CircularDependency.html-DRv79HhX.js"),__vite__mapDeps([219,1])),meta:{a:"chensino",d:16717536e5,l:"2022年12月23日",c:["Spring"],e:`

      1、循环依赖的产生

      A依赖B,B也依赖于A

      @@ -2381,12 +2381,12 @@ Spring官方提供了此类。

      `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe return this.b; } } -`,r:{minutes:1.07,words:320},y:"a",t:"Spring循环依赖"}}],["/java/framework/spring/DesignPatternInSpring.html",{loader:()=>h(()=>import("./DesignPatternInSpring.html-Cz-ZqYXH.js"),__vite__mapDeps([220,1])),meta:{d:15266016e5,l:"2018年5月18日",e:`

      1、工厂模式

      +`,r:{minutes:1.07,words:320},y:"a",t:"Spring循环依赖"}}],["/java/framework/spring/DesignPatternInSpring.html",{loader:()=>h(()=>import("./DesignPatternInSpring.html-B1tCH6O1.js"),__vite__mapDeps([220,1])),meta:{d:15266016e5,l:"2018年5月18日",e:`

      1、工厂模式

      BeanFactory是典型的工厂方法模式,其有多个实现,不同的实现有不同的getBean方法,默认实现是DefaultListalbeBeanFactory,我们也可以定义自己的工厂实现BeanFactory接口,重写里面的getBean方法

      2、单例模式

      -

      在spring中使用singleton修饰的bean都是单例模式,org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton,在spring实例化出真正的对象后,会把这个对象加到容器中

      `,r:{minutes:1.81,words:542},y:"a",t:"Spring中的设计模式"}}],["/java/framework/spring/OncePerRequestFilter.html",{loader:()=>h(()=>import("./OncePerRequestFilter.html-C_TVU5f6.js"),__vite__mapDeps([221,1])),meta:{a:"chensino",d:1527552e6,l:"2018年5月29日",e:`

      1、OncePerRequestFilter

      +

      在spring中使用singleton修饰的bean都是单例模式,org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton,在spring实例化出真正的对象后,会把这个对象加到容器中

      `,r:{minutes:1.81,words:542},y:"a",t:"Spring中的设计模式"}}],["/java/framework/spring/OncePerRequestFilter.html",{loader:()=>h(()=>import("./OncePerRequestFilter.html-DvJ1C4ir.js"),__vite__mapDeps([221,1])),meta:{a:"chensino",d:1527552e6,l:"2018年5月29日",e:`

      1、OncePerRequestFilter

      org.springframework.web.filter.OncePerRequestFilter是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次? -其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤。因为Springweb无法保证每个用户使用的Servlet容器是一样的,因此,在有些场景如果我们要确保同一个请求只能被过滤器处理一次,那就需要spring自己来实现了,因此有了这个类。其注释下写的很清楚,就是为了要陈any servlet container都确保只执行一次。

      `,r:{minutes:1.26,words:378},y:"a",t:"OncePerRequestFilter"}}],["/java/framework/spring/",{loader:()=>h(()=>import("./index.html-B9UffgOy.js"),__vite__mapDeps([222,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.02,words:6},y:"a",t:"Spring"}}],["/java/framework/spring/SpringAOP.html",{loader:()=>h(()=>import("./SpringAOP.html-0F2yJUPF.js"),__vite__mapDeps([223,1])),meta:{d:15060384e5,l:"2017年9月22日",u:100,e:`

      1、SpringAOP

      +其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤。因为Springweb无法保证每个用户使用的Servlet容器是一样的,因此,在有些场景如果我们要确保同一个请求只能被过滤器处理一次,那就需要spring自己来实现了,因此有了这个类。其注释下写的很清楚,就是为了要陈any servlet container都确保只执行一次。

      `,r:{minutes:1.26,words:378},y:"a",t:"OncePerRequestFilter"}}],["/java/framework/spring/",{loader:()=>h(()=>import("./index.html-D_iYE-Ij.js"),__vite__mapDeps([222,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.02,words:6},y:"a",t:"Spring"}}],["/java/framework/spring/SpringAOP.html",{loader:()=>h(()=>import("./SpringAOP.html-BR3lenMn.js"),__vite__mapDeps([223,1])),meta:{d:15060384e5,l:"2017年9月22日",u:100,e:`

      1、SpringAOP

      > SpringAOP的本质就是动态代理,底层使用JDK动态代理或者CGlib动态代理,通过代理框架生成代理类,实现对目标类的增强,Spring代理是方法级别的代理,是对方法增强,
       > 
       > 代理有四个要素:
      @@ -2394,8 +2394,8 @@ Spring官方提供了此类。

      `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe > 2. 额外功能(增强) > 3. 切入点(被增强的方法) > 4. 组装(把切入点和额外功能进行整合,就是确认哪些方法需要增强) -
      `,r:{minutes:3.52,words:1056},y:"a",t:"SpringAOP"}}],["/java/framework/spring/SpringCache.html",{loader:()=>h(()=>import("./SpringCache.html-BcT_2YyF.js"),__vite__mapDeps([224,1])),meta:{d:15691104e5,l:"2019年9月22日",u:100,e:`

      Spring缓存大揭秘

      -`,r:{minutes:1.97,words:592},y:"a",t:"Spring缓存"}}],["/java/framework/spring/SpringExtensionPoint.html",{loader:()=>h(()=>import("./SpringExtensionPoint.html-BY3OGoZJ.js"),__vite__mapDeps([225,1])),meta:{d:15268608e5,l:"2018年5月21日",e:`

      [TOC]

      +`,r:{minutes:3.52,words:1056},y:"a",t:"SpringAOP"}}],["/java/framework/spring/SpringCache.html",{loader:()=>h(()=>import("./SpringCache.html-B2PNt9GB.js"),__vite__mapDeps([224,1])),meta:{d:15691104e5,l:"2019年9月22日",u:100,e:`

      Spring缓存大揭秘

      +`,r:{minutes:1.97,words:592},y:"a",t:"Spring缓存"}}],["/java/framework/spring/SpringExtensionPoint.html",{loader:()=>h(()=>import("./SpringExtensionPoint.html-BdwK_BjA.js"),__vite__mapDeps([225,1])),meta:{d:15268608e5,l:"2018年5月21日",e:`

      [TOC]

      1. spring生命周期

      spring容器实例化一个对象往大说主要是分为两步

      1.1 第一步:根据配置生成BeanDefinition

      @@ -2406,8 +2406,8 @@ Spring官方提供了此类。

      `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe
    1. scope :可取singleton、prototype,即单例还是多例;
    2. lazyInit:表明此bean是否为懒加载,如果是true代表容器启动后自动创建出bean真实对象,若为false,则代表需要显示的调用容器的getBean()方法时才会实例化出对象;
    3. 其它字段不再介绍
    4. -
    `,r:{minutes:3.59,words:1077},y:"a",t:"Spring框架扩展点"}}],["/java/framework/spring/SpringIOC.html",{loader:()=>h(()=>import("./SpringIOC.html-D2ZKIq-l.js"),__vite__mapDeps([226,1])),meta:{d:16215552e5,l:"2021年5月21日",c:"open-source",e:`

    Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器。既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文并不能让你成为 Spring 专家,不过一定有助于大家理解 Spring 的很多概念,帮助大家排查应用中和 Spring 相关的一些问题。

    -

    本文采用的源码版本是 4.3.11.RELEASE,算是 5.0.x 前比较新的版本了。为了降低难度,本文所说的所有的内容都是基于 xml 的配置的方式,实际使用已经很少人这么做了,至少不是纯 xml 配置,不过从理解源码的角度来看用这种方式来说无疑是最合适的。

    `,r:{minutes:62.1,words:18630},y:"a",t:"Spring IOC 容器源码分析"}}],["/java/framework/spring/SpringMVC.html",{loader:()=>h(()=>import("./SpringMVC.html-CW7qiD-c.js"),__vite__mapDeps([227,1])),meta:{d:16876512e5,l:"2023年6月25日",o:!0,e:`

    SpringMVC处理请求的流程

    +`,r:{minutes:3.59,words:1077},y:"a",t:"Spring框架扩展点"}}],["/java/framework/spring/SpringIOC.html",{loader:()=>h(()=>import("./SpringIOC.html-BqsqDvdX.js"),__vite__mapDeps([226,1])),meta:{d:16215552e5,l:"2021年5月21日",c:"open-source",e:`

    Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器。既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文并不能让你成为 Spring 专家,不过一定有助于大家理解 Spring 的很多概念,帮助大家排查应用中和 Spring 相关的一些问题。

    +

    本文采用的源码版本是 4.3.11.RELEASE,算是 5.0.x 前比较新的版本了。为了降低难度,本文所说的所有的内容都是基于 xml 的配置的方式,实际使用已经很少人这么做了,至少不是纯 xml 配置,不过从理解源码的角度来看用这种方式来说无疑是最合适的。

    `,r:{minutes:62.1,words:18630},y:"a",t:"Spring IOC 容器源码分析"}}],["/java/framework/spring/SpringMVC.html",{loader:()=>h(()=>import("./SpringMVC.html-D8cETPq8.js"),__vite__mapDeps([227,1])),meta:{d:16876512e5,l:"2023年6月25日",o:!0,e:`

    SpringMVC处理请求的流程

    20230625180034

    前端控制器源码

    
    @@ -2473,7 +2473,7 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe //无关代码省略。。。。。 } -
    `,r:{minutes:3.77,words:1131},y:"a",t:"SpringMVC基本原理"}}],["/java/framework/spring/SpringSourceAnalize.html",{loader:()=>h(()=>import("./SpringSourceAnalize.html-C86lj3k3.js"),__vite__mapDeps([228,1])),meta:{d:16763328e5,l:"2023年2月14日",r:{minutes:.03,words:10},y:"a",t:"SpringIOC源码分析"}}],["/java/framework/spring/Validator.html",{loader:()=>h(()=>import("./Validator.html-CvrJ26C9.js"),__vite__mapDeps([229,1])),meta:{d:16833312e5,l:"2023年5月6日",o:!0,e:`

    自定义校验

    +`,r:{minutes:3.77,words:1131},y:"a",t:"SpringMVC基本原理"}}],["/java/framework/spring/SpringSourceAnalize.html",{loader:()=>h(()=>import("./SpringSourceAnalize.html-D0vztr7E.js"),__vite__mapDeps([228,1])),meta:{d:16763328e5,l:"2023年2月14日",r:{minutes:.03,words:10},y:"a",t:"SpringIOC源码分析"}}],["/java/framework/spring/Validator.html",{loader:()=>h(()=>import("./Validator.html-DOW_vPxb.js"),__vite__mapDeps([229,1])),meta:{d:16833312e5,l:"2023年5月6日",o:!0,e:`

    自定义校验

    
     import com.chensino.core.api.dto.UserLoginDTO;
     import com.chensino.core.api.validate.group.PhoneLogin;
    @@ -2525,7 +2525,7 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe } } } -
    `,r:{minutes:1.07,words:322},y:"a",t:"自定义validator分组检验"}}],["/java/framework/springboot/AOPLog.html",{loader:()=>h(()=>import("./AOPLog.html-jSrFg30s.js"),__vite__mapDeps([230,1])),meta:{d:16475616e5,l:"2022年3月18日",e:`

    1. 博客背景

    +`,r:{minutes:1.07,words:322},y:"a",t:"自定义validator分组检验"}}],["/java/framework/springboot/AOPLog.html",{loader:()=>h(()=>import("./AOPLog.html-WkAcygqg.js"),__vite__mapDeps([230,1])),meta:{d:16475616e5,l:"2022年3月18日",e:`

    1. 博客背景

    最近业务提了一个需求,让记录每个用户的每个操作请求到数据库,保证每个操作都可追溯,这个需求很典型,实现起来也不难,一个自定义注解就搞定了。

    2. 实现

    @@ -2544,7 +2544,7 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe String value(); } -`,r:{minutes:2,words:601},y:"a",t:"使用aop记录请求log"}}],["/java/framework/springboot/CollectionInject.html",{loader:()=>h(()=>import("./CollectionInject.html-Ds4tphSm.js"),__vite__mapDeps([231,1])),meta:{d:16726176e5,l:"2023年1月2日",c:["Spring"],e:`

    1、测试

    +`,r:{minutes:2,words:601},y:"a",t:"使用aop记录请求log"}}],["/java/framework/springboot/CollectionInject.html",{loader:()=>h(()=>import("./CollectionInject.html-Wk5anYr4.js"),__vite__mapDeps([231,1])),meta:{d:16726176e5,l:"2023年1月2日",c:["Spring"],e:`

    1、测试

    加入有以下代码,~MyProcessor~是一个接口,没有提供任何实现,然后启动容器会发现执行Bean1的构造方法时并不会空指针,容器会自动提供一个Collection的实现类~LinkedHashMap$LinkedValues~,那么容器如何注入我自己的MyProcessor呢?

    @Component
     public class Bean1 {
    @@ -2555,9 +2555,9 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe System.out.println(processors.size()); } } -
    `,r:{minutes:1.35,words:404},y:"a",t:"注入集合"}}],["/java/framework/springboot/Http2.html",{loader:()=>h(()=>import("./Http2.html-yFvJ3oRy.js"),__vite__mapDeps([232,1])),meta:{d:16749504e5,l:"2023年1月29日",e:`

    1、http2.0优势

    +`,r:{minutes:1.35,words:404},y:"a",t:"注入集合"}}],["/java/framework/springboot/Http2.html",{loader:()=>h(()=>import("./Http2.html-B3cAbYAj.js"),__vite__mapDeps([232,1])),meta:{d:16749504e5,l:"2023年1月29日",e:`

    1、http2.0优势

    2、springboot开启http2.0

    -`,r:{minutes:.06,words:18},y:"a",t:"springboot开启http2.0"}}],["/java/framework/springboot/",{loader:()=>h(()=>import("./index.html-hGAHmgqz.js"),__vite__mapDeps([233,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.02,words:6},y:"a",t:"SpringBoot"}}],["/java/framework/springboot/SpringBootAutoConfiguration.html",{loader:()=>h(()=>import("./SpringBootAutoConfiguration.html-B2Ew9z9M.js"),__vite__mapDeps([234,1])),meta:{a:"chenkun",d:15708384e5,l:"2019年10月12日",e:`

    1. springboot自动配置的原理初探

    +`,r:{minutes:.06,words:18},y:"a",t:"springboot开启http2.0"}}],["/java/framework/springboot/",{loader:()=>h(()=>import("./index.html-D4BNQ3HG.js"),__vite__mapDeps([233,1])),meta:{d:1527552e6,l:"2018年5月29日",r:{minutes:.02,words:6},y:"a",t:"SpringBoot"}}],["/java/framework/springboot/SpringBootAutoConfiguration.html",{loader:()=>h(()=>import("./SpringBootAutoConfiguration.html-DyJd0iCn.js"),__vite__mapDeps([234,1])),meta:{a:"chenkun",d:15708384e5,l:"2019年10月12日",e:`

    1. springboot自动配置的原理初探

    ​以下注解都在springboot的自动化配置包中:spring-boot-autoconfigure

    1. @@ -2602,15 +2602,15 @@ Spring官方提供了此类。

      `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe
    2. AutoConfigurationImportSelector类中的getCandidateConfigurations方法代码如上,其调用了SpringFactoriesLoader的loadFactoryNames方法,来获取

    3. -
    `,r:{minutes:9.25,words:2774},y:"a",t:"springboot自动配置原理"}}],["/java/framework/springboot/Swagger.html",{loader:()=>h(()=>import("./Swagger.html-BrC3rGwr.js"),__vite__mapDeps([235,1])),meta:{d:16835904e5,l:"2023年5月9日",o:!0,e:`

    Swagger和SpringFox关系

    +`,r:{minutes:9.25,words:2774},y:"a",t:"springboot自动配置原理"}}],["/java/framework/springboot/Swagger.html",{loader:()=>h(()=>import("./Swagger.html-QZsEFOI2.js"),__vite__mapDeps([235,1])),meta:{d:16835904e5,l:"2023年5月9日",o:!0,e:`

    Swagger和SpringFox关系

    java项目在引入swagger的时候,一般会引入如下依赖,当时只知道照着博客抄,也有好奇为啥叫springfox,怎么没有swagger,不知你是否有同样的疑问?直到今天问了一下GPT

        <dependency>
             <groupId>io.springfox</groupId>
             <artifactId>springfox-boot-starter</artifactId>
         </dependency>
    -
    `,r:{minutes:2.29,words:687},y:"a",t:"Swagger"}}],["/java/framework/springboot/%E9%83%A8%E7%BD%B2.html",{loader:()=>h(()=>import("./部署.html-FCiywWGM.js"),__vite__mapDeps([236,1])),meta:{d:16956864e5,l:"2023年9月26日",o:!0,e:`

    参考

    +`,r:{minutes:2.29,words:687},y:"a",t:"Swagger"}}],["/java/framework/springboot/%E9%83%A8%E7%BD%B2.html",{loader:()=>h(()=>import("./部署.html-Cup61qP7.js"),__vite__mapDeps([236,1])),meta:{d:16956864e5,l:"2023年9月26日",o:!0,e:`

    参考

    官方部署说明

    -`,r:{minutes:.07,words:22},y:"a",t:"springboot部署"}}],["/java/framework/springcloud/SpringCloudGateway.html",{loader:()=>h(()=>import("./SpringCloudGateway.html-DptX-4Hc.js"),__vite__mapDeps([237,1])),meta:{d:16872192e5,l:"2023年6月20日",o:!0,e:`

    问题描述

    +`,r:{minutes:.07,words:22},y:"a",t:"springboot部署"}}],["/java/framework/springcloud/SpringCloudGateway.html",{loader:()=>h(()=>import("./SpringCloudGateway.html-Dc9fg5OX.js"),__vite__mapDeps([237,1])),meta:{d:16872192e5,l:"2023年6月20日",o:!0,e:`

    问题描述

    有一个业务服务,启动了两个做成负载均衡,分别为10.6.6.11:2221,10.6.6.11:5221,为了调试,所以把route修改为只路由到5221,但是网关服务配置好route后,发送请求无法路由到指定的10.6.6.11:5221服务,一直路由到另一个服务10.6.6.11:2221上,并且连自定义的gatewayfilter都失效了

    请求路径为:http://gateway:port/mcs/test 配置如下

    @@ -2632,10 +2632,10 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe - Path=/mcs/** filters: - StripPrefix=1 -`,r:{minutes:1.83,words:550},y:"a",t:"网关路由失效"}}],["/java/other/gradle/",{loader:()=>h(()=>import("./index.html-Cnhk4PBp.js"),__vite__mapDeps([238,1])),meta:{a:"chenkun",d:16660512e5,l:"2022年10月18日",e:`

    1. Gradle的放弃之路

    +`,r:{minutes:1.83,words:550},y:"a",t:"网关路由失效"}}],["/java/other/gradle/",{loader:()=>h(()=>import("./index.html-BGEnglY6.js"),__vite__mapDeps([238,1])),meta:{a:"chenkun",d:16660512e5,l:"2022年10月18日",e:`

    1. Gradle的放弃之路

    世上无难事,只要肯放弃

    -`,r:{minutes:.09,words:28},y:"a",t:"Gradle"}}],["/java/other/gradle/wrapper.html",{loader:()=>h(()=>import("./wrapper.html-Do0j3LhC.js"),__vite__mapDeps([239,1])),meta:{a:"chenkun",d:16660512e5,l:"2022年10月18日",r:{minutes:.04,words:11},y:"a",t:"Wrapper"}}],["/java/other/locateproblem/",{loader:()=>h(()=>import("./index.html-DiiTcF6s.js"),__vite__mapDeps([240,1])),meta:{a:"chenkun",d:16589664e5,l:"2022年7月28日",e:`

    此分类专门定位线上问题

    -`,r:{minutes:.09,words:27},y:"a",t:"线上问题定位"}}],["/java/other/locateproblem/TooManyOpenFiles.html",{loader:()=>h(()=>import("./TooManyOpenFiles.html-ChBajVz6.js"),__vite__mapDeps([241,1])),meta:{a:`chenkun +`,r:{minutes:.09,words:28},y:"a",t:"Gradle"}}],["/java/other/gradle/wrapper.html",{loader:()=>h(()=>import("./wrapper.html-aaFCqiMF.js"),__vite__mapDeps([239,1])),meta:{a:"chenkun",d:16660512e5,l:"2022年10月18日",r:{minutes:.04,words:11},y:"a",t:"Wrapper"}}],["/java/other/locateproblem/",{loader:()=>h(()=>import("./index.html-Bx1FwJMs.js"),__vite__mapDeps([240,1])),meta:{a:"chenkun",d:16589664e5,l:"2022年7月28日",e:`

    此分类专门定位线上问题

    +`,r:{minutes:.09,words:27},y:"a",t:"线上问题定位"}}],["/java/other/locateproblem/TooManyOpenFiles.html",{loader:()=>h(()=>import("./TooManyOpenFiles.html-DBlFfNwX.js"),__vite__mapDeps([241,1])),meta:{a:`chenkun - 问题定位`,d:16613856e5,l:"2022年8月25日",e:`

    Linux服务器打开文件过多

    线上异常

    java.io.IOException: Too many open files
    @@ -2646,15 +2646,15 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:70) at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:95) at java.lang.Thread.run(Thread.java:748) -
    `,r:{minutes:1.72,words:516},y:"a",t:"TooManyOpenFiles"}}],["/java/other/locateproblem/Undertow.xxxNotFount.html",{loader:()=>h(()=>import("./Undertow.xxxNotFount.html-CjycD9Jx.js"),__vite__mapDeps([242,1])),meta:{d:16833312e5,l:"2023年5月6日",o:!0,e:`

    背景

    +`,r:{minutes:1.72,words:516},y:"a",t:"TooManyOpenFiles"}}],["/java/other/locateproblem/Undertow.xxxNotFount.html",{loader:()=>h(()=>import("./Undertow.xxxNotFount.html-X1F463A1.js"),__vite__mapDeps([242,1])),meta:{d:16833312e5,l:"2023年5月6日",o:!0,e:`

    背景

    用户反馈上传图片失败,查看日志报错上传文件提示:java.nio.file.NoSuchFileException: /tmp/undertow.xxxx.xxxxx,解决方案,问题是这个问题之前用户就反馈过,之前是直接重启就好了,之后也没深究。最近用户又反馈不行了,所以花了点时间深究一下从根源解决问题。

    原因

    在 Linux 系统中,Spring Boot 应用以 java -jar 命令启动时,会在操作系统的 /tmp 目录下随机生成一个 tomcat(或 undertow )临时目录,上传的文件先要转换成临时文件保存在这个文件夹中。 由于临时 /tmp 目录下的文件,在长时间(默认10天)没有使用的情况下,操作系统会执行 tmp 目录清理服务(systemd-tmpfiles-clean.service),导致 /tmp/undertow.xxxx.xxxxxxx 文件被清理; -导致在上传文件时,java调用 Files.createFile(…) 在目录/tmp/undertow.xxxx.xxxxxxx下创建临时文件时,发现找不到目录,就会抛出以上的错误。

    `,r:{minutes:1.59,words:478},y:"a",t:"undertow.xxx not found"}}],["/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html",{loader:()=>h(()=>import("./各种框架版本的坑.html-H7SaEEcF.js"),__vite__mapDeps([243,1])),meta:{d:16835904e5,l:"2023年5月9日",o:!0,e:`

    1、Spring系列

    +导致在上传文件时,java调用 Files.createFile(…) 在目录/tmp/undertow.xxxx.xxxxxxx下创建临时文件时,发现找不到目录,就会抛出以上的错误。

    `,r:{minutes:1.59,words:478},y:"a",t:"undertow.xxx not found"}}],["/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html",{loader:()=>h(()=>import("./各种框架版本的坑.html-CpfYP_fL.js"),__vite__mapDeps([243,1])),meta:{d:16835904e5,l:"2023年5月9日",o:!0,e:`

    1、Spring系列

    1.1 坑1 spring.factories使用方式变更

    2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,同时在此文件中写入类全限定名字,多个类,每行一个,不用符号隔开

    -

    1.2 坑2 Spring Security OAuth2已被新版本security弃用

    `,r:{minutes:1.62,words:487},y:"a",t:"各种版本的坑"}}],["/java/other/locateproblem/%E5%90%84%E7%A7%8D%E9%97%AE%E9%A2%98.html",{loader:()=>h(()=>import("./各种问题.html-DVSHYPDc.js"),__vite__mapDeps([244,1])),meta:{d:16836768e5,l:"2023年5月10日",o:!0,e:`

    1. springcloudgateway负载均衡配置不生效

    +

    1.2 坑2 Spring Security OAuth2已被新版本security弃用

    `,r:{minutes:1.62,words:487},y:"a",t:"各种版本的坑"}}],["/java/other/locateproblem/%E5%90%84%E7%A7%8D%E9%97%AE%E9%A2%98.html",{loader:()=>h(()=>import("./各种问题.html-BVVorpF6.js"),__vite__mapDeps([244,1])),meta:{d:16836768e5,l:"2023年5月10日",o:!0,e:`

    1. springcloudgateway负载均衡配置不生效

    配置如下,使用lb/serviceid配置负载均衡,一直报错,服务找不到

    spring:
       cloud:
    @@ -2667,7 +2667,7 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe - Path=/mcs/** filters: - StripPrefix=1 -
    `,r:{minutes:.33,words:98},y:"a",t:"开发中遇到的各种问题"}}],["/java/other/log/logback.html",{loader:()=>h(()=>import("./logback.html-DOiHN3-e.js"),__vite__mapDeps([245,1])),meta:{d:16847136e5,l:"2023年5月22日",g:["日志"],o:!0,e:`

    配置

    +`,r:{minutes:.33,words:98},y:"a",t:"开发中遇到的各种问题"}}],["/java/other/log/logback.html",{loader:()=>h(()=>import("./logback.html-bWzTkpCR.js"),__vite__mapDeps([245,1])),meta:{d:16847136e5,l:"2023年5月22日",g:["日志"],o:!0,e:`

    配置

    <?xml version="1.0" encoding="UTF-8"?>
     <!--
         小技巧: 在根pom里面设置统一存放路径,统一管理方便维护
    @@ -2756,9 +2756,9 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe <appender-ref ref="console"/> </root> </configuration> -
    `,r:{minutes:3.56,words:1068},y:"a",t:"LogBack"}}],["/java/other/maven/",{loader:()=>h(()=>import("./index.html-Dvn_QmO3.js"),__vite__mapDeps([246,1])),meta:{a:"chenkun",d:16589664e5,l:"2022年7月28日",e:`

    1. maven的放弃之路

    +`,r:{minutes:3.56,words:1068},y:"a",t:"LogBack"}}],["/java/other/maven/",{loader:()=>h(()=>import("./index.html-l0ianYW6.js"),__vite__mapDeps([246,1])),meta:{a:"chenkun",d:16589664e5,l:"2022年7月28日",e:`

    1. maven的放弃之路

    世上无难事,只要肯放弃

    -`,r:{minutes:.09,words:28},y:"a",t:"maven"}}],["/java/other/maven/build%E6%A0%87%E7%AD%BE.html",{loader:()=>h(()=>import("./build标签.html-uLUm0P1E.js"),__vite__mapDeps([247,1])),meta:{d:16861824e5,l:"2023年6月8日",c:["maven"],o:!0,e:`

    配置解释

    +`,r:{minutes:.09,words:28},y:"a",t:"maven"}}],["/java/other/maven/build%E6%A0%87%E7%AD%BE.html",{loader:()=>h(()=>import("./build标签.html-CyXt4kH2.js"),__vite__mapDeps([247,1])),meta:{d:16861824e5,l:"2023年6月8日",c:["maven"],o:!0,e:`

    配置解释

    resource标签的操作是通过maven-resource-plugin插件来实现的,通过配置标签来实现资源文件的过滤替换。这个标签的作用是告诉Maven,在拷贝源文件到目标路径之前,对源文件内容进行参数替换。

        <build>
             <resources>
    @@ -2792,9 +2792,9 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe </resources> </build> -
    `,r:{minutes:.8,words:241},y:"a",t:"build标签"}}],["/java/other/maven/import.html",{loader:()=>h(()=>import("./import.html-DtYCD-Aq.js"),__vite__mapDeps([248,1])),meta:{a:"chenkun",d:16620768e5,l:"2022年9月2日",e:`

    1. import介绍

    +`,r:{minutes:.8,words:241},y:"a",t:"build标签"}}],["/java/other/maven/import.html",{loader:()=>h(()=>import("./import.html-CNG84pM6.js"),__vite__mapDeps([248,1])),meta:{a:"chenkun",d:16620768e5,l:"2022年9月2日",e:`

    1. import介绍

    官方介绍

    -

    maven中的import是解决maven只能单个继承的问题,有时候我们的maven项目已经有了一个公司所自定义parent,但是我们想引入另一个项目中的dependencyManagement,此时可以用import,可以理解为把另一个项目中的dependencyManagement内容直接复制到本项目。

    `,r:{minutes:1.6,words:480},y:"a",t:"import使用"}}],["/java/other/maven/multiModule.html",{loader:()=>h(()=>import("./multiModule.html-k-P4UAvN.js"),__vite__mapDeps([249,1])),meta:{a:"chenkun",d:16589664e5,l:"2022年7月28日",e:`

    1、一个真实的多模块maven项目

    +

    maven中的import是解决maven只能单个继承的问题,有时候我们的maven项目已经有了一个公司所自定义parent,但是我们想引入另一个项目中的dependencyManagement,此时可以用import,可以理解为把另一个项目中的dependencyManagement内容直接复制到本项目。

    `,r:{minutes:1.6,words:480},y:"a",t:"import使用"}}],["/java/other/maven/multiModule.html",{loader:()=>h(()=>import("./multiModule.html-vU5Z_GHP.js"),__vite__mapDeps([249,1])),meta:{a:"chenkun",d:16589664e5,l:"2022年7月28日",e:`

    1、一个真实的多模块maven项目

    $ tree -d -L 2
     .———————————————————————————————————最顶级pom所在目录
     ├── ccs-auth
    @@ -2838,7 +2838,7 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe ├── ccs-weibao-api └── ccs-weibao-biz -
    `,r:{minutes:.85,words:255},y:"a",t:"多模块maven项目的搭建"}}],["/java/other/maven/problem.html",{loader:()=>h(()=>import("./problem.html-dW-7sW20.js"),__vite__mapDeps([250,1])),meta:{a:"chenkun",d:16589664e5,l:"2022年7月28日",e:`

    1、继承

    +`,r:{minutes:.85,words:255},y:"a",t:"多模块maven项目的搭建"}}],["/java/other/maven/problem.html",{loader:()=>h(()=>import("./problem.html-220H-omj.js"),__vite__mapDeps([250,1])),meta:{a:"chenkun",d:16589664e5,l:"2022年7月28日",e:`

    1、继承

    继承使用场景
    把公共的依赖都放在父模块,在子模块通过parent引用,使用了parent后,在父模块中dependencies定义的依赖会自动传递到子模块。

    @@ -2848,10 +2848,10 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe <groupId>com.chensino</groupId> <version>0.0.1-SNAPSHOT</version> </parent> -`,r:{minutes:2.72,words:816},y:"a",t:"使用问题记录"}}],["/java/other/maven/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html",{loader:()=>h(()=>import("./生命周期.html-DmTGecRc.js"),__vite__mapDeps([251,1])),meta:{d:1686096e6,l:"2023年6月7日",c:["maven"],o:!0,e:`

    Maven的生命周期

    +`,r:{minutes:2.72,words:816},y:"a",t:"使用问题记录"}}],["/java/other/maven/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html",{loader:()=>h(()=>import("./生命周期.html-CGxeIKAF.js"),__vite__mapDeps([251,1])),meta:{d:1686096e6,l:"2023年6月7日",c:["maven"],o:!0,e:`

    Maven的生命周期

    There are three built-in build lifecycles: default, clean and site. The default lifecycle handles your project deployment, the clean lifecycle handles project cleaning, while the site lifecycle handles the creation of your project's web site.

    各个生命周期包含的phase

    -

    官方说明

    `,r:{minutes:5.36,words:1609},y:"a",t:"Maven的生命周期"}}],["/404.html",{loader:()=>h(()=>import("./404.html-2xAmqrLX.js"),__vite__mapDeps([252,1])),meta:{y:"p",t:""}}],["/cpp/other/",{loader:()=>h(()=>import("./index.html-CuWpjIbb.js"),__vite__mapDeps([253,1])),meta:{y:"p",t:"Other"}}],["/cpp/",{loader:()=>h(()=>import("./index.html-CFkGmFia.js"),__vite__mapDeps([254,1])),meta:{y:"p",t:"Cpp"}}],["/other/computerprinciple/",{loader:()=>h(()=>import("./index.html-CPxFcKAG.js"),__vite__mapDeps([255,1])),meta:{y:"p",t:"Computerprinciple"}}],["/other/docker/",{loader:()=>h(()=>import("./index.html-dkKrYb2Y.js"),__vite__mapDeps([256,1])),meta:{y:"p",t:"Docker"}}],["/other/hardware/",{loader:()=>h(()=>import("./index.html-CmHVopsM.js"),__vite__mapDeps([257,1])),meta:{y:"p",t:"Hardware"}}],["/other/software/",{loader:()=>h(()=>import("./index.html-EMVH5RI8.js"),__vite__mapDeps([258,1])),meta:{y:"p",t:"Software"}}],["/other/sono/",{loader:()=>h(()=>import("./index.html-dhU_SREp.js"),__vite__mapDeps([259,1])),meta:{y:"p",t:"Sono"}}],["/other/windows/",{loader:()=>h(()=>import("./index.html-DD8LSjG9.js"),__vite__mapDeps([260,1])),meta:{y:"p",t:"Windows"}}],["/java/framework/",{loader:()=>h(()=>import("./index.html-0QvH_nXT.js"),__vite__mapDeps([261,1])),meta:{y:"p",t:"Framework"}}],["/java/framework/springcloud/",{loader:()=>h(()=>import("./index.html-eHCo69H8.js"),__vite__mapDeps([262,1])),meta:{y:"p",t:"Springcloud"}}],["/java/other/log/",{loader:()=>h(()=>import("./index.html-aM10vDis.js"),__vite__mapDeps([263,1])),meta:{y:"p",t:"Log"}}],["/category/",{loader:()=>h(()=>import("./index.html-Dhyapq1H.js"),__vite__mapDeps([264,1])),meta:{y:"p",t:"分类",I:!1}}],["/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/",{loader:()=>h(()=>import("./index.html-FIqAJepD.js"),__vite__mapDeps([265,1])),meta:{y:"p",t:"设计模式 分类",I:!1}}],["/category/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/",{loader:()=>h(()=>import("./index.html-BKgtb9Ah.js"),__vite__mapDeps([266,1])),meta:{y:"p",t:"使用指南 分类",I:!1}}],["/category/vue%E7%9F%A5%E8%AF%86%E7%82%B9/",{loader:()=>h(()=>import("./index.html-C7LrpzTI.js"),__vite__mapDeps([267,1])),meta:{y:"p",t:"vue知识点 分类",I:!1}}],["/category/js%E5%9F%BA%E7%A1%80/",{loader:()=>h(()=>import("./index.html-C6jwACfV.js"),__vite__mapDeps([268,1])),meta:{y:"p",t:"js基础 分类",I:!1}}],["/category/npm%E7%9F%A5%E8%AF%86%E7%82%B9/",{loader:()=>h(()=>import("./index.html-D38QGC2C.js"),__vite__mapDeps([269,1])),meta:{y:"p",t:"npm知识点 分类",I:!1}}],["/category/%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/",{loader:()=>h(()=>import("./index.html-CJudebxY.js"),__vite__mapDeps([270,1])),meta:{y:"p",t:"问题定位 分类",I:!1}}],["/category/vite/",{loader:()=>h(()=>import("./index.html-BDPTsTEv.js"),__vite__mapDeps([271,1])),meta:{y:"p",t:"vite 分类",I:!1}}],["/category/%E9%9B%86%E5%90%88/",{loader:()=>h(()=>import("./index.html-CzP4p5XK.js"),__vite__mapDeps([272,1])),meta:{y:"p",t:"集合 分类",I:!1}}],["/category/jdk/",{loader:()=>h(()=>import("./index.html-BHP5lSWG.js"),__vite__mapDeps([273,1])),meta:{y:"p",t:"jdk 分类",I:!1}}],["/category/%E7%BA%BF%E7%A8%8B%E6%B1%A0/",{loader:()=>h(()=>import("./index.html-DDz84u40.js"),__vite__mapDeps([274,1])),meta:{y:"p",t:"线程池 分类",I:!1}}],["/category/%E5%A4%9A%E7%BA%BF%E7%A8%8B/",{loader:()=>h(()=>import("./index.html-CL8Rn6LN.js"),__vite__mapDeps([275,1])),meta:{y:"p",t:"多线程 分类",I:!1}}],["/category/java/",{loader:()=>h(()=>import("./index.html-BnfXAjNS.js"),__vite__mapDeps([276,1])),meta:{y:"p",t:"java 分类",I:!1}}],["/category/%E5%AF%B9%E8%B1%A1%E9%94%81/",{loader:()=>h(()=>import("./index.html-zTuDnnQ9.js"),__vite__mapDeps([277,1])),meta:{y:"p",t:"对象锁 分类",I:!1}}],["/category/java%E5%9F%BA%E7%A1%80/",{loader:()=>h(()=>import("./index.html-B1xDsJG7.js"),__vite__mapDeps([278,1])),meta:{y:"p",t:"java基础 分类",I:!1}}],["/category/jvm/",{loader:()=>h(()=>import("./index.html-BQbSFfwU.js"),__vite__mapDeps([279,1])),meta:{y:"p",t:"jvm 分类",I:!1}}],["/category/%E7%94%B5%E5%AD%90%E4%B9%A6/",{loader:()=>h(()=>import("./index.html-BX9tTXXW.js"),__vite__mapDeps([280,1])),meta:{y:"p",t:"电子书 分类",I:!1}}],["/category/%E6%95%B0%E6%8D%AE%E5%BA%93/",{loader:()=>h(()=>import("./index.html-B-zCQLtF.js"),__vite__mapDeps([281,1])),meta:{y:"p",t:"数据库 分类",I:!1}}],["/category/docker/",{loader:()=>h(()=>import("./index.html-CY45JW6T.js"),__vite__mapDeps([282,1])),meta:{y:"p",t:"docker 分类",I:!1}}],["/category/%E8%BF%90%E7%BB%B4/",{loader:()=>h(()=>import("./index.html-f8Jf88Pj.js"),__vite__mapDeps([283,1])),meta:{y:"p",t:"运维 分类",I:!1}}],["/category/git-%E6%93%8D%E4%BD%9C/",{loader:()=>h(()=>import("./index.html-Ds23VMOY.js"),__vite__mapDeps([284,1])),meta:{y:"p",t:"git 操作 分类",I:!1}}],["/category/%E5%BF%85%E4%BC%9A/",{loader:()=>h(()=>import("./index.html-Cu9v8LOw.js"),__vite__mapDeps([285,1])),meta:{y:"p",t:"必会 分类",I:!1}}],["/category/linux/",{loader:()=>h(()=>import("./index.html-DCau-SSn.js"),__vite__mapDeps([286,1])),meta:{y:"p",t:"linux 分类",I:!1}}],["/category/markdown/",{loader:()=>h(()=>import("./index.html-uyHOrigb.js"),__vite__mapDeps([287,1])),meta:{y:"p",t:"markdown 分类",I:!1}}],["/category/%E5%85%AC%E5%8F%B8%E4%B8%9A%E5%8A%A1/",{loader:()=>h(()=>import("./index.html-CpRQKI06.js"),__vite__mapDeps([288,1])),meta:{y:"p",t:"公司业务 分类",I:!1}}],["/category/%E5%B0%8F%E7%BB%84%E5%88%86%E4%BA%AB/",{loader:()=>h(()=>import("./index.html-pad8GR4o.js"),__vite__mapDeps([289,1])),meta:{y:"p",t:"小组分享 分类",I:!1}}],["/category/web/",{loader:()=>h(()=>import("./index.html-jpPjIq77.js"),__vite__mapDeps([290,1])),meta:{y:"p",t:"web 分类",I:!1}}],["/category/oauth/",{loader:()=>h(()=>import("./index.html-CF8Xpy4A.js"),__vite__mapDeps([291,1])),meta:{y:"p",t:"oauth 分类",I:!1}}],["/category/%E6%A1%86%E6%9E%B6/",{loader:()=>h(()=>import("./index.html-RGleUAuC.js"),__vite__mapDeps([292,1])),meta:{y:"p",t:"框架 分类",I:!1}}],["/category/security/",{loader:()=>h(()=>import("./index.html-BQhu6ulG.js"),__vite__mapDeps([293,1])),meta:{y:"p",t:"security 分类",I:!1}}],["/category/security/",{loader:()=>h(()=>import("./index.html-BQhu6ulG.js"),__vite__mapDeps([293,1])),meta:{y:"p",t:"Security 分类",I:!1}}],["/category/oauth/",{loader:()=>h(()=>import("./index.html-CF8Xpy4A.js"),__vite__mapDeps([291,1])),meta:{y:"p",t:"OAuth 分类",I:!1}}],["/category/spring/",{loader:()=>h(()=>import("./index.html-BvXx7kZb.js"),__vite__mapDeps([294,1])),meta:{y:"p",t:"Spring 分类",I:!1}}],["/category/open-source/",{loader:()=>h(()=>import("./index.html-DJeEzsTa.js"),__vite__mapDeps([295,1])),meta:{y:"p",t:"open-source 分类",I:!1}}],["/category/maven/",{loader:()=>h(()=>import("./index.html-Ci4VSHuy.js"),__vite__mapDeps([296,1])),meta:{y:"p",t:"maven 分类",I:!1}}],["/tag/",{loader:()=>h(()=>import("./index.html-CLj8LoVt.js"),__vite__mapDeps([297,1])),meta:{y:"p",t:"标签",I:!1}}],["/tag/%E7%A6%81%E7%94%A8/",{loader:()=>h(()=>import("./index.html-3CT6OGMP.js"),__vite__mapDeps([298,1])),meta:{y:"p",t:"标签: 禁用",I:!1}}],["/tag/%E6%96%87%E7%AB%A0%E5%8A%A0%E5%AF%86/",{loader:()=>h(()=>import("./index.html-vvugCgoe.js"),__vite__mapDeps([299,1])),meta:{y:"p",t:"标签: 文章加密",I:!1}}],["/tag/markdown/",{loader:()=>h(()=>import("./index.html-E2fKYy3S.js"),__vite__mapDeps([300,1])),meta:{y:"p",t:"标签: Markdown",I:!1}}],["/tag/%E9%A1%B5%E9%9D%A2%E9%85%8D%E7%BD%AE/",{loader:()=>h(()=>import("./index.html-BvSZt_Zx.js"),__vite__mapDeps([301,1])),meta:{y:"p",t:"标签: 页面配置",I:!1}}],["/tag/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/",{loader:()=>h(()=>import("./index.html-LnP3a8gR.js"),__vite__mapDeps([302,1])),meta:{y:"p",t:"标签: 使用指南",I:!1}}],["/tag/%E4%BD%A0%E6%89%80%E4%B8%8D%E4%BA%86%E8%A7%A3%E7%9A%84javascript/",{loader:()=>h(()=>import("./index.html-B35-Vn16.js"),__vite__mapDeps([303,1])),meta:{y:"p",t:"标签: 你所不了解的JavaScript",I:!1}}],["/tag/%E5%BF%85%E4%BC%9A/",{loader:()=>h(()=>import("./index.html-CJbe2oEP.js"),__vite__mapDeps([304,1])),meta:{y:"p",t:"标签: 必会",I:!1}}],["/tag/vue%E4%B8%AD%E7%9A%84-typescript/",{loader:()=>h(()=>import("./index.html-Dn4DJ2Jv.js"),__vite__mapDeps([305,1])),meta:{y:"p",t:"标签: vue中的 TypeScript",I:!1}}],["/tag/vite/",{loader:()=>h(()=>import("./index.html-B6iZ__OM.js"),__vite__mapDeps([306,1])),meta:{y:"p",t:"标签: vite",I:!1}}],["/tag/ts/",{loader:()=>h(()=>import("./index.html-B3FdepDG.js"),__vite__mapDeps([307,1])),meta:{y:"p",t:"标签: ts",I:!1}}],["/tag/vue3/",{loader:()=>h(()=>import("./index.html-CaPxFNNf.js"),__vite__mapDeps([308,1])),meta:{y:"p",t:"标签: vue3",I:!1}}],["/tag/%E9%9B%86%E5%90%88/",{loader:()=>h(()=>import("./index.html-BfisB8tx.js"),__vite__mapDeps([309,1])),meta:{y:"p",t:"标签: 集合",I:!1}}],["/tag/---%E5%B9%B6%E5%8F%91/",{loader:()=>h(()=>import("./index.html-DKQaObvm.js"),__vite__mapDeps([310,1])),meta:{y:"p",t:"标签: -- 并发",I:!1}}],["/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/",{loader:()=>h(()=>import("./index.html-CDsKkWs-.js"),__vite__mapDeps([311,1])),meta:{y:"p",t:"标签: 多线程",I:!1}}],["/tag/%E7%BA%BF%E7%A8%8B%E6%B1%A0/",{loader:()=>h(()=>import("./index.html-XxdR190C.js"),__vite__mapDeps([312,1])),meta:{y:"p",t:"标签: 线程池",I:!1}}],["/tag/oauth/",{loader:()=>h(()=>import("./index.html-BXMP0bPq.js"),__vite__mapDeps([313,1])),meta:{y:"p",t:"标签: oauth",I:!1}}],["/tag/sso/",{loader:()=>h(()=>import("./index.html-AC98BttZ.js"),__vite__mapDeps([314,1])),meta:{y:"p",t:"标签: sso",I:!1}}],["/tag/%E9%83%A8%E7%BD%B2%E6%90%AD%E5%BB%BA/",{loader:()=>h(()=>import("./index.html-xL8R2rvI.js"),__vite__mapDeps([315,1])),meta:{y:"p",t:"标签: 部署搭建",I:!1}}],["/tag/%E5%AD%97%E8%8A%82%E7%A0%81/",{loader:()=>h(()=>import("./index.html-D2gpmgi4.js"),__vite__mapDeps([316,1])),meta:{y:"p",t:"标签: 字节码",I:!1}}],["/tag/%E5%8F%8D%E6%B1%87%E7%BC%96/",{loader:()=>h(()=>import("./index.html-HvLMGTgp.js"),__vite__mapDeps([317,1])),meta:{y:"p",t:"标签: 反汇编",I:!1}}],["/tag/mysql/",{loader:()=>h(()=>import("./index.html-BOnFN1d7.js"),__vite__mapDeps([318,1])),meta:{y:"p",t:"标签: mysql",I:!1}}],["/tag/%E6%95%B0%E6%8D%AE%E5%BA%93/",{loader:()=>h(()=>import("./index.html-Dv16igET.js"),__vite__mapDeps([319,1])),meta:{y:"p",t:"标签: 数据库",I:!1}}],["/tag/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/",{loader:()=>h(()=>import("./index.html-CxAtUmc7.js"),__vite__mapDeps([320,1])),meta:{y:"p",t:"标签: 数据结构",I:!1}}],["/tag/%E4%BA%8C%E5%8F%89%E6%A0%91/",{loader:()=>h(()=>import("./index.html-BUg70Vrr.js"),__vite__mapDeps([321,1])),meta:{y:"p",t:"标签: 二叉树",I:!1}}],["/tag/%E8%BF%90%E7%BB%B4/",{loader:()=>h(()=>import("./index.html-CCNjkOtl.js"),__vite__mapDeps([322,1])),meta:{y:"p",t:"标签: 运维",I:!1}}],["/tag/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/",{loader:()=>h(()=>import("./index.html-CUh5IBbk.js"),__vite__mapDeps([323,1])),meta:{y:"p",t:"标签: 工具使用",I:!1}}],["/tag/linux/",{loader:()=>h(()=>import("./index.html-DZIE4r_x.js"),__vite__mapDeps([324,1])),meta:{y:"p",t:"标签: linux",I:!1}}],["/tag/markdown/",{loader:()=>h(()=>import("./index.html-E2fKYy3S.js"),__vite__mapDeps([300,1])),meta:{y:"p",t:"标签: markdown",I:!1}}],["/tag/web/",{loader:()=>h(()=>import("./index.html-Bo2PC9xb.js"),__vite__mapDeps([325,1])),meta:{y:"p",t:"标签: web",I:!1}}],["/tag/springboot/",{loader:()=>h(()=>import("./index.html-BOWROSmg.js"),__vite__mapDeps([326,1])),meta:{y:"p",t:"标签: springboot",I:!1}}],["/tag/%E6%A1%86%E6%9E%B6/",{loader:()=>h(()=>import("./index.html-CeUSh-YY.js"),__vite__mapDeps([327,1])),meta:{y:"p",t:"标签: 框架",I:!1}}],["/tag/%E6%97%A5%E5%BF%97/",{loader:()=>h(()=>import("./index.html-DKhBFgRg.js"),__vite__mapDeps([328,1])),meta:{y:"p",t:"标签: 日志",I:!1}}],["/article/",{loader:()=>h(()=>import("./index.html-D3LGsC8Z.js"),__vite__mapDeps([329,1])),meta:{y:"p",t:"文章",I:!1}}],["/star/",{loader:()=>h(()=>import("./index.html-DqHDZd6R.js"),__vite__mapDeps([330,1])),meta:{y:"p",t:"星标",I:!1}}],["/timeline/",{loader:()=>h(()=>import("./index.html-76c_E0sL.js"),__vite__mapDeps([331,1])),meta:{y:"p",t:"时间轴",I:!1}}]]);/*! +

    官方说明

    `,r:{minutes:5.36,words:1609},y:"a",t:"Maven的生命周期"}}],["/404.html",{loader:()=>h(()=>import("./404.html-CFV1KPyG.js"),__vite__mapDeps([252,1])),meta:{y:"p",t:""}}],["/cpp/other/",{loader:()=>h(()=>import("./index.html-Gf4fmU9c.js"),__vite__mapDeps([253,1])),meta:{y:"p",t:"Other"}}],["/cpp/",{loader:()=>h(()=>import("./index.html-CbyN5TWa.js"),__vite__mapDeps([254,1])),meta:{y:"p",t:"Cpp"}}],["/other/computerprinciple/",{loader:()=>h(()=>import("./index.html-B0HiH-Xn.js"),__vite__mapDeps([255,1])),meta:{y:"p",t:"Computerprinciple"}}],["/other/docker/",{loader:()=>h(()=>import("./index.html-ByKY6u5T.js"),__vite__mapDeps([256,1])),meta:{y:"p",t:"Docker"}}],["/other/hardware/",{loader:()=>h(()=>import("./index.html-CTGnNX68.js"),__vite__mapDeps([257,1])),meta:{y:"p",t:"Hardware"}}],["/other/software/",{loader:()=>h(()=>import("./index.html-Bxi5AXy-.js"),__vite__mapDeps([258,1])),meta:{y:"p",t:"Software"}}],["/other/sono/",{loader:()=>h(()=>import("./index.html-DFsL5gjr.js"),__vite__mapDeps([259,1])),meta:{y:"p",t:"Sono"}}],["/other/windows/",{loader:()=>h(()=>import("./index.html-De67gXK7.js"),__vite__mapDeps([260,1])),meta:{y:"p",t:"Windows"}}],["/java/framework/",{loader:()=>h(()=>import("./index.html-DcPH7P5y.js"),__vite__mapDeps([261,1])),meta:{y:"p",t:"Framework"}}],["/java/framework/springcloud/",{loader:()=>h(()=>import("./index.html-jAf7-iM1.js"),__vite__mapDeps([262,1])),meta:{y:"p",t:"Springcloud"}}],["/java/other/log/",{loader:()=>h(()=>import("./index.html-ZenQwYfE.js"),__vite__mapDeps([263,1])),meta:{y:"p",t:"Log"}}],["/category/",{loader:()=>h(()=>import("./index.html-BA11w6YR.js"),__vite__mapDeps([264,1])),meta:{y:"p",t:"分类",I:!1}}],["/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/",{loader:()=>h(()=>import("./index.html-D0WBxxKk.js"),__vite__mapDeps([265,1])),meta:{y:"p",t:"设计模式 分类",I:!1}}],["/category/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/",{loader:()=>h(()=>import("./index.html-BJ_XZI-3.js"),__vite__mapDeps([266,1])),meta:{y:"p",t:"使用指南 分类",I:!1}}],["/category/vue%E7%9F%A5%E8%AF%86%E7%82%B9/",{loader:()=>h(()=>import("./index.html-DpIMle5T.js"),__vite__mapDeps([267,1])),meta:{y:"p",t:"vue知识点 分类",I:!1}}],["/category/js%E5%9F%BA%E7%A1%80/",{loader:()=>h(()=>import("./index.html-Bk4Xl8z2.js"),__vite__mapDeps([268,1])),meta:{y:"p",t:"js基础 分类",I:!1}}],["/category/npm%E7%9F%A5%E8%AF%86%E7%82%B9/",{loader:()=>h(()=>import("./index.html-n0MBVKbb.js"),__vite__mapDeps([269,1])),meta:{y:"p",t:"npm知识点 分类",I:!1}}],["/category/%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/",{loader:()=>h(()=>import("./index.html-CvVo_Iy8.js"),__vite__mapDeps([270,1])),meta:{y:"p",t:"问题定位 分类",I:!1}}],["/category/vite/",{loader:()=>h(()=>import("./index.html-DmeObyHP.js"),__vite__mapDeps([271,1])),meta:{y:"p",t:"vite 分类",I:!1}}],["/category/%E9%9B%86%E5%90%88/",{loader:()=>h(()=>import("./index.html-CiPcuEin.js"),__vite__mapDeps([272,1])),meta:{y:"p",t:"集合 分类",I:!1}}],["/category/jdk/",{loader:()=>h(()=>import("./index.html-ClRCgRtJ.js"),__vite__mapDeps([273,1])),meta:{y:"p",t:"jdk 分类",I:!1}}],["/category/%E7%BA%BF%E7%A8%8B%E6%B1%A0/",{loader:()=>h(()=>import("./index.html-DtMYUjJK.js"),__vite__mapDeps([274,1])),meta:{y:"p",t:"线程池 分类",I:!1}}],["/category/%E5%A4%9A%E7%BA%BF%E7%A8%8B/",{loader:()=>h(()=>import("./index.html-C2vp8KMJ.js"),__vite__mapDeps([275,1])),meta:{y:"p",t:"多线程 分类",I:!1}}],["/category/java/",{loader:()=>h(()=>import("./index.html-CEilrxfK.js"),__vite__mapDeps([276,1])),meta:{y:"p",t:"java 分类",I:!1}}],["/category/%E5%AF%B9%E8%B1%A1%E9%94%81/",{loader:()=>h(()=>import("./index.html-1bodzj_2.js"),__vite__mapDeps([277,1])),meta:{y:"p",t:"对象锁 分类",I:!1}}],["/category/java%E5%9F%BA%E7%A1%80/",{loader:()=>h(()=>import("./index.html-Du2EK4qm.js"),__vite__mapDeps([278,1])),meta:{y:"p",t:"java基础 分类",I:!1}}],["/category/jvm/",{loader:()=>h(()=>import("./index.html-QOoLQt7U.js"),__vite__mapDeps([279,1])),meta:{y:"p",t:"jvm 分类",I:!1}}],["/category/%E7%94%B5%E5%AD%90%E4%B9%A6/",{loader:()=>h(()=>import("./index.html-2E2sJd9T.js"),__vite__mapDeps([280,1])),meta:{y:"p",t:"电子书 分类",I:!1}}],["/category/%E6%95%B0%E6%8D%AE%E5%BA%93/",{loader:()=>h(()=>import("./index.html-KBuRJKDT.js"),__vite__mapDeps([281,1])),meta:{y:"p",t:"数据库 分类",I:!1}}],["/category/docker/",{loader:()=>h(()=>import("./index.html-gaUfJQSN.js"),__vite__mapDeps([282,1])),meta:{y:"p",t:"docker 分类",I:!1}}],["/category/%E8%BF%90%E7%BB%B4/",{loader:()=>h(()=>import("./index.html-CtpU9Ex-.js"),__vite__mapDeps([283,1])),meta:{y:"p",t:"运维 分类",I:!1}}],["/category/git-%E6%93%8D%E4%BD%9C/",{loader:()=>h(()=>import("./index.html-BGmSAx78.js"),__vite__mapDeps([284,1])),meta:{y:"p",t:"git 操作 分类",I:!1}}],["/category/%E5%BF%85%E4%BC%9A/",{loader:()=>h(()=>import("./index.html-BOFOt1os.js"),__vite__mapDeps([285,1])),meta:{y:"p",t:"必会 分类",I:!1}}],["/category/linux/",{loader:()=>h(()=>import("./index.html-CV_dUHJe.js"),__vite__mapDeps([286,1])),meta:{y:"p",t:"linux 分类",I:!1}}],["/category/markdown/",{loader:()=>h(()=>import("./index.html-Bt1B_fZ7.js"),__vite__mapDeps([287,1])),meta:{y:"p",t:"markdown 分类",I:!1}}],["/category/%E5%85%AC%E5%8F%B8%E4%B8%9A%E5%8A%A1/",{loader:()=>h(()=>import("./index.html-BgghxhP9.js"),__vite__mapDeps([288,1])),meta:{y:"p",t:"公司业务 分类",I:!1}}],["/category/%E5%B0%8F%E7%BB%84%E5%88%86%E4%BA%AB/",{loader:()=>h(()=>import("./index.html-BNCitO52.js"),__vite__mapDeps([289,1])),meta:{y:"p",t:"小组分享 分类",I:!1}}],["/category/web/",{loader:()=>h(()=>import("./index.html-ukDkHwB4.js"),__vite__mapDeps([290,1])),meta:{y:"p",t:"web 分类",I:!1}}],["/category/oauth/",{loader:()=>h(()=>import("./index.html-BR_hmahK.js"),__vite__mapDeps([291,1])),meta:{y:"p",t:"oauth 分类",I:!1}}],["/category/%E6%A1%86%E6%9E%B6/",{loader:()=>h(()=>import("./index.html-BluLs2ps.js"),__vite__mapDeps([292,1])),meta:{y:"p",t:"框架 分类",I:!1}}],["/category/security/",{loader:()=>h(()=>import("./index.html-BAA1oPlT.js"),__vite__mapDeps([293,1])),meta:{y:"p",t:"security 分类",I:!1}}],["/category/security/",{loader:()=>h(()=>import("./index.html-BAA1oPlT.js"),__vite__mapDeps([293,1])),meta:{y:"p",t:"Security 分类",I:!1}}],["/category/oauth/",{loader:()=>h(()=>import("./index.html-BR_hmahK.js"),__vite__mapDeps([291,1])),meta:{y:"p",t:"OAuth 分类",I:!1}}],["/category/spring/",{loader:()=>h(()=>import("./index.html-DERaj909.js"),__vite__mapDeps([294,1])),meta:{y:"p",t:"Spring 分类",I:!1}}],["/category/open-source/",{loader:()=>h(()=>import("./index.html-D1zTD58M.js"),__vite__mapDeps([295,1])),meta:{y:"p",t:"open-source 分类",I:!1}}],["/category/maven/",{loader:()=>h(()=>import("./index.html-Bq73GOIG.js"),__vite__mapDeps([296,1])),meta:{y:"p",t:"maven 分类",I:!1}}],["/tag/",{loader:()=>h(()=>import("./index.html-CONpmD-V.js"),__vite__mapDeps([297,1])),meta:{y:"p",t:"标签",I:!1}}],["/tag/%E7%A6%81%E7%94%A8/",{loader:()=>h(()=>import("./index.html-DfN87M_p.js"),__vite__mapDeps([298,1])),meta:{y:"p",t:"标签: 禁用",I:!1}}],["/tag/%E6%96%87%E7%AB%A0%E5%8A%A0%E5%AF%86/",{loader:()=>h(()=>import("./index.html-YlcxYzGW.js"),__vite__mapDeps([299,1])),meta:{y:"p",t:"标签: 文章加密",I:!1}}],["/tag/markdown/",{loader:()=>h(()=>import("./index.html-C_jbP7SN.js"),__vite__mapDeps([300,1])),meta:{y:"p",t:"标签: Markdown",I:!1}}],["/tag/%E9%A1%B5%E9%9D%A2%E9%85%8D%E7%BD%AE/",{loader:()=>h(()=>import("./index.html-DnWLnH1N.js"),__vite__mapDeps([301,1])),meta:{y:"p",t:"标签: 页面配置",I:!1}}],["/tag/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/",{loader:()=>h(()=>import("./index.html-DsIag3k6.js"),__vite__mapDeps([302,1])),meta:{y:"p",t:"标签: 使用指南",I:!1}}],["/tag/%E4%BD%A0%E6%89%80%E4%B8%8D%E4%BA%86%E8%A7%A3%E7%9A%84javascript/",{loader:()=>h(()=>import("./index.html-BB0RPG_h.js"),__vite__mapDeps([303,1])),meta:{y:"p",t:"标签: 你所不了解的JavaScript",I:!1}}],["/tag/%E5%BF%85%E4%BC%9A/",{loader:()=>h(()=>import("./index.html-BwnKcSJN.js"),__vite__mapDeps([304,1])),meta:{y:"p",t:"标签: 必会",I:!1}}],["/tag/vue%E4%B8%AD%E7%9A%84-typescript/",{loader:()=>h(()=>import("./index.html-DNyKapJd.js"),__vite__mapDeps([305,1])),meta:{y:"p",t:"标签: vue中的 TypeScript",I:!1}}],["/tag/vite/",{loader:()=>h(()=>import("./index.html-D3l9AP0G.js"),__vite__mapDeps([306,1])),meta:{y:"p",t:"标签: vite",I:!1}}],["/tag/ts/",{loader:()=>h(()=>import("./index.html-k__z7vRJ.js"),__vite__mapDeps([307,1])),meta:{y:"p",t:"标签: ts",I:!1}}],["/tag/vue3/",{loader:()=>h(()=>import("./index.html-tdfwMWEj.js"),__vite__mapDeps([308,1])),meta:{y:"p",t:"标签: vue3",I:!1}}],["/tag/%E9%9B%86%E5%90%88/",{loader:()=>h(()=>import("./index.html-CNk3LVBO.js"),__vite__mapDeps([309,1])),meta:{y:"p",t:"标签: 集合",I:!1}}],["/tag/---%E5%B9%B6%E5%8F%91/",{loader:()=>h(()=>import("./index.html-C8vCtcrV.js"),__vite__mapDeps([310,1])),meta:{y:"p",t:"标签: -- 并发",I:!1}}],["/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/",{loader:()=>h(()=>import("./index.html-bCycLo4M.js"),__vite__mapDeps([311,1])),meta:{y:"p",t:"标签: 多线程",I:!1}}],["/tag/%E7%BA%BF%E7%A8%8B%E6%B1%A0/",{loader:()=>h(()=>import("./index.html-BDDCxsP9.js"),__vite__mapDeps([312,1])),meta:{y:"p",t:"标签: 线程池",I:!1}}],["/tag/oauth/",{loader:()=>h(()=>import("./index.html-l2dMz0nJ.js"),__vite__mapDeps([313,1])),meta:{y:"p",t:"标签: oauth",I:!1}}],["/tag/sso/",{loader:()=>h(()=>import("./index.html-43Da7xtg.js"),__vite__mapDeps([314,1])),meta:{y:"p",t:"标签: sso",I:!1}}],["/tag/%E9%83%A8%E7%BD%B2%E6%90%AD%E5%BB%BA/",{loader:()=>h(()=>import("./index.html-Be9YOXiD.js"),__vite__mapDeps([315,1])),meta:{y:"p",t:"标签: 部署搭建",I:!1}}],["/tag/%E5%AD%97%E8%8A%82%E7%A0%81/",{loader:()=>h(()=>import("./index.html-DYNHttZu.js"),__vite__mapDeps([316,1])),meta:{y:"p",t:"标签: 字节码",I:!1}}],["/tag/%E5%8F%8D%E6%B1%87%E7%BC%96/",{loader:()=>h(()=>import("./index.html-CrqLYD8s.js"),__vite__mapDeps([317,1])),meta:{y:"p",t:"标签: 反汇编",I:!1}}],["/tag/mysql/",{loader:()=>h(()=>import("./index.html-DI5pE721.js"),__vite__mapDeps([318,1])),meta:{y:"p",t:"标签: mysql",I:!1}}],["/tag/%E6%95%B0%E6%8D%AE%E5%BA%93/",{loader:()=>h(()=>import("./index.html-CXTrskFo.js"),__vite__mapDeps([319,1])),meta:{y:"p",t:"标签: 数据库",I:!1}}],["/tag/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/",{loader:()=>h(()=>import("./index.html-UDfVv4MI.js"),__vite__mapDeps([320,1])),meta:{y:"p",t:"标签: 数据结构",I:!1}}],["/tag/%E4%BA%8C%E5%8F%89%E6%A0%91/",{loader:()=>h(()=>import("./index.html-BJxwK7AA.js"),__vite__mapDeps([321,1])),meta:{y:"p",t:"标签: 二叉树",I:!1}}],["/tag/%E8%BF%90%E7%BB%B4/",{loader:()=>h(()=>import("./index.html-DqEIAryj.js"),__vite__mapDeps([322,1])),meta:{y:"p",t:"标签: 运维",I:!1}}],["/tag/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/",{loader:()=>h(()=>import("./index.html-BkurDFO4.js"),__vite__mapDeps([323,1])),meta:{y:"p",t:"标签: 工具使用",I:!1}}],["/tag/linux/",{loader:()=>h(()=>import("./index.html-C4Z9a5GS.js"),__vite__mapDeps([324,1])),meta:{y:"p",t:"标签: linux",I:!1}}],["/tag/markdown/",{loader:()=>h(()=>import("./index.html-C_jbP7SN.js"),__vite__mapDeps([300,1])),meta:{y:"p",t:"标签: markdown",I:!1}}],["/tag/web/",{loader:()=>h(()=>import("./index.html-Cwy-RPtK.js"),__vite__mapDeps([325,1])),meta:{y:"p",t:"标签: web",I:!1}}],["/tag/springboot/",{loader:()=>h(()=>import("./index.html-C6FMNcFL.js"),__vite__mapDeps([326,1])),meta:{y:"p",t:"标签: springboot",I:!1}}],["/tag/%E6%A1%86%E6%9E%B6/",{loader:()=>h(()=>import("./index.html-da2hiqxj.js"),__vite__mapDeps([327,1])),meta:{y:"p",t:"标签: 框架",I:!1}}],["/tag/%E6%97%A5%E5%BF%97/",{loader:()=>h(()=>import("./index.html-DH61zfDk.js"),__vite__mapDeps([328,1])),meta:{y:"p",t:"标签: 日志",I:!1}}],["/article/",{loader:()=>h(()=>import("./index.html-3y6s76rT.js"),__vite__mapDeps([329,1])),meta:{y:"p",t:"文章",I:!1}}],["/star/",{loader:()=>h(()=>import("./index.html-BoICU8sT.js"),__vite__mapDeps([330,1])),meta:{y:"p",t:"星标",I:!1}}],["/timeline/",{loader:()=>h(()=>import("./index.html-ChsojBeT.js"),__vite__mapDeps([331,1])),meta:{y:"p",t:"时间轴",I:!1}}]]);/*! * vue-router v4.4.0 * (c) 2024 Eduardo San Martin Morote * @license MIT @@ -2859,4 +2859,4 @@ Spring官方提供了此类。

    `,r:{minutes:3.43,words:1028},y:"a",t:"OnecePe `)},rootComponents:[]}),kp=async(n,e)=>{const{path:t,query:a}=n.currentRoute.value,{scrollBehavior:s}=n.options;n.options.scrollBehavior=void 0,await n.replace({path:t,query:a,hash:e}),n.options.scrollBehavior=s},L2=({headerLinkSelector:n,headerAnchorSelector:e,delay:t,offset:a=5})=>{const s=me();Fn("scroll",s2(()=>{var _,b;const o=Math.max(window.scrollY,document.documentElement.scrollTop,document.body.scrollTop);if(Math.abs(o-0)d.some(w=>w.hash===E.hash));for(let E=0;E=(((_=w.parentElement)==null?void 0:_.offsetTop)??0)-a,S=!L||o<(((b=L.parentElement)==null?void 0:b.offsetTop)??0)-a;if(!(f&&S))continue;const A=decodeURIComponent(s.currentRoute.value.hash),H=decodeURIComponent(w.hash);if(A===H)return;if(u){for(let M=E+1;MAn(n.title)?{title:n.title}:null;const hc=Symbol(""),P2=n=>{dc=n},j2=()=>wn(hc),D2=n=>{n.provide(hc,dc)};var R2={"/":{title:"目录",empty:"暂无目录"}};const F2=V({name:"Catalog",props:{base:{type:String,default:""},level:{type:Number,default:3},index:Boolean,hideHeading:Boolean},setup(n){const e=j2(),t=fa(R2),a=gn(),s=qk(),l=Qi(),i=Nn(ot(s.value).map(([r,{meta:u}])=>{const d=e(u);if(!d)return null;const k=r.split("/").length;return{level:Gk(r,"/")?k-2:k-1,base:r.replace(/\/[^/]+\/?$/,"/"),path:r,...d}}).filter(r=>ma(r)&&An(r.title))),c=y(()=>{const r=n.base?Ah(Bi(n.base)):a.value.path.replace(/\/[^/]+$/,"/"),u=r.split("/").length-2,d=[];return i.value.filter(({level:k,path:g})=>{if(!St(g,r)||g===r)return!1;if(r==="/"){const _=re(l.value.locales).filter(b=>b!=="/");if(g==="/404.html"||_.some(b=>St(g,b)))return!1}return k-u<=n.level}).sort(({title:k,level:g,order:_},{title:b,level:E,order:w})=>{const L=g-E;return L||(js(_)?js(w)?_>0?w>0?_-w:-1:w<0?_-w:1:_:js(w)?w:k.localeCompare(b))}).forEach(k=>{var b;const{base:g,level:_}=k;switch(_-u){case 1:{d.push(k);break}case 2:{const E=d.find(w=>w.path===g);E&&(E.children??(E.children=[])).push(k);break}default:{const E=d.find(w=>w.path===g.replace(/\/[^/]+\/$/,"/"));if(E){const w=(b=E.children)==null?void 0:b.find(L=>L.path===g);w&&(w.children??(w.children=[])).push(k)}}}}),d});return()=>{const r=c.value.some(u=>u.children);return p("div",{class:["vp-catalog-wrapper",{index:n.index}]},[n.hideHeading?null:p("h2",{class:"vp-catalog-main-title"},t.value.title),c.value.length?p(n.index?"ol":"ul",{class:["vp-catalogs",{deep:r}]},c.value.map(({children:u=[],title:d,path:k,content:g})=>{const _=p(Tn,{class:"vp-catalog-title",to:k},()=>g?p(g):d);return p("li",{class:"vp-catalog"},r?[p("h3",{id:d,class:["vp-catalog-child-title",{"has-children":u.length}]},[p("a",{href:`#${d}`,class:"vp-catalog-header-anchor","aria-hidden":!0},"#"),_]),u.length?p(n.index?"ol":"ul",{class:"vp-child-catalogs"},u.map(({children:b=[],content:E,path:w,title:L})=>p("li",{class:"vp-child-catalog"},[p("div",{class:["vp-catalog-sub-title",{"has-children":b.length}]},[p("a",{href:`#${L}`,class:"vp-catalog-header-anchor"},"#"),p(Tn,{class:"vp-catalog-title",to:w},()=>E?p(E):L)]),b.length?p(n.index?"ol":"div",{class:n.index?"vp-sub-catalogs":"vp-sub-catalogs-wrapper"},b.map(({content:f,path:S,title:F})=>n.index?p("li",{class:"vp-sub-catalog"},p(Tn,{to:S},()=>f?p(f):F)):p(Tn,{class:"vp-sub-catalog-link",to:S},()=>f?p(f):F))):null]))):null]:p("div",{class:"vp-catalog-child-title"},_))})):p("p",{class:"vp-empty-catalog"},t.value.empty)])}}}),B2=ae({enhance:({app:n})=>{D2(n),xe("Catalog",n)||n.component("Catalog",F2)}});var M2={"/":{backToTop:"返回顶部"}};const V2=V({name:"BackToTop",setup(n){const e=fn(),t=fa(M2),a=Nn(),{height:s}=f2(a),{height:l}=E2(),{y:o}=w2(),i=y(()=>e.value.backToTop!==!1&&o.value>100),c=y(()=>o.value/(s.value-l.value)*100);return En(()=>{a.value=document.body}),()=>p($e,{name:"back-to-top"},()=>i.value?p("button",{type:"button",class:"vp-back-to-top-button","aria-label":t.value.backToTop,onClick:()=>{window.scrollTo({top:0,behavior:"smooth"})}},[p("span",{class:"vp-scroll-progress",role:"progressbar","aria-labelledby":"loadinglabel","aria-valuenow":c.value},p("svg",p("circle",{cx:"50%",cy:"50%",style:{"stroke-dasharray":`calc(${Math.PI*c.value}% - ${4*Math.PI}px) calc(${Math.PI*100}% - ${4*Math.PI}px)`}}))),p("div",{class:"back-to-top-icon"})]):null)}}),q2=ae({rootComponents:[V2]}),N2=p("svg",{class:"external-link-icon",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"},[p("path",{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"}),p("polygon",{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"})]),kc=V({name:"ExternalLinkIcon",props:{locales:{type:Object,required:!1,default:()=>({})}},setup(n){const e=fe(),t=y(()=>n.locales[e.value]??{openInNewWindow:"open in new window"});return()=>p("span",[N2,p("span",{class:"external-link-icon-sr-only"},t.value.openInNewWindow)])}});var H2={};const $2=H2,z2=ae({enhance({app:n}){n.component("ExternalLinkIcon",p(kc,{locales:$2}))}});/** * NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress * @license MIT - */const pn={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
    '},status:null,set:n=>{const e=pn.isStarted();n=Fs(n,pn.settings.minimum,1),pn.status=n===1?null:n;const t=pn.render(!e),a=t.querySelector(pn.settings.barSelector),s=pn.settings.speed,l=pn.settings.easing;return t.offsetWidth,U2(o=>{qa(a,{transform:"translate3d("+gp(n)+"%,0,0)",transition:"all "+s+"ms "+l}),n===1?(qa(t,{transition:"none",opacity:"1"}),t.offsetWidth,setTimeout(function(){qa(t,{transition:"all "+s+"ms linear",opacity:"0"}),setTimeout(function(){pn.remove(),o()},s)},s)):setTimeout(()=>o(),s)}),pn},isStarted:()=>typeof pn.status=="number",start:()=>{pn.status||pn.set(0);const n=()=>{setTimeout(()=>{pn.status&&(pn.trickle(),n())},pn.settings.trickleSpeed)};return pn.settings.trickle&&n(),pn},done:n=>!n&&!pn.status?pn:pn.inc(.3+.5*Math.random()).set(1),inc:n=>{let e=pn.status;return e?(typeof n!="number"&&(n=(1-e)*Fs(Math.random()*e,.1,.95)),e=Fs(e+n,0,.994),pn.set(e)):pn.start()},trickle:()=>pn.inc(Math.random()*pn.settings.trickleRate),render:n=>{if(pn.isRendered())return document.getElementById("nprogress");mp(document.documentElement,"nprogress-busy");const e=document.createElement("div");e.id="nprogress",e.innerHTML=pn.settings.template;const t=e.querySelector(pn.settings.barSelector),a=n?"-100":gp(pn.status||0),s=document.querySelector(pn.settings.parent);return qa(t,{transition:"all 0 linear",transform:"translate3d("+a+"%,0,0)"}),s!==document.body&&mp(s,"nprogress-custom-parent"),s==null||s.appendChild(e),e},remove:()=>{vp(document.documentElement,"nprogress-busy"),vp(document.querySelector(pn.settings.parent),"nprogress-custom-parent");const n=document.getElementById("nprogress");n&&J2(n)},isRendered:()=>!!document.getElementById("nprogress")},Fs=(n,e,t)=>nt?t:n,gp=n=>(-1+n)*100,U2=function(){const n=[];function e(){const t=n.shift();t&&t(e)}return function(t){n.push(t),n.length===1&&e()}}(),qa=function(){const n=["Webkit","O","Moz","ms"],e={};function t(o){return o.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(i,c){return c.toUpperCase()})}function a(o){const i=document.body.style;if(o in i)return o;let c=n.length;const r=o.charAt(0).toUpperCase()+o.slice(1);let u;for(;c--;)if(u=n[c]+r,u in i)return u;return o}function s(o){return o=t(o),e[o]??(e[o]=a(o))}function l(o,i,c){i=s(i),o.style[i]=c}return function(o,i){for(const c in i){const r=i[c];r!==void 0&&Object.prototype.hasOwnProperty.call(i,c)&&l(o,c,r)}}}(),gc=(n,e)=>(typeof n=="string"?n:$l(n)).indexOf(" "+e+" ")>=0,mp=(n,e)=>{const t=$l(n),a=t+e;gc(t,e)||(n.className=a.substring(1))},vp=(n,e)=>{const t=$l(n);if(!gc(n,e))return;const a=t.replace(" "+e+" "," ");n.className=a.substring(1,a.length-1)},$l=n=>(" "+(n.className||"")+" ").replace(/\s+/gi," "),J2=n=>{n&&n.parentNode&&n.parentNode.removeChild(n)},G2=()=>{En(()=>{const n=me(),e=new Set;e.add(n.currentRoute.value.path),n.beforeEach(t=>{e.has(t.path)||pn.start()}),n.afterEach(t=>{e.add(t.path),pn.done()})})},W2=ae({setup(){G2()}}),K2=JSON.parse('{"encrypt":{"config":{"/guide/encrypt.html":["$2a$10$LQfNk6LhUPanOi.l0iItuustw6M36YEM9myUio6Fl6T12fh/axsrS"]}},"fullscreen":true,"author":{"name":"ChenSino","url":"https://ChenSino.github.io"},"logo":"/logo.svg","repo":"ChenSino/ChenSino.github.io","docsDir":"docs","docsBranch":"dev","footer":"除了自渡,其他人爱莫能助","displayFooter":true,"themeColor":true,"pageInfo":["Author","Original","Date","Category","Tag","ReadingTime"],"blog":{"name":"ChenSino","avatar":"https://afatpig.oss-cn-chengdu.aliyuncs.com/blog/20220802180305.png","roundAvatar":true,"description":"洛星星的爸爸","intro":"https://chensino.github.io/","medias":{"Baidu":"https://example.com","Weibo":"https://example.com","Zhihu":"https://example.com"}},"locales":{"/":{"lang":"zh-CN","navbarLocales":{"langName":"简体中文","selectLangAriaLabel":"选择语言"},"metaLocales":{"author":"作者","date":"写作日期","origin":"原创","views":"访问量","category":"分类","tag":"标签","readingTime":"阅读时间","words":"字数","toc":"此页内容","prev":"上一页","next":"下一页","lastUpdated":"上次编辑于","contributors":"贡献者","editLink":"编辑此页","print":"打印"},"blogLocales":{"article":"文章","articleList":"文章列表","category":"分类","tag":"标签","timeline":"时间轴","timelineTitle":"昨日不在","all":"全部","intro":"个人介绍","star":"星标","empty":"$text 为空"},"paginationLocales":{"prev":"上一页","next":"下一页","navigate":"跳转到","action":"前往","errorText":"请输入 1 到 $page 之前的页码!"},"outlookLocales":{"themeColor":"主题色","darkmode":"外观","fullscreen":"全屏"},"encryptLocales":{"iconLabel":"文章已加密","placeholder":"输入密码","remember":"记住密码","errorHint":"请输入正确的密码"},"routeLocales":{"skipToContent":"跳至主要內容","notFoundTitle":"页面不存在","notFoundMsg":["这里什么也没有","我们是怎么来到这儿的?","这 是 四 零 四 !","看起来你访问了一个失效的链接"],"back":"返回上一页","home":"带我回家","openInNewWindow":"Open in new window"},"navbar":["/home","/",{"text":"Java","icon":"java","prefix":"/java/","children":[{"text":"Java基础","icon":"java","link":"base/Serialization"},{"text":"Java进阶","icon":"java","link":"advance/ProxyInJava"},{"text":"Java虚拟机","icon":"java","link":"jvm/NewObject"},{"text":"Java框架","icon":"java","prefix":"framework/","children":[{"text":"Spring","link":"spring/"},{"text":"Security","link":"security/"},{"text":"SpringBoot","link":"springboot/"},{"text":"Mybatis","link":"mybatis/"}]},{"text":"其他","icon":"other","prefix":"other/","children":[{"text":"Maven","link":"maven/"},{"text":"Gradle","icon":"java","link":"gradle/"},{"text":"Java问题定位","icon":"java","link":"locateproblem/"},{"text":"Java版本","icon":"java","link":"JdkVersion.md"}]}]},{"text":"前端","icon":"javascript","prefix":"/frontweb/","children":[{"text":"Vue","icon":"vue","link":"vue/"},{"text":"Vite","icon":"vue","link":"vite/"},{"text":"ES5","icon":"javascript","link":"es5/"},{"text":"ES6","icon":"javascript","link":"es6/"},{"text":"TypeScript","icon":"javascript","link":"typeScript/"},{"text":"NodeJS","icon":"nodejs","link":"nodejs/"}]},{"text":"设计模式","icon":"java","link":"/designpattern/"},{"text":"前后分离项目搭建","icon":"app","link":"/other/web/README.md"},{"text":"C++学习","icon":"app","link":"/cpp/study/README.md"},{"text":"家庭服务器","icon":"app","link":"/myserver/README.md"},{"text":"其他","icon":"others","prefix":"/other/","children":[{"text":"Web","icon":"vue","link":"web/"},{"text":"Git","icon":"git","link":"git/GitCommands"},{"text":"Linux","icon":"linux","link":"linux/CommonUsedCMD"},{"text":"Docker","icon":"ubuntu","link":"docker/Docker.md"},{"text":"DataBase","icon":"java","link":"database/CPUOverLoad"},{"text":"MarkDown","icon":"markdown","link":"markdown/"},{"text":"工具软件","icon":"software","link":"tools/idea"},{"text":"小组分享","icon":"share","link":"training/CloudServiceTraining"},{"text":"随笔杂记","icon":"activity","link":"essay/2022-04-12"},{"text":"电子书资源","icon":"app","link":"books/ebooks"},{"text":"分布式微服务","icon":"class","link":"distributeservice/DistributeLock"},{"text":"OAuth2.0","icon":"class","link":"oauth2/"},{"text":"PVE虚拟机","icon":"class","link":"pve/"}]}],"sidebar":{"/java/":[{"text":"Java 基础","icon":"discover","prefix":"base/","collapsible":true,"children":"structure"},{"text":"Java 进阶","icon":"blog","prefix":"advance/","collapsible":true,"children":"structure"},{"text":"Java 虚拟机","icon":"write","prefix":"jvm/","collapsible":true,"children":"structure"},{"text":"Java框架","icon":"write","prefix":"framework/","collapsible":true,"children":"structure"}],"/java/other/":["JdkVersion",{"text":"Maven","icon":"discover","prefix":"maven/","collapsible":true,"children":"structure"},{"text":"Gradle","icon":"discover","prefix":"gradle/","collapsible":true,"children":"structure"},{"text":"Java问题定位","icon":"java","prefix":"locateproblem/","collapsible":true,"children":"structure"}],"/frontweb/":[{"text":"Vue","icon":"vue","prefix":"vue/","collapsible":true,"children":"structure"},{"text":"Vite","icon":"vue","prefix":"vite/","collapsible":true,"children":[]},{"text":"ECMAScript 5","icon":"write","prefix":"es5/","collapsible":true,"children":"structure"},{"text":"ECMAScript 6","icon":"blog","prefix":"es6/","collapsible":true,"children":"structure"},{"text":"TypeScript","icon":"typescript","prefix":"typeScript/","collapsible":true,"children":"structure"},{"text":"NodeJS","icon":"nodejs","prefix":"nodejs/","collapsible":true,"children":"structure"}],"/designpattern/":"structure","/cpp/":[{"text":"基础语法学习","icon":"app","prefix":"study/","collapsible":true,"children":"structure"},{"text":"其他","icon":"app","prefix":"other/","collapsible":true,"children":"structure"}],"/myserver/":"structure","/other/":[{"text":"Git","icon":"git","prefix":"git/","collapsible":true,"children":"structure"},{"text":"Linux","icon":"linux","prefix":"linux/","collapsible":true,"children":"structure"},{"text":"数据库","icon":"repo","prefix":"database/","collapsible":true,"children":"structure"},{"text":"MarkDown","icon":"markdown","prefix":"markdown/","collapsible":true,"children":"structure"},{"text":"工具软件","icon":"software","prefix":"tools/","collapsible":true,"children":"structure"},{"text":"小组分享","icon":"share","prefix":"training/","collapsible":true,"children":"structure"},{"text":"随笔分享","icon":"share","prefix":"essay/","collapsible":true,"children":"structure"},{"text":"web","icon":"share","prefix":"web/","collapsible":true,"children":"structure"},{"text":"分布式微服务","icon":"share","prefix":"distributeservice/","collapsible":true,"children":"structure"},{"text":"OAuth2.0","icon":"share","prefix":"oauth2/","collapsible":true,"children":"structure"},{"text":"Docker","icon":"ubuntu","prefix":"docker/","collapsible":true,"children":"structure"},{"text":"PVE","icon":"class","prefix":"pve/","collapsible":true,"children":"structure"}]}}}}'),X2=K(K2),mc=()=>X2,vc=Symbol(""),Y2=()=>{const n=wn(vc);if(!n)throw new Error("useThemeLocaleData() is called without provider.");return n},Q2=(n,e)=>{const{locales:t,...a}=n;return{...a,...t==null?void 0:t[e]}},Z2=ae({enhance({app:n}){const e=mc(),t=n._context.provides[Vl],a=y(()=>Q2(e.value,t.routeLocale.value));n.provide(vc,a),Object.defineProperties(n.config.globalProperties,{$theme:{get(){return e.value}},$themeLocale:{get(){return a.value}}})}}),n1=/\b(?:Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini)/i,e1=()=>typeof window<"u"&&window.navigator&&"userAgent"in window.navigator&&n1.test(navigator.userAgent),t1=({delay:n=500,duration:e=2e3,locales:t,selector:a,showInMobile:s})=>{const{copy:l,copied:o}=r2({legacy:!0,copiedDuring:e}),i=fa(t),c=gn(),r=k=>{if(!k.hasAttribute("copy-code-registered")){const g=document.createElement("button");g.type="button",g.classList.add("vp-copy-code-button"),g.innerHTML='
    ',g.setAttribute("aria-label",i.value.copy),g.setAttribute("data-copied",i.value.copied),k.parentElement&&k.parentElement.insertBefore(g,k),k.setAttribute("copy-code-registered","")}},u=()=>{Ue().then(()=>tc(n)).then(()=>{a.forEach(k=>{document.querySelectorAll(k).forEach(r)})})},d=(k,g,_)=>{let{innerText:b=""}=g;/language-(shellscript|shell|bash|sh|zsh)/.test(k.classList.toString())&&(b=b.replace(/^ *(\$|>) /gm,"")),l(b).then(()=>{_.classList.add("copied"),dn(o,()=>{_.classList.remove("copied"),_.blur()},{once:!0})})};En(()=>{const k=!e1()||s;k&&u(),Fn("click",g=>{const _=g.target;if(_.matches('div[class*="language-"] > button.copy')){const b=_.parentElement,E=_.nextElementSibling;E&&d(b,E,_)}else if(_.matches('div[class*="language-"] div.vp-copy-icon')){const b=_.parentElement,E=b.parentElement,w=b.nextElementSibling;w&&d(E,w,b)}}),dn(()=>c.value.path,()=>{k&&u()})})};var a1={"/":{copy:"复制代码",copied:"已复制"}},s1=['.theme-hope-content div[class*="language-"] pre'];const l1=500,o1=2e3,p1=a1,i1=s1,c1=!1,r1=ae({setup:()=>{t1({selector:i1,locales:p1,duration:o1,delay:l1,showInMobile:c1})}}),u1=()=>{Fn("beforeprint",()=>{document.querySelectorAll("details").forEach(n=>{n.open=!0})})},d1=ae({enhance:({app:n})=>{},setup:()=>{u1()}});let h1={};const fc=Symbol(""),k1=()=>wn(fc),g1=n=>{n.provide(fc,h1)},m1='
    ',v1=n=>An(n)?Array.from(document.querySelectorAll(n)):n.map(e=>Array.from(document.querySelectorAll(e))).flat(),_c=n=>new Promise((e,t)=>{n.complete?e({type:"image",element:n,src:n.src,width:n.naturalWidth,height:n.naturalHeight,alt:n.alt,msrc:n.src}):(n.onload=()=>e(_c(n)),n.onerror=a=>t(a))}),f1=n=>{const{isSupported:e,toggle:t}=Hl();n.on("uiRegister",()=>{e.value&&n.ui.registerElement({name:"fullscreen",order:7,isButton:!0,html:'',onClick:()=>{t()}}),n.ui.registerElement({name:"download",order:8,isButton:!0,tagName:"a",html:{isCustomSVG:!0,inner:'',outlineID:"pswp__icn-download"},onInit:(a,s)=>{a.setAttribute("download",""),a.setAttribute("target","_blank"),a.setAttribute("rel","noopener"),s.on("change",()=>{a.setAttribute("href",s.currSlide.data.src)})}}),n.ui.registerElement({name:"bulletsIndicator",className:"photo-swipe-bullets-indicator",appendTo:"wrapper",onInit:(a,s)=>{const l=[];let o=-1;for(let i=0;i{s.goTo(l.indexOf(r.target))},l.push(c),a.appendChild(c)}s.on("change",()=>{o>=0&&l[o].classList.remove("active"),l[s.currIndex].classList.add("active"),o=s.currIndex})}})})},_1=(n,e,t=!0)=>h(()=>import("./photoswipe.esm-GXRgw7eJ.js"),__vite__mapDeps([])).then(({default:a})=>{let s=null;const l=n.map(o=>({html:m1,element:o,msrc:o.src}));return n.forEach((o,i)=>{const c=()=>{s==null||s.destroy(),s=new a({preloaderDelay:0,showHideAnimationType:"zoom",...e,dataSource:l,index:i,...t?{closeOnVerticalDrag:!0,wheelToZoom:!1}:{}}),f1(s),s.addFilter("thumbEl",()=>o),s.addFilter("placeholderSrc",()=>o.src),s.init()};o.getAttribute("photo-swipe")||(o.style.cursor="zoom-in",o.addEventListener("click",()=>{c()}),o.addEventListener("keypress",({key:r})=>{r==="Enter"&&c()}),o.setAttribute("photo-swipe","")),_c(o).then(r=>{l.splice(i,1,r),s==null||s.refreshSlideContent(i)})}),t?Fn("wheel",()=>{s==null||s.close()}):()=>{}}),y1=({selector:n,locales:e,delay:t=500,scrollToClose:a=!0})=>{const s=k1(),l=fa(e),o=gn(),i=fn();let c=null;const r=()=>{const{photoSwipe:u}=i.value;u!==!1&&Ue().then(()=>tc(t)).then(async()=>{const d=An(u)?u:n;c=await _1(v1(d),{...s,...l.value},a)})};En(()=>{r(),dn(()=>o.value.path,()=>{c==null||c(),r()})}),da(()=>{c==null||c()})};var b1={"/":{closeTitle:"关闭",downloadTitle:"下载图片",fullscreenTitle:"切换全屏",zoomTitle:"缩放",arrowPrevTitle:"上一个 (左箭头)",arrowNextTitle:"下一个 (右箭头)"}};const w1=".theme-hope-content :not(a) > img:not([no-view])",E1=b1,x1=800,A1=!0,L1=ae({enhance:({app:n})=>{g1(n)},setup:()=>{y1({selector:w1,delay:x1,locales:E1,scrollToClose:A1})}}),S1=n=>n instanceof Element?document.activeElement===n&&(["TEXTAREA","SELECT","INPUT"].includes(n.tagName)||n.hasAttribute("contenteditable")):!1,C1=(n,e)=>e.some(t=>{if(An(t))return t===n.key;const{key:a,ctrl:s=!1,shift:l=!1,alt:o=!1}=t;return a===n.key&&s===n.ctrlKey&&l===n.shiftKey&&o===n.altKey}),T1=/[^\x00-\x7F]/,O1=n=>n.split(/\s+/g).map(e=>e.trim()).filter(e=>!!e),fp=n=>n.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),_p=(n,e)=>{const t=e.join(" "),a=O1(n);if(T1.test(n))return a.some(o=>t.toLowerCase().indexOf(o)>-1);const s=n.endsWith(" ");return new RegExp(a.map((o,i)=>a.length===i+1&&!s?`(?=.*\\b${fp(o)})`:`(?=.*\\b${fp(o)}\\b)`).join("")+".+","gi").test(t)},I1=({input:n,hotKeys:e})=>{if(e.value.length===0)return;const t=a=>{n.value&&C1(a,e.value)&&!S1(a.target)&&(a.preventDefault(),n.value.focus())};En(()=>{document.addEventListener("keydown",t)}),Il(()=>{document.removeEventListener("keydown",t)})},P1=[{title:"ChenSino",headers:[],path:"/",pathLocale:"/",extraFields:[]},{title:"Home",headers:[],path:"/home.html",pathLocale:"/",extraFields:[]},{title:"幻灯片页",headers:[{level:2,title:"幻灯片演示",slug:"幻灯片演示",link:"#幻灯片演示",children:[]},{level:2,title:"标注幻灯片",slug:"标注幻灯片",link:"#标注幻灯片",children:[]},{level:2,title:"标注幻灯片",slug:"标注幻灯片-1",link:"#标注幻灯片-1",children:[]},{level:2,title:"Markdown",slug:"markdown",link:"#markdown",children:[]},{level:2,title:"Markdown",slug:"markdown-1",link:"#markdown-1",children:[{level:3,title:"这是一个 H3",slug:"这是一个-h3",link:"#这是一个-h3",children:[]}]},{level:2,title:"Markdown",slug:"markdown-2",link:"#markdown-2",children:[]},{level:2,title:"Markdown",slug:"markdown-3",link:"#markdown-3",children:[]},{level:2,title:"Markdown",slug:"markdown-4",link:"#markdown-4",children:[]},{level:2,title:"Markdown",slug:"markdown-5",link:"#markdown-5",children:[]},{level:2,title:"布局",slug:"布局",link:"#布局",children:[]},{level:2,title:"布局",slug:"布局-1",link:"#布局-1",children:[]},{level:2,title:"布局",slug:"布局-2",link:"#布局-2",children:[]},{level:2,title:"布局",slug:"布局-3",link:"#布局-3",children:[{level:3,title:"背景",slug:"背景",link:"#背景",children:[]}]},{level:2,title:"动画片段",slug:"动画片段",link:"#动画片段",children:[]},{level:2,title:"动画片段",slug:"动画片段-1",link:"#动画片段-1",children:[]},{level:2,title:"动画片段",slug:"动画片段-2",link:"#动画片段-2",children:[{level:3,title:"动画 class",slug:"动画-class",link:"#动画-class",children:[]}]},{level:2,title:"动画片段",slug:"动画片段-3",link:"#动画片段-3",children:[{level:3,title:"动画 class",slug:"动画-class-1",link:"#动画-class-1",children:[]}]},{level:2,title:"动画片段",slug:"动画片段-4",link:"#动画片段-4",children:[{level:3,title:"多个动画片段",slug:"多个动画片段",link:"#多个动画片段",children:[]}]},{level:2,title:"动画片段",slug:"动画片段-5",link:"#动画片段-5",children:[{level:3,title:"顺序",slug:"顺序",link:"#顺序",children:[]}]},{level:2,title:"渐变",slug:"渐变",link:"#渐变",children:[]},{level:2,title:"渐变",slug:"渐变-1",link:"#渐变-1",children:[]},{level:2,title:"渐变",slug:"渐变-2",link:"#渐变-2",children:[{level:3,title:"过渡动画",slug:"过渡动画",link:"#过渡动画",children:[]}]},{level:2,title:"功能",slug:"功能",link:"#功能",children:[]},{level:2,title:"功能",slug:"功能-1",link:"#功能-1",children:[{level:3,title:"代码",slug:"代码",link:"#代码",children:[]}]},{level:2,title:"功能",slug:"功能-2",link:"#功能-2",children:[{level:3,title:"预览模式",slug:"预览模式",link:"#预览模式",children:[]}]},{level:2,title:"功能",slug:"功能-3",link:"#功能-3",children:[{level:3,title:"全屏模式",slug:"全屏模式",link:"#全屏模式",children:[]}]},{level:2,title:"功能",slug:"功能-4",link:"#功能-4",children:[{level:3,title:"缩放",slug:"缩放",link:"#缩放",children:[]}]},{level:2,title:"结束",slug:"结束",link:"#结束",children:[]}],path:"/slide.html",pathLocale:"/",extraFields:[]},{title:"README",headers:[{level:2,title:"说明",slug:"说明",link:"#说明",children:[]}],path:"/designpattern/",pathLocale:"/",extraFields:[]},{title:"Builder Pattern",headers:[{level:2,title:"建造者模式",slug:"建造者模式",link:"#建造者模式",children:[]},{level:2,title:"mybatis中",slug:"mybatis中",link:"#mybatis中",children:[]}],path:"/designpattern/%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"Template Method",headers:[{level:2,title:"定义",slug:"定义",link:"#定义",children:[]},{level:2,title:"例子",slug:"例子",link:"#例子",children:[]}],path:"/designpattern/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"Strategy Pattern",headers:[{level:3,title:"策略模式【CHATGPT回答】",slug:"策略模式【chatgpt回答】",link:"#策略模式【chatgpt回答】",children:[]},{level:3,title:"Spring中策略模式的使用",slug:"spring中策略模式的使用",link:"#spring中策略模式的使用",children:[]},{level:3,title:"我的工作中用到的策略模式",slug:"我的工作中用到的策略模式",link:"#我的工作中用到的策略模式",children:[]}],path:"/designpattern/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"Decorator Design Pattern",headers:[{level:2,title:"定义",slug:"定义",link:"#定义",children:[]},{level:2,title:"框架中使用",slug:"框架中使用",link:"#框架中使用",children:[]}],path:"/designpattern/%E8%A3%85%E9%A5%B0%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"Chain of Responsibility Pattern",headers:[{level:3,title:"定义",slug:"定义",link:"#定义",children:[]},{level:3,title:"Spring中的责任链",slug:"spring中的责任链",link:"#spring中的责任链",children:[]}],path:"/designpattern/%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"JavaScript成神之路",headers:[],path:"/frontweb/",pathLocale:"/",extraFields:[]},{title:"主要功能与配置演示",headers:[{level:2,title:"目录",slug:"目录",link:"#目录",children:[]}],path:"/guide/",pathLocale:"/",extraFields:[]},{title:"布局与功能禁用",headers:[],path:"/guide/disable.html",pathLocale:"/",extraFields:[]},{title:"Markdown 展示",headers:[{level:2,title:"Markdown 介绍",slug:"markdown-介绍",link:"#markdown-介绍",children:[]},{level:2,title:"Markdown 配置",slug:"markdown-配置",link:"#markdown-配置",children:[]},{level:2,title:"Markdown 扩展",slug:"markdown-扩展",link:"#markdown-扩展",children:[{level:3,title:"VuePress 扩展",slug:"vuepress-扩展",link:"#vuepress-扩展",children:[]},{level:3,title:"主题扩展",slug:"主题扩展",link:"#主题扩展",children:[]},{level:3,title:"图片增强",slug:"图片增强",link:"#图片增强",children:[]}]},{level:2,title:"幻灯片 1",slug:"幻灯片-1",link:"#幻灯片-1",children:[]},{level:2,title:"幻灯片 2",slug:"幻灯片-2",link:"#幻灯片-2",children:[]},{level:2,title:"幻灯片 3.1",slug:"幻灯片-3-1",link:"#幻灯片-3-1",children:[]},{level:2,title:"幻灯片 3.2",slug:"幻灯片-3-2",link:"#幻灯片-3-2",children:[]}],path:"/guide/markdown.html",pathLocale:"/",extraFields:[]},{title:"页面配置",headers:[{level:2,title:"页面信息",slug:"页面信息",link:"#页面信息",children:[]},{level:2,title:"页面内容",slug:"页面内容",link:"#页面内容",children:[]},{level:2,title:"页面结构",slug:"页面结构",link:"#页面结构",children:[]}],path:"/guide/page.html",pathLocale:"/",extraFields:[]},{title:"Java入门到放弃",headers:[],path:"/java/",pathLocale:"/",extraFields:[]},{title:"jellyfin搭建",headers:[{level:2,title:"家庭影院",slug:"家庭影院",link:"#家庭影院",children:[]}],path:"/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html",pathLocale:"/",extraFields:[]},{title:"个人服务器",headers:[{level:3,title:"个人服务器搭建",slug:"个人服务器搭建",link:"#个人服务器搭建",children:[]},{level:3,title:"搭建家庭影院",slug:"搭建家庭影院",link:"#搭建家庭影院",children:[]}],path:"/myserver/",pathLocale:"/",extraFields:[]},{title:"旁路由网关",headers:[{level:2,title:"vmware中使用openwrt做旁路由网关",slug:"vmware中使用openwrt做旁路由网关",link:"#vmware中使用openwrt做旁路由网关",children:[]},{level:2,title:"参考",slug:"参考",link:"#参考",children:[]}],path:"/myserver/x86_openwrt.html",pathLocale:"/",extraFields:[]},{title:"旁路由网关",headers:[{level:3,title:"问题1. 内网主机把网关指向旁路由无法通过主路由端口转发",slug:"问题1-内网主机把网关指向旁路由无法通过主路由端口转发",link:"#问题1-内网主机把网关指向旁路由无法通过主路由端口转发",children:[]}],path:"/myserver/%E6%97%81%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3.html",pathLocale:"/",extraFields:[]},{title:"网络设置",headers:[{level:2,title:"家庭组网",slug:"家庭组网",link:"#家庭组网",children:[{level:3,title:"为什么要组网",slug:"为什么要组网",link:"#为什么要组网",children:[]},{level:3,title:"家庭网线图",slug:"家庭网线图",link:"#家庭网线图",children:[]}]},{level:2,title:"桥接拨号还是光猫拨号",slug:"桥接拨号还是光猫拨号",link:"#桥接拨号还是光猫拨号",children:[]},{level:2,title:"开通公网",slug:"开通公网",link:"#开通公网",children:[{level:3,title:"ipv4玩法",slug:"ipv4玩法",link:"#ipv4玩法",children:[]},{level:3,title:"ipv6玩法",slug:"ipv6玩法",link:"#ipv6玩法",children:[]}]}],path:"/myserver/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE.html",pathLocale:"/",extraFields:[]},{title:"自建nas",headers:[{level:2,title:"nas介绍",slug:"nas介绍",link:"#nas介绍",children:[]}],path:"/myserver/%E8%87%AA%E5%BB%BAnas.html",pathLocale:"/",extraFields:[]},{title:"其他",headers:[],path:"/other/",pathLocale:"/",extraFields:[]},{title:"c++中使用的编译工具介绍",headers:[{level:3,title:"gcc",slug:"gcc",link:"#gcc",children:[]},{level:3,title:"make",slug:"make",link:"#make",children:[]},{level:3,title:"makefile",slug:"makefile",link:"#makefile",children:[]},{level:3,title:"cmake",slug:"cmake",link:"#cmake",children:[]},{level:3,title:"CMakeList.txt",slug:"cmakelist-txt",link:"#cmakelist-txt",children:[]},{level:3,title:"nmake[1]",slug:"nmake-1",link:"#nmake-1",children:[]}],path:"/cpp/other/1.html",pathLocale:"/",extraFields:[]},{title:"使用Clion搭建jdk源码调试环境",headers:[{level:2,title:"占坑",slug:"占坑",link:"#占坑",children:[]}],path:"/cpp/other/2.html",pathLocale:"/",extraFields:[]},{title:"index",headers:[{level:2,title:"c++基础学习",slug:"c-基础学习",link:"#c-基础学习",children:[]}],path:"/cpp/study/",pathLocale:"/",extraFields:[]},{title:"ECMAScript 5",headers:[{level:2,title:"目录",slug:"目录",link:"#目录",children:[]}],path:"/frontweb/es5/",pathLocale:"/",extraFields:[]},{title:"异步async函数",headers:[{level:2,title:"一,async函数的定义",slug:"一-async函数的定义",link:"#一-async函数的定义",children:[]},{level:2,title:"二,async函数的返回值",slug:"二-async函数的返回值",link:"#二-async函数的返回值",children:[]},{level:2,title:"三, await命令",slug:"三-await命令",link:"#三-await命令",children:[]},{level:2,title:"五,错误处理",slug:"五-错误处理",link:"#五-错误处理",children:[]},{level:2,title:"六,总结",slug:"六-总结",link:"#六-总结",children:[]}],path:"/frontweb/es5/aboutAsync.html",pathLocale:"/",extraFields:[]},{title:"JS原生事件",headers:[{level:2,title:"一,事件注册的三种方式",slug:"一-事件注册的三种方式",link:"#一-事件注册的三种方式",children:[]},{level:2,title:"二,解绑事件",slug:"二-解绑事件",link:"#二-解绑事件",children:[]},{level:2,title:"三,事件流",slug:"三-事件流",link:"#三-事件流",children:[]},{level:2,title:"四,事件委托",slug:"四-事件委托",link:"#四-事件委托",children:[]},{level:2,title:"五、事件方法",slug:"五、事件方法",link:"#五、事件方法",children:[]}],path:"/frontweb/es5/aboutEvent.html",pathLocale:"/",extraFields:[]},{title:"this指向问题",headers:[{level:2,title:"一,函数内部的this指向",slug:"一-函数内部的this指向",link:"#一-函数内部的this指向",children:[]},{level:2,title:"二,setTimeout & setInterval",slug:"二-settimeout-setinterval",link:"#二-settimeout-setinterval",children:[]},{level:2,title:"三,箭头函数中的this",slug:"三-箭头函数中的this",link:"#三-箭头函数中的this",children:[]},{level:2,title:"四,关于promise中的this指向问题",slug:"四-关于promise中的this指向问题",link:"#四-关于promise中的this指向问题",children:[]}],path:"/frontweb/es5/aboutThis.html",pathLocale:"/",extraFields:[]},{title:"关于async/await的异常捕获",headers:[{level:2,title:"一,为什么要捕获异常",slug:"一-为什么要捕获异常",link:"#一-为什么要捕获异常",children:[]},{level:2,title:"二,相关案例",slug:"二-相关案例",link:"#二-相关案例",children:[]}],path:"/frontweb/es5/asyncError.html",pathLocale:"/",extraFields:[]},{title:"前端跨域(一)之proxy配置",headers:[{level:3,title:"一,同源策略",slug:"一-同源策略",link:"#一-同源策略",children:[]},{level:3,title:"二,什么是跨域",slug:"二-什么是跨域",link:"#二-什么是跨域",children:[]},{level:3,title:"三,跨域的解决方案",slug:"三-跨域的解决方案",link:"#三-跨域的解决方案",children:[]}],path:"/frontweb/es5/crossDomain.html",pathLocale:"/",extraFields:[]},{title:"前端跨域(二)之JSONP跨域",headers:[{level:3,title:"一,什么是JSONP",slug:"一-什么是jsonp",link:"#一-什么是jsonp",children:[]},{level:3,title:"二,JSONP的原理",slug:"二-jsonp的原理",link:"#二-jsonp的原理",children:[]},{level:3,title:"三,JSONP的具体实现",slug:"三-jsonp的具体实现",link:"#三-jsonp的具体实现",children:[]},{level:3,title:"四,总结",slug:"四-总结",link:"#四-总结",children:[]}],path:"/frontweb/es5/crossDomain2.html",pathLocale:"/",extraFields:[]},{title:"图片懒加载",headers:[{level:3,title:"一,为什么需要图片懒加载",slug:"一-为什么需要图片懒加载",link:"#一-为什么需要图片懒加载",children:[]},{level:3,title:"二,图片懒加载原理",slug:"二-图片懒加载原理",link:"#二-图片懒加载原理",children:[]},{level:3,title:"三,实战",slug:"三-实战",link:"#三-实战",children:[]}],path:"/frontweb/es5/lazyLoad.html",pathLocale:"/",extraFields:[]},{title:"节流与防抖",headers:[{level:2,title:"一,节流概念(Throttle)",slug:"一-节流概念-throttle",link:"#一-节流概念-throttle",children:[]},{level:2,title:"二,节流实现",slug:"二-节流实现",link:"#二-节流实现",children:[]},{level:2,title:"",slug:"",link:"#",children:[]},{level:2,title:"三,防抖概念(Debounce)",slug:"三-防抖概念-debounce",link:"#三-防抖概念-debounce",children:[]},{level:2,title:"四,防抖实现",slug:"四-防抖实现",link:"#四-防抖实现",children:[]}],path:"/frontweb/es5/throttle.html",pathLocale:"/",extraFields:[]},{title:"ECMAScript 6",headers:[],path:"/frontweb/es6/",pathLocale:"/",extraFields:[]},{title:"js能识别最大的正整数",headers:[{level:2,title:"背景",slug:"背景",link:"#背景",children:[]},{level:2,title:"原因,参考gpt回答",slug:"原因-参考gpt回答",link:"#原因-参考gpt回答",children:[]},{level:2,title:"解决方法",slug:"解决方法",link:"#解决方法",children:[]}],path:"/frontweb/es6/js%E4%B8%AD%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.html",pathLocale:"/",extraFields:[]},{title:"Promise介绍",headers:[{level:3,title:"1. Promise介绍",slug:"_1-promise介绍",link:"#_1-promise介绍",children:[]},{level:3,title:"2. 使用",slug:"_2-使用",link:"#_2-使用",children:[]},{level:3,title:"3. promise中使用then和await的区别",slug:"_3-promise中使用then和await的区别",link:"#_3-promise中使用then和await的区别",children:[]}],path:"/frontweb/es6/promise.html",pathLocale:"/",extraFields:[]},{title:"前端模块化",headers:[{level:2,title:"一,什么是前端模块化",slug:"一-什么是前端模块化",link:"#一-什么是前端模块化",children:[]},{level:2,title:"二,CommonJS",slug:"二-commonjs",link:"#二-commonjs",children:[]},{level:2,title:"三,AMD(异步模块定义)",slug:"三-amd-异步模块定义",link:"#三-amd-异步模块定义",children:[]},{level:2,title:"四, CMD(公共模块定义)",slug:"四-cmd-公共模块定义",link:"#四-cmd-公共模块定义",children:[]},{level:2,title:"五,UMD",slug:"五-umd",link:"#五-umd",children:[]},{level:2,title:"六,ES6模块化",slug:"六-es6模块化",link:"#六-es6模块化",children:[{level:3,title:"6.1 在浏览器中使用 ES 模块化",slug:"_6-1-在浏览器中使用-es-模块化",link:"#_6-1-在浏览器中使用-es-模块化",children:[]},{level:3,title:"6.2 在 Node.js 中使用 ES 模块化",slug:"_6-2-在-node-js-中使用-es-模块化",link:"#_6-2-在-node-js-中使用-es-模块化",children:[]},{level:3,title:"其他",slug:"其他",link:"#其他",children:[]}]}],path:"/frontweb/es6/useModule.html",pathLocale:"/",extraFields:[]},{title:"搞懂npm与cnpm",headers:[{level:2,title:"一,什么是npm",slug:"一-什么是npm",link:"#一-什么是npm",children:[]},{level:2,title:"二,什么时候使用cnpm?",slug:"二-什么时候使用cnpm",link:"#二-什么时候使用cnpm",children:[{level:3,title:"【混淆系列】三问:npx、npm、cnpm、pnpm区别你搞清楚了吗?",slug:"【混淆系列】三问-npx、npm、cnpm、pnpm区别你搞清楚了吗",link:"#【混淆系列】三问-npx、npm、cnpm、pnpm区别你搞清楚了吗",children:[]}]}],path:"/frontweb/es6/useNpm.html",pathLocale:"/",extraFields:[]},{title:"搞懂npm与pnpm",headers:[],path:"/frontweb/es6/usePnpm.html",pathLocale:"/",extraFields:[]},{title:"npm与yarn的区别",headers:[{level:2,title:"一,yarn简介",slug:"一-yarn简介",link:"#一-yarn简介",children:[]},{level:2,title:"二,npm的缺陷",slug:"二-npm的缺陷",link:"#二-npm的缺陷",children:[]},{level:2,title:"三,yarn优点",slug:"三-yarn优点",link:"#三-yarn优点",children:[]}],path:"/frontweb/es6/useYarn.html",pathLocale:"/",extraFields:[]},{title:"NodeJS",headers:[{level:2,title:"",slug:"",link:"#",children:[]}],path:"/frontweb/nodejs/",pathLocale:"/",extraFields:[]},{title:"CentOS7安装node18",headers:[{level:3,title:"背景",slug:"背景",link:"#背景",children:[]},{level:3,title:"安装非官方的node",slug:"安装非官方的node",link:"#安装非官方的node",children:[]}],path:"/frontweb/nodejs/%E5%AE%89%E8%A3%85%E9%97%AE%E9%A2%98.html",pathLocale:"/",extraFields:[]},{title:"typeScript学习资料",headers:[{level:2,title:"学习typeScript",slug:"学习typescript",link:"#学习typescript",children:[]}],path:"/frontweb/typeScript/",pathLocale:"/",extraFields:[]},{title:"typeScript项目实战",headers:[{level:2,title:"一,利用typeScript实现新增,删除一行数据",slug:"一-利用typescript实现新增-删除一行数据",link:"#一-利用typescript实现新增-删除一行数据",children:[]}],path:"/frontweb/typeScript/action-usage.html",pathLocale:"/",extraFields:[]},{title:"typeScript中使用axios",headers:[{level:2,title:"项目一",slug:"项目一",link:"#项目一",children:[]}],path:"/frontweb/typeScript/axios.html",pathLocale:"/",extraFields:[]},{title:"typeScript在vue项目中常见用法",headers:[{level:3,title:"一,模板中的 TypeScript",slug:"一-模板中的-typescript",link:"#一-模板中的-typescript",children:[]},{level:3,title:"二,为组件的prop标注类型",slug:"二-为组件的prop标注类型",link:"#二-为组件的prop标注类型",children:[]},{level:3,title:"三,为组件的 emit 标注类型",slug:"三-为组件的-emit-标注类型",link:"#三-为组件的-emit-标注类型",children:[]}],path:"/frontweb/typeScript/basic-usage.html",pathLocale:"/",extraFields:[]},{title:"typeScript中的泛型",headers:[{level:2,title:"一,泛型",slug:"一-泛型",link:"#一-泛型",children:[]},{level:2,title:"二,定义泛型接口",slug:"二-定义泛型接口",link:"#二-定义泛型接口",children:[]},{level:2,title:"三 对象字面量泛型",slug:"三-对象字面量泛型",link:"#三-对象字面量泛型",children:[]},{level:2,title:"四,泛型接口",slug:"四-泛型接口",link:"#四-泛型接口",children:[]},{level:2,title:",泛型约束",slug:"泛型约束",link:"#泛型约束",children:[]},{level:2,title:"五,泛型参数默认值",slug:"五-泛型参数默认值",link:"#五-泛型参数默认值",children:[]}],path:"/frontweb/typeScript/fanType.html",pathLocale:"/",extraFields:[]},{title:"typeScript在vue3中的实战",headers:[{level:2,title:"ts在表单中的应用",slug:"ts在表单中的应用",link:"#ts在表单中的应用",children:[]}],path:"/frontweb/typeScript/tsAndvue3.html",pathLocale:"/",extraFields:[]},{title:"Vite",headers:[],path:"/frontweb/vite/",pathLocale:"/",extraFields:[]},{title:"Vue",headers:[],path:"/frontweb/vue/",pathLocale:"/",extraFields:[]},{title:"ElementUI表单自定义校验",headers:[{level:3,title:"常规",slug:"常规",link:"#常规",children:[]},{level:3,title:"动态获取参数",slug:"动态获取参数",link:"#动态获取参数",children:[]}],path:"/frontweb/vue/elementui%E8%A1%A8%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C.html",pathLocale:"/",extraFields:[]},{title:"事件总线Mitt",headers:[],path:"/frontweb/vue/eventBus.html",pathLocale:"/",extraFields:[]},{title:"vue自定义指令控制按钮级别权限",headers:[{level:2,title:"一,自定义指令介绍",slug:"一-自定义指令介绍",link:"#一-自定义指令介绍",children:[]},{level:2,title:"二,注册自定义指令",slug:"二-注册自定义指令",link:"#二-注册自定义指令",children:[]},{level:2,title:"三,指令钩子",slug:"三-指令钩子",link:"#三-指令钩子",children:[]},{level:2,title:"四,钩子函数的参数",slug:"四-钩子函数的参数",link:"#四-钩子函数的参数",children:[]},{level:2,title:"五,实际使用",slug:"五-实际使用",link:"#五-实际使用",children:[]}],path:"/frontweb/vue/vue-Direactive.html",pathLocale:"/",extraFields:[]},{title:"vue中权限相关的问题",headers:[{level:2,title:"前端主要有哪些权限控制?",slug:"前端主要有哪些权限控制",link:"#前端主要有哪些权限控制",children:[{level:3,title:"一,接口访问权限",slug:"一-接口访问权限",link:"#一-接口访问权限",children:[]},{level:3,title:"二,菜单权限",slug:"二-菜单权限",link:"#二-菜单权限",children:[]},{level:3,title:"三,按钮权限",slug:"三-按钮权限",link:"#三-按钮权限",children:[]}]}],path:"/frontweb/vue/vue-authority.html",pathLocale:"/",extraFields:[]},{title:"利用vue-cli搭建项目",headers:[{level:3,title:"一、创建一个新项目",slug:"一、创建一个新项目",link:"#一、创建一个新项目",children:[]},{level:3,title:"二、创建vue.config.js",slug:"二、创建vue-config-js",link:"#二、创建vue-config-js",children:[]}],path:"/frontweb/vue/vue-in-action.html",pathLocale:"/",extraFields:[]},{title:"关于vue-nextTick",headers:[{level:2,title:"一,什么是nextTick",slug:"一-什么是nexttick",link:"#一-什么是nexttick",children:[]},{level:2,title:"二,nextTick应用场景",slug:"二-nexttick应用场景",link:"#二-nexttick应用场景",children:[]},{level:2,title:"三,Vue.nextTick(callback) 使用原理",slug:"三-vue-nexttick-callback-使用原理",link:"#三-vue-nexttick-callback-使用原理",children:[]}],path:"/frontweb/vue/vue-nextTick.html",pathLocale:"/",extraFields:[]},{title:"vue图片路径问题",headers:[{level:3,title:"一,在vue中静态导入相对路径",slug:"一-在vue中静态导入相对路径",link:"#一-在vue中静态导入相对路径",children:[]},{level:3,title:"二,静态导入绝对路径",slug:"二-静态导入绝对路径",link:"#二-静态导入绝对路径",children:[]},{level:3,title:"三,动态导入相对路径",slug:"三-动态导入相对路径",link:"#三-动态导入相对路径",children:[]}],path:"/frontweb/vue/vue-pic.html",pathLocale:"/",extraFields:[]},{title:"vue-router4.0的基本使用",headers:[{level:3,title:"一,安装",slug:"一-安装",link:"#一-安装",children:[]},{level:3,title:"二,基本用法",slug:"二-基本用法",link:"#二-基本用法",children:[]},{level:3,title:"三,路由的访问",slug:"三-路由的访问",link:"#三-路由的访问",children:[]}],path:"/frontweb/vue/vue-router1.html",pathLocale:"/",extraFields:[]},{title:"vue-router源码浅析",headers:[{level:2,title:"一, 路由的本质",slug:"一-路由的本质",link:"#一-路由的本质",children:[]},{level:2,title:"二, 路由的区别",slug:"二-路由的区别",link:"#二-路由的区别",children:[]},{level:2,title:"三,路由简单实现",slug:"三-路由简单实现",link:"#三-路由简单实现",children:[{level:3,title:"3.1 hash模式",slug:"_3-1-hash模式",link:"#_3-1-hash模式",children:[]},{level:3,title:"3.2 history模式",slug:"_3-2-history模式",link:"#_3-2-history模式",children:[]}]},{level:2,title:"四,原生js实现前端路由",slug:"四-原生js实现前端路由",link:"#四-原生js实现前端路由",children:[]},{level:2,title:"五,剖析Vue-router",slug:"五-剖析vue-router",link:"#五-剖析vue-router",children:[]},{level:2,title:"六,分析Vue.use",slug:"六-分析vue-use",link:"#六-分析vue-use",children:[]},{level:2,title:"七、 完善install方法",slug:"七、-完善install方法",link:"#七、-完善install方法",children:[]},{level:2,title:"八,完善VueRouter类",slug:"八-完善vuerouter类",link:"#八-完善vuerouter类",children:[]},{level:2,title:"九,完善$route",slug:"九-完善-route",link:"#九-完善-route",children:[]},{level:2,title:"十,完善router-view组件",slug:"十-完善router-view组件",link:"#十-完善router-view组件",children:[]},{level:2,title:"十一、完善router-link组件",slug:"十一、完善router-link组件",link:"#十一、完善router-link组件",children:[]}],path:"/frontweb/vue/vue-router2.html",pathLocale:"/",extraFields:[]},{title:"vue3使用emit进行父子组件传值",headers:[{level:2,title:"vue3 使用组合式api时如何进行父子组件通信",slug:"vue3-使用组合式api时如何进行父子组件通信",link:"#vue3-使用组合式api时如何进行父子组件通信",children:[]}],path:"/frontweb/vue/vue3Emit.html",pathLocale:"/",extraFields:[]},{title:"vue中组件的生命周期",headers:[{level:2,title:"一,vue生命周期钩子函数",slug:"一-vue生命周期钩子函数",link:"#一-vue生命周期钩子函数",children:[{level:3,title:"1.1 beforeCreate",slug:"_1-1-beforecreate",link:"#_1-1-beforecreate",children:[]},{level:3,title:"1.2 created",slug:"_1-2-created",link:"#_1-2-created",children:[]},{level:3,title:"1.3 beforeMount",slug:"_1-3-beforemount",link:"#_1-3-beforemount",children:[]},{level:3,title:"1.4 mounted",slug:"_1-4-mounted",link:"#_1-4-mounted",children:[]},{level:3,title:"1.5 beforeUpdate",slug:"_1-5-beforeupdate",link:"#_1-5-beforeupdate",children:[]},{level:3,title:"1.6 updated",slug:"_1-6-updated",link:"#_1-6-updated",children:[]},{level:3,title:"1.7 beforeDestroy",slug:"_1-7-beforedestroy",link:"#_1-7-beforedestroy",children:[]},{level:3,title:"1.8 destroyed",slug:"_1-8-destroyed",link:"#_1-8-destroyed",children:[]},{level:3,title:"1.9 activated",slug:"_1-9-activated",link:"#_1-9-activated",children:[]},{level:3,title:"1.10 deactivated",slug:"_1-10-deactivated",link:"#_1-10-deactivated",children:[]},{level:3,title:"1.11 errorCaptured",slug:"_1-11-errorcaptured",link:"#_1-11-errorcaptured",children:[]}]},{level:2,title:"二、 父子组件生命周期执行顺序",slug:"二、-父子组件生命周期执行顺序",link:"#二、-父子组件生命周期执行顺序",children:[]}],path:"/frontweb/vue/vue3LifeTime.html",pathLocale:"/",extraFields:[]},{title:"利用Vue.extend定义全局组件",headers:[{level:2,title:"一,vue.extend基本概念和用法",slug:"一-vue-extend基本概念和用法",link:"#一-vue-extend基本概念和用法",children:[]},{level:2,title:"二,为什么要使用extend",slug:"二-为什么要使用extend",link:"#二-为什么要使用extend",children:[]}],path:"/frontweb/vue/vueExtend.html",pathLocale:"/",extraFields:[]},{title:"使用自定义指令实现高亮",headers:[{level:3,title:"需求",slug:"需求",link:"#需求",children:[]},{level:3,title:"实现方案",slug:"实现方案",link:"#实现方案",children:[]},{level:3,title:"代码",slug:"代码",link:"#代码",children:[]},{level:3,title:"扩展",slug:"扩展",link:"#扩展",children:[]}],path:"/frontweb/vue/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4%E7%9A%84%E5%AE%9E%E8%B7%B5.html",pathLocale:"/",extraFields:[]},{title:"使用Arthas定位线上问题",headers:[{level:2,title:"1、使用arthas来跟踪",slug:"_1、使用arthas来跟踪",link:"#_1、使用arthas来跟踪",children:[]}],path:"/java/advance/Arthas.html",pathLocale:"/",extraFields:[]},{title:"java集合",headers:[{level:2,title:"1、Collection",slug:"_1、collection",link:"#_1、collection",children:[]}],path:"/java/advance/Collection.html",pathLocale:"/",extraFields:[]},{title:"在Manjaro中编译JDK11",headers:[{level:2,title:"1 下载源码",slug:"_1-下载源码",link:"#_1-下载源码",children:[]},{level:2,title:"2 编译",slug:"_2-编译",link:"#_2-编译",children:[{level:3,title:"2.1 configure",slug:"_2-1-configure",link:"#_2-1-configure",children:[]},{level:3,title:"2.2 执行编译make images",slug:"_2-2-执行编译make-images",link:"#_2-2-执行编译make-images",children:[]},{level:3,title:"2.3 解决编译器gcc版本问题",slug:"_2-3-解决编译器gcc版本问题",link:"#_2-3-解决编译器gcc版本问题",children:[]}]},{level:2,title:"3、jdk源码目录结构",slug:"_3、jdk源码目录结构",link:"#_3、jdk源码目录结构",children:[]}],path:"/java/advance/CompileJdk11.html",pathLocale:"/",extraFields:[]},{title:"并发问题",headers:[{level:2,title:"1、背景",slug:"_1、背景",link:"#_1、背景",children:[]},{level:2,title:"解决方法",slug:"解决方法",link:"#解决方法",children:[]},{level:2,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/java/advance/Concurrent.html",pathLocale:"/",extraFields:[]},{title:"多线程中的Future",headers:[{level:2,title:"1、Future的作用",slug:"_1、future的作用",link:"#_1、future的作用",children:[]}],path:"/java/advance/Future.html",pathLocale:"/",extraFields:[]},{title:"100%搞懂5中I/O模型",headers:[{level:2,title:"1、从TCP发送数据的流程说起",slug:"_1、从tcp发送数据的流程说起",link:"#_1、从tcp发送数据的流程说起",children:[]},{level:2,title:"2、阻塞IO |非阻塞IO",slug:"_2、阻塞io-非阻塞io",link:"#_2、阻塞io-非阻塞io",children:[{level:3,title:"2.1 什么是阻塞IO",slug:"_2-1-什么是阻塞io",link:"#_2-1-什么是阻塞io",children:[]},{level:3,title:"2.2 什么是非阻塞IO",slug:"_2-2-什么是非阻塞io",link:"#_2-2-什么是非阻塞io",children:[]}]},{level:2,title:"3、IO复用模型",slug:"_3、io复用模型",link:"#_3、io复用模型",children:[{level:3,title:"3.1 IO多路复用模型扩展",slug:"_3-1-io多路复用模型扩展",link:"#_3-1-io多路复用模型扩展",children:[]}]},{level:2,title:"4、信号驱动IO模型",slug:"_4、信号驱动io模型",link:"#_4、信号驱动io模型",children:[]},{level:2,title:"5、异步IO",slug:"_5、异步io",link:"#_5、异步io",children:[]},{level:2,title:"6、再谈IO模型里面的同步异步",slug:"_6、再谈io模型里面的同步异步",link:"#_6、再谈io模型里面的同步异步",children:[]},{level:2,title:"7、tomcat中的IO模型",slug:"_7、tomcat中的io模型",link:"#_7、tomcat中的io模型",children:[]},{level:2,title:"8、Redis中的IO模型",slug:"_8、redis中的io模型",link:"#_8、redis中的io模型",children:[]},{level:2,title:"9、Nginx中的IO模型",slug:"_9、nginx中的io模型",link:"#_9、nginx中的io模型",children:[]},{level:2,title:"10、java中的IO模型",slug:"_10、java中的io模型",link:"#_10、java中的io模型",children:[]},{level:2,title:"10、netty中的io模型",slug:"_10、netty中的io模型",link:"#_10、netty中的io模型",children:[]}],path:"/java/advance/IO-Model.html",pathLocale:"/",extraFields:[]},{title:"I/O模型总结",headers:[{level:2,title:"1 BIO",slug:"_1-bio",link:"#_1-bio",children:[]},{level:2,title:"2 NIO",slug:"_2-nio",link:"#_2-nio",children:[]},{level:2,title:"3 IO多路复用",slug:"_3-io多路复用",link:"#_3-io多路复用",children:[]},{level:2,title:"4 异步IO",slug:"_4-异步io",link:"#_4-异步io",children:[]},{level:2,title:"5 事件驱动的io",slug:"_5-事件驱动的io",link:"#_5-事件驱动的io",children:[]},{level:2,title:"6 reactor线程模型",slug:"_6-reactor线程模型",link:"#_6-reactor线程模型",children:[{level:3,title:"6.1 单reactor单线程",slug:"_6-1-单reactor单线程",link:"#_6-1-单reactor单线程",children:[]},{level:3,title:"6.2 单reactor多线程",slug:"_6-2-单reactor多线程",link:"#_6-2-单reactor多线程",children:[]},{level:3,title:"6.3 主从(多)reactor多线程",slug:"_6-3-主从-多-reactor多线程",link:"#_6-3-主从-多-reactor多线程",children:[]}]}],path:"/java/advance/IO-model1.html",pathLocale:"/",extraFields:[]},{title:"子类和父类(或者父接口)实现同一个接口",headers:[{level:2,title:"1、背景",slug:"_1、背景",link:"#_1、背景",children:[]},{level:2,title:"2、探索",slug:"_2、探索",link:"#_2、探索",children:[{level:3,title:"2.1 demo验证",slug:"_2-1-demo验证",link:"#_2-1-demo验证",children:[]},{level:3,title:"2.2 回到security源码验证",slug:"_2-2-回到security源码验证",link:"#_2-2-回到security源码验证",children:[]}]},{level:2,title:"3、结论",slug:"_3、结论",link:"#_3、结论",children:[]}],path:"/java/advance/ImplementSameInterface.html",pathLocale:"/",extraFields:[]},{title:"mysql8搭建主从复制",headers:[{level:3,title:"1、mysql主从复制",slug:"_1、mysql主从复制",link:"#_1、mysql主从复制",children:[]},{level:3,title:"2、使用mybatis-plus实现读写分离(mysql的主从复制是实现读写分离的基础)",slug:"_2、使用mybatis-plus实现读写分离-mysql的主从复制是实现读写分离的基础",link:"#_2、使用mybatis-plus实现读写分离-mysql的主从复制是实现读写分离的基础",children:[]}],path:"/java/advance/MysqlMasterSlave.html",pathLocale:"/",extraFields:[]},{title:"自定义native方法",headers:[{level:2,title:"1、native方法",slug:"_1、native方法",link:"#_1、native方法",children:[]},{level:2,title:"2、自定义实现native方法",slug:"_2、自定义实现native方法",link:"#_2、自定义实现native方法",children:[{level:3,title:"2.1 定义java源文件",slug:"_2-1-定义java源文件",link:"#_2-1-定义java源文件",children:[]},{level:3,title:"2.2 使用jdk自带工具生成c++头文件",slug:"_2-2-使用jdk自带工具生成c-头文件",link:"#_2-2-使用jdk自带工具生成c-头文件",children:[]},{level:3,title:"2.3 添加cpp文件",slug:"_2-3-添加cpp文件",link:"#_2-3-添加cpp文件",children:[]},{level:3,title:"2.4 编译C++源码为动态库",slug:"_2-4-编译c-源码为动态库",link:"#_2-4-编译c-源码为动态库",children:[]},{level:3,title:"2.5 执行java程序验证",slug:"_2-5-执行java程序验证",link:"#_2-5-执行java程序验证",children:[]}]},{level:2,title:"3、总结",slug:"_3、总结",link:"#_3、总结",children:[]}],path:"/java/advance/NativeMethod.html",pathLocale:"/",extraFields:[]},{title:"证明SPI打破双亲委派模式",headers:[{level:2,title:"1、什么是双亲委派?",slug:"_1、什么是双亲委派",link:"#_1、什么是双亲委派",children:[]},{level:2,title:"2、什么是SPI",slug:"_2、什么是spi",link:"#_2、什么是spi",children:[{level:3,title:"2.1 定义",slug:"_2-1-定义",link:"#_2-1-定义",children:[]},{level:3,title:"2.2 使用场景",slug:"_2-2-使用场景",link:"#_2-2-使用场景",children:[]},{level:3,title:"2.3 自己写一个SPI模拟jdbc的spi",slug:"_2-3-自己写一个spi模拟jdbc的spi",link:"#_2-3-自己写一个spi模拟jdbc的spi",children:[]}]},{level:2,title:"3、为什么SPI打破了双亲委派",slug:"_3、为什么spi打破了双亲委派",link:"#_3、为什么spi打破了双亲委派",children:[{level:3,title:"3.1 ContextClassLoader",slug:"_3-1-contextclassloader",link:"#_3-1-contextclassloader",children:[]},{level:3,title:"4.1 jdbc介绍",slug:"_4-1-jdbc介绍",link:"#_4-1-jdbc介绍",children:[]},{level:3,title:"4.2 jdbc一定打破双亲委派吗?",slug:"_4-2-jdbc一定打破双亲委派吗",link:"#_4-2-jdbc一定打破双亲委派吗",children:[]},{level:3,title:"4.3 调试jdbc4.0+、mysql5.1.6+版本的spi打破双亲委派",slug:"_4-3-调试jdbc4-0-、mysql5-1-6-版本的spi打破双亲委派",link:"#_4-3-调试jdbc4-0-、mysql5-1-6-版本的spi打破双亲委派",children:[]}]}],path:"/java/advance/ParentDelegationClassLoader.html",pathLocale:"/",extraFields:[]},{title:"彻底理清Java中的几种代理",headers:[{level:3,title:"1、代理的分类",slug:"_1、代理的分类",link:"#_1、代理的分类",children:[]},{level:3,title:"2、各种代理的区别",slug:"_2、各种代理的区别",link:"#_2、各种代理的区别",children:[]},{level:3,title:"3、Spring中提到的AOP和AspectJ属于什么代理",slug:"_3、spring中提到的aop和aspectj属于什么代理",link:"#_3、spring中提到的aop和aspectj属于什么代理",children:[]},{level:3,title:"4、比较几种代理",slug:"_4、比较几种代理",link:"#_4、比较几种代理",children:[]},{level:3,title:"5、各种代理的详细介绍以及示例代码",slug:"_5、各种代理的详细介绍以及示例代码",link:"#_5、各种代理的详细介绍以及示例代码",children:[]},{level:3,title:"6、参考",slug:"_6、参考",link:"#_6、参考",children:[]}],path:"/java/advance/ProxyInJava.html",pathLocale:"/",extraFields:[]},{title:"Java进阶",headers:[],path:"/java/advance/",pathLocale:"/",extraFields:[]},{title:"synchronized实现 原理",headers:[{level:2,title:"1、",slug:"_1、",link:"#_1、",children:[]}],path:"/java/advance/Synchronized.html",pathLocale:"/",extraFields:[]},{title:"ThreadLocal",headers:[{level:2,title:"1、介绍",slug:"_1、介绍",link:"#_1、介绍",children:[]},{level:2,title:"2、使用",slug:"_2、使用",link:"#_2、使用",children:[]},{level:2,title:"3、 使用反射在当前线程获取所有的ThreadLocal",slug:"_3、-使用反射在当前线程获取所有的threadlocal",link:"#_3、-使用反射在当前线程获取所有的threadlocal",children:[]}],path:"/java/advance/ThreadLocal.html",pathLocale:"/",extraFields:[]},{title:"线程池总结",headers:[{level:2,title:"1、先上测试代码",slug:"_1、先上测试代码",link:"#_1、先上测试代码",children:[]},{level:2,title:"2、 源码解析",slug:"_2、-源码解析",link:"#_2、-源码解析",children:[{level:3,title:"2.1 修改测试代码继续测试",slug:"_2-1-修改测试代码继续测试",link:"#_2-1-修改测试代码继续测试",children:[]},{level:3,title:"2.2 测试带返回值的多线程任务",slug:"_2-2-测试带返回值的多线程任务",link:"#_2-2-测试带返回值的多线程任务",children:[]}]},{level:2,title:"3、线程池的总结",slug:"_3、线程池的总结",link:"#_3、线程池的总结",children:[{level:3,title:"3.1 不建议使用官方工具类直接创建",slug:"_3-1-不建议使用官方工具类直接创建",link:"#_3-1-不建议使用官方工具类直接创建",children:[]},{level:3,title:"3.2 java.util.concurrent.ThreadPoolExecutor",slug:"_3-2-java-util-concurrent-threadpoolexecutor",link:"#_3-2-java-util-concurrent-threadpoolexecutor",children:[]},{level:3,title:"3.3 线程池中的异常处理",slug:"_3-3-线程池中的异常处理",link:"#_3-3-线程池中的异常处理",children:[]},{level:3,title:"3.4 补充说明",slug:"_3-4-补充说明",link:"#_3-4-补充说明",children:[]}]}],path:"/java/advance/ThreadPool.html",pathLocale:"/",extraFields:[]},{title:"I/O模型",headers:[{level:2,title:"1、参考",slug:"_1、参考",link:"#_1、参考",children:[]},{level:2,title:"2、模型分类",slug:"_2、模型分类",link:"#_2、模型分类",children:[]},{level:2,title:"3、形象生活例子",slug:"_3、形象生活例子",link:"#_3、形象生活例子",children:[]},{level:2,title:"4、同步阻塞I/O(传统BIO)",slug:"_4、同步阻塞i-o-传统bio",link:"#_4、同步阻塞i-o-传统bio",children:[{level:3,title:"4.1 介绍",slug:"_4-1-介绍",link:"#_4-1-介绍",children:[]},{level:3,title:"4.2 传统BIO存在的问题",slug:"_4-2-传统bio存在的问题",link:"#_4-2-传统bio存在的问题",children:[]},{level:3,title:"4.3 使用多线程改进BIO",slug:"_4-3-使用多线程改进bio",link:"#_4-3-使用多线程改进bio",children:[]},{level:3,title:"4.4 验证上下文切换带来的CPU消耗巨大",slug:"_4-4-验证上下文切换带来的cpu消耗巨大",link:"#_4-4-验证上下文切换带来的cpu消耗巨大",children:[]},{level:3,title:"4.5 NIO",slug:"_4-5-nio",link:"#_4-5-nio",children:[]}]}],path:"/java/advance/io.html",pathLocale:"/",extraFields:[]},{title:"ConstantPool",headers:[{level:3,title:"1. Integer常量池默认的范围",slug:"_1-integer常量池默认的范围",link:"#_1-integer常量池默认的范围",children:[]},{level:3,title:"2、测试",slug:"_2、测试",link:"#_2、测试",children:[]}],path:"/java/base/ConstantPool.html",pathLocale:"/",extraFields:[]},{title:"自定义LRU实现",headers:[{level:2,title:"LRU介绍",slug:"lru介绍",link:"#lru介绍",children:[]},{level:2,title:"自定义lru算法",slug:"自定义lru算法",link:"#自定义lru算法",children:[{level:3,title:"第一次测试",slug:"第一次测试",link:"#第一次测试",children:[]},{level:3,title:"第二次测试",slug:"第二次测试",link:"#第二次测试",children:[]}]}],path:"/java/base/CustomLRU.html",pathLocale:"/",extraFields:[]},{title:"Integer常量池",headers:[{level:3,title:"1. Integer常量池默认的范围",slug:"_1-integer常量池默认的范围",link:"#_1-integer常量池默认的范围",children:[]},{level:3,title:"2、测试",slug:"_2、测试",link:"#_2、测试",children:[]}],path:"/java/base/IntegerConstantPool.html",pathLocale:"/",extraFields:[]},{title:"Java基础",headers:[],path:"/java/base/",pathLocale:"/",extraFields:[]},{title:"序列化",headers:[],path:"/java/base/Serialization.html",pathLocale:"/",extraFields:[]},{title:"字符串设计",headers:[],path:"/java/base/String.html",pathLocale:"/",extraFields:[]},{title:"自定义类加载器",headers:[{level:2,title:"1、自定义类加载器",slug:"_1、自定义类加载器",link:"#_1、自定义类加载器",children:[]}],path:"/java/jvm/ClassLoader.html",pathLocale:"/",extraFields:[]},{title:"内存模型",headers:[{level:2,title:"1、元空间",slug:"_1、元空间",link:"#_1、元空间",children:[]}],path:"/java/jvm/MemoryModel.html",pathLocale:"/",extraFields:[]},{title:"对象创建过程",headers:[{level:3,title:"1、在类中本地变量引用自身类,会引发的问题",slug:"_1、在类中本地变量引用自身类-会引发的问题",link:"#_1、在类中本地变量引用自身类-会引发的问题",children:[]}],path:"/java/jvm/NewObject.html",pathLocale:"/",extraFields:[]},{title:"对象引用类型",headers:[{level:2,title:"1、强引用",slug:"_1、强引用",link:"#_1、强引用",children:[]},{level:2,title:"2、软引用",slug:"_2、软引用",link:"#_2、软引用",children:[]},{level:2,title:"3、弱引用",slug:"_3、弱引用",link:"#_3、弱引用",children:[]},{level:2,title:"4、虚引用",slug:"_4、虚引用",link:"#_4、虚引用",children:[]}],path:"/java/jvm/ObjectReference.html",pathLocale:"/",extraFields:[]},{title:"JVM",headers:[],path:"/java/jvm/",pathLocale:"/",extraFields:[]},{title:"给对象设置null的意义",headers:[{level:3,title:"1、开始写代码测试(所有测试都要加上以下指令)",slug:"_1、开始写代码测试-所有测试都要加上以下指令",link:"#_1、开始写代码测试-所有测试都要加上以下指令",children:[]},{level:3,title:"2、结论",slug:"_2、结论",link:"#_2、结论",children:[]},{level:3,title:"3、参考",slug:"_3、参考",link:"#_3、参考",children:[]}],path:"/java/jvm/SetObjectNull.html",pathLocale:"/",extraFields:[]},{title:"通过反汇编来看String的拼接",headers:[],path:"/java/jvm/StringAdd.html",pathLocale:"/",extraFields:[]},{title:"volatile关键字",headers:[{level:2,title:"1、双重检查的单例模式是否真的线程安全?",slug:"_1、双重检查的单例模式是否真的线程安全",link:"#_1、双重检查的单例模式是否真的线程安全",children:[]},{level:2,title:"2、问题剖析",slug:"_2、问题剖析",link:"#_2、问题剖析",children:[{level:3,title:"2.1 另一个例子",slug:"_2-1-另一个例子",link:"#_2-1-另一个例子",children:[]}]},{level:2,title:"3、正确的双重检查单例模式写法",slug:"_3、正确的双重检查单例模式写法",link:"#_3、正确的双重检查单例模式写法",children:[]},{level:2,title:"4、参考",slug:"_4、参考",link:"#_4、参考",children:[]}],path:"/java/jvm/volatile.html",pathLocale:"/",extraFields:[]},{title:"名词解释",headers:[{level:3,title:"vm中说的字面量和符号引用是什么",slug:"vm中说的字面量和符号引用是什么",link:"#vm中说的字面量和符号引用是什么",children:[]}],path:"/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html",pathLocale:"/",extraFields:[]},{title:"Jdk版本",headers:[{level:2,title:"1、Jdk11对比jdk1.8",slug:"_1、jdk11对比jdk1-8",link:"#_1、jdk11对比jdk1-8",children:[{level:3,title:"1.1 Java9",slug:"_1-1-java9",link:"#_1-1-java9",children:[]},{level:3,title:"1.2 Java10",slug:"_1-2-java10",link:"#_1-2-java10",children:[]},{level:3,title:"1.3 Java11(LTS)",slug:"_1-3-java11-lts",link:"#_1-3-java11-lts",children:[]}]}],path:"/java/other/JdkVersion.html",pathLocale:"/",extraFields:[]},{title:"其他",headers:[{level:2,title:"其他分类",slug:"其他分类",link:"#其他分类",children:[]}],path:"/java/other/",pathLocale:"/",extraFields:[]},{title:"电子书资源",headers:[{level:2,title:"目录",slug:"目录",link:"#目录",children:[]}],path:"/other/books/",pathLocale:"/",extraFields:[]},{title:"电子书资源汇总",headers:[{level:3,title:"1、springboot",slug:"_1、springboot",link:"#_1、springboot",children:[]},{level:3,title:"2、Nacos原理",slug:"_2、nacos原理",link:"#_2、nacos原理",children:[]}],path:"/other/books/ebooks.html",pathLocale:"/",extraFields:[]},{title:"深入理解TCP/IP",headers:[{level:2,title:"1、TCP/IP与OSI的关系",slug:"_1、tcp-ip与osi的关系",link:"#_1、tcp-ip与osi的关系",children:[]},{level:2,title:"2、TCP/IP",slug:"_2、tcp-ip",link:"#_2、tcp-ip",children:[{level:3,title:"2.1 应用层",slug:"_2-1-应用层",link:"#_2-1-应用层",children:[]},{level:3,title:"2.2 传输控制层(TCP,不讲UDP)",slug:"_2-2-传输控制层-tcp-不讲udp",link:"#_2-2-传输控制层-tcp-不讲udp",children:[]},{level:3,title:"2.3 网络层",slug:"_2-3-网络层",link:"#_2-3-网络层",children:[]},{level:3,title:"2.4 数据链路层",slug:"_2-4-数据链路层",link:"#_2-4-数据链路层",children:[]}]},{level:2,title:"3、参考资料",slug:"_3、参考资料",link:"#_3、参考资料",children:[]}],path:"/other/computerprinciple/TCP-IP.html",pathLocale:"/",extraFields:[]},{title:"Mysql CPU负载过高",headers:[{level:3,title:"问题",slug:"问题",link:"#问题",children:[]},{level:3,title:"分析原因",slug:"分析原因",link:"#分析原因",children:[]},{level:3,title:"结论",slug:"结论",link:"#结论",children:[]}],path:"/other/database/CPUOverLoad.html",pathLocale:"/",extraFields:[]},{title:"Mysql中的Collate和charset",headers:[{level:3,title:"COLLATE是用来做什么的?",slug:"collate是用来做什么的",link:"#collate是用来做什么的",children:[]},{level:3,title:"各种COLLATE的区别",slug:"各种collate的区别",link:"#各种collate的区别",children:[]},{level:3,title:"COLLATE设置级别及其优先级",slug:"collate设置级别及其优先级",link:"#collate设置级别及其优先级",children:[]}],path:"/other/database/MysqlCollate.html",pathLocale:"/",extraFields:[]},{title:"Mysql知识点记录",headers:[{level:3,title:"1、批量插入速度慢",slug:"_1、批量插入速度慢",link:"#_1、批量插入速度慢",children:[]},{level:3,title:"2、唯一索引",slug:"_2、唯一索引",link:"#_2、唯一索引",children:[]}],path:"/other/database/MysqlNote.html",pathLocale:"/",extraFields:[]},{title:"Mysql开启远程连接权限",headers:[],path:"/other/database/MysqlRemoteConnect.html",pathLocale:"/",extraFields:[]},{title:"数据库",headers:[],path:"/other/database/",pathLocale:"/",extraFields:[]},{title:"递归下钻",headers:[{level:2,title:"1、递归下钻",slug:"_1、递归下钻",link:"#_1、递归下钻",children:[]},{level:2,title:"2、示例",slug:"_2、示例",link:"#_2、示例",children:[]}],path:"/other/database/Recurse.html",pathLocale:"/",extraFields:[]},{title:"联合查询sql优化",headers:[{level:3,title:"1、改造后的语句如下",slug:"_1、改造后的语句如下",link:"#_1、改造后的语句如下",children:[]},{level:3,title:"2、原因分析",slug:"_2、原因分析",link:"#_2、原因分析",children:[]}],path:"/other/database/SQLOptimization.html",pathLocale:"/",extraFields:[]},{title:"定时备份数据库",headers:[{level:3,title:"1.备份脚本",slug:"_1-备份脚本",link:"#_1-备份脚本",children:[]},{level:3,title:"2. 给脚本添加可执行权限",slug:"_2-给脚本添加可执行权限",link:"#_2-给脚本添加可执行权限",children:[]},{level:3,title:"3. 设置crontab定时任务(每天0点执行一次)",slug:"_3-设置crontab定时任务-每天0点执行一次",link:"#_3-设置crontab定时任务-每天0点执行一次",children:[]}],path:"/other/database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD.html",pathLocale:"/",extraFields:[]},{title:"分布式锁",headers:[],path:"/other/distributeservice/DistributeLock.html",pathLocale:"/",extraFields:[]},{title:"Nacos学习",headers:[{level:2,title:"1、配置管理",slug:"_1、配置管理",link:"#_1、配置管理",children:[]}],path:"/other/distributeservice/Nacos.html",pathLocale:"/",extraFields:[]},{title:"分布式微服务",headers:[],path:"/other/distributeservice/",pathLocale:"/",extraFields:[]},{title:"Docker常用命令",headers:[{level:2,title:"1 命令速查",slug:"_1-命令速查",link:"#_1-命令速查",children:[]}],path:"/other/docker/Docker.html",pathLocale:"/",extraFields:[]},{title:"使用docker安装常见服务",headers:[{level:2,title:"1. docker安装Redis",slug:"_1-docker安装redis",link:"#_1-docker安装redis",children:[]}],path:"/other/docker/ServiceInstall.html",pathLocale:"/",extraFields:[]},{title:"2022-04-02",headers:[{level:2,title:"1、库A导出",slug:"_1、库a导出",link:"#_1、库a导出",children:[]},{level:2,title:"2、使用source导入",slug:"_2、使用source导入",link:"#_2、使用source导入",children:[]}],path:"/other/essay/2022-04-12.html",pathLocale:"/",extraFields:[]},{title:"二叉树",headers:[{level:2,title:"二叉树进化图",slug:"二叉树进化图",link:"#二叉树进化图",children:[]}],path:"/other/essay/BTree.html",pathLocale:"/",extraFields:[]},{title:"CDN静态资源加速",headers:[],path:"/other/essay/CDN.html",pathLocale:"/",extraFields:[]},{title:"ChromeDevTools学习",headers:[],path:"/other/essay/ChromeDevTools.html",pathLocale:"/",extraFields:[]},{title:"云服务问题分析及总结",headers:[{level:2,title:"1、我的云服务使用场景",slug:"_1、我的云服务使用场景",link:"#_1、我的云服务使用场景",children:[]},{level:2,title:"2、遇到的问题",slug:"_2、遇到的问题",link:"#_2、遇到的问题",children:[]},{level:2,title:"3、问题分析",slug:"_3、问题分析",link:"#_3、问题分析",children:[{level:3,title:"3.1 使用cdn 第一次访问慢原因分析",slug:"_3-1-使用cdn-第一次访问慢原因分析",link:"#_3-1-使用cdn-第一次访问慢原因分析",children:[]},{level:3,title:"3.2 解决第一次访问慢的问题",slug:"_3-2-解决第一次访问慢的问题",link:"#_3-2-解决第一次访问慢的问题",children:[]},{level:3,title:"3.3、问题2分析",slug:"_3-3、问题2分析",link:"#_3-3、问题2分析",children:[]},{level:3,title:"3.4、问题解决",slug:"_3-4、问题解决",link:"#_3-4、问题解决",children:[]}]}],path:"/other/essay/CloudService.html",pathLocale:"/",extraFields:[]},{title:"如何在github部署静态网站",headers:[{level:2,title:"1、 GitHubPage介绍",slug:"_1、-githubpage介绍",link:"#_1、-githubpage介绍",children:[{level:3,title:"1.1 ok",slug:"_1-1-ok",link:"#_1-1-ok",children:[]},{level:3,title:"1.2 搭建个人githubpage",slug:"_1-2-搭建个人githubpage",link:"#_1-2-搭建个人githubpage",children:[]}]},{level:2,title:"2、配合github的Action实现自动化部署",slug:"_2、配合github的action实现自动化部署",link:"#_2、配合github的action实现自动化部署",children:[{level:3,title:"2.1 自动部署脚本",slug:"_2-1-自动部署脚本",link:"#_2-1-自动部署脚本",children:[]}]}],path:"/other/essay/DeployGithubPage.html",pathLocale:"/",extraFields:[]},{title:"IM即时通信选型",headers:[{level:2,title:"1. 需求",slug:"_1-需求",link:"#_1-需求",children:[]},{level:2,title:"2. 原有方案评估",slug:"_2-原有方案评估",link:"#_2-原有方案评估",children:[{level:3,title:"2.1 webrtc即时通信",slug:"_2-1-webrtc即时通信",link:"#_2-1-webrtc即时通信",children:[]}]},{level:2,title:"3. IM通信的难点",slug:"_3-im通信的难点",link:"#_3-im通信的难点",children:[{level:3,title:"3.1 消息传递可靠性问题",slug:"_3-1-消息传递可靠性问题",link:"#_3-1-消息传递可靠性问题",children:[]},{level:3,title:"3.2 消息重复问题",slug:"_3-2-消息重复问题",link:"#_3-2-消息重复问题",children:[]},{level:3,title:"3.3 消息时序问题",slug:"_3-3-消息时序问题",link:"#_3-3-消息时序问题",children:[]},{level:3,title:"3.4 音视频QoS问题",slug:"_3-4-音视频qos问题",link:"#_3-4-音视频qos问题",children:[]},{level:3,title:"3.5 消息推送问题",slug:"_3-5-消息推送问题",link:"#_3-5-消息推送问题",children:[]},{level:3,title:"3.6 技术栈问题",slug:"_3-6-技术栈问题",link:"#_3-6-技术栈问题",children:[]}]},{level:2,title:"4. 技术选型目标",slug:"_4-技术选型目标",link:"#_4-技术选型目标",children:[]},{level:2,title:"4. 候选方案",slug:"_4-候选方案",link:"#_4-候选方案",children:[{level:3,title:"4.1 uni-im",slug:"_4-1-uni-im",link:"#_4-1-uni-im",children:[]},{level:3,title:"4.1.1 简介",slug:"_4-1-1-简介",link:"#_4-1-1-简介",children:[]},{level:3,title:"4.1.2 优势",slug:"_4-1-2-优势",link:"#_4-1-2-优势",children:[]},{level:3,title:"4.1.3 劣势",slug:"_4-1-3-劣势",link:"#_4-1-3-劣势",children:[]},{level:3,title:"4.1.4 费用",slug:"_4-1-4-费用",link:"#_4-1-4-费用",children:[]},{level:3,title:"4.2 J-IM",slug:"_4-2-j-im",link:"#_4-2-j-im",children:[]},{level:3,title:"4.3 cim",slug:"_4-3-cim",link:"#_4-3-cim",children:[]},{level:3,title:"4.4 V-IM",slug:"_4-4-v-im",link:"#_4-4-v-im",children:[]},{level:3,title:"4.5 MobileIMSDK",slug:"_4-5-mobileimsdk",link:"#_4-5-mobileimsdk",children:[]},{level:3,title:"4.6 野火IM/im-server",slug:"_4-6-野火im-im-server",link:"#_4-6-野火im-im-server",children:[]}]},{level:2,title:"5. 总结",slug:"_5-总结",link:"#_5-总结",children:[]}],path:"/other/essay/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B.html",pathLocale:"/",extraFields:[]},{title:"jenkins部署及使用",headers:[{level:2,title:"1、jenkins插件更新报错",slug:"_1、jenkins插件更新报错",link:"#_1、jenkins插件更新报错",children:[{level:3,title:"1.1 报错如下,ssl证书问题",slug:"_1-1-报错如下-ssl证书问题",link:"#_1-1-报错如下-ssl证书问题",children:[]},{level:3,title:"1.2 解决办法",slug:"_1-2-解决办法",link:"#_1-2-解决办法",children:[]}]},{level:2,title:"2、gitlab通过webhook调用jenkins",slug:"_2、gitlab通过webhook调用jenkins",link:"#_2、gitlab通过webhook调用jenkins",children:[{level:3,title:"2.1 jenkins端配置",slug:"_2-1-jenkins端配置",link:"#_2-1-jenkins端配置",children:[]},{level:3,title:"2.1 、gitlab服务端配置",slug:"_2-1-、gitlab服务端配置",link:"#_2-1-、gitlab服务端配置",children:[]}]},{level:2,title:"3、使用部署springboot项目滚动显示日志",slug:"_3、使用部署springboot项目滚动显示日志",link:"#_3、使用部署springboot项目滚动显示日志",children:[]}],path:"/other/essay/Jenkins.html",pathLocale:"/",extraFields:[]},{title:"随笔分享",headers:[],path:"/other/essay/",pathLocale:"/",extraFields:[]},{title:"在Typora中使用Picgo",headers:[{level:3,title:"前言",slug:"前言",link:"#前言",children:[]},{level:3,title:"1、搭建Typora + PicGo + gitee",slug:"_1、搭建typora-picgo-gitee",link:"#_1、搭建typora-picgo-gitee",children:[]},{level:3,title:"2、给typora中picgo设置代理",slug:"_2、给typora中picgo设置代理",link:"#_2、给typora中picgo设置代理",children:[]}],path:"/other/essay/TyporaPicgo.html",pathLocale:"/",extraFields:[]},{title:"elasticsearch操作",headers:[{level:3,title:"1.索引库操作",slug:"_1-索引库操作",link:"#_1-索引库操作",children:[]},{level:3,title:"2.文档操作",slug:"_2-文档操作",link:"#_2-文档操作",children:[]},{level:3,title:"3. ES搜索引擎",slug:"_3-es搜索引擎",link:"#_3-es搜索引擎",children:[]},{level:3,title:"4.客户端",slug:"_4-客户端",link:"#_4-客户端",children:[]}],path:"/other/essay/elasticSearch%E6%93%8D%E4%BD%9C.html",pathLocale:"/",extraFields:[]},{title:"ELK 部署",headers:[{level:3,title:"1. 目录结构",slug:"_1-目录结构",link:"#_1-目录结构",children:[]},{level:3,title:"2. docker-compose",slug:"_2-docker-compose",link:"#_2-docker-compose",children:[]},{level:3,title:"3. 初始化密码",slug:"_3-初始化密码",link:"#_3-初始化密码",children:[]},{level:3,title:"4. 安装IK中文分词器",slug:"_4-安装ik中文分词器",link:"#_4-安装ik中文分词器",children:[]},{level:3,title:"5. 安装拼音分词器",slug:"_5-安装拼音分词器",link:"#_5-安装拼音分词器",children:[]}],path:"/other/essay/elk%E9%83%A8%E7%BD%B2.html",pathLocale:"/",extraFields:[]},{title:"即时通信软件需求",headers:[{level:2,title:"1. 概述",slug:"_1-概述",link:"#_1-概述",children:[{level:3,title:"1.1 项目背景",slug:"_1-1-项目背景",link:"#_1-1-项目背景",children:[]},{level:3,title:"1.2 项目目标",slug:"_1-2-项目目标",link:"#_1-2-项目目标",children:[]},{level:3,title:"1.3 用户群体",slug:"_1-3-用户群体",link:"#_1-3-用户群体",children:[]}]},{level:2,title:"2. 功能需求",slug:"_2-功能需求",link:"#_2-功能需求",children:[{level:3,title:"2.1 基础功能",slug:"_2-1-基础功能",link:"#_2-1-基础功能",children:[]},{level:3,title:"2.2 自动回复",slug:"_2-2-自动回复",link:"#_2-2-自动回复",children:[]},{level:3,title:"2.3 安全与合规",slug:"_2-3-安全与合规",link:"#_2-3-安全与合规",children:[]},{level:3,title:"2.4 高级功能",slug:"_2-4-高级功能",link:"#_2-4-高级功能",children:[]},{level:3,title:"2.5 跨平台支持",slug:"_2-5-跨平台支持",link:"#_2-5-跨平台支持",children:[]},{level:3,title:"2.6 其他需求",slug:"_2-6-其他需求",link:"#_2-6-其他需求",children:[]}]},{level:2,title:"3. 非功能需求",slug:"_3-非功能需求",link:"#_3-非功能需求",children:[{level:3,title:"3.1 性能要求",slug:"_3-1-性能要求",link:"#_3-1-性能要求",children:[]},{level:3,title:"3.2 安全要求",slug:"_3-2-安全要求",link:"#_3-2-安全要求",children:[]},{level:3,title:"3.3 可维护性",slug:"_3-3-可维护性",link:"#_3-3-可维护性",children:[]}]},{level:2,title:"4. 风险管理",slug:"_4-风险管理",link:"#_4-风险管理",children:[]}],path:"/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html",pathLocale:"/",extraFields:[]},{title:"windows下服务注册",headers:[{level:2,title:"1.需求",slug:"_1-需求",link:"#_1-需求",children:[]},{level:2,title:"2. 实现方式",slug:"_2-实现方式",link:"#_2-实现方式",children:[{level:3,title:"2.1 使用nssm",slug:"_2-1-使用nssm",link:"#_2-1-使用nssm",children:[]},{level:3,title:"2.2 使用winsw",slug:"_2-2-使用winsw",link:"#_2-2-使用winsw",children:[]}]}],path:"/other/essay/windows%E4%B8%8B%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C.html",pathLocale:"/",extraFields:[]},{title:"系统引导基本名词BIOS/EFI/MBR/GPT/GRUB",headers:[{level:3,title:"对应关系",slug:"对应关系",link:"#对应关系",children:[]}],path:"/other/essay/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%BC%95%E5%AF%BC.html",pathLocale:"/",extraFields:[]},{title:"批量删除github仓库",headers:[{level:2,title:"背景",slug:"背景",link:"#背景",children:[]},{level:2,title:"方法",slug:"方法",link:"#方法",children:[]}],path:"/other/git/BatchDeleteGitHubRepo.html",pathLocale:"/",extraFields:[]},{title:"git命令",headers:[{level:2,title:"一、善用手册",slug:"一、善用手册",link:"#一、善用手册",children:[]},{level:2,title:"二、 本地基本命令使用",slug:"二、-本地基本命令使用",link:"#二、-本地基本命令使用",children:[{level:3,title:"2.1 分支操作",slug:"_2-1-分支操作",link:"#_2-1-分支操作",children:[]},{level:3,title:"2.2、撤销修改",slug:"_2-2、撤销修改",link:"#_2-2、撤销修改",children:[]},{level:3,title:"2.3、使用git reflog在各个commit中穿梭",slug:"_2-3、使用git-reflog在各个commit中穿梭",link:"#_2-3、使用git-reflog在各个commit中穿梭",children:[]},{level:3,title:"2.4、git rm",slug:"_2-4、git-rm",link:"#_2-4、git-rm",children:[]}]},{level:2,title:"三、 远程命令使用",slug:"三、-远程命令使用",link:"#三、-远程命令使用",children:[{level:3,title:"1、把一个本地仓库关联到远程",slug:"_1、把一个本地仓库关联到远程",link:"#_1、把一个本地仓库关联到远程",children:[]},{level:3,title:"2、查看本地和远程分支关联关系",slug:"_2、查看本地和远程分支关联关系",link:"#_2、查看本地和远程分支关联关系",children:[]},{level:3,title:"3、解除和远程仓库关联",slug:"_3、解除和远程仓库关联",link:"#_3、解除和远程仓库关联",children:[]},{level:3,title:"4、git pull",slug:"_4、git-pull",link:"#_4、git-pull",children:[]},{level:3,title:"5、git rebase",slug:"_5、git-rebase",link:"#_5、git-rebase",children:[]}]}],path:"/other/git/GitCommands.html",pathLocale:"/",extraFields:[]},{title:"Git",headers:[],path:"/other/git/",pathLocale:"/",extraFields:[]},{title:"git 拉取远程分支到本地",headers:[{level:3,title:"1,查看远程分支",slug:"_1-查看远程分支",link:"#_1-查看远程分支",children:[]},{level:3,title:"2, 查看本地分支",slug:"_2-查看本地分支",link:"#_2-查看本地分支",children:[]},{level:3,title:"3,拉取远程分支",slug:"_3-拉取远程分支",link:"#_3-拉取远程分支",children:[]},{level:3,title:"4,新建本地dev分支,并关联远程dev分支",slug:"_4-新建本地dev分支-并关联远程dev分支",link:"#_4-新建本地dev分支-并关联远程dev分支",children:[]}],path:"/other/git/branch01.html",pathLocale:"/",extraFields:[]},{title:"git分支操作",headers:[{level:3,title:"一,推送本地分支到远程",slug:"一-推送本地分支到远程",link:"#一-推送本地分支到远程",children:[]},{level:3,title:"二,从远程拉取分支到d本地",slug:"二-从远程拉取分支到d本地",link:"#二-从远程拉取分支到d本地",children:[]},{level:3,title:"三,删除本地分支",slug:"三-删除本地分支",link:"#三-删除本地分支",children:[]},{level:3,title:"四,合并分支到master上",slug:"四-合并分支到master上",link:"#四-合并分支到master上",children:[]}],path:"/other/git/branch02.html",pathLocale:"/",extraFields:[]},{title:"Git克隆出现连接错误",headers:[{level:2,title:"如何解决 fatal: unable to access...的问题",slug:"如何解决-fatal-unable-to-access-的问题",link:"#如何解决-fatal-unable-to-access-的问题",children:[{level:3,title:"现象",slug:"现象",link:"#现象",children:[]},{level:3,title:"确定是否是因为代理问题",slug:"确定是否是因为代理问题",link:"#确定是否是因为代理问题",children:[]},{level:3,title:"解决问题",slug:"解决问题",link:"#解决问题",children:[]}]}],path:"/other/git/fatal.html",pathLocale:"/",extraFields:[]},{title:"git冲突出现的原因",headers:[{level:2,title:"一,git冲突出现的原因及解决方案",slug:"一-git冲突出现的原因及解决方案",link:"#一-git冲突出现的原因及解决方案",children:[]}],path:"/other/git/gitConflict.html",pathLocale:"/",extraFields:[]},{title:"git rebase的使用",headers:[{level:3,title:"一,用于合并当前分支的多个commit记录",slug:"一-用于合并当前分支的多个commit记录",link:"#一-用于合并当前分支的多个commit记录",children:[]},{level:3,title:"",slug:"",link:"#",children:[]},{level:3,title:"二、使用rebase替代merge合并分支",slug:"二、使用rebase替代merge合并分支",link:"#二、使用rebase替代merge合并分支",children:[]}],path:"/other/git/gitRebase.html",pathLocale:"/",extraFields:[]},{title:"git工作区、暂存区、和版本库",headers:[{level:3,title:"一,基本概念",slug:"一-基本概念",link:"#一-基本概念",children:[]}],path:"/other/git/gitwork.html",pathLocale:"/",extraFields:[]},{title:"git pull产生临时Merge branch的问题",headers:[{level:3,title:"一,问题",slug:"一-问题",link:"#一-问题",children:[]},{level:3,title:"二,产生的原因",slug:"二-产生的原因",link:"#二-产生的原因",children:[]},{level:3,title:"三,解决方法",slug:"三-解决方法",link:"#三-解决方法",children:[]}],path:"/other/git/mergeBranch.html",pathLocale:"/",extraFields:[]},{title:"git代理",headers:[{level:2,title:"1、全局代理",slug:"_1、全局代理",link:"#_1、全局代理",children:[]},{level:2,title:"2、部分代理",slug:"_2、部分代理",link:"#_2、部分代理",children:[]},{level:2,title:"3、参考",slug:"_3、参考",link:"#_3、参考",children:[]}],path:"/other/git/proxy.html",pathLocale:"/",extraFields:[]},{title:"git rebase与merge的区别",headers:[],path:"/other/git/rebaseAndMerge.html",pathLocale:"/",extraFields:[]},{title:"git reset命令使用",headers:[{level:3,title:"1、几种reset介绍",slug:"_1、几种reset介绍",link:"#_1、几种reset介绍",children:[]}],path:"/other/git/reset.html",pathLocale:"/",extraFields:[]},{title:"git stash 暂存",headers:[{level:2,title:"一,使用场景",slug:"一-使用场景",link:"#一-使用场景",children:[]},{level:2,title:"二,stash的作用",slug:"二-stash的作用",link:"#二-stash的作用",children:[]},{level:2,title:"三,使用命令",slug:"三-使用命令",link:"#三-使用命令",children:[]},{level:2,title:"四,本地解决冲突",slug:"四-本地解决冲突",link:"#四-本地解决冲突",children:[]}],path:"/other/git/stash.html",pathLocale:"/",extraFields:[]},{title:"cpu介绍",headers:[{level:2,title:"intel cpu型号",slug:"intel-cpu型号",link:"#intel-cpu型号",children:[{level:3,title:"cpu型号命名",slug:"cpu型号命名",link:"#cpu型号命名",children:[]},{level:3,title:"cpu 后缀",slug:"cpu-后缀",link:"#cpu-后缀",children:[]}]}],path:"/other/hardware/CPU.html",pathLocale:"/",extraFields:[]},{title:"RedHat系",headers:[{level:2,title:"1、安装JDK11",slug:"_1、安装jdk11",link:"#_1、安装jdk11",children:[]},{level:2,title:"2、多版本JDK切换",slug:"_2、多版本jdk切换",link:"#_2、多版本jdk切换",children:[]}],path:"/other/linux/CentOS.html",pathLocale:"/",extraFields:[]},{title:"常用命令",headers:[{level:3,title:"1、查找多个文件中是否包含字符串",slug:"_1、查找多个文件中是否包含字符串",link:"#_1、查找多个文件中是否包含字符串",children:[]},{level:3,title:"2、高亮关键字",slug:"_2、高亮关键字",link:"#_2、高亮关键字",children:[]},{level:3,title:"3、查询大文件",slug:"_3、查询大文件",link:"#_3、查询大文件",children:[]},{level:3,title:"4、查看文件被哪个进程占用",slug:"_4、查看文件被哪个进程占用",link:"#_4、查看文件被哪个进程占用",children:[]},{level:3,title:"5、看开机启动项启动耗时",slug:"_5、看开机启动项启动耗时",link:"#_5、看开机启动项启动耗时",children:[]},{level:3,title:"6、Curl命令",slug:"_6、curl命令",link:"#_6、curl命令",children:[]},{level:3,title:"7、strace命令",slug:"_7、strace命令",link:"#_7、strace命令",children:[]},{level:3,title:"8、 nc命令",slug:"_8、-nc命令",link:"#_8、-nc命令",children:[]},{level:3,title:"9、tcpdump抓包",slug:"_9、tcpdump抓包",link:"#_9、tcpdump抓包",children:[]}],path:"/other/linux/CommonUsedCMD.html",pathLocale:"/",extraFields:[]},{title:"Curl命令",headers:[{level:2,title:"1、使用CURL分析接口请求耗时",slug:"_1、使用curl分析接口请求耗时",link:"#_1、使用curl分析接口请求耗时",children:[{level:3,title:"1.1 构造curl命令",slug:"_1-1-构造curl命令",link:"#_1-1-构造curl命令",children:[]},{level:3,title:"1.2、分析耗时",slug:"_1-2、分析耗时",link:"#_1-2、分析耗时",children:[]}]}],path:"/other/linux/Curl.html",pathLocale:"/",extraFields:[]},{title:"docker安装mysql",headers:[{level:2,title:"1. 不要用太新的版本",slug:"_1-不要用太新的版本",link:"#_1-不要用太新的版本",children:[]},{level:2,title:"2 安装",slug:"_2-安装",link:"#_2-安装",children:[]},{level:2,title:"3 报错",slug:"_3-报错",link:"#_3-报错",children:[]}],path:"/other/linux/InstallMysqlWithDocker.html",pathLocale:"/",extraFields:[]},{title:"Manjaro问题搜集",headers:[{level:3,title:"1、降级软件包",slug:"_1、降级软件包",link:"#_1、降级软件包",children:[]},{level:3,title:"2、开机报错failed to start rotate log files",slug:"_2、开机报错failed-to-start-rotate-log-files",link:"#_2、开机报错failed-to-start-rotate-log-files",children:[]},{level:3,title:"linux下输入法无法输入中文中括号问题",slug:"linux下输入法无法输入中文中括号问题",link:"#linux下输入法无法输入中文中括号问题",children:[]},{level:3,title:"3、美化",slug:"_3、美化",link:"#_3、美化",children:[]},{level:3,title:"4、设置yakuake提示没有权限修改文件",slug:"_4、设置yakuake提示没有权限修改文件",link:"#_4、设置yakuake提示没有权限修改文件",children:[]},{level:3,title:"4、Manjaro不支持Mysql",slug:"_4、manjaro不支持mysql",link:"#_4、manjaro不支持mysql",children:[]},{level:3,title:"5、Manjaro设置DNS",slug:"_5、manjaro设置dns",link:"#_5、manjaro设置dns",children:[]},{level:3,title:"6、Manjaro fcitx5中文大括号问题",slug:"_6、manjaro-fcitx5中文大括号问题",link:"#_6、manjaro-fcitx5中文大括号问题",children:[]},{level:3,title:"7、分支管理",slug:"_7、分支管理",link:"#_7、分支管理",children:[]},{level:3,title:"9、解压缩中文乱码",slug:"_9、解压缩中文乱码",link:"#_9、解压缩中文乱码",children:[]},{level:3,title:"10、vmware使用问题",slug:"_10、vmware使用问题",link:"#_10、vmware使用问题",children:[]},{level:3,title:"11、vnc",slug:"_11、vnc",link:"#_11、vnc",children:[]},{level:3,title:"12、Remmina远程windows字体发虚",slug:"_12、remmina远程windows字体发虚",link:"#_12、remmina远程windows字体发虚",children:[]},{level:3,title:"13、manjaro-kde下idea新版本UI问题",slug:"_13、manjaro-kde下idea新版本ui问题",link:"#_13、manjaro-kde下idea新版本ui问题",children:[]},{level:3,title:"12、GTK3主题设置",slug:"_12、gtk3主题设置",link:"#_12、gtk3主题设置",children:[]}],path:"/other/linux/Manjaro.html",pathLocale:"/",extraFields:[]},{title:"系统挂载磁盘",headers:[{level:2,title:"参考",slug:"参考",link:"#参考",children:[]},{level:2,title:"挂载",slug:"挂载",link:"#挂载",children:[{level:3,title:"划分分区并挂载磁盘",slug:"划分分区并挂载磁盘",link:"#划分分区并挂载磁盘",children:[]},{level:3,title:"设置开机自动挂载磁盘分区",slug:"设置开机自动挂载磁盘分区",link:"#设置开机自动挂载磁盘分区",children:[]}]}],path:"/other/linux/MountDisk.html",pathLocale:"/",extraFields:[]},{title:"双网卡问题",headers:[{level:2,title:"1.1 kde桌面双网卡内外网设置",slug:"_1-1-kde桌面双网卡内外网设置",link:"#_1-1-kde桌面双网卡内外网设置",children:[{level:3,title:"1.1 内网设置",slug:"_1-1-内网设置",link:"#_1-1-内网设置",children:[]},{level:3,title:"1.2 外网(无线)设置(保留默认即可)",slug:"_1-2-外网-无线-设置-保留默认即可",link:"#_1-2-外网-无线-设置-保留默认即可",children:[]}]},{level:2,title:"2、双网卡DNS解析",slug:"_2、双网卡dns解析",link:"#_2、双网卡dns解析",children:[{level:3,title:"3、搜索域",slug:"_3、搜索域",link:"#_3、搜索域",children:[]}]}],path:"/other/linux/MultiNetworkCard.html",pathLocale:"/",extraFields:[]},{title:"Linux",headers:[],path:"/other/linux/",pathLocale:"/",extraFields:[]},{title:"部署Samba",headers:[{level:2,title:"1、安装过程省略",slug:"_1、安装过程省略",link:"#_1、安装过程省略",children:[]},{level:2,title:"2、配置",slug:"_2、配置",link:"#_2、配置",children:[{level:3,title:"2.1 配置文件",slug:"_2-1-配置文件",link:"#_2-1-配置文件",children:[]},{level:3,title:"2.2 创建samba用户",slug:"_2-2-创建samba用户",link:"#_2-2-创建samba用户",children:[]},{level:3,title:"2.3 windows连接samba",slug:"_2-3-windows连接samba",link:"#_2-3-windows连接samba",children:[]}]}],path:"/other/linux/Samba.html",pathLocale:"/",extraFields:[]},{title:"Linux挂载windows共享目录",headers:[{level:3,title:"1、在windows设置共享目录",slug:"_1、在windows设置共享目录",link:"#_1、在windows设置共享目录",children:[]},{level:3,title:"2、在linux下挂载",slug:"_2、在linux下挂载",link:"#_2、在linux下挂载",children:[]},{level:3,title:"参考博客",slug:"参考博客",link:"#参考博客",children:[]}],path:"/other/linux/ShareBetweenWindowsAndLinux.html",pathLocale:"/",extraFields:[]},{title:"tcpdump抓包",headers:[{level:3,title:"1、所用抓报命令",slug:"_1、所用抓报命令",link:"#_1、所用抓报命令",children:[]},{level:3,title:"2、抓包后的处理",slug:"_2、抓包后的处理",link:"#_2、抓包后的处理",children:[]},{level:3,title:"3、其他使用实例",slug:"_3、其他使用实例",link:"#_3、其他使用实例",children:[]}],path:"/other/linux/TcpDump.html",pathLocale:"/",extraFields:[]},{title:"Linux下加装wifi模块",headers:[{level:2,title:"1、需求场景",slug:"_1、需求场景",link:"#_1、需求场景",children:[]},{level:2,title:"2、解决方案——使用板载无线网卡",slug:"_2、解决方案——使用板载无线网卡",link:"#_2、解决方案——使用板载无线网卡",children:[]}],path:"/other/linux/Wifi.html",pathLocale:"/",extraFields:[]},{title:"linux中防火墙",headers:[{level:2,title:"集中常用的防火墙配置工具",slug:"集中常用的防火墙配置工具",link:"#集中常用的防火墙配置工具",children:[]},{level:2,title:"关系图",slug:"关系图",link:"#关系图",children:[]},{level:2,title:"chatgpt中的回答",slug:"chatgpt中的回答",link:"#chatgpt中的回答",children:[]},{level:2,title:"结论",slug:"结论",link:"#结论",children:[]},{level:2,title:"参考",slug:"参考",link:"#参考",children:[]}],path:"/other/linux/firewall.html",pathLocale:"/",extraFields:[]},{title:"windows子系统wsl",headers:[{level:2,title:"2.1 更换国内镜像源",slug:"_2-1-更换国内镜像源",link:"#_2-1-更换国内镜像源",children:[]},{level:2,title:"2.2 从windows进入wsl",slug:"_2-2-从windows进入wsl",link:"#_2-2-从windows进入wsl",children:[]},{level:2,title:"2.3 wsl域名解析慢的问题",slug:"_2-3-wsl域名解析慢的问题",link:"#_2-3-wsl域名解析慢的问题",children:[]}],path:"/other/linux/wsl.html",pathLocale:"/",extraFields:[]},{title:"使用Flameshot截图",headers:[{level:2,title:"manjaro-kde为flameshot设置快捷截图",slug:"manjaro-kde为flameshot设置快捷截图",link:"#manjaro-kde为flameshot设置快捷截图",children:[]}],path:"/other/linux/%E6%88%AA%E5%9B%BE.html",pathLocale:"/",extraFields:[]},{title:"MarkDown资源",headers:[{level:2,title:"Emoji",slug:"emoji",link:"#emoji",children:[]},{level:2,title:"vue-press-theme-hope Icon",slug:"vue-press-theme-hope-icon",link:"#vue-press-theme-hope-icon",children:[]}],path:"/other/markdown/",pathLocale:"/",extraFields:[]},{title:"01 | OAuth 2.0是要通过什么方式解决什么问题?",headers:[{level:3,title:"OAuth 2.0 是什么?",slug:"oauth-2-0-是什么",link:"#oauth-2-0-是什么",children:[]},{level:3,title:"为什么用 OAuth 2.0?",slug:"为什么用-oauth-2-0",link:"#为什么用-oauth-2-0",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/01.html",pathLocale:"/",extraFields:[]},{title:"02 | 授权码许可类型中,为什么一定要有授权码?",headers:[{level:3,title:"为什么需要授权码?",slug:"为什么需要授权码",link:"#为什么需要授权码",children:[]},{level:3,title:"授权码许可类型的通信过程",slug:"授权码许可类型的通信过程",link:"#授权码许可类型的通信过程",children:[]},{level:3,title:"直接通信",slug:"直接通信",link:"#直接通信",children:[]},{level:3,title:"两个 “一伙”",slug:"两个-一伙",link:"#两个-一伙",children:[]},{level:3,title:"一定要有浏览器吗?",slug:"一定要有浏览器吗",link:"#一定要有浏览器吗",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/02.html",pathLocale:"/",extraFields:[]},{title:"03 | 授权服务:授权码和访问令牌的颁发流程是怎样",headers:[{level:3,title:"授权服务的工作过程",slug:"授权服务的工作过程",link:"#授权服务的工作过程",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/03.html",pathLocale:"/",extraFields:[]},{title:"04 | 在OAuth 2.0中,如何使用JWT结构化令牌?",headers:[{level:3,title:"JWT 结构化令牌",slug:"jwt-结构化令牌",link:"#jwt-结构化令牌",children:[]},{level:3,title:"令牌内检",slug:"令牌内检",link:"#令牌内检",children:[]},{level:3,title:"JWT 是如何被使用的?",slug:"jwt-是如何被使用的",link:"#jwt-是如何被使用的",children:[]},{level:3,title:"为什么要使用 JWT 令牌?",slug:"为什么要使用-jwt-令牌",link:"#为什么要使用-jwt-令牌",children:[]},{level:3,title:"令牌的生命周期",slug:"令牌的生命周期",link:"#令牌的生命周期",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/04.html",pathLocale:"/",extraFields:[]},{title:"05 | 如何安全、快速地接入OAuth 2.0?",headers:[{level:3,title:"构建第三方软件应用",slug:"构建第三方软件应用",link:"#构建第三方软件应用",children:[]},{level:3,title:"服务市场中的第三方应用软件",slug:"服务市场中的第三方应用软件",link:"#服务市场中的第三方应用软件",children:[]},{level:3,title:"构建受保护资源服务",slug:"构建受保护资源服务",link:"#构建受保护资源服务",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/05.html",pathLocale:"/",extraFields:[]},{title:"06 | 除了授权码许可类型,OAuth 2.0还支持什么授权流程?",headers:[{level:3,title:"资源拥有者凭据许可",slug:"资源拥有者凭据许可",link:"#资源拥有者凭据许可",children:[]},{level:3,title:"客户端凭据许可",slug:"客户端凭据许可",link:"#客户端凭据许可",children:[]},{level:3,title:"隐式许可",slug:"隐式许可",link:"#隐式许可",children:[]},{level:3,title:"如何选择?",slug:"如何选择",link:"#如何选择",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/06.html",pathLocale:"/",extraFields:[]},{title:"07 | 如何在移动App中使用OAuth 2.0?",headers:[{level:3,title:"没有 Server 端的 App",slug:"没有-server-端的-app",link:"#没有-server-端的-app",children:[]},{level:3,title:"有 Server 端的 App",slug:"有-server-端的-app",link:"#有-server-端的-app",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/07.html",pathLocale:"/",extraFields:[]},{title:"08 | 实践OAuth 2.0时,使用不当可能会导致哪些安全漏洞?",headers:[{level:3,title:"CSRF 攻击",slug:"csrf-攻击",link:"#csrf-攻击",children:[]},{level:3,title:"XSS 攻击",slug:"xss-攻击",link:"#xss-攻击",children:[]},{level:3,title:"水平越权",slug:"水平越权",link:"#水平越权",children:[]},{level:3,title:"授权码失窃",slug:"授权码失窃",link:"#授权码失窃",children:[]},{level:3,title:"重定向 URI 被篡改",slug:"重定向-uri-被篡改",link:"#重定向-uri-被篡改",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/08.html",pathLocale:"/",extraFields:[]},{title:"09 | 实战:利用OAuth 2.0实现一个OpenID Connect用户身份认证协议",headers:[{level:3,title:"OIDC 是什么?",slug:"oidc-是什么",link:"#oidc-是什么",children:[]},{level:3,title:"OIDC 和 OAuth 2.0 的角色对应关系",slug:"oidc-和-oauth-2-0-的角色对应关系",link:"#oidc-和-oauth-2-0-的角色对应关系",children:[]},{level:3,title:"OIDC 和 OAuth 2.0 的关键区别",slug:"oidc-和-oauth-2-0-的关键区别",link:"#oidc-和-oauth-2-0-的关键区别",children:[]},{level:3,title:"OIDC 中的 ID 令牌生成和解析方法",slug:"oidc-中的-id-令牌生成和解析方法",link:"#oidc-中的-id-令牌生成和解析方法",children:[]},{level:3,title:"用访问令牌获取 ID 令牌之外的信息",slug:"用访问令牌获取-id-令牌之外的信息",link:"#用访问令牌获取-id-令牌之外的信息",children:[]},{level:3,title:"单点登录",slug:"单点登录",link:"#单点登录",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/09.html",pathLocale:"/",extraFields:[]},{title:"10 | 串讲:OAuth 2.0的工作流程与安全问题",headers:[{level:3,title:"OAuth 2.0 安全问题串讲",slug:"oauth-2-0-安全问题串讲",link:"#oauth-2-0-安全问题串讲",children:[]},{level:3,title:"再强调都不为过的安全意识",slug:"再强调都不为过的安全意识",link:"#再强调都不为过的安全意识",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/10.html",pathLocale:"/",extraFields:[]},{title:"11 | 实战案例:使用Spring Security搭建一套基于JWT的OAuth 2.0架构",headers:[{level:3,title:"项目准备工作",slug:"项目准备工作",link:"#项目准备工作",children:[]},{level:3,title:"搭建授权服务器",slug:"搭建授权服务器",link:"#搭建授权服务器",children:[]},{level:3,title:"搭建受保护资源服务器",slug:"搭建受保护资源服务器",link:"#搭建受保护资源服务器",children:[]},{level:3,title:"初始化数据配置",slug:"初始化数据配置",link:"#初始化数据配置",children:[]},{level:3,title:"演示三种授权许可类型",slug:"演示三种授权许可类型",link:"#演示三种授权许可类型",children:[]},{level:3,title:"资源拥有者凭据许可类型",slug:"资源拥有者凭据许可类型",link:"#资源拥有者凭据许可类型",children:[]},{level:3,title:"客户端授权许可类型",slug:"客户端授权许可类型",link:"#客户端授权许可类型",children:[]},{level:3,title:"授权码许可类型",slug:"授权码许可类型",link:"#授权码许可类型",children:[]},{level:3,title:"演示权限控制",slug:"演示权限控制",link:"#演示权限控制",children:[]},{level:3,title:"搭建客户端程序",slug:"搭建客户端程序",link:"#搭建客户端程序",children:[]},{level:3,title:"演示单点登录",slug:"演示单点登录",link:"#演示单点登录",children:[]},{level:3,title:"演示客户端请求资源服务器资源",slug:"演示客户端请求资源服务器资源",link:"#演示客户端请求资源服务器资源",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/11.html",pathLocale:"/",extraFields:[]},{title:"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构",headers:[{level:2,title:"微服务分层架构",slug:"微服务分层架构",link:"#微服务分层架构",children:[{level:3,title:"Nginx 反向代理层",slug:"nginx-反向代理层",link:"#nginx-反向代理层",children:[]},{level:3,title:"Web 应用层",slug:"web-应用层",link:"#web-应用层",children:[]},{level:3,title:"Gateway 网关层",slug:"gateway-网关层",link:"#gateway-网关层",children:[]},{level:3,title:"IDP 服务",slug:"idp-服务",link:"#idp-服务",children:[]},{level:3,title:"BFF 层",slug:"bff-层",link:"#bff-层",children:[]},{level:3,title:"领域服务层",slug:"领域服务层",link:"#领域服务层",children:[]}]},{level:2,title:"OAuth 2.0/JWT 如何与微服务进行集成?",slug:"oauth-2-0-jwt-如何与微服务进行集成",link:"#oauth-2-0-jwt-如何与微服务进行集成",children:[{level:3,title:"场景 1:第一方 Web 应用 + 资源拥有者凭据模式",slug:"场景-1-第一方-web-应用-资源拥有者凭据模式",link:"#场景-1-第一方-web-应用-资源拥有者凭据模式",children:[]},{level:3,title:"场景 2:第一方移动应用 + 授权码许可模式",slug:"场景-2-第一方移动应用-授权码许可模式",link:"#场景-2-第一方移动应用-授权码许可模式",children:[]},{level:3,title:"场景 3:第三方 Web 应用 + 授权码模式",slug:"场景-3-第三方-web-应用-授权码模式",link:"#场景-3-第三方-web-应用-授权码模式",children:[]}]},{level:2,title:"小结",slug:"小结",link:"#小结",children:[]},{level:2,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/12.html",pathLocale:"/",extraFields:[]},{title:"13 | 各大开放平台是如何使用OAuth 2.0的?",headers:[{level:3,title:"开放平台体系是什么样子的?",slug:"开放平台体系是什么样子的",link:"#开放平台体系是什么样子的",children:[]},{level:3,title:"各大开放平台授权流程",slug:"各大开放平台授权流程",link:"#各大开放平台授权流程",children:[]},{level:3,title:"授权码流程中的参数说明",slug:"授权码流程中的参数说明",link:"#授权码流程中的参数说明",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/13.html",pathLocale:"/",extraFields:[]},{title:"14 | 查漏补缺:OAuth 2.0 常见问题答疑",headers:[{level:3,title:"发明 OAuth 的目的到底是什么?",slug:"发明-oauth-的目的到底是什么",link:"#发明-oauth-的目的到底是什么",children:[]},{level:3,title:"OAuth 2.0 是身份认证协议吗?",slug:"oauth-2-0-是身份认证协议吗",link:"#oauth-2-0-是身份认证协议吗",children:[]},{level:3,title:"有了刷新令牌,是不是就可以让访问令牌一直有效了?",slug:"有了刷新令牌-是不是就可以让访问令牌一直有效了",link:"#有了刷新令牌-是不是就可以让访问令牌一直有效了",children:[]},{level:3,title:"使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?",slug:"使用了-https-是不是就能确保-jwt-格式令牌的数据安全",link:"#使用了-https-是不是就能确保-jwt-格式令牌的数据安全",children:[]},{level:3,title:"ID 令牌和访问令牌之间有联系吗?",slug:"id-令牌和访问令牌之间有联系吗",link:"#id-令牌和访问令牌之间有联系吗",children:[]},{level:3,title:"PKCE 协议到底解决的是什么问题?",slug:"pkce-协议到底解决的是什么问题",link:"#pkce-协议到底解决的是什么问题",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/14.html",pathLocale:"/",extraFields:[]},{title:"开篇词 | 为什么要学OAuth 2.0?",headers:[{level:3,title:"OAuth 2.0是什么?",slug:"oauth-2-0是什么",link:"#oauth-2-0是什么",children:[]},{level:3,title:"为什么会有这门课?",slug:"为什么会有这门课",link:"#为什么会有这门课",children:[]},{level:3,title:"这门课是怎么设计的?",slug:"这门课是怎么设计的",link:"#这门课是怎么设计的",children:[]}],path:"/other/oauth2/",pathLocale:"/",extraFields:[]},{title:"玩转PVE虚拟机",headers:[{level:3,title:"pve",slug:"pve",link:"#pve",children:[]}],path:"/other/pve/",pathLocale:"/",extraFields:[]},{title:"ddns证书",headers:[{level:2,title:"1. 申请动态域名(省略)",slug:"_1-申请动态域名-省略",link:"#_1-申请动态域名-省略",children:[]},{level:2,title:"2. 使用acme申请免费的",slug:"_2-使用acme申请免费的",link:"#_2-使用acme申请免费的",children:[{level:3,title:"2.1 使用acme申请证书",slug:"_2-1-使用acme申请证书",link:"#_2-1-使用acme申请证书",children:[]},{level:3,title:"2.2 使用openssl创建密钥对",slug:"_2-2-使用openssl创建密钥对",link:"#_2-2-使用openssl创建密钥对",children:[]},{level:3,title:"2.3 上传公钥到dynv6",slug:"_2-3-上传公钥到dynv6",link:"#_2-3-上传公钥到dynv6",children:[]},{level:3,title:"2.4 执行申请证书",slug:"_2-4-执行申请证书",link:"#_2-4-执行申请证书",children:[]}]},{level:2,title:"3.给pve的web管理配置证书",slug:"_3-给pve的web管理配置证书",link:"#_3-给pve的web管理配置证书",children:[]},{level:2,title:"4. 自动续签",slug:"_4-自动续签",link:"#_4-自动续签",children:[]},{level:2,title:"pve的webui界面自带的acme插件",slug:"pve的webui界面自带的acme插件",link:"#pve的webui界面自带的acme插件",children:[]}],path:"/other/pve/ddns%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6.html",pathLocale:"/",extraFields:[]},{title:"pve防火墙",headers:[{level:3,title:"一、防火墙简介",slug:"一、防火墙简介",link:"#一、防火墙简介",children:[]},{level:3,title:"二: 防火墙配置文件",slug:"二-防火墙配置文件",link:"#二-防火墙配置文件",children:[]},{level:3,title:"三、实际配置",slug:"三、实际配置",link:"#三、实际配置",children:[]}],path:"/other/pve/firewall.html",pathLocale:"/",extraFields:[]},{title:"rabbitmq",headers:[{level:3,title:"从入门到放弃",slug:"从入门到放弃",link:"#从入门到放弃",children:[]}],path:"/other/rabbitmq/",pathLocale:"/",extraFields:[]},{title:"软件激活",headers:[{level:3,title:"smartsvn",slug:"smartsvn",link:"#smartsvn",children:[]}],path:"/other/software/%E7%A0%B4%E8%A7%A3%E8%BD%AF%E4%BB%B6.html",pathLocale:"/",extraFields:[]},{title:"客户端对接用户中心",headers:[{level:2,title:"1、基础环境搭建",slug:"_1、基础环境搭建",link:"#_1、基础环境搭建",children:[{level:3,title:"1.1 引入依赖",slug:"_1-1-引入依赖",link:"#_1-1-引入依赖",children:[]},{level:3,title:"1.2 配置",slug:"_1-2-配置",link:"#_1-2-配置",children:[]}]},{level:2,title:"2、用户中心用户转本地用户",slug:"_2、用户中心用户转本地用户",link:"#_2、用户中心用户转本地用户",children:[{level:3,title:"2.1 过滤器进行拦截处理",slug:"_2-1-过滤器进行拦截处理",link:"#_2-1-过滤器进行拦截处理",children:[]}]}],path:"/other/sono/SUC.html",pathLocale:"/",extraFields:[]},{title:"Idea",headers:[{level:2,title:"1、多线程debug遇到的问题",slug:"_1、多线程debug遇到的问题",link:"#_1、多线程debug遇到的问题",children:[]},{level:2,title:"2、Idea指定jdk启动",slug:"_2、idea指定jdk启动",link:"#_2、idea指定jdk启动",children:[]},{level:2,title:"3、idea中git操作",slug:"_3、idea中git操作",link:"#_3、idea中git操作",children:[{level:3,title:"3.1 对比任意非连续的两次提交文件差异",slug:"_3-1-对比任意非连续的两次提交文件差异",link:"#_3-1-对比任意非连续的两次提交文件差异",children:[]}]},{level:2,title:"4、debug无法进入jdk源码",slug:"_4、debug无法进入jdk源码",link:"#_4、debug无法进入jdk源码",children:[]}],path:"/other/tools/Idea.html",pathLocale:"/",extraFields:[]},{title:"软件工具",headers:[],path:"/other/tools/",pathLocale:"/",extraFields:[]},{title:"软件分享",headers:[{level:3,title:"1、Beyond Compare3",slug:"_1、beyond-compare3",link:"#_1、beyond-compare3",children:[]}],path:"/other/tools/SoftWare.html",pathLocale:"/",extraFields:[]},{title:"vscode配置",headers:[{level:3,title:"1. vscode终端无法打开",slug:"_1-vscode终端无法打开",link:"#_1-vscode终端无法打开",children:[]},{level:3,title:"2、在vscode中使用git-bash作为默认终端",slug:"_2、在vscode中使用git-bash作为默认终端",link:"#_2、在vscode中使用git-bash作为默认终端",children:[]},{level:3,title:"3. vscode代码片段",slug:"_3-vscode代码片段",link:"#_3-vscode代码片段",children:[]},{level:3,title:"4. 设置jdk环境",slug:"_4-设置jdk环境",link:"#_4-设置jdk环境",children:[]}],path:"/other/tools/VsCode.html",pathLocale:"/",extraFields:[]},{title:"小组分享-云服务",headers:[{level:2,title:"1.1 什么是DNS",slug:"_1-1-什么是dns",link:"#_1-1-什么是dns",children:[]},{level:2,title:"1.1 A(Address)记录",slug:"_1-1-a-address-记录",link:"#_1-1-a-address-记录",children:[]},{level:2,title:"1.2 CNAME(Canonical Name)",slug:"_1-2-cname-canonical-name",link:"#_1-2-cname-canonical-name",children:[{level:3,title:"1.2.1 定义",slug:"_1-2-1-定义",link:"#_1-2-1-定义",children:[]},{level:3,title:"1.2.2 参考博客",slug:"_1-2-2-参考博客",link:"#_1-2-2-参考博客",children:[]},{level:3,title:"1.2.2 场景",slug:"_1-2-2-场景",link:"#_1-2-2-场景",children:[]}]},{level:2,title:"1.3 NS(Name Server)记录",slug:"_1-3-ns-name-server-记录",link:"#_1-3-ns-name-server-记录",children:[]},{level:2,title:"1.4 DNS分析实战",slug:"_1-4-dns分析实战",link:"#_1-4-dns分析实战",children:[]},{level:2,title:"2.1 什么是CDN",slug:"_2-1-什么是cdn",link:"#_2-1-什么是cdn",children:[]},{level:2,title:"2.2 CDN原理",slug:"_2-2-cdn原理",link:"#_2-2-cdn原理",children:[]},{level:2,title:"2.3 CDN是如何使用CNAME",slug:"_2-3-cdn是如何使用cname",link:"#_2-3-cdn是如何使用cname",children:[]},{level:2,title:"2.4 CNAME为什么不直接解析到ip,而是域名",slug:"_2-4-cname为什么不直接解析到ip-而是域名",link:"#_2-4-cname为什么不直接解析到ip-而是域名",children:[]},{level:2,title:"3.1 什么是对象存储",slug:"_3-1-什么是对象存储",link:"#_3-1-什么是对象存储",children:[]},{level:2,title:"3.2 华为SDK使用",slug:"_3-2-华为sdk使用",link:"#_3-2-华为sdk使用",children:[]},{level:2,title:"3.3 S3协议sdk",slug:"_3-3-s3协议sdk",link:"#_3-3-s3协议sdk",children:[]},{level:2,title:"3.3 CDN加速OBS中存储的图片.",slug:"_3-3-cdn加速obs中存储的图片",link:"#_3-3-cdn加速obs中存储的图片",children:[]},{level:2,title:"4.1 一个域名能解析多少个IP?",slug:"_4-1-一个域名能解析多少个ip",link:"#_4-1-一个域名能解析多少个ip",children:[]},{level:2,title:"4.2 请求一个地址,加www和不加是否有区别?",slug:"_4-2-请求一个地址-加www和不加是否有区别",link:"#_4-2-请求一个地址-加www和不加是否有区别",children:[]}],path:"/other/training/CloudServiceTraining.html",pathLocale:"/",extraFields:[]},{title:"小组分享",headers:[],path:"/other/training/",pathLocale:"/",extraFields:[]},{title:"OAuth2分享",headers:[{level:2,title:"1、SSO",slug:"_1、sso",link:"#_1、sso",children:[{level:3,title:"1.1 SSO介绍",slug:"_1-1-sso介绍",link:"#_1-1-sso介绍",children:[]},{level:3,title:"1.2 SSO使用场景(解决了什么问题)",slug:"_1-2-sso使用场景-解决了什么问题",link:"#_1-2-sso使用场景-解决了什么问题",children:[]},{level:3,title:"1.3 使用SSO的好处",slug:"_1-3-使用sso的好处",link:"#_1-3-使用sso的好处",children:[]}]},{level:2,title:"2、SSO常见实现方案",slug:"_2、sso常见实现方案",link:"#_2、sso常见实现方案",children:[{level:3,title:"2.1 基于 Cookie 的单点登录",slug:"_2-1-基于-cookie-的单点登录",link:"#_2-1-基于-cookie-的单点登录",children:[]},{level:3,title:"2.2 分布式 Session 实现单点登录",slug:"_2-2-分布式-session-实现单点登录",link:"#_2-2-分布式-session-实现单点登录",children:[]},{level:3,title:"2.3 统一认证授权方式实现单点登录",slug:"_2-3-统一认证授权方式实现单点登录",link:"#_2-3-统一认证授权方式实现单点登录",children:[]}]},{level:2,title:"3、实际场景中SSO实现",slug:"_3、实际场景中sso实现",link:"#_3、实际场景中sso实现",children:[{level:3,title:"3.1 JWT(完全跨域方案)",slug:"_3-1-jwt-完全跨域方案",link:"#_3-1-jwt-完全跨域方案",children:[]},{level:3,title:"3.2 使用 OAuth2.0 实现单点登录",slug:"_3-2-使用-oauth2-0-实现单点登录",link:"#_3-2-使用-oauth2-0-实现单点登录",children:[]}]},{level:2,title:"4、参考资料",slug:"_4、参考资料",link:"#_4、参考资料",children:[]}],path:"/other/training/SSO.html",pathLocale:"/",extraFields:[]},{title:"画图工具",headers:[{level:2,title:"1 示例",slug:"_1-示例",link:"#_1-示例",children:[]},{level:2,title:"2 制图工具",slug:"_2-制图工具",link:"#_2-制图工具",children:[]},{level:2,title:"3 工具选择",slug:"_3-工具选择",link:"#_3-工具选择",children:[]}],path:"/other/training/draw.html",pathLocale:"/",extraFields:[]},{title:"前后分离项目搭建",headers:[{level:2,title:"1、后端篇",slug:"_1、后端篇",link:"#_1、后端篇",children:[{level:3,title:"1.1 初始化springboot项目",slug:"_1-1-初始化springboot项目",link:"#_1-1-初始化springboot项目",children:[]},{level:3,title:"1.2 全局异常处理",slug:"_1-2-全局异常处理",link:"#_1-2-全局异常处理",children:[]},{level:3,title:"1.3 aop统一日志处理",slug:"_1-3-aop统一日志处理",link:"#_1-3-aop统一日志处理",children:[]},{level:3,title:"1.4 添加Mybatis-plus",slug:"_1-4-添加mybatis-plus",link:"#_1-4-添加mybatis-plus",children:[]},{level:3,title:"1.5 添加Redis",slug:"_1-5-添加redis",link:"#_1-5-添加redis",children:[]},{level:3,title:"1.6 添加swagger",slug:"_1-6-添加swagger",link:"#_1-6-添加swagger",children:[]},{level:3,title:"1.7 添加Security",slug:"_1-7-添加security",link:"#_1-7-添加security",children:[]},{level:3,title:"1.8 角色权限控制",slug:"_1-8-角色权限控制",link:"#_1-8-角色权限控制",children:[]},{level:3,title:"1.9 自定义token认证(前后分离)",slug:"_1-9-自定义token认证-前后分离",link:"#_1-9-自定义token认证-前后分离",children:[]},{level:3,title:"1.9.3 放行登录接口,去掉表单登录",slug:"_1-9-3-放行登录接口-去掉表单登录",link:"#_1-9-3-放行登录接口-去掉表单登录",children:[]},{level:3,title:"next 菜单权限",slug:"next-菜单权限",link:"#next-菜单权限",children:[]}]},{level:2,title:"2、前端篇",slug:"_2、前端篇",link:"#_2、前端篇",children:[{level:3,title:"2.1 初始化vue2",slug:"_2-1-初始化vue2",link:"#_2-1-初始化vue2",children:[]},{level:3,title:"2.2 axios统一处理",slug:"_2-2-axios统一处理",link:"#_2-2-axios统一处理",children:[]}]}],path:"/other/web/BuildWebProject.html",pathLocale:"/",extraFields:[]},{title:"Cookie",headers:[{level:2,title:"Cookie的作用域domain",slug:"cookie的作用域domain",link:"#cookie的作用域domain",children:[]},{level:2,title:"Cookie的path",slug:"cookie的path",link:"#cookie的path",children:[]},{level:2,title:"实例",slug:"实例",link:"#实例",children:[{level:3,title:"域名可以看到当前域以及父域名下的cookie,看不到其子域名下的cookie",slug:"域名可以看到当前域以及父域名下的cookie-看不到其子域名下的cookie",link:"#域名可以看到当前域以及父域名下的cookie-看不到其子域名下的cookie",children:[]},{level:3,title:"设置了path,要同时满足url中有指定path才能看到",slug:"设置了path-要同时满足url中有指定path才能看到",link:"#设置了path-要同时满足url中有指定path才能看到",children:[]}]},{level:2,title:"浏览器请求时会自动携带其所有能看到的cookie发送到后端",slug:"浏览器请求时会自动携带其所有能看到的cookie发送到后端",link:"#浏览器请求时会自动携带其所有能看到的cookie发送到后端",children:[]},{level:2,title:"java中Session和Cookie交互",slug:"java中session和cookie交互",link:"#java中session和cookie交互",children:[]}],path:"/other/web/Cookie.html",pathLocale:"/",extraFields:[]},{title:"Http",headers:[{level:2,title:"一、状态码",slug:"一、状态码",link:"#一、状态码",children:[{level:3,title:"1.1 3xx",slug:"_1-1-3xx",link:"#_1-1-3xx",children:[]},{level:3,title:"1.1.1 304",slug:"_1-1-1-304",link:"#_1-1-1-304",children:[]}]},{level:2,title:"二、http请求",slug:"二、http请求",link:"#二、http请求",children:[{level:3,title:"2.1 302重定向",slug:"_2-1-302重定向",link:"#_2-1-302重定向",children:[]}]},{level:2,title:"二、http协议",slug:"二、http协议",link:"#二、http协议",children:[{level:3,title:"2.1 http协议是什么",slug:"_2-1-http协议是什么",link:"#_2-1-http协议是什么",children:[]}]},{level:2,title:"三、Headers",slug:"三、headers",link:"#三、headers",children:[{level:3,title:"3.1 中文header",slug:"_3-1-中文header",link:"#_3-1-中文header",children:[]}]}],path:"/other/web/Http.html",pathLocale:"/",extraFields:[]},{title:"jwt",headers:[{level:2,title:"1、jwt在服务端如何校验的?",slug:"_1、jwt在服务端如何校验的",link:"#_1、jwt在服务端如何校验的",children:[]},{level:2,title:"2、为什么相同用户每次登陆得到的jwt token不一样?",slug:"_2、为什么相同用户每次登陆得到的jwt-token不一样",link:"#_2、为什么相同用户每次登陆得到的jwt-token不一样",children:[]},{level:2,title:"3、Jwt VS 普通token",slug:"_3、jwt-vs-普通token",link:"#_3、jwt-vs-普通token",children:[]}],path:"/other/web/Jwt.html",pathLocale:"/",extraFields:[]},{title:"oauth第三方登录",headers:[{level:2,title:"1 oauth2",slug:"_1-oauth2",link:"#_1-oauth2",children:[{level:3,title:"1.1 典型应用场景",slug:"_1-1-典型应用场景",link:"#_1-1-典型应用场景",children:[]},{level:3,title:"1.2 完整时序图",slug:"_1-2-完整时序图",link:"#_1-2-完整时序图",children:[]}]},{level:2,title:"2 GitHub配置",slug:"_2-github配置",link:"#_2-github配置",children:[]},{level:2,title:"3 前后分离项目前后端交互",slug:"_3-前后分离项目前后端交互",link:"#_3-前后分离项目前后端交互",children:[]},{level:2,title:"4 前端代码",slug:"_4-前端代码",link:"#_4-前端代码",children:[]},{level:2,title:"5 后端处理",slug:"_5-后端处理",link:"#_5-后端处理",children:[{level:3,title:"5.1 处理github授权请求",slug:"_5-1-处理github授权请求",link:"#_5-1-处理github授权请求",children:[]},{level:3,title:"5.2 重定向后的处理",slug:"_5-2-重定向后的处理",link:"#_5-2-重定向后的处理",children:[]},{level:3,title:"5.3 前后交互数据问题",slug:"_5-3-前后交互数据问题",link:"#_5-3-前后交互数据问题",children:[]}]}],path:"/other/web/OAUTH_LOGIN.html",pathLocale:"/",extraFields:[]},{title:"web开发通用知识",headers:[],path:"/other/web/",pathLocale:"/",extraFields:[]},{title:"refresh_token",headers:[{level:2,title:"1、 refresh_token介绍",slug:"_1、-refresh-token介绍",link:"#_1、-refresh-token介绍",children:[]},{level:2,title:"2、token过期处理",slug:"_2、token过期处理",link:"#_2、token过期处理",children:[{level:3,title:"2.1 方式一,重新登录",slug:"_2-1-方式一-重新登录",link:"#_2-1-方式一-重新登录",children:[]},{level:3,title:"2.2 方式二,续签(刷新)token",slug:"_2-2-方式二-续签-刷新-token",link:"#_2-2-方式二-续签-刷新-token",children:[]}]}],path:"/other/web/RefreshToken.html",pathLocale:"/",extraFields:[]},{title:"HTTP Restful",headers:[{level:2,title:"一、参考",slug:"一、参考",link:"#一、参考",children:[]}],path:"/other/web/Restful.html",pathLocale:"/",extraFields:[]},{title:"wsl问题",headers:[{level:2,title:"1. wsl系统设置桥接网络",slug:"_1-wsl系统设置桥接网络",link:"#_1-wsl系统设置桥接网络",children:[{level:3,title:"1.1 开启hyper-v",slug:"_1-1-开启hyper-v",link:"#_1-1-开启hyper-v",children:[]},{level:3,title:"1.2 桥接网络",slug:"_1-2-桥接网络",link:"#_1-2-桥接网络",children:[]},{level:3,title:"1.3 修改wsl",slug:"_1-3-修改wsl",link:"#_1-3-修改wsl",children:[]},{level:3,title:"1.4 取消桥接",slug:"_1-4-取消桥接",link:"#_1-4-取消桥接",children:[]}]}],path:"/other/windows/WSL.html",pathLocale:"/",extraFields:[]},{title:"MybatisPlus多线程数据源切换问题",headers:[{level:2,title:"1、问题的背景",slug:"_1、问题的背景",link:"#_1、问题的背景",children:[]},{level:2,title:"2、问题产生的原因",slug:"_2、问题产生的原因",link:"#_2、问题产生的原因",children:[]},{level:2,title:"3、解决方法",slug:"_3、解决方法",link:"#_3、解决方法",children:[]},{level:2,title:"4、MybatisPlus多线程数据源切换的源码分析",slug:"_4、mybatisplus多线程数据源切换的源码分析",link:"#_4、mybatisplus多线程数据源切换的源码分析",children:[]}],path:"/java/framework/mybatis/MybatisPlusDataSource.html",pathLocale:"/",extraFields:[]},{title:"Mybatis",headers:[],path:"/java/framework/mybatis/",pathLocale:"/",extraFields:[]},{title:"Mybatis使用",headers:[{level:2,title:"mybatis缓存",slug:"mybatis缓存",link:"#mybatis缓存",children:[]}],path:"/java/framework/mybatis/mybatis.html",pathLocale:"/",extraFields:[]},{title:"权限校验原理",headers:[{level:2,title:"1、权限说明",slug:"_1、权限说明",link:"#_1、权限说明",children:[]},{level:2,title:"2、Security中负责权限校验的类结构图",slug:"_2、security中负责权限校验的类结构图",link:"#_2、security中负责权限校验的类结构图",children:[]}],path:"/java/framework/security/Authorization.html",pathLocale:"/",extraFields:[]},{title:"Security扩展自定义登录方式",headers:[{level:2,title:"1、需求",slug:"_1、需求",link:"#_1、需求",children:[]},{level:2,title:"2、实现方法",slug:"_2、实现方法",link:"#_2、实现方法",children:[{level:3,title:"2.1 自定义AuthenticationProvider",slug:"_2-1-自定义authenticationprovider",link:"#_2-1-自定义authenticationprovider",children:[]},{level:3,title:"2.2 自定义AuthenticationToken",slug:"_2-2-自定义authenticationtoken",link:"#_2-2-自定义authenticationtoken",children:[]},{level:3,title:"2.3 自定义登录接口",slug:"_2-3-自定义登录接口",link:"#_2-3-自定义登录接口",children:[]},{level:3,title:"2.4 配置自定义的AuthenticationProvider",slug:"_2-4-配置自定义的authenticationprovider",link:"#_2-4-配置自定义的authenticationprovider",children:[]}]}],path:"/java/framework/security/CustomAuthenticationProvider.html",pathLocale:"/",extraFields:[]},{title:"自定义登陆页面",headers:[{level:2,title:"1、修改自定义的登陆页面以及登陆请求校验",slug:"_1、修改自定义的登陆页面以及登陆请求校验",link:"#_1、修改自定义的登陆页面以及登陆请求校验",children:[{level:3,title:"1.1 Secutity配置",slug:"_1-1-secutity配置",link:"#_1-1-secutity配置",children:[]},{level:3,title:"1.2 自定义登陆页面",slug:"_1-2-自定义登陆页面",link:"#_1-2-自定义登陆页面",children:[]}]},{level:2,title:"2、开启表单认证",slug:"_2、开启表单认证",link:"#_2、开启表单认证",children:[]},{level:2,title:"3、关闭表单认证",slug:"_3、关闭表单认证",link:"#_3、关闭表单认证",children:[]}],path:"/java/framework/security/CustomLoginPage.html",pathLocale:"/",extraFields:[]},{title:"前后分离项目自定义token认证",headers:[{level:2,title:"1、Security配置注意事项",slug:"_1、security配置注意事项",link:"#_1、security配置注意事项",children:[]},{level:2,title:"3、自定义过滤器",slug:"_3、自定义过滤器",link:"#_3、自定义过滤器",children:[]}],path:"/java/framework/security/CustomTokenAuthentication.html",pathLocale:"/",extraFields:[]},{title:"DelegatingFilterProxy介绍",headers:[{level:3,title:"作用",slug:"作用",link:"#作用",children:[]}],path:"/java/framework/security/DelegatingFilterProxy.html",pathLocale:"/",extraFields:[]},{title:"Ruoyi使用oauth对接pig",headers:[{level:2,title:"1、时序图",slug:"_1、时序图",link:"#_1、时序图",children:[]},{level:2,title:"2、流程解析",slug:"_2、流程解析",link:"#_2、流程解析",children:[{level:3,title:"2.1 第1步",slug:"_2-1-第1步",link:"#_2-1-第1步",children:[]},{level:3,title:"2.2 第2步",slug:"_2-2-第2步",link:"#_2-2-第2步",children:[]},{level:3,title:"2.3 第3步",slug:"_2-3-第3步",link:"#_2-3-第3步",children:[]},{level:3,title:"2.4 第4步",slug:"_2-4-第4步",link:"#_2-4-第4步",children:[]},{level:3,title:"2.5 第5步",slug:"_2-5-第5步",link:"#_2-5-第5步",children:[]},{level:3,title:"2.6 第6步",slug:"_2-6-第6步",link:"#_2-6-第6步",children:[]},{level:3,title:"2.7 第7步",slug:"_2-7-第7步",link:"#_2-7-第7步",children:[]},{level:3,title:"2.8 第8步",slug:"_2-8-第8步",link:"#_2-8-第8步",children:[]},{level:3,title:"2.9 第9步",slug:"_2-9-第9步",link:"#_2-9-第9步",children:[]},{level:3,title:"2.10 第10步",slug:"_2-10-第10步",link:"#_2-10-第10步",children:[]},{level:3,title:"2.11 第11步",slug:"_2-11-第11步",link:"#_2-11-第11步",children:[]}]}],path:"/java/framework/security/OAuth2Authentication.html",pathLocale:"/",extraFields:[]},{title:"OnecePerRequestFilter",headers:[{level:2,title:"1、OnecePerRequestFilter初识",slug:"_1、oneceperrequestfilter初识",link:"#_1、oneceperrequestfilter初识",children:[{level:3,title:"1.1 源码doc",slug:"_1-1-源码doc",link:"#_1-1-源码doc",children:[]}]},{level:2,title:"2、 源码剖析",slug:"_2、-源码剖析",link:"#_2、-源码剖析",children:[]},{level:2,title:"3、总结",slug:"_3、总结",link:"#_3、总结",children:[]}],path:"/java/framework/security/OncePerRequestFilter.html",pathLocale:"/",extraFields:[]},{title:"PreAuthorize注解",headers:[{level:2,title:"1、使用方式",slug:"_1、使用方式",link:"#_1、使用方式",children:[]},{level:2,title:"2、@PreAuthorize注解是何时生效的?",slug:"_2、-preauthorize注解是何时生效的",link:"#_2、-preauthorize注解是何时生效的",children:[{level:3,title:"2.1",slug:"_2-1",link:"#_2-1",children:[]}]}],path:"/java/framework/security/PreAuthorize.html",pathLocale:"/",extraFields:[]},{title:"Security学习",headers:[],path:"/java/framework/security/",pathLocale:"/",extraFields:[]},{title:"SSO协议",headers:[{level:2,title:"1、写在前面",slug:"_1、写在前面",link:"#_1、写在前面",children:[]},{level:2,title:"2、SSO和OAUTH2介绍",slug:"_2、sso和oauth2介绍",link:"#_2、sso和oauth2介绍",children:[]},{level:2,title:"3、SSO实现",slug:"_3、sso实现",link:"#_3、sso实现",children:[{level:3,title:"3.1 CAS",slug:"_3-1-cas",link:"#_3-1-cas",children:[]}]},{level:2,title:"4、各种协议对比",slug:"_4、各种协议对比",link:"#_4、各种协议对比",children:[]}],path:"/java/framework/security/SSO.html",pathLocale:"/",extraFields:[]},{title:"Security配置类",headers:[{level:2,title:"1、配置类",slug:"_1、配置类",link:"#_1、配置类",children:[]},{level:2,title:"2、在配置类配置和在Controller方法注解上使用@PreAuthorized有何区别",slug:"_2、在配置类配置和在controller方法注解上使用-preauthorized有何区别",link:"#_2、在配置类配置和在controller方法注解上使用-preauthorized有何区别",children:[{level:3,title:"2.1 在配置配给资源配置权限",slug:"_2-1-在配置配给资源配置权限",link:"#_2-1-在配置配给资源配置权限",children:[]},{level:3,title:"2.2 使用注解@PreAuthorized",slug:"_2-2-使用注解-preauthorized",link:"#_2-2-使用注解-preauthorized",children:[]}]},{level:2,title:"3、新版本配置",slug:"_3、新版本配置",link:"#_3、新版本配置",children:[]}],path:"/java/framework/security/SecurityFilterChain.html",pathLocale:"/",extraFields:[]},{title:"Security中的Session",headers:[{level:2,title:"1、背景(问题复现)",slug:"_1、背景-问题复现",link:"#_1、背景-问题复现",children:[]},{level:2,title:"2、sessionid变化的原因",slug:"_2、sessionid变化的原因",link:"#_2、sessionid变化的原因",children:[]},{level:2,title:"3、源码查看",slug:"_3、源码查看",link:"#_3、源码查看",children:[]},{level:2,title:"4、基于Session的认证",slug:"_4、基于session的认证",link:"#_4、基于session的认证",children:[]}],path:"/java/framework/security/Session.html",pathLocale:"/",extraFields:[]},{title:"Security入门笔记",headers:[{level:2,title:"1、加密解密流程",slug:"_1、加密解密流程",link:"#_1、加密解密流程",children:[]},{level:2,title:"2、HttpSecurity和WebSecurity区别",slug:"_2、httpsecurity和websecurity区别",link:"#_2、httpsecurity和websecurity区别",children:[]},{level:2,title:"3、SpringSecurity中User实体中Lock和enabled区别",slug:"_3、springsecurity中user实体中lock和enabled区别",link:"#_3、springsecurity中user实体中lock和enabled区别",children:[]},{level:2,title:"4、开启方法级别的权限控制",slug:"_4、开启方法级别的权限控制",link:"#_4、开启方法级别的权限控制",children:[]}],path:"/java/framework/security/note.html",pathLocale:"/",extraFields:[]},{title:"spring-security-oauth2-authorization-server",headers:[{level:2,title:"介绍",slug:"介绍",link:"#介绍",children:[]},{level:2,title:"/oauth2/token",slug:"oauth2-token",link:"#oauth2-token",children:[]}],path:"/java/framework/security/spring-security-oauth2-authorization-server.html",pathLocale:"/",extraFields:[]},{title:"注解",headers:[{level:2,title:"1、依赖注入",slug:"_1、依赖注入",link:"#_1、依赖注入",children:[]},{level:2,title:"2、关于Spring不建议使用@Autowired在字段上",slug:"_2、关于spring不建议使用-autowired在字段上",link:"#_2、关于spring不建议使用-autowired在字段上",children:[{level:3,title:"2.1 如果使用@Autowoired在字段上,可能带来的问题",slug:"_2-1-如果使用-autowoired在字段上-可能带来的问题",link:"#_2-1-如果使用-autowoired在字段上-可能带来的问题",children:[]}]}],path:"/java/framework/spring/Annotation.html",pathLocale:"/",extraFields:[]},{title:"BeanPostProcessor介绍",headers:[{level:3,title:"1. BeanPostProcessor介绍",slug:"_1-beanpostprocessor介绍",link:"#_1-beanpostprocessor介绍",children:[]},{level:3,title:"2. 再看看BeanPostProcessor能做什么,有什么使用场景",slug:"_2-再看看beanpostprocessor能做什么-有什么使用场景",link:"#_2-再看看beanpostprocessor能做什么-有什么使用场景",children:[]},{level:3,title:"3. bean实例化过程",slug:"_3-bean实例化过程",link:"#_3-bean实例化过程",children:[]}],path:"/java/framework/spring/BeanPostProcessor.html",pathLocale:"/",extraFields:[]},{title:"Spring循环依赖",headers:[{level:2,title:"1、循环依赖的产生",slug:"_1、循环依赖的产生",link:"#_1、循环依赖的产生",children:[]},{level:2,title:"2、Spring是如何解决的循环依赖问题",slug:"_2、spring是如何解决的循环依赖问题",link:"#_2、spring是如何解决的循环依赖问题",children:[{level:3,title:"2.1 Bean的生命周期",slug:"_2-1-bean的生命周期",link:"#_2-1-bean的生命周期",children:[]},{level:3,title:"2.2 依赖注入的过程",slug:"_2-2-依赖注入的过程",link:"#_2-2-依赖注入的过程",children:[]}]}],path:"/java/framework/spring/CircularDependency.html",pathLocale:"/",extraFields:[]},{title:"Spring中的设计模式",headers:[{level:3,title:"1、工厂模式",slug:"_1、工厂模式",link:"#_1、工厂模式",children:[]},{level:3,title:"2、单例模式",slug:"_2、单例模式",link:"#_2、单例模式",children:[]},{level:3,title:"3、观察者模式",slug:"_3、观察者模式",link:"#_3、观察者模式",children:[]},{level:3,title:"4、适配器模式",slug:"_4、适配器模式",link:"#_4、适配器模式",children:[]},{level:3,title:"5、代理模式",slug:"_5、代理模式",link:"#_5、代理模式",children:[]},{level:3,title:"6、模板方法模式",slug:"_6、模板方法模式",link:"#_6、模板方法模式",children:[]},{level:3,title:"7、包装模式(装饰者模式)",slug:"_7、包装模式-装饰者模式",link:"#_7、包装模式-装饰者模式",children:[]},{level:3,title:"8、原型模式",slug:"_8、原型模式",link:"#_8、原型模式",children:[]},{level:3,title:"9、建造者模式",slug:"_9、建造者模式",link:"#_9、建造者模式",children:[]}],path:"/java/framework/spring/DesignPatternInSpring.html",pathLocale:"/",extraFields:[]},{title:"OncePerRequestFilter",headers:[{level:2,title:"1、OncePerRequestFilter",slug:"_1、onceperrequestfilter",link:"#_1、onceperrequestfilter",children:[]},{level:2,title:"2、参考",slug:"_2、参考",link:"#_2、参考",children:[]}],path:"/java/framework/spring/OncePerRequestFilter.html",pathLocale:"/",extraFields:[]},{title:"Spring",headers:[],path:"/java/framework/spring/",pathLocale:"/",extraFields:[]},{title:"SpringAOP",headers:[{level:2,title:"1、SpringAOP",slug:"_1、springaop",link:"#_1、springaop",children:[]},{level:2,title:"2、SpringAOP - 非注解实现",slug:"_2、springaop-非注解实现",link:"#_2、springaop-非注解实现",children:[{level:3,title:"2.1 演示1(有接口实现)",slug:"_2-1-演示1-有接口实现",link:"#_2-1-演示1-有接口实现",children:[]},{level:3,title:"2.2 演示2(无接口实现)",slug:"_2-2-演示2-无接口实现",link:"#_2-2-演示2-无接口实现",children:[]}]},{level:2,title:"3、SpringAOP注解实现",slug:"_3、springaop注解实现",link:"#_3、springaop注解实现",children:[]},{level:2,title:"4、 SpringAOP实现原理",slug:"_4、-springaop实现原理",link:"#_4、-springaop实现原理",children:[{level:3,title:"4.1 SpringAOP是如何产生代理类的",slug:"_4-1-springaop是如何产生代理类的",link:"#_4-1-springaop是如何产生代理类的",children:[]},{level:3,title:"4.2 源码层面查看SpringAOP是使用JDK动态代理还是CGlib",slug:"_4-2-源码层面查看springaop是使用jdk动态代理还是cglib",link:"#_4-2-源码层面查看springaop是使用jdk动态代理还是cglib",children:[]},{level:3,title:"4.3 时序图",slug:"_4-3-时序图",link:"#_4-3-时序图",children:[]}]}],path:"/java/framework/spring/SpringAOP.html",pathLocale:"/",extraFields:[]},{title:"Spring缓存",headers:[{level:3,title:"1、背景",slug:"_1、背景",link:"#_1、背景",children:[]},{level:3,title:"2、spring-cache介绍",slug:"_2、spring-cache介绍",link:"#_2、spring-cache介绍",children:[]},{level:3,title:"3、springcache的实现",slug:"_3、springcache的实现",link:"#_3、springcache的实现",children:[]},{level:3,title:"4、springcache的注解使用",slug:"_4、springcache的注解使用",link:"#_4、springcache的注解使用",children:[]}],path:"/java/framework/spring/SpringCache.html",pathLocale:"/",extraFields:[]},{title:"Spring框架扩展点",headers:[{level:3,title:"1. spring生命周期",slug:"_1-spring生命周期",link:"#_1-spring生命周期",children:[]},{level:3,title:"2. 扩展点介绍",slug:"_2-扩展点介绍",link:"#_2-扩展点介绍",children:[]}],path:"/java/framework/spring/SpringExtensionPoint.html",pathLocale:"/",extraFields:[]},{title:"Spring IOC 容器源码分析",headers:[{level:2,title:"引言",slug:"引言",link:"#引言",children:[]},{level:2,title:"BeanFactory 简介",slug:"beanfactory-简介",link:"#beanfactory-简介",children:[]},{level:2,title:"启动过程分析",slug:"启动过程分析",link:"#启动过程分析",children:[{level:3,title:"创建 Bean 容器前的准备工作",slug:"创建-bean-容器前的准备工作",link:"#创建-bean-容器前的准备工作",children:[]},{level:3,title:"创建 Bean 容器,加载并注册 Bean",slug:"创建-bean-容器-加载并注册-bean",link:"#创建-bean-容器-加载并注册-bean",children:[]},{level:3,title:"Bean 容器实例化完成后",slug:"bean-容器实例化完成后",link:"#bean-容器实例化完成后",children:[]},{level:3,title:"准备 Bean 容器: prepareBeanFactory",slug:"准备-bean-容器-preparebeanfactory",link:"#准备-bean-容器-preparebeanfactory",children:[]},{level:3,title:"初始化所有的 singleton beans",slug:"初始化所有的-singleton-beans",link:"#初始化所有的-singleton-beans",children:[]}]},{level:2,title:"附录",slug:"附录",link:"#附录",children:[{level:3,title:"id 和 name",slug:"id-和-name",link:"#id-和-name",children:[]},{level:3,title:"配置是否允许 Bean 覆盖、是否允许循环依赖",slug:"配置是否允许-bean-覆盖、是否允许循环依赖",link:"#配置是否允许-bean-覆盖、是否允许循环依赖",children:[]},{level:3,title:"profile",slug:"profile",link:"#profile",children:[]},{level:3,title:"工厂模式生成 Bean",slug:"工厂模式生成-bean",link:"#工厂模式生成-bean",children:[]},{level:3,title:"FactoryBean",slug:"factorybean",link:"#factorybean",children:[]},{level:3,title:"初始化 Bean 的回调",slug:"初始化-bean-的回调",link:"#初始化-bean-的回调",children:[]},{level:3,title:"销毁 Bean 的回调",slug:"销毁-bean-的回调",link:"#销毁-bean-的回调",children:[]},{level:3,title:"ConversionService",slug:"conversionservice",link:"#conversionservice",children:[]},{level:3,title:"Bean 继承",slug:"bean-继承",link:"#bean-继承",children:[]},{level:3,title:"方法注入",slug:"方法注入",link:"#方法注入",children:[]},{level:3,title:"BeanPostProcessor",slug:"beanpostprocessor",link:"#beanpostprocessor",children:[]}]},{level:2,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/java/framework/spring/SpringIOC.html",pathLocale:"/",extraFields:[]},{title:"SpringMVC基本原理",headers:[{level:2,title:"SpringMVC处理请求的流程",slug:"springmvc处理请求的流程",link:"#springmvc处理请求的流程",children:[]},{level:2,title:"前端控制器源码",slug:"前端控制器源码",link:"#前端控制器源码",children:[{level:3,title:"步骤1 客户端发起请求,请求到达前端控制器",slug:"步骤1-客户端发起请求-请求到达前端控制器",link:"#步骤1-客户端发起请求-请求到达前端控制器",children:[]},{level:3,title:"步骤2. 根据请求获取HandlerMethod",slug:"步骤2-根据请求获取handlermethod",link:"#步骤2-根据请求获取handlermethod",children:[]}]},{level:2,title:"步骤3 获取HandlerAdapter",slug:"步骤3-获取handleradapter",link:"#步骤3-获取handleradapter",children:[]},{level:2,title:"步骤4 通过HandlerAdapter真正调用业务处理逻辑",slug:"步骤4-通过handleradapter真正调用业务处理逻辑",link:"#步骤4-通过handleradapter真正调用业务处理逻辑",children:[]},{level:2,title:"步骤5 渲染页面",slug:"步骤5-渲染页面",link:"#步骤5-渲染页面",children:[]}],path:"/java/framework/spring/SpringMVC.html",pathLocale:"/",extraFields:[]},{title:"SpringIOC源码分析",headers:[],path:"/java/framework/spring/SpringSourceAnalize.html",pathLocale:"/",extraFields:[]},{title:"自定义validator分组检验",headers:[{level:2,title:"自定义校验",slug:"自定义校验",link:"#自定义校验",children:[]},{level:2,title:"调用",slug:"调用",link:"#调用",children:[]}],path:"/java/framework/spring/Validator.html",pathLocale:"/",extraFields:[]},{title:"使用aop记录请求log",headers:[],path:"/java/framework/springboot/AOPLog.html",pathLocale:"/",extraFields:[]},{title:"注入集合",headers:[{level:2,title:"1、测试",slug:"_1、测试",link:"#_1、测试",children:[]},{level:2,title:"2、实现方法",slug:"_2、实现方法",link:"#_2、实现方法",children:[]},{level:2,title:"3、以上方法如何保证只注入部分的实现类呢?",slug:"_3、以上方法如何保证只注入部分的实现类呢",link:"#_3、以上方法如何保证只注入部分的实现类呢",children:[]}],path:"/java/framework/springboot/CollectionInject.html",pathLocale:"/",extraFields:[]},{title:"springboot开启http2.0",headers:[{level:2,title:"1、http2.0优势",slug:"_1、http2-0优势",link:"#_1、http2-0优势",children:[]},{level:2,title:"2、springboot开启http2.0",slug:"_2、springboot开启http2-0",link:"#_2、springboot开启http2-0",children:[]}],path:"/java/framework/springboot/Http2.html",pathLocale:"/",extraFields:[]},{title:"SpringBoot",headers:[],path:"/java/framework/springboot/",pathLocale:"/",extraFields:[]},{title:"springboot自动配置原理",headers:[],path:"/java/framework/springboot/SpringBootAutoConfiguration.html",pathLocale:"/",extraFields:[]},{title:"Swagger",headers:[{level:2,title:"Swagger和SpringFox关系",slug:"swagger和springfox关系",link:"#swagger和springfox关系",children:[]}],path:"/java/framework/springboot/Swagger.html",pathLocale:"/",extraFields:[]},{title:"springboot部署",headers:[{level:3,title:"参考",slug:"参考",link:"#参考",children:[]}],path:"/java/framework/springboot/%E9%83%A8%E7%BD%B2.html",pathLocale:"/",extraFields:[]},{title:"网关路由失效",headers:[{level:2,title:"问题描述",slug:"问题描述",link:"#问题描述",children:[]},{level:2,title:"原因",slug:"原因",link:"#原因",children:[]},{level:2,title:"针对本次问题的解决方法",slug:"针对本次问题的解决方法",link:"#针对本次问题的解决方法",children:[{level:3,title:"方法1",slug:"方法1",link:"#方法1",children:[]},{level:3,title:"方法2",slug:"方法2",link:"#方法2",children:[]}]}],path:"/java/framework/springcloud/SpringCloudGateway.html",pathLocale:"/",extraFields:[]},{title:"Gradle",headers:[{level:2,title:"1. Gradle的放弃之路",slug:"_1-gradle的放弃之路",link:"#_1-gradle的放弃之路",children:[]}],path:"/java/other/gradle/",pathLocale:"/",extraFields:[]},{title:"Wrapper",headers:[],path:"/java/other/gradle/wrapper.html",pathLocale:"/",extraFields:[]},{title:"线上问题定位",headers:[{level:2,title:"此分类专门定位线上问题",slug:"此分类专门定位线上问题",link:"#此分类专门定位线上问题",children:[]}],path:"/java/other/locateproblem/",pathLocale:"/",extraFields:[]},{title:"TooManyOpenFiles",headers:[{level:2,title:"Linux服务器打开文件过多",slug:"linux服务器打开文件过多",link:"#linux服务器打开文件过多",children:[{level:3,title:"线上异常",slug:"线上异常",link:"#线上异常",children:[]},{level:3,title:"问题原因分析",slug:"问题原因分析",link:"#问题原因分析",children:[]}]}],path:"/java/other/locateproblem/TooManyOpenFiles.html",pathLocale:"/",extraFields:[]},{title:"undertow.xxx not found",headers:[{level:2,title:"背景",slug:"背景",link:"#背景",children:[]},{level:2,title:"原因",slug:"原因",link:"#原因",children:[]},{level:2,title:"解决方法",slug:"解决方法",link:"#解决方法",children:[{level:3,title:"方法一",slug:"方法一",link:"#方法一",children:[]},{level:3,title:"方法二",slug:"方法二",link:"#方法二",children:[]},{level:3,title:"方法三",slug:"方法三",link:"#方法三",children:[]}]},{level:2,title:"验证是否生效",slug:"验证是否生效",link:"#验证是否生效",children:[{level:3,title:"方式1",slug:"方式1",link:"#方式1",children:[]},{level:3,title:"方式2",slug:"方式2",link:"#方式2",children:[]}]}],path:"/java/other/locateproblem/Undertow.xxxNotFount.html",pathLocale:"/",extraFields:[]},{title:"各种版本的坑",headers:[{level:2,title:"1、Spring系列",slug:"_1、spring系列",link:"#_1、spring系列",children:[{level:3,title:"1.1 坑1 spring.factories使用方式变更",slug:"_1-1-坑1-spring-factories使用方式变更",link:"#_1-1-坑1-spring-factories使用方式变更",children:[]},{level:3,title:"1.2 坑2 Spring Security OAuth2已被新版本security弃用",slug:"_1-2-坑2-spring-security-oauth2已被新版本security弃用",link:"#_1-2-坑2-spring-security-oauth2已被新版本security弃用",children:[]}]},{level:2,title:"2、Oauth2.0",slug:"_2、oauth2-0",link:"#_2、oauth2-0",children:[{level:3,title:"2.1 移除password授权类型",slug:"_2-1-移除password授权类型",link:"#_2-1-移除password授权类型",children:[]}]}],path:"/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html",pathLocale:"/",extraFields:[]},{title:"开发中遇到的各种问题",headers:[{level:3,title:"1. springcloudgateway负载均衡配置不生效",slug:"_1-springcloudgateway负载均衡配置不生效",link:"#_1-springcloudgateway负载均衡配置不生效",children:[]}],path:"/java/other/locateproblem/%E5%90%84%E7%A7%8D%E9%97%AE%E9%A2%98.html",pathLocale:"/",extraFields:[]},{title:"LogBack",headers:[{level:2,title:"配置",slug:"配置",link:"#配置",children:[]},{level:2,title:"分解配置",slug:"分解配置",link:"#分解配置",children:[{level:3,title:"logger",slug:"logger",link:"#logger",children:[]},{level:3,title:"appender",slug:"appender",link:"#appender",children:[]}]},{level:2,title:"在logger 标签已经定义了level为什么在appender下的filter中还要定义?",slug:"在logger-标签已经定义了level为什么在appender下的filter中还要定义",link:"#在logger-标签已经定义了level为什么在appender下的filter中还要定义",children:[]}],path:"/java/other/log/logback.html",pathLocale:"/",extraFields:[]},{title:"maven",headers:[{level:2,title:"1. maven的放弃之路",slug:"_1-maven的放弃之路",link:"#_1-maven的放弃之路",children:[]}],path:"/java/other/maven/",pathLocale:"/",extraFields:[]},{title:"build标签",headers:[{level:2,title:"配置解释",slug:"配置解释",link:"#配置解释",children:[]}],path:"/java/other/maven/build%E6%A0%87%E7%AD%BE.html",pathLocale:"/",extraFields:[]},{title:"import使用",headers:[{level:2,title:"1. import介绍",slug:"_1-import介绍",link:"#_1-import介绍",children:[]},{level:2,title:"2. 使用方式",slug:"_2-使用方式",link:"#_2-使用方式",children:[]},{level:2,title:"3.使用parent",slug:"_3-使用parent",link:"#_3-使用parent",children:[]}],path:"/java/other/maven/import.html",pathLocale:"/",extraFields:[]},{title:"多模块maven项目的搭建",headers:[{level:2,title:"1、一个真实的多模块maven项目",slug:"_1、一个真实的多模块maven项目",link:"#_1、一个真实的多模块maven项目",children:[]}],path:"/java/other/maven/multiModule.html",pathLocale:"/",extraFields:[]},{title:"使用问题记录",headers:[{level:2,title:"1、继承",slug:"_1、继承",link:"#_1、继承",children:[]},{level:2,title:"2、聚合",slug:"_2、聚合",link:"#_2、聚合",children:[]},{level:2,title:"3、依赖",slug:"_3、依赖",link:"#_3、依赖",children:[]},{level:2,title:"4、dependencies vs dependencyManagement",slug:"_4、dependencies-vs-dependencymanagement",link:"#_4、dependencies-vs-dependencymanagement",children:[]},{level:2,title:"5、springboot 找不到包",slug:"_5、springboot-找不到包",link:"#_5、springboot-找不到包",children:[]}],path:"/java/other/maven/problem.html",pathLocale:"/",extraFields:[]},{title:"Maven的生命周期",headers:[{level:2,title:"Maven的生命周期",slug:"maven的生命周期",link:"#maven的生命周期",children:[]},{level:2,title:"各个生命周期包含的phase",slug:"各个生命周期包含的phase",link:"#各个生命周期包含的phase",children:[{level:3,title:"default lifecycle包含的phase",slug:"default-lifecycle包含的phase",link:"#default-lifecycle包含的phase",children:[]},{level:3,title:"clean lifecycle生命周期所包含的phase",slug:"clean-lifecycle生命周期所包含的phase",link:"#clean-lifecycle生命周期所包含的phase",children:[]},{level:3,title:"site lificycle生命周期包含的phase",slug:"site-lificycle生命周期包含的phase",link:"#site-lificycle生命周期包含的phase",children:[]}]},{level:2,title:"关于默认绑定的插件",slug:"关于默认绑定的插件",link:"#关于默认绑定的插件",children:[{level:3,title:"packaging类型pom 类型默认绑定插件",slug:"packaging类型pom-类型默认绑定插件",link:"#packaging类型pom-类型默认绑定插件",children:[]},{level:3,title:"packaging类型为jar,绑定的插件",slug:"packaging类型为jar-绑定的插件",link:"#packaging类型为jar-绑定的插件",children:[]}]},{level:2,title:"我对maven生命周期的理解",slug:"我对maven生命周期的理解",link:"#我对maven生命周期的理解",children:[]},{level:2,title:"写在最后",slug:"写在最后",link:"#写在最后",children:[]}],path:"/java/other/maven/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html",pathLocale:"/",extraFields:[]},{title:"",headers:[],path:"/404.html",pathLocale:"/",extraFields:[]},{title:"Other",headers:[],path:"/cpp/other/",pathLocale:"/",extraFields:[]},{title:"Cpp",headers:[],path:"/cpp/",pathLocale:"/",extraFields:[]},{title:"Computerprinciple",headers:[],path:"/other/computerprinciple/",pathLocale:"/",extraFields:[]},{title:"Docker",headers:[],path:"/other/docker/",pathLocale:"/",extraFields:[]},{title:"Hardware",headers:[],path:"/other/hardware/",pathLocale:"/",extraFields:[]},{title:"Software",headers:[],path:"/other/software/",pathLocale:"/",extraFields:[]},{title:"Sono",headers:[],path:"/other/sono/",pathLocale:"/",extraFields:[]},{title:"Windows",headers:[],path:"/other/windows/",pathLocale:"/",extraFields:[]},{title:"Framework",headers:[],path:"/java/framework/",pathLocale:"/",extraFields:[]},{title:"Springcloud",headers:[],path:"/java/framework/springcloud/",pathLocale:"/",extraFields:[]},{title:"Log",headers:[],path:"/java/other/log/",pathLocale:"/",extraFields:[]},{title:"分类",headers:[],path:"/category/",pathLocale:"/",extraFields:[]},{title:"设计模式 分类",headers:[],path:"/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/",pathLocale:"/",extraFields:[]},{title:"使用指南 分类",headers:[],path:"/category/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/",pathLocale:"/",extraFields:[]},{title:"vue知识点 分类",headers:[],path:"/category/vue%E7%9F%A5%E8%AF%86%E7%82%B9/",pathLocale:"/",extraFields:[]},{title:"js基础 分类",headers:[],path:"/category/js%E5%9F%BA%E7%A1%80/",pathLocale:"/",extraFields:[]},{title:"npm知识点 分类",headers:[],path:"/category/npm%E7%9F%A5%E8%AF%86%E7%82%B9/",pathLocale:"/",extraFields:[]},{title:"问题定位 分类",headers:[],path:"/category/%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/",pathLocale:"/",extraFields:[]},{title:"vite 分类",headers:[],path:"/category/vite/",pathLocale:"/",extraFields:[]},{title:"集合 分类",headers:[],path:"/category/%E9%9B%86%E5%90%88/",pathLocale:"/",extraFields:[]},{title:"jdk 分类",headers:[],path:"/category/jdk/",pathLocale:"/",extraFields:[]},{title:"线程池 分类",headers:[],path:"/category/%E7%BA%BF%E7%A8%8B%E6%B1%A0/",pathLocale:"/",extraFields:[]},{title:"多线程 分类",headers:[],path:"/category/%E5%A4%9A%E7%BA%BF%E7%A8%8B/",pathLocale:"/",extraFields:[]},{title:"java 分类",headers:[],path:"/category/java/",pathLocale:"/",extraFields:[]},{title:"对象锁 分类",headers:[],path:"/category/%E5%AF%B9%E8%B1%A1%E9%94%81/",pathLocale:"/",extraFields:[]},{title:"java基础 分类",headers:[],path:"/category/java%E5%9F%BA%E7%A1%80/",pathLocale:"/",extraFields:[]},{title:"jvm 分类",headers:[],path:"/category/jvm/",pathLocale:"/",extraFields:[]},{title:"电子书 分类",headers:[],path:"/category/%E7%94%B5%E5%AD%90%E4%B9%A6/",pathLocale:"/",extraFields:[]},{title:"数据库 分类",headers:[],path:"/category/%E6%95%B0%E6%8D%AE%E5%BA%93/",pathLocale:"/",extraFields:[]},{title:"docker 分类",headers:[],path:"/category/docker/",pathLocale:"/",extraFields:[]},{title:"运维 分类",headers:[],path:"/category/%E8%BF%90%E7%BB%B4/",pathLocale:"/",extraFields:[]},{title:"git 操作 分类",headers:[],path:"/category/git-%E6%93%8D%E4%BD%9C/",pathLocale:"/",extraFields:[]},{title:"必会 分类",headers:[],path:"/category/%E5%BF%85%E4%BC%9A/",pathLocale:"/",extraFields:[]},{title:"linux 分类",headers:[],path:"/category/linux/",pathLocale:"/",extraFields:[]},{title:"markdown 分类",headers:[],path:"/category/markdown/",pathLocale:"/",extraFields:[]},{title:"公司业务 分类",headers:[],path:"/category/%E5%85%AC%E5%8F%B8%E4%B8%9A%E5%8A%A1/",pathLocale:"/",extraFields:[]},{title:"小组分享 分类",headers:[],path:"/category/%E5%B0%8F%E7%BB%84%E5%88%86%E4%BA%AB/",pathLocale:"/",extraFields:[]},{title:"web 分类",headers:[],path:"/category/web/",pathLocale:"/",extraFields:[]},{title:"oauth 分类",headers:[],path:"/category/oauth/",pathLocale:"/",extraFields:[]},{title:"框架 分类",headers:[],path:"/category/%E6%A1%86%E6%9E%B6/",pathLocale:"/",extraFields:[]},{title:"security 分类",headers:[],path:"/category/security/",pathLocale:"/",extraFields:[]},{title:"Security 分类",headers:[],path:"/category/security/",pathLocale:"/",extraFields:[]},{title:"OAuth 分类",headers:[],path:"/category/oauth/",pathLocale:"/",extraFields:[]},{title:"Spring 分类",headers:[],path:"/category/spring/",pathLocale:"/",extraFields:[]},{title:"open-source 分类",headers:[],path:"/category/open-source/",pathLocale:"/",extraFields:[]},{title:"maven 分类",headers:[],path:"/category/maven/",pathLocale:"/",extraFields:[]},{title:"标签",headers:[],path:"/tag/",pathLocale:"/",extraFields:[]},{title:"标签: 禁用",headers:[],path:"/tag/%E7%A6%81%E7%94%A8/",pathLocale:"/",extraFields:[]},{title:"标签: 文章加密",headers:[],path:"/tag/%E6%96%87%E7%AB%A0%E5%8A%A0%E5%AF%86/",pathLocale:"/",extraFields:[]},{title:"标签: Markdown",headers:[],path:"/tag/markdown/",pathLocale:"/",extraFields:[]},{title:"标签: 页面配置",headers:[],path:"/tag/%E9%A1%B5%E9%9D%A2%E9%85%8D%E7%BD%AE/",pathLocale:"/",extraFields:[]},{title:"标签: 使用指南",headers:[],path:"/tag/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/",pathLocale:"/",extraFields:[]},{title:"标签: 你所不了解的JavaScript",headers:[],path:"/tag/%E4%BD%A0%E6%89%80%E4%B8%8D%E4%BA%86%E8%A7%A3%E7%9A%84javascript/",pathLocale:"/",extraFields:[]},{title:"标签: 必会",headers:[],path:"/tag/%E5%BF%85%E4%BC%9A/",pathLocale:"/",extraFields:[]},{title:"标签: vue中的 TypeScript",headers:[],path:"/tag/vue%E4%B8%AD%E7%9A%84-typescript/",pathLocale:"/",extraFields:[]},{title:"标签: vite",headers:[],path:"/tag/vite/",pathLocale:"/",extraFields:[]},{title:"标签: ts",headers:[],path:"/tag/ts/",pathLocale:"/",extraFields:[]},{title:"标签: vue3",headers:[],path:"/tag/vue3/",pathLocale:"/",extraFields:[]},{title:"标签: 集合",headers:[],path:"/tag/%E9%9B%86%E5%90%88/",pathLocale:"/",extraFields:[]},{title:"标签: -- 并发",headers:[],path:"/tag/---%E5%B9%B6%E5%8F%91/",pathLocale:"/",extraFields:[]},{title:"标签: 多线程",headers:[],path:"/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/",pathLocale:"/",extraFields:[]},{title:"标签: 线程池",headers:[],path:"/tag/%E7%BA%BF%E7%A8%8B%E6%B1%A0/",pathLocale:"/",extraFields:[]},{title:"标签: oauth",headers:[],path:"/tag/oauth/",pathLocale:"/",extraFields:[]},{title:"标签: sso",headers:[],path:"/tag/sso/",pathLocale:"/",extraFields:[]},{title:"标签: 部署搭建",headers:[],path:"/tag/%E9%83%A8%E7%BD%B2%E6%90%AD%E5%BB%BA/",pathLocale:"/",extraFields:[]},{title:"标签: 字节码",headers:[],path:"/tag/%E5%AD%97%E8%8A%82%E7%A0%81/",pathLocale:"/",extraFields:[]},{title:"标签: 反汇编",headers:[],path:"/tag/%E5%8F%8D%E6%B1%87%E7%BC%96/",pathLocale:"/",extraFields:[]},{title:"标签: mysql",headers:[],path:"/tag/mysql/",pathLocale:"/",extraFields:[]},{title:"标签: 数据库",headers:[],path:"/tag/%E6%95%B0%E6%8D%AE%E5%BA%93/",pathLocale:"/",extraFields:[]},{title:"标签: 数据结构",headers:[],path:"/tag/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/",pathLocale:"/",extraFields:[]},{title:"标签: 二叉树",headers:[],path:"/tag/%E4%BA%8C%E5%8F%89%E6%A0%91/",pathLocale:"/",extraFields:[]},{title:"标签: 运维",headers:[],path:"/tag/%E8%BF%90%E7%BB%B4/",pathLocale:"/",extraFields:[]},{title:"标签: 工具使用",headers:[],path:"/tag/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/",pathLocale:"/",extraFields:[]},{title:"标签: linux",headers:[],path:"/tag/linux/",pathLocale:"/",extraFields:[]},{title:"标签: markdown",headers:[],path:"/tag/markdown/",pathLocale:"/",extraFields:[]},{title:"标签: web",headers:[],path:"/tag/web/",pathLocale:"/",extraFields:[]},{title:"标签: springboot",headers:[],path:"/tag/springboot/",pathLocale:"/",extraFields:[]},{title:"标签: 框架",headers:[],path:"/tag/%E6%A1%86%E6%9E%B6/",pathLocale:"/",extraFields:[]},{title:"标签: 日志",headers:[],path:"/tag/%E6%97%A5%E5%BF%97/",pathLocale:"/",extraFields:[]},{title:"文章",headers:[],path:"/article/",pathLocale:"/",extraFields:[]},{title:"星标",headers:[],path:"/star/",pathLocale:"/",extraFields:[]},{title:"时间轴",headers:[],path:"/timeline/",pathLocale:"/",extraFields:[]}],j1=K(P1),D1=()=>j1,R1=({searchIndex:n,routeLocale:e,query:t,maxSuggestions:a})=>{const s=y(()=>n.value.filter(l=>l.pathLocale===e.value));return y(()=>{const l=t.value.trim().toLowerCase();if(!l)return[];const o=[],i=(c,r)=>{_p(l,[r.title])&&o.push({link:`${c.path}#${r.slug}`,title:c.title,header:r.title});for(const u of r.children){if(o.length>=a.value)return;i(c,u)}};for(const c of s.value){if(o.length>=a.value)break;if(_p(l,[c.title,...c.extraFields])){o.push({link:c.path,title:c.title});continue}for(const r of c.headers){if(o.length>=a.value)break;i(c,r)}}return o})},F1=n=>{const e=K(0);return{focusIndex:e,focusNext:()=>{e.value{e.value>0?e.value-=1:e.value=n.value.length-1}}},B1=V({name:"SearchBox",props:{locales:{type:Object,required:!1,default:()=>({})},hotKeys:{type:Array,required:!1,default:()=>[]},maxSuggestions:{type:Number,required:!1,default:5}},setup(n){const{locales:e,hotKeys:t,maxSuggestions:a}=Su(n),s=me(),l=fe(),o=D1(),i=K(null),c=K(!1),r=K(""),u=y(()=>e.value[l.value]??{}),d=R1({searchIndex:o,routeLocale:l,query:r,maxSuggestions:a}),{focusIndex:k,focusNext:g,focusPrev:_}=F1(d);I1({input:i,hotKeys:t});const b=y(()=>c.value&&!!d.value.length),E=()=>{b.value&&_()},w=()=>{b.value&&g()},L=f=>{if(!b.value)return;const S=d.value[f];S&&s.push(S.link).then(()=>{r.value="",k.value=0})};return()=>p("form",{class:"search-box",role:"search"},[p("input",{ref:i,type:"search",placeholder:u.value.placeholder,autocomplete:"off",spellcheck:!1,value:r.value,onFocus:()=>c.value=!0,onBlur:()=>c.value=!1,onInput:f=>r.value=f.target.value,onKeydown:f=>{switch(f.key){case"ArrowUp":{E();break}case"ArrowDown":{w();break}case"Enter":{f.preventDefault(),L(k.value);break}}}}),b.value&&p("ul",{class:"suggestions",onMouseleave:()=>k.value=-1},d.value.map(({link:f,title:S,header:F},A)=>p("li",{class:["suggestion",{focus:k.value===A}],onMouseenter:()=>k.value=A,onMousedown:()=>L(A)},p("a",{href:f,onClick:H=>H.preventDefault()},[p("span",{class:"page-title"},S),F&&p("span",{class:"page-header"},`> ${F}`)]))))])}});var M1=["s","/"],V1={"/":{placeholder:"Search"},"/zh/":{placeholder:"搜索"}};const q1=V1,N1=M1,H1=5,$1=ae({enhance({app:n}){n.component("SearchBox",e=>p(B1,{locales:q1,hotKeys:N1,maxSuggestions:H1,...e}))}}),un=({name:n="",color:e="currentColor"},{slots:t})=>{var a;return p("svg",{xmlns:"http://www.w3.org/2000/svg",class:["icon",`${n}-icon`],viewBox:"0 0 1024 1024",fill:e,"aria-label":`${n} icon`},(a=t.default)==null?void 0:a.call(t))};un.displayName="IconBase";const yc=(n,{slots:e})=>{var t;return(t=e.default)==null?void 0:t.call(e)},bc=()=>p(un,{name:"github"},()=>p("path",{d:"M511.957 21.333C241.024 21.333 21.333 240.981 21.333 512c0 216.832 140.544 400.725 335.574 465.664 24.49 4.395 32.256-10.07 32.256-23.083 0-11.69.256-44.245 0-85.205-136.448 29.61-164.736-64.64-164.736-64.64-22.315-56.704-54.4-71.765-54.4-71.765-44.587-30.464 3.285-29.824 3.285-29.824 49.195 3.413 75.179 50.517 75.179 50.517 43.776 75.008 114.816 53.333 142.762 40.79 4.523-31.66 17.152-53.377 31.19-65.537-108.971-12.458-223.488-54.485-223.488-242.602 0-53.547 19.114-97.323 50.517-131.67-5.035-12.33-21.93-62.293 4.779-129.834 0 0 41.258-13.184 134.912 50.346a469.803 469.803 0 0 1 122.88-16.554c41.642.213 83.626 5.632 122.88 16.554 93.653-63.488 134.784-50.346 134.784-50.346 26.752 67.541 9.898 117.504 4.864 129.834 31.402 34.347 50.474 78.123 50.474 131.67 0 188.586-114.73 230.016-224.042 242.09 17.578 15.232 33.578 44.672 33.578 90.454v135.85c0 13.142 7.936 27.606 32.854 22.87C862.25 912.597 1002.667 728.747 1002.667 512c0-271.019-219.648-490.667-490.71-490.667z"}));bc.displayName="GitHubIcon";const wc=()=>p(un,{name:"gitlab"},()=>p("path",{d:"M229.333 78.688C223.52 62 199.895 62 193.895 78.688L87.958 406.438h247.5c-.188 0-106.125-327.75-106.125-327.75zM33.77 571.438c-4.875 15 .563 31.687 13.313 41.25l464.812 345L87.77 406.438zm301.5-165 176.813 551.25 176.812-551.25zm655.125 165-54-165-424.312 551.25 464.812-345c12.938-9.563 18.188-26.25 13.5-41.25zM830.27 78.688c-5.812-16.688-29.437-16.688-35.437 0l-106.125 327.75h247.5z"}));wc.displayName="GitLabIcon";const Ec=()=>p(un,{name:"gitee"},()=>p("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm242.97-533.34H482.39a23.7 23.7 0 0 0-23.7 23.7l-.03 59.28c0 13.08 10.59 23.7 23.7 23.7h165.96a23.7 23.7 0 0 1 23.7 23.7v11.85a71.1 71.1 0 0 1-71.1 71.1H375.71a23.7 23.7 0 0 1-23.7-23.7V423.11a71.1 71.1 0 0 1 71.1-71.1h331.8a23.7 23.7 0 0 0 23.7-23.7l.06-59.25a23.73 23.73 0 0 0-23.7-23.73H423.11a177.78 177.78 0 0 0-177.78 177.75v331.83c0 13.08 10.62 23.7 23.7 23.7h349.62a159.99 159.99 0 0 0 159.99-159.99V482.33a23.7 23.7 0 0 0-23.7-23.7z"}));Ec.displayName="GiteeIcon";const xc=()=>p(un,{name:"bitbucket"},()=>p("path",{d:"M575.256 490.862c6.29 47.981-52.005 85.723-92.563 61.147-45.714-20.004-45.714-92.562-1.133-113.152 38.29-23.442 93.696 7.424 93.696 52.005zm63.451-11.996c-10.276-81.152-102.29-134.839-177.152-101.156-47.433 21.138-79.433 71.424-77.129 124.562 2.853 69.705 69.157 126.866 138.862 120.576S647.3 548.571 638.708 478.83zm136.558-309.723c-25.161-33.134-67.986-38.839-105.728-45.13-106.862-17.151-216.576-17.7-323.438 1.134-35.438 5.706-75.447 11.996-97.719 43.996 36.572 34.304 88.576 39.424 135.424 45.129 84.553 10.862 171.447 11.447 256 .585 47.433-5.705 99.987-10.276 135.424-45.714zm32.585 591.433c-16.018 55.99-6.839 131.438-66.304 163.986-102.29 56.576-226.304 62.867-338.87 42.862-59.43-10.862-129.135-29.696-161.72-85.723-14.3-54.858-23.442-110.848-32.585-166.84l3.438-9.142 10.276-5.157c170.277 112.567 408.576 112.567 579.438 0 26.844 8.01 6.84 40.558 6.29 60.014zm103.424-549.157c-19.42 125.148-41.728 249.71-63.415 374.272-6.29 36.572-41.728 57.162-71.424 72.558-106.862 53.724-231.424 62.866-348.562 50.286-79.433-8.558-160.585-29.696-225.134-79.433-30.28-23.443-30.28-63.415-35.986-97.134-20.005-117.138-42.862-234.277-57.161-352.585 6.839-51.42 64.585-73.728 107.447-89.71 57.16-21.138 118.272-30.866 178.87-36.571 129.134-12.58 261.157-8.01 386.304 28.562 44.581 13.13 92.563 31.415 122.844 69.705 13.714 17.7 9.143 40.01 6.29 60.014z"}));xc.displayName="BitbucketIcon";const Ac=()=>p(un,{name:"source"},()=>p("path",{d:"M601.92 475.2c0 76.428-8.91 83.754-28.512 99.594-14.652 11.88-43.956 14.058-78.012 16.434-18.81 1.386-40.392 2.97-62.172 6.534-18.612 2.97-36.432 9.306-53.064 17.424V299.772c37.818-21.978 63.36-62.766 63.36-109.692 0-69.894-56.826-126.72-126.72-126.72S190.08 120.186 190.08 190.08c0 46.926 25.542 87.714 63.36 109.692v414.216c-37.818 21.978-63.36 62.766-63.36 109.692 0 69.894 56.826 126.72 126.72 126.72s126.72-56.826 126.72-126.72c0-31.086-11.286-59.598-29.7-81.576 13.266-9.504 27.522-17.226 39.996-19.206 16.038-2.574 32.868-3.762 50.688-5.148 48.312-3.366 103.158-7.326 148.896-44.55 61.182-49.698 74.25-103.158 75.24-187.902V475.2h-126.72zM316.8 126.72c34.848 0 63.36 28.512 63.36 63.36s-28.512 63.36-63.36 63.36-63.36-28.512-63.36-63.36 28.512-63.36 63.36-63.36zm0 760.32c-34.848 0-63.36-28.512-63.36-63.36s28.512-63.36 63.36-63.36 63.36 28.512 63.36 63.36-28.512 63.36-63.36 63.36zM823.68 158.4h-95.04V63.36h-126.72v95.04h-95.04v126.72h95.04v95.04h126.72v-95.04h95.04z"}));Ac.displayName="SourceIcon";const ks=(n,e)=>{let t=1;for(let a=0;a>6;return t+=t<<3,t^=t>>11,t%e};let z1=class{constructor(){this.messageElements={};const e="message-container",t=document.getElementById(e);t?this.containerElement=t:(this.containerElement=document.createElement("div"),this.containerElement.id=e,document.body.appendChild(this.containerElement))}pop(e,t=2e3){const a=document.createElement("div"),s=Date.now();return a.className="message move-in",a.innerHTML=e,this.containerElement.appendChild(a),this.messageElements[s]=a,t>0&&setTimeout(()=>{this.close(s)},t),s}close(e){if(e){const t=this.messageElements[e];t.classList.remove("move-in"),t.classList.add("move-out"),t.addEventListener("animationend",()=>{t.remove(),delete this.messageElements[e]})}else re(this.messageElements).forEach(t=>this.close(Number(t)))}destroy(){document.body.removeChild(this.containerElement)}};const Lc=/#.*$/u,U1=n=>{const e=Lc.exec(n);return e?e[0]:""},yp=n=>decodeURI(n).replace(Lc,"").replace(/\/index\.html$/iu,"/").replace(/\.html$/iu,"").replace(/(README|index)?\.md$/iu,""),Sc=(n,e)=>{if(!Jk(e))return!1;const t=yp(n.path),a=yp(e),s=U1(e);return s?s===n.hash&&(!a||t===a):t===a},J1=n=>Je(n)?n:`https://github.com/${n}`,Cc=n=>!Je(n)||/github\.com/.test(n)?"GitHub":/bitbucket\.org/.test(n)?"Bitbucket":/gitlab\.com/.test(n)?"GitLab":/gitee\.com/.test(n)?"Gitee":null;var G1=n=>Object.prototype.toString.call(n)==="[object Object]",aa=n=>typeof n=="string";const Tc=Array.isArray,bp=n=>G1(n)&&aa(n.name),sa=(n,e=!1)=>n?Tc(n)?n.map(t=>aa(t)?{name:t}:bp(t)?t:null).filter(t=>t!==null):aa(n)?[{name:n}]:bp(n)?[n]:(console.error(`Expect "author" to be \`AuthorInfo[] | AuthorInfo | string[] | string ${e?"":"| false"} | undefined\`, but got`,n),[]):[],Oc=(n,e)=>{if(n){if(Tc(n)&&n.every(aa))return n;if(aa(n))return[n];console.error(`Expect ${e||"value"} to be \`string[] | string | undefined\`, but got`,n)}return[]},Ic=n=>Oc(n,"category"),Pc=n=>Oc(n,"tag"),jc=()=>{const n=gn();return y(()=>n.value.readingTime??null)},Dc=(n,e)=>{const{minutes:t,words:a}=n,{less1Minute:s,word:l,time:o}=e;return{time:t<1?s:o.replace("$time",Math.round(t).toString()),words:l.replace("$word",a.toString())}};var wp={"/":{word:"约 $word 字",less1Minute:"小于 1 分钟",time:"大约 $time 分钟"}};const Ep={words:"",time:""},al=typeof wp>"u"?null:wp,Rc=()=>al?fa(al):y(()=>null),W1=()=>{if(typeof al>"u")return y(()=>Ep);const n=jc(),e=Rc();return y(()=>n.value&&e.value?Dc(n.value,e.value):Ep)},Se=()=>mc(),ln=()=>Y2(),it=()=>{const n=Se();return y(()=>!!n.value.pure)},Bs=()=>null,K1="719px",X1="1440px",Y1="false",zl={mobileBreakPoint:K1,pcBreakPoint:X1,enableThemeColor:Y1},Ul={"/java/base/":["","ConstantPool","IntegerConstantPool","String","Serialization","CustomLRU"],"/java/advance/":["","IO-Model","io","IO-model1","Collection","MysqlMasterSlave","Synchronized","ThreadLocal","Arthas","CompileJdk11","Future","ImplementSameInterface","Concurrent","ProxyInJava","ThreadPool","NativeMethod","ParentDelegationClassLoader"],"/java/jvm/":["","volatile","MemoryModel","名词解释","NewObject","ObjectReference","SetObjectNull","ClassLoader","StringAdd"],"/java/framework/":[{text:"Mybatis",prefix:"mybatis/",collapsible:!0,children:["","MybatisPlusDataSource","mybatis"]},{text:"Security学习",prefix:"security/",collapsible:!0,children:["","DelegatingFilterProxy","OncePerRequestFilter","PreAuthorize","OAuth2Authentication","Session","note","CustomAuthenticationProvider","SecurityFilterChain","spring-security-oauth2-authorization-server","SSO","CustomTokenAuthentication","Authorization","CustomLoginPage"]},{text:"Spring",prefix:"spring/",collapsible:!0,children:["","BeanPostProcessor","OncePerRequestFilter","SpringIOC","SpringAOP","SpringSourceAnalize","SpringMVC","DesignPatternInSpring","CircularDependency","SpringExtensionPoint","SpringCache","Annotation","Validator"]},{text:"SpringBoot",prefix:"springboot/",collapsible:!0,children:["","Http2","SpringBootAutoConfiguration","部署","Swagger","AOPLog","CollectionInject"]},{text:"Springcloud",prefix:"springcloud/",collapsible:!0,children:["SpringCloudGateway"]}],"/java/other/maven/":["","build标签","import","生命周期","problem","multiModule"],"/java/other/gradle/":["","wrapper"],"/java/other/locateproblem/":["","TooManyOpenFiles","Undertow.xxxNotFount","各种框架版本的坑","各种问题"],"/frontweb/vue/":["","elementui表单自定义校验","vue-router1","vue-router2","vue3Emit","vue-authority","vue3LifeTime","vue-pic","vue-Direactive","eventBus","自定义指令的实践","vue-nextTick","vue-in-action","vueExtend"],"/frontweb/es5/":["","aboutEvent","aboutThis","asyncError","crossDomain","crossDomain2","lazyLoad","aboutAsync","throttle"],"/frontweb/es6/":["","js中整数的最大值","useYarn","promise","useModule","useNpm","usePnpm"],"/frontweb/typeScript/":["","axios","fanType","tsAndvue3","basic-usage","action-usage"],"/frontweb/nodejs/":["readme","安装问题"],"/designpattern/":["","建造者模式","责任链模式","装饰模式","策略模式","模板方法模式"],"/cpp/study/":[""],"/cpp/other/":["1","2"],"/myserver/":["","Jellyfin搭建","x86_openwrt","旁路由网关","网络设置","自建nas"],"/other/git/":["","mergeBranch","rebaseAndMerge","gitRebase","reset","stash","branch01","proxy","fatal","gitConflict","branch02","GitCommands","gitwork","BatchDeleteGitHubRepo"],"/other/linux/":["","Curl","InstallMysqlWithDocker","Wifi","firewall","ShareBetweenWindowsAndLinux","Manjaro","CentOS","TcpDump","wsl","截图","MultiNetworkCard","CommonUsedCMD","MountDisk","Samba"],"/other/database/":["","CPUOverLoad","MysqlCollate","MysqlRemoteConnect","MysqlNote","数据库备份","SQLOptimization","Recurse"],"/other/markdown/":[""],"/other/tools/":["","Idea","VsCode","SoftWare"],"/other/training/":["","SSO","CloudServiceTraining","draw"],"/other/essay/":["","2022-04-12","CDN","ChromeDevTools","elasticSearch操作","elk部署","IM即时通信技术选型","Jenkins","windows下服务注册","BTree","CloudService","im即时通信的需求","TyporaPicgo","DeployGithubPage","操作系统引导"],"/other/web/":["","Cookie","Http","Restful","Jwt","OAUTH_LOGIN","RefreshToken","BuildWebProject"],"/other/distributeservice/":["","Nacos","DistributeLock"],"/other/oauth2/":["","01","02","03","04","05","06","07","08","09","10","11","12","13","14"],"/other/docker/":["Docker","ServiceInstall"],"/other/pve/":["","ddns申请证书","firewall"]},Fc=n=>{const{icon:e="",color:t,size:a}=n,s=t||a?{}:null;return t&&(s.color=t),a&&(s.height=Number.isNaN(Number(a))?a:`${a}px`),Je(e)?p("img",{class:"icon",src:e,alt:"","no-view":"",style:s}):us(e)?p("img",{class:"icon",src:xn(e),alt:"","aria-hidden":"","no-view":"",style:s}):p(ee("FontIcon"),n)};Fc.displayName="HopeIcon";var Vn=Fc;const ba=()=>{const n=me(),e=Ge();return t=>{if(t)if(us(t))e.path!==t&&n.push(t);else if(Mi(t))window&&window.open(t);else{const a=e.path.slice(0,e.path.lastIndexOf("/"));n.push(`${a}/${encodeURI(t)}`)}}},Bc=()=>{const n=ln(),e=fn();return y(()=>{const{author:t}=e.value;return t?sa(t):t===!1?[]:sa(n.value.author,!1)})},Q1=()=>{const n=fn(),e=wn(Symbol.for("categoryMap"));return y(()=>Ic(n.value.category).map(t=>{var a;return{name:t,path:((a=e==null?void 0:e.value.map[t])==null?void 0:a.path)||""}}))},Z1=()=>{const n=fn(),e=wn(Symbol.for("tagMap"));return y(()=>Pc(n.value.tag).map(t=>{var a;return{name:t,path:((a=e==null?void 0:e.value.map[t])==null?void 0:a.path)||""}}))},n0=()=>{const n=fn(),e=gn();return y(()=>{const t=ql(n.value.date);if(t)return t;const{createdTime:a}=e.value.git||{};return a?new Date(a):null})},e0=()=>{const n=ln(),e=gn(),t=fn(),a=Bc(),s=Q1(),l=Z1(),o=n0(),i=jc(),c=W1(),r=y(()=>({author:a.value,category:s.value,date:o.value,localizedDate:e.value.localizedDate,tag:l.value,isOriginal:t.value.isOriginal||!1,readingTime:i.value,readingTimeLocale:c.value,pageview:"pageview"in t.value?t.value.pageview:!0})),u=y(()=>"pageInfo"in t.value?t.value.pageInfo:"pageInfo"in n.value?n.value.pageInfo:null);return{info:r,items:u}},{mobileBreakPoint:t0,pcBreakPoint:a0}=zl,xp=n=>n.endsWith("px")?Number(n.slice(0,-2)):null,wa=()=>{const n=K(!1),e=K(!1),t=()=>{n.value=window.innerWidth<=(xp(t0)??719),e.value=window.innerWidth>=(xp(a0)??1440)};return En(()=>{t(),Fn("resize",t,!1),Fn("orientationchange",t,!1)}),{isMobile:n,isPC:e}},Mc=Symbol(""),Ea=()=>{const n=wn(Mc);if(!n)throw new Error("useDarkmode() is called without provider.");return n},s0=n=>{const e=Se(),t=m2(),a=y(()=>e.value.darkmode||"switch"),s=hs("vuepress-theme-hope-scheme","auto"),l=y(()=>{const i=a.value;return i==="disable"?!1:i==="enable"?!0:i==="auto"?t.value:i==="toggle"?s.value==="dark":s.value==="dark"||s.value==="auto"&&t.value}),o=y(()=>{const i=a.value;return i==="switch"||i==="toggle"});n.provide(Mc,{canToggle:o,config:a,isDarkmode:l,status:s}),Object.defineProperties(n.config.globalProperties,{$isDarkmode:{get:()=>l.value}})},l0=()=>{const{config:n,isDarkmode:e,status:t}=Ea();ii(()=>{n.value==="disable"?t.value="light":n.value==="enable"?t.value="dark":n.value==="toggle"&&t.value==="auto"&&(t.value="light")}),En(()=>{dn(e,a=>document.documentElement.setAttribute("data-theme",a?"dark":"light"),{immediate:!0})})};var vn=(n=>(n.type="y",n.title="t",n.shortTitle="s",n.icon="i",n.author="a",n.date="d",n.localizedDate="l",n.category="c",n.tag="g",n.isEncrypted="n",n.isOriginal="o",n.readingTime="r",n.excerpt="e",n.sticky="u",n.cover="v",n.index="I",n.order="O",n))(vn||{}),Vc=(n=>(n.article="a",n.home="h",n.slide="s",n.page="p",n))(Vc||{});const nt=(n="",e="")=>us(e)?e:`${Bi(n)}${e}`,yt=(n,e=!1)=>{const{meta:t,path:a,notFound:s}=It(n);return s?{text:a,link:a}:{text:!e&&t[vn.shortTitle]?t[vn.shortTitle]:t[vn.title]||a,link:a,...t[vn.icon]?{icon:t[vn.icon]}:{}}},sl=({config:n,prefix:e=""})=>{const t=(a,s=e)=>{const l=An(a)?yt(nt(s,a)):a.link?{...a,...Zt(a.link)?{}:{link:yt(nt(s,a.link)).link}}:a;if("children"in l){const o=nt(s,l.prefix),i=l.children==="structure"?Ul[o]:l.children;return{type:"group",...l,prefix:o,children:i.map(c=>t(c,o))}}return{type:"page",...l}};return n.map(a=>t(a))},o0=({config:n,page:e,headerDepth:t})=>{const a=re(n).sort((s,l)=>l.length-s.length);for(const s of a)if(St(decodeURI(e.path),s)){const l=n[s];return l?sl({config:l==="structure"?Ul[s]:l,page:e,headerDepth:t,prefix:s}):[]}return console.warn(`${e.path} is missing sidebar config.`),[]},p0=({config:n,routeLocale:e,page:t,headerDepth:a})=>n==="structure"?sl({config:Ul[e],page:t,headerDepth:a,prefix:e}):el(n)?sl({config:n,page:t,headerDepth:a}):ma(n)?o0({config:n,page:t,headerDepth:a}):[],qc=Symbol(""),i0=()=>{const n=fn(),e=ln(),t=gn(),a=fe(),s=y(()=>n.value.home?!1:n.value.sidebar??e.value.sidebar??"structure"),l=y(()=>n.value.headerDepth??e.value.headerDepth??2),o=ac(()=>[s.value,l.value,t.value.path,null],()=>p0({config:s.value,routeLocale:a.value,page:t.value,headerDepth:l.value}));ie(qc,o)},Jl=()=>{const n=wn(qc);if(!n)throw new Error("useSidebarItems() is called without provider.");return n};var c0=V({name:"PageFooter",setup(){const n=Se(),e=ln(),t=fn(),a=Bc(),s=y(()=>{const{copyright:r,footer:u}=t.value;return u!==!1&&!!(r||u||e.value.displayFooter)}),l=y(()=>{const{footer:r}=t.value;return r===!1?!1:An(r)?r:e.value.footer||""}),o=y(()=>a.value.map(({name:r})=>r).join(", ")),i=r=>`Copyright © ${new Date().getFullYear()} ${o.value} ${r?`${r} Licensed`:""}`,c=y(()=>{const{copyright:r,license:u=""}=t.value,{license:d}=n.value,{copyright:k}=e.value;return r??(u?i(u):An(k)?k:o.value||d?i(d):!1)});return()=>s.value?p("footer",{class:"vp-footer-wrapper"},[l.value?p("div",{class:"vp-footer",innerHTML:l.value}):null,c.value?p("div",{class:"vp-copyright",innerHTML:c.value}):null]):null}}),Un=V({name:"AutoLink",inheritAttrs:!1,props:{config:{type:Object,required:!0},exact:Boolean,noExternalLinkIcon:Boolean},emits:["focusout"],slots:Object,setup(n,{attrs:e,emit:t,slots:a}){const s=Ge(),l=Qi(),o=Ot(n,"config"),i=y(()=>Je(o.value.link)),c=y(()=>!i.value&&Mi(o.value.link)),r=y(()=>o.value.target||(i.value?"_blank":void 0)),u=y(()=>r.value==="_blank"),d=y(()=>!i.value&&!c.value&&!u.value),k=y(()=>o.value.rel||(u.value?"noopener noreferrer":null)),g=y(()=>o.value.ariaLabel||o.value.text),_=y(()=>{if(n.exact)return!1;const E=re(l.value.locales);return E.length?E.every(w=>w!==o.value.link):o.value.link!=="/"}),b=y(()=>d.value?o.value.activeMatch?new RegExp(o.value.activeMatch,"u").test(s.path):_.value?St(s.path,o.value.link):s.path===o.value.link:!1);return()=>{const{before:E,after:w,default:L}=a,{text:f,icon:S,link:F}=o.value;return d.value?p(Tn,{to:F,"aria-label":g.value,...e,class:["nav-link",{active:b.value},e.class],onFocusout:()=>t("focusout")},()=>L?L():[E?E():p(Vn,{icon:S}),f,w==null?void 0:w()]):p("a",{href:F,rel:k.value,target:r.value,"aria-label":g.value,...e,class:["nav-link",e.class],onFocusout:()=>t("focusout")},L?L():[E?E():p(Vn,{icon:S}),f,n.noExternalLinkIcon?null:p(kc),w==null?void 0:w()])}}}),r0=V({name:"NavbarDropdownLink",props:{config:{type:Object,required:!0}},slots:Object,setup(n,{slots:e}){const t=gn(),a=Ot(n,"config"),s=y(()=>a.value.ariaLabel||a.value.text),l=K(!1);dn(()=>t.value.path,()=>{l.value=!1});const o=i=>{i.detail===0&&(l.value=!l.value)};return()=>{var i;return p("div",{class:["dropdown-wrapper",{open:l.value}]},[p("button",{type:"button",class:"dropdown-title","aria-label":s.value,onClick:o},[((i=e.title)==null?void 0:i.call(e))||p("span",{class:"title"},[p(Vn,{icon:a.value.icon}),n.config.text]),p("span",{class:"arrow"}),p("ul",{class:"nav-dropdown"},a.value.children.map((c,r)=>{const u=r===a.value.children.length-1;return p("li",{class:"dropdown-item"},"children"in c?[p("h4",{class:"dropdown-subtitle"},c.link?p(Un,{config:c,onFocusout:()=>{c.children.length===0&&u&&(l.value=!1)}}):p("span",c.text)),p("ul",{class:"dropdown-subitem-wrapper"},c.children.map((d,k)=>p("li",{class:"dropdown-subitem"},p(Un,{config:d,onFocusout:()=>{k===c.children.length-1&&u&&(l.value=!1)}}))))]:p(Un,{config:c,onFocusout:()=>{u&&(l.value=!1)}}))}))])])}}});const Nc=(n,e="")=>An(n)?yt(nt(e,n)):"children"in n?{...n,...n.link&&!Zt(n.link)?yt(nt(e,n.link)):{},children:n.children.map(t=>Nc(t,nt(e,n.prefix)))}:{...n,link:Zt(n.link)?n.link:yt(nt(e,n.link)).link},Hc=()=>{const n=ln(),e=()=>(n.value.navbar||[]).map(t=>Nc(t));return ac(()=>n.value.navbar,()=>e())},u0=()=>{const n=ln(),e=y(()=>n.value.repo||null),t=y(()=>e.value?J1(e.value):null),a=y(()=>e.value?Cc(e.value):null),s=y(()=>t.value?n.value.repoLabel??(a.value===null?"Source":a.value):null);return y(()=>!t.value||!s.value||n.value.repoDisplay===!1?null:{type:a.value||"Source",label:s.value,link:t.value})};var d0=V({name:"NavScreenDropdown",props:{config:{type:Object,required:!0}},setup(n){const e=gn(),t=Ot(n,"config"),a=y(()=>t.value.ariaLabel||t.value.text),s=K(!1);dn(()=>e.value.path,()=>{s.value=!1});const l=(o,i)=>i[i.length-1]===o;return()=>[p("button",{type:"button",class:["nav-screen-dropdown-title",{active:s.value}],"aria-label":a.value,onClick:()=>{s.value=!s.value}},[p("span",{class:"title"},[p(Vn,{icon:t.value.icon}),n.config.text]),p("span",{class:["arrow",s.value?"down":"end"]})]),p("ul",{class:["nav-screen-dropdown",{hide:!s.value}]},t.value.children.map(o=>p("li",{class:"dropdown-item"},"children"in o?[p("h4",{class:"dropdown-subtitle"},o.link?p(Un,{config:o,onFocusout:()=>{l(o,t.value.children)&&o.children.length===0&&(s.value=!1)}}):p("span",o.text)),p("ul",{class:"dropdown-subitem-wrapper"},o.children.map(i=>p("li",{class:"dropdown-subitem"},p(Un,{config:i,onFocusout:()=>{l(i,o.children)&&l(o,t.value.children)&&(s.value=!1)}}))))]:p(Un,{config:o,onFocusout:()=>{l(o,t.value.children)&&(s.value=!1)}}))))]}}),h0=V({name:"NavScreenLinks",setup(){const n=Hc();return()=>n.value.length?p("nav",{class:"nav-screen-links"},n.value.map(e=>p("div",{class:"navbar-links-item"},"children"in e?p(d0,{config:e}):p(Un,{config:e})))):null}});const $c=()=>p(un,{name:"dark"},()=>p("path",{d:"M524.8 938.667h-4.267a439.893 439.893 0 0 1-313.173-134.4 446.293 446.293 0 0 1-11.093-597.334A432.213 432.213 0 0 1 366.933 90.027a42.667 42.667 0 0 1 45.227 9.386 42.667 42.667 0 0 1 10.24 42.667 358.4 358.4 0 0 0 82.773 375.893 361.387 361.387 0 0 0 376.747 82.774 42.667 42.667 0 0 1 54.187 55.04 433.493 433.493 0 0 1-99.84 154.88 438.613 438.613 0 0 1-311.467 128z"}));$c.displayName="DarkIcon";const zc=()=>p(un,{name:"light"},()=>p("path",{d:"M952 552h-80a40 40 0 0 1 0-80h80a40 40 0 0 1 0 80zM801.88 280.08a41 41 0 0 1-57.96-57.96l57.96-58a41.04 41.04 0 0 1 58 58l-58 57.96zM512 752a240 240 0 1 1 0-480 240 240 0 0 1 0 480zm0-560a40 40 0 0 1-40-40V72a40 40 0 0 1 80 0v80a40 40 0 0 1-40 40zm-289.88 88.08-58-57.96a41.04 41.04 0 0 1 58-58l57.96 58a41 41 0 0 1-57.96 57.96zM192 512a40 40 0 0 1-40 40H72a40 40 0 0 1 0-80h80a40 40 0 0 1 40 40zm30.12 231.92a41 41 0 0 1 57.96 57.96l-57.96 58a41.04 41.04 0 0 1-58-58l58-57.96zM512 832a40 40 0 0 1 40 40v80a40 40 0 0 1-80 0v-80a40 40 0 0 1 40-40zm289.88-88.08 58 57.96a41.04 41.04 0 0 1-58 58l-57.96-58a41 41 0 0 1 57.96-57.96z"}));zc.displayName="LightIcon";const Uc=()=>p(un,{name:"auto"},()=>p("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm0-840c-198.78 0-360 161.22-360 360 0 198.84 161.22 360 360 360s360-161.16 360-360c0-198.78-161.22-360-360-360zm0 660V212c165.72 0 300 134.34 300 300 0 165.72-134.28 300-300 300z"}));Uc.displayName="AutoIcon";const Jc=()=>p(un,{name:"enter-fullscreen"},()=>p("path",{d:"M762.773 90.24h-497.28c-96.106 0-174.4 78.293-174.4 174.4v497.28c0 96.107 78.294 174.4 174.4 174.4h497.28c96.107 0 175.04-78.293 174.4-174.4V264.64c0-96.213-78.186-174.4-174.4-174.4zm-387.2 761.173H215.04c-21.867 0-40.427-17.92-41.067-41.066V649.92c0-22.507 17.92-40.427 40.427-40.427 11.307 0 21.227 4.694 28.48 11.947 7.253 7.253 11.947 17.92 11.947 28.48v62.293l145.28-145.28c15.893-15.893 41.813-15.893 57.706 0 15.894 15.894 15.894 41.814 0 57.707l-145.28 145.28h62.294c22.506 0 40.426 17.92 40.426 40.427s-17.173 41.066-39.68 41.066zM650.24 165.76h160.427c21.866 0 40.426 17.92 41.066 41.067v160.426c0 22.507-17.92 40.427-40.426 40.427-11.307 0-21.227-4.693-28.48-11.947-7.254-7.253-11.947-17.92-11.947-28.48v-62.186L625.6 450.347c-15.893 15.893-41.813 15.893-57.707 0-15.893-15.894-15.893-41.814 0-57.707l145.28-145.28H650.88c-22.507 0-40.427-17.92-40.427-40.427s17.174-41.173 39.787-41.173z"}));Jc.displayName="EnterFullScreenIcon";const Gc=()=>p(un,{name:"cancel-fullscreen"},()=>p("path",{d:"M778.468 78.62H247.922c-102.514 0-186.027 83.513-186.027 186.027V795.08c0 102.514 83.513 186.027 186.027 186.027h530.432c102.514 0 186.71-83.513 186.026-186.027V264.647C964.494 162.02 880.981 78.62 778.468 78.62zM250.88 574.35h171.122c23.324 0 43.122 19.115 43.804 43.805v171.121c0 24.008-19.114 43.122-43.122 43.122-12.06 0-22.641-5.006-30.378-12.743s-12.743-19.115-12.743-30.379V722.83L224.597 877.91c-16.953 16.952-44.6 16.952-61.553 0-16.953-16.954-16.953-44.602 0-61.554L318.009 661.39h-66.446c-24.007 0-43.122-19.114-43.122-43.122 0-24.12 18.432-43.918 42.439-43.918zm521.899-98.873H601.657c-23.325 0-43.122-19.114-43.805-43.804V260.55c0-24.007 19.115-43.122 43.122-43.122 12.06 0 22.642 5.007 30.379 12.743s12.743 19.115 12.743 30.38v66.445l154.965-154.965c16.953-16.953 44.601-16.953 61.554 0 16.953 16.953 16.953 44.6 0 61.554L705.536 388.55h66.446c24.007 0 43.122 19.115 43.122 43.122.114 24.007-18.318 43.804-42.325 43.804z"}));Gc.displayName="CancelFullScreenIcon";const Wc=()=>p(un,{name:"outlook"},()=>[p("path",{d:"M224 800c0 9.6 3.2 44.8 6.4 54.4 6.4 48-48 76.8-48 76.8s80 41.6 147.2 0 134.4-134.4 38.4-195.2c-22.4-12.8-41.6-19.2-57.6-19.2C259.2 716.8 227.2 761.6 224 800zM560 675.2l-32 51.2c-51.2 51.2-83.2 32-83.2 32 25.6 67.2 0 112-12.8 128 25.6 6.4 51.2 9.6 80 9.6 54.4 0 102.4-9.6 150.4-32l0 0c3.2 0 3.2-3.2 3.2-3.2 22.4-16 12.8-35.2 6.4-44.8-9.6-12.8-12.8-25.6-12.8-41.6 0-54.4 60.8-99.2 137.6-99.2 6.4 0 12.8 0 22.4 0 12.8 0 38.4 9.6 48-25.6 0-3.2 0-3.2 3.2-6.4 0-3.2 3.2-6.4 3.2-6.4 6.4-16 6.4-16 6.4-19.2 9.6-35.2 16-73.6 16-115.2 0-105.6-41.6-198.4-108.8-268.8C704 396.8 560 675.2 560 675.2zM224 419.2c0-28.8 22.4-51.2 51.2-51.2 28.8 0 51.2 22.4 51.2 51.2 0 28.8-22.4 51.2-51.2 51.2C246.4 470.4 224 448 224 419.2zM320 284.8c0-22.4 19.2-41.6 41.6-41.6 22.4 0 41.6 19.2 41.6 41.6 0 22.4-19.2 41.6-41.6 41.6C339.2 326.4 320 307.2 320 284.8zM457.6 208c0-12.8 12.8-25.6 25.6-25.6 12.8 0 25.6 12.8 25.6 25.6 0 12.8-12.8 25.6-25.6 25.6C470.4 233.6 457.6 220.8 457.6 208zM128 505.6C128 592 153.6 672 201.6 736c28.8-60.8 112-60.8 124.8-60.8-16-51.2 16-99.2 16-99.2l316.8-422.4c-48-19.2-99.2-32-150.4-32C297.6 118.4 128 291.2 128 505.6zM764.8 86.4c-22.4 19.2-390.4 518.4-390.4 518.4-22.4 28.8-12.8 76.8 22.4 99.2l9.6 6.4c35.2 22.4 80 12.8 99.2-25.6 0 0 6.4-12.8 9.6-19.2 54.4-105.6 275.2-524.8 288-553.6 6.4-19.2-3.2-32-19.2-32C777.6 76.8 771.2 80 764.8 86.4z"})]);Wc.displayName="OutlookIcon";var Kc=V({name:"AppearanceSwitch",setup(){const{config:n,isDarkmode:e,status:t}=Ea(),a=it(),s=()=>{n.value==="switch"?t.value={light:"dark",dark:"auto",auto:"light"}[t.value]:t.value=t.value==="light"?"dark":"light"},l=async o=>{if(!(document.startViewTransition&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches&&!a.value)||!o){s();return}const i=o.clientX,c=o.clientY,r=Math.hypot(Math.max(i,innerWidth-i),Math.max(c,innerHeight-c)),u=e.value;await document.startViewTransition(async()=>{s(),await Ue()}).ready,e.value!==u&&document.documentElement.animate({clipPath:e.value?[`circle(${r}px at ${i}px ${c}px)`,`circle(0px at ${i}px ${c}px)`]:[`circle(0px at ${i}px ${c}px)`,`circle(${r}px at ${i}px ${c}px)`]},{duration:400,pseudoElement:e.value?"::view-transition-old(root)":"::view-transition-new(root)"})};return()=>p("button",{type:"button",id:"appearance-switch",onClick:l},[p(Uc,{style:{display:t.value==="auto"?"block":"none"}}),p($c,{style:{display:t.value==="dark"?"block":"none"}}),p(zc,{style:{display:t.value==="light"?"block":"none"}})])}}),k0=V({name:"AppearanceMode",setup(){const n=ln(),{canToggle:e}=Ea(),t=y(()=>n.value.outlookLocales.darkmode);return()=>e.value?p("div",{class:"appearance-wrapper"},[p("label",{class:"appearance-title",for:"appearance-switch"},t.value),p(Kc)]):null}});const Ms="VUEPRESS_THEME_COLOR";var g0=V({name:"ThemeColorPicker",props:{themeColor:{type:Object,required:!0}},setup(n){const e=(t="")=>{const a=document.documentElement.classList,s=re(n.themeColor);if(!t){localStorage.removeItem(Ms),a.remove(...s);return}a.remove(...s.filter(l=>l!==t)),a.add(t),localStorage.setItem(Ms,t)};return En(()=>{const t=localStorage.getItem(Ms);t&&e(t)}),()=>p("ul",{id:"theme-color-picker"},[p("li",p("span",{class:"theme-color",onClick:()=>e()})),ot(n.themeColor).map(([t,a])=>p("li",p("span",{style:{background:a},onClick:()=>e(t)})))])}});const bt=zl.enableThemeColor==="true",m0=bt?Wk(ot(zl).filter(([n])=>n.startsWith("theme-"))):{};var v0=V({name:"ThemeColor",setup(){const n=ln(),e=y(()=>n.value.outlookLocales.themeColor);return()=>bt?p("div",{class:"theme-color-wrapper"},[p("label",{class:"theme-color-title",for:"theme-color-picker"},e.value),p(g0,{themeColor:m0})]):null}}),Xc=V({name:"ToggleFullScreenButton",setup(){const n=ln(),{isSupported:e,isFullscreen:t,toggle:a}=Hl(),s=y(()=>n.value.outlookLocales.fullscreen);return()=>e?p("div",{class:"full-screen-wrapper"},[p("label",{class:"full-screen-title",for:"full-screen-switch"},s.value),p("button",{type:"button",id:"full-screen-switch",class:"full-screen",ariaPressed:t.value,onClick:()=>a()},t.value?p(Gc):p(Jc))]):null}}),Yc=V({name:"OutlookSettings",setup(){const n=Se(),e=it(),t=y(()=>!e.value&&n.value.fullscreen);return()=>p(rs,()=>[bt?p(v0):null,p(k0),t.value?p(Xc):null])}}),f0=V({name:"NavScreen",props:{show:Boolean},emits:["close"],slots:Object,setup(n,{emit:e,slots:t}){const a=gn(),{isMobile:s}=wa(),l=Nn(),o=cc(l);return En(()=>{l.value=document.body,dn(s,i=>{!i&&n.show&&(o.value=!1,e("close"))}),dn(()=>a.value.path,()=>{o.value=!1,e("close")})}),da(()=>{o.value=!1}),()=>p($e,{name:"fade",onEnter:()=>{o.value=!0},onAfterLeave:()=>{o.value=!1}},()=>{var i,c;return n.show?p("div",{id:"nav-screen"},p("div",{class:"vp-nav-screen-container"},[(i=t.before)==null?void 0:i.call(t),p(h0),p("div",{class:"vp-outlook-wrapper"},p(Yc)),(c=t.after)==null?void 0:c.call(t)])):null})}}),_0=V({name:"NavbarBrand",setup(){const n=fe(),e=va(),t=ln(),a=y(()=>t.value.home||n.value),s=y(()=>e.value.title),l=y(()=>t.value.navTitle??s.value),o=y(()=>t.value.logo?xn(t.value.logo):null),i=y(()=>t.value.logoDark?xn(t.value.logoDark):null);return()=>p(Tn,{to:a.value,class:"vp-brand"},()=>[o.value?p("img",{class:["vp-nav-logo",{light:!!i.value}],src:o.value,alt:""}):null,i.value?p("img",{class:["vp-nav-logo dark"],src:i.value,alt:""}):null,l.value?p("span",{class:["vp-site-name",{"hide-in-pad":o.value&&t.value.hideSiteNameOnMobile!==!1}]},l.value):null])}}),y0=V({name:"NavbarLinks",setup(){const n=Hc();return()=>n.value.length?p("nav",{class:"vp-nav-links"},n.value.map(e=>p("div",{class:"nav-item hide-in-mobile"},"children"in e?p(r0,{config:e}):p(Un,{config:e})))):null}}),b0=V({name:"RepoLink",components:{BitbucketIcon:xc,GiteeIcon:Ec,GitHubIcon:bc,GitLabIcon:wc,SourceIcon:Ac},setup(){const n=u0();return()=>n.value?p("div",{class:"nav-item vp-repo"},p("a",{class:"vp-repo-link",href:n.value.link,target:"_blank",rel:"noopener noreferrer","aria-label":n.value.label},p(ee(`${n.value.type}Icon`),{style:{width:"1.25rem",height:"1.25rem",verticalAlign:"middle"}}))):null}});const Qc=({active:n=!1},{emit:e})=>p("button",{type:"button",class:["vp-toggle-navbar-button",{"is-active":n}],"aria-label":"Toggle Navbar","aria-expanded":n,"aria-controls":"nav-screen",onClick:()=>e("toggle")},p("span",[p("span",{class:"vp-top"}),p("span",{class:"vp-middle"}),p("span",{class:"vp-bottom"})]));Qc.displayName="ToggleNavbarButton";var w0=Qc;const ll=(n,{emit:e})=>p("button",{type:"button",class:"vp-toggle-sidebar-button",title:"Toggle Sidebar",onClick:()=>e("toggle")},p("span",{class:"icon"}));ll.displayName="ToggleSidebarButton",ll.emits=["toggle"];var E0=ll,x0=V({name:"OutlookButton",setup(){const{isSupported:n}=Hl(),e=Se(),t=it(),a=gn(),{canToggle:s}=Ea(),l=K(!1),o=y(()=>!t.value&&e.value.fullscreen&&n);return dn(()=>a.value.path,()=>{l.value=!1}),()=>s.value||o.value||bt?p("div",{class:"nav-item hide-in-mobile"},s.value&&!o.value&&!bt?p(Kc):o.value&&!s.value&&!bt?p(Xc):p("button",{type:"button",class:["outlook-button",{open:l.value}],tabindex:"-1","aria-hidden":!0},[p(Wc),p("div",{class:"outlook-dropdown"},p(Yc))])):null}}),A0=V({name:"NavBar",emits:["toggleSidebar"],slots:Object,setup(n,{emit:e,slots:t}){const a=ln(),{isMobile:s}=wa(),l=K(!1),o=y(()=>{const{navbarAutoHide:u="mobile"}=a.value;return u!=="none"&&(u==="always"||s.value)}),i=y(()=>a.value.navbarLayout||{start:["Brand"],center:["Links"],end:["Language","Repo","Outlook","Search"]}),c={Brand:_0,Language:Bs,Links:y0,Repo:b0,Outlook:x0,Search:xe("Docsearch")?ee("Docsearch"):xe("SearchBox")?ee("SearchBox"):Bs},r=u=>c[u]??(xe(u)?ee(u):Bs);return()=>{var u,d,k,g,_,b;return[p("header",{key:"navbar",id:"navbar",class:["vp-navbar",{"auto-hide":o.value,"hide-icon":a.value.navbarIcon===!1}]},[p("div",{class:"vp-navbar-start"},[p(E0,{onToggle:()=>{l.value&&(l.value=!1),e("toggleSidebar")}}),(u=t.startBefore)==null?void 0:u.call(t),(i.value.start||[]).map(E=>p(r(E))),(d=t.startAfter)==null?void 0:d.call(t)]),p("div",{class:"vp-navbar-center"},[(k=t.centerBefore)==null?void 0:k.call(t),(i.value.center||[]).map(E=>p(r(E))),(g=t.centerAfter)==null?void 0:g.call(t)]),p("div",{class:"vp-navbar-end"},[(_=t.endBefore)==null?void 0:_.call(t),(i.value.end||[]).map(E=>p(r(E))),(b=t.endAfter)==null?void 0:b.call(t),p(w0,{active:l.value,onToggle:()=>{l.value=!l.value}})])]),p(f0,{show:l.value,onClose:()=>{l.value=!1}},{before:()=>{var E;return(E=t.screenTop)==null?void 0:E.call(t)},after:()=>{var E;return(E=t.screenBottom)==null?void 0:E.call(t)}})]}}});const la=(n,e,t=!1)=>"activeMatch"in e?new RegExp(e.activeMatch,"u").test(n.path):Sc(n,e.link)?!0:"children"in e&&!t?e.children.some(a=>la(n,a)):!1,Zc=(n,e)=>e.type==="group"?e.children.some(t=>t.type==="group"?Zc(n,t):t.type==="page"&&la(n,t,!0))||"prefix"in e&&Sc(n,e.prefix):!1;var L0=V({name:"SidebarChild",props:{config:{type:Object,required:!0}},setup(n){const e=Ge();return()=>An(n.config.link)?p(Un,{class:["vp-sidebar-link","vp-sidebar-page",{active:la(e,n.config,!0)}],exact:!0,config:n.config}):p("p",n,[p(Vn,{icon:n.config.icon}),n.config.text])}}),S0=V({name:"SidebarGroup",props:{config:{type:Object,required:!0},open:{type:Boolean,required:!0}},emits:["toggle"],setup(n,{emit:e}){const t=Ge(),a=y(()=>la(t,n.config)),s=y(()=>la(t,n.config,!0));return()=>{const{collapsible:l,children:o=[],icon:i,prefix:c,link:r,text:u}=n.config;return p("section",{class:"vp-sidebar-group"},[p(l?"button":"p",{class:["vp-sidebar-header",{clickable:l||r,exact:s.value,active:a.value}],...l?{type:"button",onClick:()=>e("toggle"),onKeydown:d=>{d.key==="Enter"&&e("toggle")}}:{}},[p(Vn,{icon:i}),r?p(Un,{class:"vp-sidebar-title",config:{text:u,link:r},noExternalLinkIcon:!0}):p("span",{class:"vp-sidebar-title"},u),l?p("span",{class:["vp-arrow",n.open?"down":"end"]}):null]),n.open||!l?p(nr,{key:c,config:o}):null])}}}),nr=V({name:"SidebarLinks",props:{config:{type:Array,required:!0}},setup(n){const e=Ge(),t=K(-1),a=s=>{t.value=s===t.value?-1:s};return dn(()=>e.path,()=>{const s=n.config.findIndex(l=>Zc(e,l));t.value=s},{immediate:!0,flush:"post"}),()=>p("ul",{class:"vp-sidebar-links"},n.config.map((s,l)=>p("li",s.type==="group"?p(S0,{config:s,open:l===t.value,onToggle:()=>a(l)}):p(L0,{config:s}))))}}),C0=V({name:"SideBar",slots:Object,setup(n,{slots:e}){const t=Ge(),a=ln(),s=Jl(),l=Nn();return En(()=>{dn(()=>t.hash,o=>{const i=document.querySelector(`.vp-sidebar a.vp-sidebar-link[href="${t.path}${o}"]`);if(!i)return;const{top:c,height:r}=l.value.getBoundingClientRect(),{top:u,height:d}=i.getBoundingClientRect();uc+r&&i.scrollIntoView(!1)},{immediate:!0})}),()=>{var o,i,c;return p("aside",{ref:l,id:"sidebar",class:["vp-sidebar",{"hide-icon":a.value.sidebarIcon===!1}],key:"sidebar"},[(o=e.top)==null?void 0:o.call(e),((i=e.default)==null?void 0:i.call(e))||p(nr,{config:s.value}),(c=e.bottom)==null?void 0:c.call(e)])}}}),Gl=V({name:"CommonWrapper",props:{containerClass:{type:String,default:""},noNavbar:Boolean,noSidebar:Boolean,noToc:Boolean},slots:Object,setup(n,{slots:e}){const t=me(),a=gn(),s=fn(),l=ln(),{isMobile:o,isPC:i}=wa(),[c,r]=tl(!1),[u,d]=tl(!1),k=Jl(),g=K(!1),_=y(()=>n.noNavbar||s.value.navbar===!1||l.value.navbar===!1?!1:!!(a.value.title||l.value.logo||l.value.repo||l.value.navbar)),b=y(()=>n.noSidebar?!1:s.value.sidebar!==!1&&k.value.length!==0&&!s.value.home),E=y(()=>n.noToc||s.value.home?!1:s.value.toc||l.value.toc!==!1&&s.value.toc!==!1),w={x:0,y:0},L=A=>{w.x=A.changedTouches[0].clientX,w.y=A.changedTouches[0].clientY},f=A=>{const H=A.changedTouches[0].clientX-w.x,M=A.changedTouches[0].clientY-w.y;Math.abs(H)>Math.abs(M)*1.5&&Math.abs(H)>40&&(H>0&&w.x<=80?r(!0):r(!1))},S=()=>window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;let F=0;return Fn("scroll",l2(()=>{const A=S();A<=58||A{A||r(!1)}),En(()=>{const A=cc(document.body);dn(c,M=>{A.value=M});const H=t.afterEach(()=>{r(!1)});da(()=>{A.value=!1,H()})}),()=>p(xe("GlobalEncrypt")?ee("GlobalEncrypt"):yc,()=>p("div",{class:["theme-container",{"no-navbar":!_.value,"no-sidebar":!b.value&&!(e.sidebar||e.sidebarTop||e.sidebarBottom),"has-toc":E.value,"hide-navbar":g.value,"sidebar-collapsed":!o.value&&!i.value&&u.value,"sidebar-open":o.value&&c.value},n.containerClass,s.value.containerClass||""],onTouchStart:L,onTouchEnd:f},[_.value?p(A0,{onToggleSidebar:()=>r()},{startBefore:()=>{var A;return(A=e.navbarStartBefore)==null?void 0:A.call(e)},startAfter:()=>{var A;return(A=e.navbarStartAfter)==null?void 0:A.call(e)},centerBefore:()=>{var A;return(A=e.navbarCenterBefore)==null?void 0:A.call(e)},centerAfter:()=>{var A;return(A=e.navbarCenterAfter)==null?void 0:A.call(e)},endBefore:()=>{var A;return(A=e.navbarEndBefore)==null?void 0:A.call(e)},endAfter:()=>{var A;return(A=e.navbarEndAfter)==null?void 0:A.call(e)},screenTop:()=>{var A;return(A=e.navScreenTop)==null?void 0:A.call(e)},screenBottom:()=>{var A;return(A=e.navScreenBottom)==null?void 0:A.call(e)}}):null,p($e,{name:"fade"},()=>c.value?p("div",{class:"vp-sidebar-mask",onClick:()=>r(!1)}):null),p($e,{name:"fade"},()=>o.value?null:p("div",{class:"toggle-sidebar-wrapper",onClick:()=>d()},p("span",{class:["arrow",u.value?"end":"start"]}))),p(C0,{},{...e.sidebar?{default:()=>e.sidebar()}:{},top:()=>{var A;return(A=e.sidebarTop)==null?void 0:A.call(e)},bottom:()=>{var A;return(A=e.sidebarBottom)==null?void 0:A.call(e)}}),e.default(),p(c0)]))}}),rn=V({name:"DropTransition",props:{type:{type:String,default:"single"},delay:{type:Number,default:0},duration:{type:Number,default:.25},appear:Boolean},slots:Object,setup(n,{slots:e}){const t=s=>{s.style.transition=`transform ${n.duration}s ease-in-out ${n.delay}s, opacity ${n.duration}s ease-in-out ${n.delay}s`,s.style.transform="translateY(-20px)",s.style.opacity="0"},a=s=>{s.style.transform="translateY(0)",s.style.opacity="1"};return()=>p(n.type==="single"?$e:uh,{name:"drop",appear:n.appear,onAppear:t,onAfterAppear:a,onEnter:t,onAfterEnter:a,onBeforeLeave:t},()=>e.default())}});const ol=({custom:n})=>p(ec,{class:["theme-hope-content",{custom:n}]});ol.displayName="MarkdownContent",ol.props={custom:Boolean};var Wl=ol;const er=()=>p(un,{name:"author"},()=>p("path",{d:"M649.6 633.6c86.4-48 147.2-144 147.2-249.6 0-160-128-288-288-288s-288 128-288 288c0 108.8 57.6 201.6 147.2 249.6-121.6 48-214.4 153.6-240 288-3.2 9.6 0 19.2 6.4 25.6 3.2 9.6 12.8 12.8 22.4 12.8h704c9.6 0 19.2-3.2 25.6-12.8 6.4-6.4 9.6-16 6.4-25.6-25.6-134.4-121.6-240-243.2-288z"}));er.displayName="AuthorIcon";const tr=()=>p(un,{name:"calendar"},()=>p("path",{d:"M716.4 110.137c0-18.753-14.72-33.473-33.472-33.473-18.753 0-33.473 14.72-33.473 33.473v33.473h66.993v-33.473zm-334.87 0c0-18.753-14.72-33.473-33.473-33.473s-33.52 14.72-33.52 33.473v33.473h66.993v-33.473zm468.81 33.52H716.4v100.465c0 18.753-14.72 33.473-33.472 33.473a33.145 33.145 0 01-33.473-33.473V143.657H381.53v100.465c0 18.753-14.72 33.473-33.473 33.473a33.145 33.145 0 01-33.473-33.473V143.657H180.6A134.314 134.314 0 0046.66 277.595v535.756A134.314 134.314 0 00180.6 947.289h669.74a134.36 134.36 0 00133.94-133.938V277.595a134.314 134.314 0 00-133.94-133.938zm33.473 267.877H147.126a33.145 33.145 0 01-33.473-33.473c0-18.752 14.72-33.473 33.473-33.473h736.687c18.752 0 33.472 14.72 33.472 33.473a33.145 33.145 0 01-33.472 33.473z"}));tr.displayName="CalendarIcon";const ar=()=>p(un,{name:"category"},()=>p("path",{d:"M148.41 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H148.41c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.311-40.31zM147.556 553.478H429.73c22.263 0 40.311 18.048 40.311 40.31v282.176c0 22.263-18.048 40.312-40.31 40.312H147.555c-22.263 0-40.311-18.049-40.311-40.312V593.79c0-22.263 18.048-40.311 40.31-40.311zM593.927 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H593.927c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.31-40.31zM730.22 920.502H623.926c-40.925 0-74.22-33.388-74.22-74.425V623.992c0-41.038 33.387-74.424 74.425-74.424h222.085c41.038 0 74.424 33.226 74.424 74.067v114.233c0 10.244-8.304 18.548-18.547 18.548s-18.548-8.304-18.548-18.548V623.635c0-20.388-16.746-36.974-37.33-36.974H624.13c-20.585 0-37.331 16.747-37.331 37.33v222.086c0 20.585 16.654 37.331 37.126 37.331H730.22c10.243 0 18.547 8.304 18.547 18.547 0 10.244-8.304 18.547-18.547 18.547z"}));ar.displayName="CategoryIcon";const sr=()=>p(un,{name:"print"},()=>p("path",{d:"M819.2 364.8h-44.8V128c0-17.067-14.933-32-32-32H281.6c-17.067 0-32 14.933-32 32v236.8h-44.8C145.067 364.8 96 413.867 96 473.6v192c0 59.733 49.067 108.8 108.8 108.8h44.8V896c0 17.067 14.933 32 32 32h460.8c17.067 0 32-14.933 32-32V774.4h44.8c59.733 0 108.8-49.067 108.8-108.8v-192c0-59.733-49.067-108.8-108.8-108.8zM313.6 160h396.8v204.8H313.6V160zm396.8 704H313.6V620.8h396.8V864zM864 665.6c0 25.6-19.2 44.8-44.8 44.8h-44.8V588.8c0-17.067-14.933-32-32-32H281.6c-17.067 0-32 14.933-32 32v121.6h-44.8c-25.6 0-44.8-19.2-44.8-44.8v-192c0-25.6 19.2-44.8 44.8-44.8h614.4c25.6 0 44.8 19.2 44.8 44.8v192z"}));sr.displayName="PrintIcon";const lr=()=>p(un,{name:"tag"},()=>p("path",{d:"M939.902 458.563L910.17 144.567c-1.507-16.272-14.465-29.13-30.737-30.737L565.438 84.098h-.402c-3.215 0-5.726 1.005-7.634 2.913l-470.39 470.39a10.004 10.004 0 000 14.164l365.423 365.424c1.909 1.908 4.42 2.913 7.132 2.913s5.223-1.005 7.132-2.913l470.39-470.39c2.01-2.11 3.014-5.023 2.813-8.036zm-240.067-72.121c-35.458 0-64.286-28.828-64.286-64.286s28.828-64.285 64.286-64.285 64.286 28.828 64.286 64.285-28.829 64.286-64.286 64.286z"}));lr.displayName="TagIcon";const or=()=>p(un,{name:"timer"},()=>p("path",{d:"M799.387 122.15c4.402-2.978 7.38-7.897 7.38-13.463v-1.165c0-8.933-7.38-16.312-16.312-16.312H256.33c-8.933 0-16.311 7.38-16.311 16.312v1.165c0 5.825 2.977 10.874 7.637 13.592 4.143 194.44 97.22 354.963 220.201 392.763-122.204 37.542-214.893 196.511-220.2 389.397-4.661 5.049-7.638 11.651-7.638 19.03v5.825h566.49v-5.825c0-7.379-2.849-13.981-7.509-18.9-5.049-193.016-97.867-351.985-220.2-389.527 123.24-37.67 216.446-198.453 220.588-392.892zM531.16 450.445v352.632c117.674 1.553 211.787 40.778 211.787 88.676H304.097c0-48.286 95.149-87.382 213.728-88.676V450.445c-93.077-3.107-167.901-81.297-167.901-177.093 0-8.803 6.99-15.793 15.793-15.793 8.803 0 15.794 6.99 15.794 15.793 0 80.261 63.69 145.635 142.01 145.635s142.011-65.374 142.011-145.635c0-8.803 6.99-15.793 15.794-15.793s15.793 6.99 15.793 15.793c0 95.019-73.789 172.82-165.96 177.093z"}));or.displayName="TimerIcon";const pr=()=>p(un,{name:"word"},()=>[p("path",{d:"M518.217 432.64V73.143A73.143 73.143 0 01603.43 1.097a512 512 0 01419.474 419.474 73.143 73.143 0 01-72.046 85.212H591.36a73.143 73.143 0 01-73.143-73.143z"}),p("path",{d:"M493.714 566.857h340.297a73.143 73.143 0 0173.143 85.577A457.143 457.143 0 11371.566 117.76a73.143 73.143 0 0185.577 73.143v339.383a36.571 36.571 0 0036.571 36.571z"})]);pr.displayName="WordIcon";const We=()=>{const n=ln();return y(()=>n.value.metaLocales)};var T0=V({name:"AuthorInfo",inheritAttrs:!1,props:{author:{type:Array,required:!0},pure:Boolean},setup(n){const e=We();return()=>n.author.length?p("span",{class:"page-author-info","aria-label":`${e.value.author}${n.pure?"":"🖊"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(er),p("span",n.author.map(t=>t.url?p("a",{class:"page-author-item",href:t.url,target:"_blank",rel:"noopener noreferrer"},t.name):p("span",{class:"page-author-item"},t.name))),p("span",{property:"author",content:n.author.map(t=>t.name).join(", ")})]):null}}),O0=V({name:"CategoryInfo",inheritAttrs:!1,props:{category:{type:Array,required:!0},pure:Boolean},setup(n){const e=me(),t=gn(),a=We(),s=(l,o="")=>{o&&t.value.path!==o&&(l.preventDefault(),e.push(o))};return()=>n.category.length?p("span",{class:"page-category-info","aria-label":`${a.value.category}${n.pure?"":"🌈"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(ar),n.category.map(({name:l,path:o})=>p("span",{class:["page-category-item",{[`category${ks(l,9)}`]:!n.pure,clickable:o}],role:o?"navigation":"",onClick:i=>s(i,o)},l)),p("meta",{property:"articleSection",content:n.category.map(({name:l})=>l).join(",")})]):null}}),I0=V({name:"DateInfo",inheritAttrs:!1,props:{date:{type:Object,default:null},localizedDate:{type:String,default:""},pure:Boolean},setup(n){const e=Yi(),t=We();return()=>n.date?p("span",{class:"page-date-info","aria-label":`${t.value.date}${n.pure?"":"📅"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(tr),p("span",p(rs,()=>n.localizedDate||n.date.toLocaleDateString(e.value))),p("meta",{property:"datePublished",content:n.date.toISOString()||""})]):null}}),P0=V({name:"OriginalInfo",inheritAttrs:!1,props:{isOriginal:Boolean},setup(n){const e=We();return()=>n.isOriginal?p("span",{class:"page-original-info"},e.value.origin):null}}),j0=V({name:"ReadingTimeInfo",inheritAttrs:!1,props:{readingTime:{type:Object,default:()=>null},readingTimeLocale:{type:Object,default:()=>null},pure:Boolean},setup(n){const e=We(),t=y(()=>{if(!n.readingTime)return null;const{minutes:a}=n.readingTime;return a<1?"PT1M":`PT${Math.round(a)}M`});return()=>{var a,s;return(a=n.readingTimeLocale)!=null&&a.time?p("span",{class:"page-reading-time-info","aria-label":`${e.value.readingTime}${n.pure?"":"⌛"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(or),p("span",(s=n.readingTimeLocale)==null?void 0:s.time),p("meta",{property:"timeRequired",content:t.value})]):null}}}),D0=V({name:"TagInfo",inheritAttrs:!1,props:{tag:{type:Array,default:()=>[]},pure:Boolean},setup(n){const e=me(),t=gn(),a=We(),s=(l,o="")=>{o&&t.value.path!==o&&(l.preventDefault(),e.push(o))};return()=>n.tag.length?p("span",{class:"page-tag-info","aria-label":`${a.value.tag}${n.pure?"":"🏷"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(lr),n.tag.map(({name:l,path:o})=>p("span",{class:["page-tag-item",{[`tag${ks(l,9)}`]:!n.pure,clickable:o}],role:o?"navigation":"",onClick:i=>s(i,o)},l)),p("meta",{property:"keywords",content:n.tag.map(({name:l})=>l).join(",")})]):null}}),R0=V({name:"ReadTimeInfo",inheritAttrs:!1,props:{readingTime:{type:Object,default:()=>null},readingTimeLocale:{type:Object,default:()=>null},pure:Boolean},setup(n){const e=We();return()=>{var t,a,s;return(t=n.readingTimeLocale)!=null&&t.words?p("span",{class:"page-word-info","aria-label":`${e.value.words}${n.pure?"":"🔠"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(pr),p("span",(a=n.readingTimeLocale)==null?void 0:a.words),p("meta",{property:"wordCount",content:(s=n.readingTime)==null?void 0:s.words})]):null}}}),ir=V({name:"PageInfo",components:{AuthorInfo:T0,CategoryInfo:O0,DateInfo:I0,OriginalInfo:P0,PageViewInfo:()=>null,ReadingTimeInfo:j0,TagInfo:D0,WordInfo:R0},props:{items:{type:[Array,Boolean],default:()=>["Author","Original","Date","PageView","ReadingTime","Category","Tag"]},info:{type:Object,required:!0}},setup(n){const e=it();return()=>n.items?p("div",{class:"page-info"},n.items.map(t=>p(ee(`${t}Info`),{...n.info,pure:e.value}))):null}}),F0=V({name:"PrintButton",setup(){const n=Se(),e=ln();return()=>n.value.print===!1?null:p("button",{type:"button",class:"print-button",title:e.value.metaLocales.print,onClick:()=>{window.print()}},p(sr))}}),cr=V({name:"TOC",props:{items:{type:Array,default:()=>[]},headerDepth:{type:Number,default:2}},slots:Object,setup(n,{slots:e}){const t=Ge(),a=gn(),s=We(),[l,o]=tl(),i=Nn(),c=K("-1.7rem"),r=d=>{var k;(k=i.value)==null||k.scrollTo({top:d,behavior:"smooth"})},u=()=>{if(i.value){const d=document.querySelector(".vp-toc-item.active");d?c.value=`${d.getBoundingClientRect().top-i.value.getBoundingClientRect().top+i.value.scrollTop}px`:c.value="-1.7rem"}else c.value="-1.7rem"};return En(()=>{dn(()=>t.hash,d=>{if(i.value){const k=document.querySelector(`#toc a.toc-link[href$="${d}"]`);if(!k)return;const{top:g,height:_}=i.value.getBoundingClientRect(),{top:b,height:E}=k.getBoundingClientRect();bg+_&&r(i.value.scrollTop+b+E-g-_)}}),dn(()=>t.fullPath,u,{flush:"post",immediate:!0})}),()=>{var _,b;const d=({title:E,level:w,slug:L})=>p(Tn,{to:`#${L}`,class:["vp-toc-link",`level${w}`],onClick:()=>{o()}},()=>E),k=(E,w)=>E.length&&w>0?p("ul",{class:"vp-toc-list"},E.map(L=>{const f=k(L.children,w-1);return[p("li",{class:["vp-toc-item",{active:t.hash===`#${L.slug}`}]},d(L)),f?p("li",f):null]})):null,g=n.items.length?k(n.items,n.headerDepth):a.value.headers?k(a.value.headers,n.headerDepth):null;return g?p("div",{class:"vp-toc-placeholder"},[p("aside",{id:"toc"},[(_=e.before)==null?void 0:_.call(e),p("div",{class:"vp-toc-header",onClick:()=>{o()}},[s.value.toc,p(F0),p("div",{class:["arrow",l.value?"down":"end"]})]),p("div",{class:["vp-toc-wrapper",l.value?"open":""],ref:i},[g,p("div",{class:"vp-toc-marker",style:{top:c.value}})]),(b=e.after)==null?void 0:b.call(e)])]):null}}}),Kl=V({name:"SkipLink",props:{content:{type:String,default:"main-content"}},setup(n){const e=gn(),t=ln(),a=Nn(),s=({target:l})=>{const o=document.querySelector(l.hash);if(o){const i=()=>{o.removeAttribute("tabindex"),o.removeEventListener("blur",i)};o.setAttribute("tabindex","-1"),o.addEventListener("blur",i),o.focus(),window.scrollTo(0,0)}};return En(()=>{dn(()=>e.value.path,()=>a.value.focus())}),()=>[p("span",{ref:a,tabindex:"-1"}),p("a",{href:`#${n.content}`,class:"vp-skip-link sr-only",onClick:s},t.value.routeLocales.skipToContent)]}});let Vs=null,Rt=null;const B0={wait:()=>Vs,pending:()=>{Vs=new Promise(n=>{Rt=n})},resolve:()=>{Rt==null||Rt(),Vs=null,Rt=null}},rr=()=>B0;var ur=V({name:"FadeSlideY",slots:Object,setup(n,{slots:e}){const{resolve:t,pending:a}=rr();return()=>p($e,{name:"fade-slide-y",mode:"out-in",onBeforeEnter:t,onBeforeLeave:a},()=>{var s;return(s=e.default)==null?void 0:s.call(e)})}});const M0=(n,e)=>{const t=n.replace(e,"/").split("/"),a=[];let s=Fl(e);return t.forEach((l,o)=>{o!==t.length-1?(s+=`${l}/`,a.push({link:s,name:l||"Home"})):l!==""&&(s+=l,a.push({link:s,name:l}))}),a},dr=(n,{slots:e})=>{var d,k;const{bgImage:t,bgImageDark:a,bgImageStyle:s,color:l,description:o,image:i,imageDark:c,header:r,features:u=[]}=n;return p("div",{class:"vp-feature-wrapper"},[t?p("div",{class:["vp-feature-bg",{light:a}],style:[{"background-image":`url(${t})`},s]}):null,a?p("div",{class:"vp-feature-bg dark",style:[{"background-image":`url(${a})`},s]}):null,p("div",{class:"vp-feature",style:l?{color:l}:{}},[((d=e.image)==null?void 0:d.call(e,n))||[i?p("img",{class:["vp-feature-image",{light:c}],src:xn(i),alt:""}):null,c?p("img",{class:"vp-feature-image dark",src:xn(c),alt:""}):null],((k=e.info)==null?void 0:k.call(e,n))||[r?p("h2",{class:"vp-feature-header"},r):null,o?p("p",{class:"vp-feature-description",innerHTML:o}):null],u.length?p("div",{class:"vp-features"},u.map(({icon:g,title:_,details:b,link:E})=>{const w=[p("h3",{class:"vp-feature-title"},[p(Vn,{icon:g}),p("span",{innerHTML:_})]),p("p",{class:"vp-feature-details",innerHTML:b})];return E?Zt(E)?p("a",{class:"vp-feature-item link",href:E,"aria-label":_,target:"_blank"},w):p(Tn,{class:"vp-feature-item link",to:E,"aria-label":_},()=>w):p("div",{class:"vp-feature-item"},w)})):null])])};dr.displayName="FeaturePanel";var Ap=dr,V0=V({name:"HeroInfo",slots:Object,setup(n,{slots:e}){const t=fn(),a=va(),s=y(()=>t.value.heroFullScreen??!1),l=y(()=>{const{heroText:r,tagline:u}=t.value;return{text:r??a.value.title??"Hello",tagline:u??a.value.description??"",isFullScreen:s.value}}),o=y(()=>{const{heroText:r,heroImage:u,heroImageDark:d,heroAlt:k,heroImageStyle:g}=t.value;return{image:u?xn(u):null,imageDark:d?xn(d):null,style:g,alt:k||r||"",isFullScreen:s.value}}),i=y(()=>{const{bgImage:r,bgImageDark:u,bgImageStyle:d}=t.value;return{image:An(r)?xn(r):null,imageDark:An(u)?xn(u):null,bgStyle:d,isFullScreen:s.value}}),c=y(()=>t.value.actions??[]);return()=>{var r,u,d;return p("header",{class:["vp-hero-info-wrapper",{fullscreen:s.value}]},[((r=e.heroBg)==null?void 0:r.call(e,i.value))||[i.value.image?p("div",{class:["vp-hero-mask",{light:i.value.imageDark}],style:[{"background-image":`url(${i.value.image})`},i.value.bgStyle]}):null,i.value.imageDark?p("div",{class:"vp-hero-mask dark",style:[{"background-image":`url(${i.value.imageDark})`},i.value.bgStyle]}):null],p("div",{class:"vp-hero-info"},[((u=e.heroImage)==null?void 0:u.call(e,o.value))||p(rn,{appear:!0,type:"group"},()=>[o.value.image?p("img",{key:"light",class:["vp-hero-image",{light:o.value.imageDark}],style:o.value.style,src:o.value.image,alt:o.value.alt}):null,o.value.imageDark?p("img",{key:"dark",class:"vp-hero-image dark",style:o.value.style,src:o.value.imageDark,alt:o.value.alt}):null]),((d=e.heroInfo)==null?void 0:d.call(e,l.value))??p("div",{class:"vp-hero-infos"},[l.value.text?p(rn,{appear:!0,delay:.04},()=>p("h1",{id:"main-title"},l.value.text)):null,l.value.tagline?p(rn,{appear:!0,delay:.08},()=>p("p",{id:"main-description",innerHTML:l.value.tagline})):null,c.value.length?p(rn,{appear:!0,delay:.12},()=>p("p",{class:"vp-hero-actions"},c.value.map(k=>p(Un,{class:["vp-hero-action",k.type||"default"],config:k,noExternalLinkIcon:!0},k.icon?{before:()=>p(Vn,{icon:k.icon})}:{})))):null])])])}}});const hr=(n,{slots:e})=>{var k,g,_;const{bgImage:t,bgImageDark:a,bgImageStyle:s,color:l,description:o,image:i,imageDark:c,header:r,highlights:u=[],type:d="un-order"}=n;return p("div",{class:"vp-highlight-wrapper",style:l?{color:l}:{}},[t?p("div",{class:["vp-highlight-bg",{light:a}],style:[{"background-image":`url(${t})`},s]}):null,a?p("div",{class:"vp-highlight-bg dark",style:[{"background-image":`url(${a})`},s]}):null,p("div",{class:"vp-highlight"},[((k=e.image)==null?void 0:k.call(e,n))||[i?p("img",{class:["vp-highlight-image",{light:c}],src:xn(i),alt:""}):null,c?p("img",{class:"vp-highlight-image dark",src:xn(c),alt:""}):null],((g=e.info)==null?void 0:g.call(e,n))||[p("div",{class:"vp-highlight-info-wrapper"},p("div",{class:"vp-highlight-info"},[r?p("h2",{class:"vp-highlight-header",innerHTML:r}):null,o?p("p",{class:"vp-highlight-description",innerHTML:o}):null,((_=e.highlights)==null?void 0:_.call(e,u))||p(d==="order"?"ol":d==="no-order"?"dl":"ul",{class:"vp-highlights"},u.map(({icon:b,title:E,details:w,link:L})=>{const f=[p(d==="no-order"?"dt":"h3",{class:"vp-highlight-title"},[b?p(Vn,{class:"vp-highlight-icon",icon:b}):null,p("span",{innerHTML:E})]),w?p(d==="no-order"?"dd":"p",{class:"vp-highlight-details",innerHTML:w}):null];return p(d==="no-order"?"div":"li",{class:["vp-highlight-item-wrapper",{link:L}]},L?Zt(L)?p("a",{class:"vp-highlight-item link",href:L,"aria-label":E,target:"_blank"},f):p(Tn,{class:"vp-highlight-item link",to:L,"aria-label":E},()=>f):p("div",{class:"vp-highlight-item"},f))}))]))]])])};hr.displayName="HighlightPanel";var q0=hr,N0=V({name:"HomePage",slots:Object,setup(n,{slots:e}){const t=it(),a=fn(),s=y(()=>{const{features:o}=a.value;return el(o)?o:null}),l=y(()=>{const{highlights:o}=a.value;return el(o)?o:null});return()=>{var o,i,c,r;return p("main",{id:"main-content",class:["vp-project-home ",{pure:t.value}],"aria-labelledby":a.value.heroText===null?"":"main-title"},[(o=e.top)==null?void 0:o.call(e),p(V0),((i=l.value)==null?void 0:i.map(u=>"features"in u?p(Ap,u):p(q0,u)))||(s.value?p(rn,{appear:!0,delay:.24},()=>p(Ap,{features:s.value})):null),(c=e.center)==null?void 0:c.call(e),p(rn,{appear:!0,delay:.32},()=>p(Wl)),(r=e.bottom)==null?void 0:r.call(e)])}}}),H0=V({name:"BreadCrumb",setup(){const n=gn(),e=fe(),t=fn(),a=ln(),s=Nn([]),l=y(()=>(t.value.breadcrumb||t.value.breadcrumb!==!1&&a.value.breadcrumb!==!1)&&s.value.length>1),o=y(()=>t.value.breadcrumbIcon||t.value.breadcrumbIcon!==!1&&a.value.breadcrumbIcon!==!1),i=()=>{const c=M0(n.value.path,e.value).map(({link:r,name:u})=>{const{path:d,meta:k,notFound:g}=It(r);return g?null:{title:k[vn.shortTitle]||k[vn.title]||u,icon:k[vn.icon],path:d}}).filter(r=>r!==null);c.length>1&&(s.value=c)};return En(()=>{dn(()=>n.value.path,i,{immediate:!0})}),()=>p("nav",{class:["vp-breadcrumb",{disable:!l.value}]},l.value?p("ol",{vocab:"https://schema.org/",typeof:"BreadcrumbList"},s.value.map((c,r)=>p("li",{class:{"is-active":s.value.length-1===r},property:"itemListElement",typeof:"ListItem"},[p(Tn,{to:c.path,property:"item",typeof:"WebPage"},()=>[o.value?p(Vn,{icon:c.icon}):null,p("span",{property:"name"},c.title||"Unknown")]),p("meta",{property:"position",content:r+1})]))):[])}});const Lp=n=>n===!1||ma(n)?n:An(n)?yt(n,!0):null,pl=(n,e,t)=>{const a=n.findIndex(s=>s.link===e);if(a!==-1){const s=n[a+t];return s!=null&&s.link?s:null}for(const s of n)if("children"in s){const l=pl(s.children,e,t);if(l)return l}return null};var $0=V({name:"PageNav",setup(){const n=ln(),e=fn(),t=Jl(),a=gn(),s=ba(),l=y(()=>{const i=Lp(e.value.prev);return i===!1?null:i||(n.value.prevLink===!1?null:pl(t.value,a.value.path,-1))}),o=y(()=>{const i=Lp(e.value.next);return i===!1?null:i||(n.value.nextLink===!1?null:pl(t.value,a.value.path,1))});return Fn("keydown",i=>{i.altKey&&(i.key==="ArrowRight"?o.value&&(s(o.value.link),i.preventDefault()):i.key==="ArrowLeft"&&l.value&&(s(l.value.link),i.preventDefault()))}),()=>l.value||o.value?p("nav",{class:"vp-page-nav"},[l.value?p(Un,{class:"prev",config:l.value},()=>{var i,c;return[p("div",{class:"hint"},[p("span",{class:"arrow start"}),n.value.metaLocales.prev]),p("div",{class:"link"},[p(Vn,{icon:(i=l.value)==null?void 0:i.icon}),(c=l.value)==null?void 0:c.text])]}):null,o.value?p(Un,{class:"next",config:o.value},()=>{var i,c;return[p("div",{class:"hint"},[n.value.metaLocales.next,p("span",{class:"arrow end"})]),p("div",{class:"link"},[(i=o.value)==null?void 0:i.text,p(Vn,{icon:(c=o.value)==null?void 0:c.icon})])]}):null]):null}});const z0={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},U0=({docsRepo:n,docsBranch:e,docsDir:t,filePathRelative:a,editLinkPattern:s})=>{if(!a)return null;const l=Cc(n);let o;return s?o=s:l!==null&&(o=z0[l]),o?o.replace(/:repo/u,Je(n)?n:`https://github.com/${n}`).replace(/:branch/u,e).replace(/:path/u,Vi(`${Fl(t)}/${a}`)):null},J0=()=>{const n=ln(),e=gn(),t=fn();return y(()=>{const{repo:a,docsRepo:s=a,docsBranch:l="main",docsDir:o="",editLink:i,editLinkPattern:c=""}=n.value;if(!(t.value.editLink??i??!0)||!s)return null;const r=U0({docsRepo:s,docsBranch:l,docsDir:o,editLinkPattern:c,filePathRelative:e.value.filePathRelative});return r?{text:n.value.metaLocales.editLink,link:r}:null})},G0=()=>{const n=va(),e=ln(),t=gn(),a=fn();return y(()=>{var s,l;return!(a.value.lastUpdated??e.value.lastUpdated??!0)||!((s=t.value.git)!=null&&s.updatedTime)?null:new Date((l=t.value.git)==null?void 0:l.updatedTime).toLocaleString(n.value.lang)})},W0=()=>{const n=ln(),e=gn(),t=fn();return y(()=>{var a;return t.value.contributors??n.value.contributors??!0?((a=e.value.git)==null?void 0:a.contributors)??null:null})};var K0=V({name:"PageTitle",setup(){const n=gn(),e=fn(),t=ln(),{info:a,items:s}=e0();return()=>p("div",{class:"vp-page-title"},[p("h1",[t.value.titleIcon===!1?null:p(Vn,{icon:e.value.icon}),n.value.title]),p(ir,{info:a.value,...s.value===null?{}:{items:s.value}}),p("hr")])}});const kr=()=>p(un,{name:"edit"},()=>[p("path",{d:"M430.818 653.65a60.46 60.46 0 0 1-50.96-93.281l71.69-114.012 7.773-10.365L816.038 80.138A60.46 60.46 0 0 1 859.225 62a60.46 60.46 0 0 1 43.186 18.138l43.186 43.186a60.46 60.46 0 0 1 0 86.373L588.879 565.55l-8.637 8.637-117.466 68.234a60.46 60.46 0 0 1-31.958 11.229z"}),p("path",{d:"M728.802 962H252.891A190.883 190.883 0 0 1 62.008 771.98V296.934a190.883 190.883 0 0 1 190.883-192.61h267.754a60.46 60.46 0 0 1 0 120.92H252.891a69.962 69.962 0 0 0-69.098 69.099V771.98a69.962 69.962 0 0 0 69.098 69.098h475.911A69.962 69.962 0 0 0 797.9 771.98V503.363a60.46 60.46 0 1 1 120.922 0V771.98A190.883 190.883 0 0 1 728.802 962z"})]);kr.displayName="EditIcon";var X0=V({name:"PageMeta",setup(){const n=ln(),e=J0(),t=G0(),a=W0();return()=>{const{metaLocales:s}=n.value;return p("footer",{class:"page-meta"},[e.value?p("div",{class:"meta-item edit-link"},p(Un,{class:"label",config:e.value},{before:()=>p(kr)})):null,p("div",{class:"meta-item git-info"},[t.value?p("div",{class:"update-time"},[p("span",{class:"label"},`${s.lastUpdated}: `),p(rs,()=>p("span",{class:"info"},t.value))]):null,a.value&&a.value.length?p("div",{class:"contributors"},[p("span",{class:"label"},`${s.contributors}: `),a.value.map(({email:l,name:o},i)=>[p("span",{class:"contributor",title:`email: ${l}`},o),i!==a.value.length-1?",":""])]):null])])}}}),Y0=V({name:"NormalPage",slots:Object,setup(n,{slots:e}){const t=fn(),{isDarkmode:a}=Ea(),s=ln(),l=y(()=>t.value.toc||t.value.toc!==!1&&s.value.toc!==!1);return()=>p("main",{id:"main-content",class:"vp-page"},p(xe("LocalEncrypt")?ee("LocalEncrypt"):yc,()=>{var o,i,c,r;return[(o=e.top)==null?void 0:o.call(e),t.value.cover?p("div",{class:"page-cover"},p("img",{src:xn(t.value.cover),alt:"","no-view":""})):null,p(H0),p(K0),l.value?p(cr,{headerDepth:t.value.headerDepth??s.value.headerDepth??2},{before:()=>{var u;return(u=e.tocBefore)==null?void 0:u.call(e)},after:()=>{var u;return(u=e.tocAfter)==null?void 0:u.call(e)}}):null,(i=e.contentBefore)==null?void 0:i.call(e),p(Wl),(c=e.contentAfter)==null?void 0:c.call(e),p(X0),p($0),xe("CommentService")?p(ee("CommentService"),{darkmode:a.value}):null,(r=e.bottom)==null?void 0:r.call(e)]}))}}),Q0=V({name:"Layout",slots:Object,setup(n,{slots:e}){const t=Se(),a=ln(),s=gn(),l=fn(),{isMobile:o}=wa(),i=y(()=>{var c,r;return((c=a.value.blog)==null?void 0:c.sidebarDisplay)||((r=t.value.blog)==null?void 0:r.sidebarDisplay)||"mobile"});return()=>[p(Kl),p(Gl,{},{default:()=>{var c;return((c=e.default)==null?void 0:c.call(e))||(l.value.home?p(N0):p(ur,()=>p(Y0,{key:s.value.path},{top:()=>{var r;return(r=e.top)==null?void 0:r.call(e)},bottom:()=>{var r;return(r=e.bottom)==null?void 0:r.call(e)},contentBefore:()=>{var r;return(r=e.contentBefore)==null?void 0:r.call(e)},contentAfter:()=>{var r;return(r=e.contentAfter)==null?void 0:r.call(e)},tocBefore:()=>{var r;return(r=e.tocBefore)==null?void 0:r.call(e)},tocAfter:()=>{var r;return(r=e.tocAfter)==null?void 0:r.call(e)}})))},...i.value==="none"?{}:{navScreenBottom:()=>p(ee("BloggerInfo"))},...!o.value&&i.value==="always"?{sidebar:()=>p(ee("BloggerInfo"))}:{}})]}}),Z0=V({name:"NotFoundHint",setup(){const n=ln(),e=()=>{const t=n.value.routeLocales.notFoundMsg;return t[Math.floor(Math.random()*t.length)]};return()=>p("div",{class:"not-found-hint"},[p("p",{class:"error-code"},"404"),p("h1",{class:"error-title"},n.value.routeLocales.notFoundTitle),p("p",{class:"error-hint"},e())])}}),ng=V({name:"NotFound",slots:Object,setup(n,{slots:e}){const t=me(),a=fe(),s=ln();return()=>[p(Kl),p(Gl,{noSidebar:!0},()=>{var l;return p("main",{id:"main-content",class:"vp-page not-found"},((l=e.default)==null?void 0:l.call(e))||[p(Z0),p("div",{class:"actions"},[p("button",{type:"button",class:"action-button",onClick:()=>{window.history.go(-1)}},s.value.routeLocales.back),p("button",{type:"button",class:"action-button",onClick:()=>{t.push(s.value.home??a.value)}},s.value.routeLocales.home)])])})]}});const eg={Baidu:'',Weibo:'',Zhihu:''},tg={category:{"/":{path:"/category/",map:{设计模式:{path:"/category/设计模式/",indexes:[0,1,2,3,4]},使用指南:{path:"/category/使用指南/",indexes:[5,6,7,8,9]},vue知识点:{path:"/category/vue知识点/",indexes:[10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]},js基础:{path:"/category/js基础/",indexes:[33,34,35,36]},npm知识点:{path:"/category/npm知识点/",indexes:[37]},问题定位:{path:"/category/问题定位/",indexes:[38,39]},vite:{path:"/category/vite/",indexes:[40]},集合:{path:"/category/集合/",indexes:[41]},jdk:{path:"/category/jdk/",indexes:[42]},线程池:{path:"/category/线程池/",indexes:[43,44]},多线程:{path:"/category/多线程/",indexes:[43,44]},java:{path:"/category/java/",indexes:[45,46,47,48]},对象锁:{path:"/category/对象锁/",indexes:[49]},java基础:{path:"/category/java基础/",indexes:[50,51,52,53,54]},jvm:{path:"/category/jvm/",indexes:[45,46]},电子书:{path:"/category/电子书/",indexes:[55]},数据库:{path:"/category/数据库/",indexes:[56,57,58]},docker:{path:"/category/docker/",indexes:[59,60]},运维:{path:"/category/运维/",indexes:[61,62]},"git 操作":{path:"/category/git-操作/",indexes:[63,64,65,66,67,68,69,70,71,72,73]},必会:{path:"/category/必会/",indexes:[73]},linux:{path:"/category/linux/",indexes:[74]},markdown:{path:"/category/markdown/",indexes:[75]},公司业务:{path:"/category/公司业务/",indexes:[76]},小组分享:{path:"/category/小组分享/",indexes:[77,78,79]},web:{path:"/category/web/",indexes:[80,81,82,83]},oauth:{path:"/category/oauth/",indexes:[84]},框架:{path:"/category/框架/",indexes:[85]},security:{path:"/category/security/",indexes:[86,87]},Security:{path:"/category/security/",indexes:[88,89,90,91]},OAuth:{path:"/category/oauth/",indexes:[88]},Spring:{path:"/category/spring/",indexes:[92,93,94]},"open-source":{path:"/category/open-source/",indexes:[95]},maven:{path:"/category/maven/",indexes:[96,97]}}}},tag:{"/":{path:"/tag/",map:{禁用:{path:"/tag/禁用/",indexes:[7]},文章加密:{path:"/tag/文章加密/",indexes:[8]},Markdown:{path:"/tag/markdown/",indexes:[9]},页面配置:{path:"/tag/页面配置/",indexes:[5]},使用指南:{path:"/tag/使用指南/",indexes:[5]},你所不了解的JavaScript:{path:"/tag/你所不了解的javascript/",indexes:[98,99]},必会:{path:"/tag/必会/",indexes:[10,11,100,12,101,13,14,37,15,16,17,18,19,20,21,63,64,65,33,22,23,24,25,26,34,27,28,35,36,50,29,30,66,67,68,69,70,71,72,31,51,32]},"vue中的 TypeScript":{path:"/tag/vue中的-typescript/",indexes:[11,13,18,19,20,21]},vite:{path:"/tag/vite/",indexes:[40]},ts:{path:"/tag/ts/",indexes:[102]},vue3:{path:"/tag/vue3/",indexes:[102]},集合:{path:"/tag/集合/",indexes:[41]},"-- 并发":{path:"/tag/---并发/",indexes:[103]},多线程:{path:"/tag/多线程/",indexes:[43,44]},线程池:{path:"/tag/线程池/",indexes:[43,44]},oauth:{path:"/tag/oauth/",indexes:[84,104,105]},sso:{path:"/tag/sso/",indexes:[104,105]},部署搭建:{path:"/tag/部署搭建/",indexes:[106,48]},字节码:{path:"/tag/字节码/",indexes:[46]},反汇编:{path:"/tag/反汇编/",indexes:[46]},mysql:{path:"/tag/mysql/",indexes:[107]},数据库:{path:"/tag/数据库/",indexes:[58]},数据结构:{path:"/tag/数据结构/",indexes:[108]},二叉树:{path:"/tag/二叉树/",indexes:[108]},运维:{path:"/tag/运维/",indexes:[109,110]},工具使用:{path:"/tag/工具使用/",indexes:[111]},linux:{path:"/tag/linux/",indexes:[74]},markdown:{path:"/tag/markdown/",indexes:[75]},web:{path:"/tag/web/",indexes:[80,81,82,83]},springboot:{path:"/tag/springboot/",indexes:[81]},框架:{path:"/tag/框架/",indexes:[85]},日志:{path:"/tag/日志/",indexes:[112]}}}}},gr=["/designpattern/%E8%A3%85%E9%A5%B0%E6%A8%A1%E5%BC%8F.html","/designpattern/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F.html","/designpattern/%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F.html","/designpattern/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F.html","/designpattern/%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F.html","/guide/page.html","/guide/","/guide/disable.html","/guide/encrypt.html","/guide/markdown.html","/frontweb/es5/asyncError.html","/frontweb/typeScript/axios.html","/frontweb/es6/useModule.html","/frontweb/typeScript/action-usage.html","/frontweb/es6/useNpm.html","/frontweb/es6/useYarn.html","/frontweb/vue/vue-router1.html","/frontweb/vue/vue-router2.html","/frontweb/typeScript/","/frontweb/typeScript/basic-usage.html","/frontweb/typeScript/fanType.html","/frontweb/typeScript/tsAndvue3.html","/frontweb/vue/vue-Direactive.html","/frontweb/vue/vue-authority.html","/frontweb/vue/vue-in-action.html","/frontweb/vue/vue3LifeTime.html","/frontweb/vue/vueExtend.html","/frontweb/es5/crossDomain.html","/frontweb/es5/crossDomain2.html","/frontweb/vue/eventBus.html","/frontweb/vue/vue-pic.html","/frontweb/vue/vue-nextTick.html","/frontweb/es5/aboutAsync.html","/frontweb/es5/aboutEvent.html","/frontweb/es5/aboutThis.html","/frontweb/es5/lazyLoad.html","/frontweb/es5/throttle.html","/frontweb/es6/usePnpm.html","/frontweb/nodejs/","/java/advance/Arthas.html","/frontweb/vite/","/java/advance/Collection.html","/java/advance/CompileJdk11.html","/java/advance/Future.html","/java/advance/ThreadPool.html","/java/jvm/SetObjectNull.html","/java/jvm/StringAdd.html","/java/advance/ProxyInJava.html","/java/advance/MysqlMasterSlave.html","/java/advance/Synchronized.html","/java/base/CustomLRU.html","/java/base/ConstantPool.html","/java/base/IntegerConstantPool.html","/java/base/Serialization.html","/java/base/String.html","/other/books/ebooks.html","/other/database/CPUOverLoad.html","/other/database/SQLOptimization.html","/other/essay/2022-04-12.html","/other/docker/ServiceInstall.html","/other/docker/Docker.html","/other/essay/Jenkins.html","/other/linux/","/other/git/branch01.html","/other/git/branch02.html","/other/git/rebaseAndMerge.html","/other/git/GitCommands.html","/other/git/","/other/git/fatal.html","/other/git/gitConflict.html","/other/git/gitRebase.html","/other/git/gitwork.html","/other/git/mergeBranch.html","/other/git/stash.html","/other/linux/Curl.html","/other/markdown/","/other/sono/SUC.html","/other/training/draw.html","/other/training/SSO.html","/other/training/CloudServiceTraining.html","/other/web/Jwt.html","/other/web/BuildWebProject.html","/other/web/Restful.html","/other/web/","/other/web/OAUTH_LOGIN.html","/java/framework/mybatis/mybatis.html","/java/framework/security/Authorization.html","/java/framework/security/SecurityFilterChain.html","/java/framework/security/OAuth2Authentication.html","/java/framework/security/Session.html","/java/framework/security/CustomAuthenticationProvider.html","/java/framework/security/OncePerRequestFilter.html","/java/framework/springboot/CollectionInject.html","/java/framework/spring/CircularDependency.html","/java/framework/spring/Annotation.html","/java/framework/spring/SpringIOC.html","/java/other/maven/build%E6%A0%87%E7%AD%BE.html","/java/other/maven/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html","/frontweb/es5/","/frontweb/vue/","/other/web/Http.html","/other/web/Cookie.html","/frontweb/vue/vue3Emit.html","/java/advance/Concurrent.html","/java/advance/ImplementSameInterface.html","/java/framework/security/SSO.html","/other/essay/DeployGithubPage.html","/other/database/MysqlCollate.html","/other/essay/BTree.html","/other/essay/CDN.html","/other/essay/CloudService.html","/other/essay/ChromeDevTools.html","/java/other/log/logback.html","/java/framework/spring/SpringCache.html","/java/framework/spring/SpringAOP.html","/java/advance/ParentDelegationClassLoader.html","/other/pve/","/other/pve/ddns%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6.html","/other/pve/firewall.html","/other/windows/WSL.html","/java/framework/security/DelegatingFilterProxy.html","/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html","/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html","/frontweb/nodejs/%E5%AE%89%E8%A3%85%E9%97%AE%E9%A2%98.html","/other/essay/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B.html","/other/essay/elasticSearch%E6%93%8D%E4%BD%9C.html","/other/essay/elk%E9%83%A8%E7%BD%B2.html","/frontweb/vue/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4%E7%9A%84%E5%AE%9E%E8%B7%B5.html","/frontweb/vue/elementui%E8%A1%A8%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C.html","/other/essay/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%BC%95%E5%AF%BC.html","/myserver/%E6%97%81%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3.html","/other/linux/%E6%88%AA%E5%9B%BE.html","/myserver/x86_openwrt.html","/other/essay/windows%E4%B8%8B%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C.html","/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html","/myserver/","/myserver/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE.html","/myserver/%E8%87%AA%E5%BB%BAnas.html","/other/software/%E7%A0%B4%E8%A7%A3%E8%BD%AF%E4%BB%B6.html","/other/linux/ShareBetweenWindowsAndLinux.html","/java/framework/springboot/%E9%83%A8%E7%BD%B2.html","/frontweb/es6/promise.html","/other/hardware/CPU.html","/other/linux/firewall.html","/frontweb/es6/js%E4%B8%AD%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.html","/java/framework/spring/SpringMVC.html","/java/framework/springcloud/SpringCloudGateway.html","/java/framework/security/PreAuthorize.html","/designpattern/","/java/other/locateproblem/%E5%90%84%E7%A7%8D%E9%97%AE%E9%A2%98.html","/java/framework/security/spring-security-oauth2-authorization-server.html","/java/framework/springboot/Swagger.html","/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html","/other/web/RefreshToken.html","/java/framework/spring/Validator.html","/java/other/locateproblem/Undertow.xxxNotFount.html","/other/database/Recurse.html","/other/git/BatchDeleteGitHubRepo.html","/java/framework/spring/SpringSourceAnalize.html","/java/advance/IO-model1.html","/java/jvm/volatile.html","/java/jvm/ClassLoader.html","/java/jvm/MemoryModel.html","/java/jvm/ObjectReference.html","/other/computerprinciple/TCP-IP.html","/java/framework/springboot/Http2.html","/other/linux/MountDisk.html","/cpp/other/1.html","/cpp/other/2.html","/cpp/study/","/java/advance/NativeMethod.html","/java/advance/IO-Model.html","/java/framework/security/CustomTokenAuthentication.html","/other/oauth2/01.html","/other/oauth2/02.html","/other/oauth2/03.html","/other/oauth2/04.html","/other/oauth2/05.html","/other/oauth2/06.html","/other/oauth2/07.html","/other/oauth2/08.html","/other/oauth2/09.html","/other/oauth2/10.html","/other/oauth2/11.html","/other/oauth2/12.html","/other/oauth2/13.html","/other/oauth2/14.html","/other/oauth2/","/other/rabbitmq/","/java/framework/security/CustomLoginPage.html","/java/framework/security/","/java/other/gradle/","/java/other/gradle/wrapper.html","/other/linux/InstallMysqlWithDocker.html","/java/framework/security/note.html","/java/other/maven/import.html","/other/linux/Wifi.html","/other/linux/MultiNetworkCard.html","/java/other/locateproblem/TooManyOpenFiles.html","/java/other/JdkVersion.html","/other/git/proxy.html","/other/linux/CentOS.html","/java/other/","/java/advance/io.html","/other/","/other/books/","/slide.html","/java/advance/ThreadLocal.html","/other/git/reset.html","/other/tools/Idea.html","/java/framework/mybatis/MybatisPlusDataSource.html","/java/other/locateproblem/","/java/other/maven/","/java/other/maven/multiModule.html","/java/other/maven/problem.html","/other/linux/wsl.html","/other/tools/SoftWare.html","/other/tools/VsCode.html","/other/training/","/other/linux/Samba.html","/other/database/MysqlNote.html","/other/database/MysqlRemoteConnect.html","/other/linux/TcpDump.html","/other/database/","/java/jvm/NewObject.html","/other/linux/Manjaro.html","/other/tools/","/java/framework/springboot/AOPLog.html","/other/distributeservice/Nacos.html","/other/distributeservice/","/other/essay/","/other/linux/CommonUsedCMD.html","/frontweb/es6/","/other/essay/TyporaPicgo.html","/java/framework/spring/BeanPostProcessor.html","/java/framework/springboot/SpringBootAutoConfiguration.html","/other/database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD.html","/frontweb/","/java/","/java/advance/","/java/base/","/java/jvm/","/java/framework/mybatis/","/java/framework/spring/OncePerRequestFilter.html","/java/framework/spring/","/java/framework/springboot/","/java/framework/spring/SpringExtensionPoint.html","/java/framework/spring/DesignPatternInSpring.html","/other/distributeservice/DistributeLock.html"],il=Nn(tg);ze(il);const mr=n=>{const e=gn(),t=fn(),a=fe();return y(()=>{var i;const s=n??((i=t.value.blog)==null?void 0:i.key)??"";if(!s)return console.warn("useBlogCategory: key not found"),{path:"/",map:{}};if(!il.value[s])throw new Error(`useBlogCategory: key ${s} is invalid`);const l=il.value[s][a.value],o={path:l.path,map:{}};for(const c in l.map){const r=l.map[c];o.map[c]={path:r.path,items:[]};for(const u of r.indexes){const{path:d,meta:k}=It(gr[u]);o.map[c].items.push({path:d,info:k})}e.value.path===r.path&&(o.currentItems=o.map[c].items)}return o})},ag={article:{"/":{path:"/article/",indexes:[113,114,108,109,106,45,46,5,115,47,116,117,118,119,120,121,122,38,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,96,97,0,1,147,112,148,2,3,4,149,150,151,152,153,154,155,84,102,156,157,103,107,158,159,160,49,161,162,163,76,164,165,166,167,168,169,42,170,104,88,105,89,92,171,93,90,172,94,86,59,77,10,11,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,100,12,101,80,13,191,192,14,37,15,60,78,16,17,193,194,195,196,197,198,199,200,74,201,202,203,18,19,20,21,63,64,204,205,206,6,7,8,9,207,208,209,210,211,212,213,214,65,55,215,216,56,33,217,81,22,79,218,75,82,23,24,25,26,219,111,110,220,221,222,39,223,57,98,34,27,28,35,61,43,58,44,36,99,50,224,225,226,227,228,229,230,231,62,83,29,30,66,67,68,69,70,71,72,73,31,51,87,95,32,232,40,91,233,85,234,235,48,52,53,54,41,236,237,238,239,240,241,242,243,244,245,246,247,248]}},star:{"/":{path:"/star/",indexes:[5]}},timeline:{"/":{path:"/timeline/",indexes:[116,117,118,119,120,121,122,38,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,96,97,0,1,147,112,148,2,3,4,149,150,151,152,153,154,155,84,102,156,157,103,107,158,159,160,49,161,162,163,76,164,165,166,167,168,169,42,170,104,88,105,89,92,171,93,90,172,94,86,59,77,10,11,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,100,12,101,80,13,191,192,14,37,15,60,78,16,17,193,194,195,196,197,198,199,200,74,201,202,203,18,19,20,21,63,64,204,205,206,6,7,8,9,207,208,209,210,211,212,213,214,65,55,215,216,56,33,217,81,22,79,218,75,82,23,24,25,26,219,111,110,220,221,222,39,223,57,98,34,27,28,35,61,43,58,115,44,36,99,50,224,225,226,227,228,229,230,231,62,83,106,47,45,46,29,30,66,67,68,69,70,71,72,73,31,51,87,95,32,232,40,108,91,233,109,5,85,234,235,113,48,52,53,54,41,236,237,238,239,240,241,242,243,244,245,246,247,114,248]}}},cl=Nn(ag);ze(cl);const gs=n=>{const e=fn(),t=fe();return y(()=>{var o;const a=n??((o=e.value.blog)==null?void 0:o.key)??"";if(!a)return console.warn("useBlogType: key not found"),{path:"/",items:[]};if(!cl.value[a])throw new Error(`useBlogType: key ${n} is invalid`);const s=cl.value[a][t.value],l={path:s.path,items:[]};for(const i of s.indexes){const{path:c,meta:r}=It(gr[i]);l.items.push({path:c,info:r})}return l})},Xl=()=>p(un,{name:"lock"},()=>p("path",{d:"M787.168 952.268H236.832c-30.395 0-55.033-24.638-55.033-55.033V429.45c0-30.395 24.638-55.034 55.033-55.034h82.55V264.35c0-106.38 86.238-192.618 192.618-192.618S704.618 157.97 704.618 264.35v110.066h82.55c30.395 0 55.033 24.639 55.033 55.034v467.785c0 30.395-24.639 55.033-55.033 55.033zM484.483 672.046v115.122h55.034V672.046c31.99-11.373 55.033-41.605 55.033-77.496 0-45.592-36.958-82.55-82.55-82.55s-82.55 36.958-82.55 82.55c0 35.89 23.042 66.123 55.033 77.496zM622.067 264.35c0-60.788-49.28-110.067-110.067-110.067s-110.067 49.28-110.067 110.067v110.066h220.135V264.35z"}));Xl.displayName="LockIcon";var sg=[];const vr=Symbol.for("categoryMap"),xa=()=>{const n=wn(vr);if(!n)throw new Error("useCategoryMap() is called without provider.");return n},lg=()=>{const n=mr("category");ie(vr,n)},Aa=()=>{const n=Se(),e=ln();return y(()=>({...n.value.blog,...e.value.blog}))},fr=Symbol.for("tagMap"),La=()=>{const n=wn(fr);if(!n)throw new Error("useTagMap() is called without provider.");return n},og=()=>{const n=mr("tag");ie(fr,n)},pg=n=>{const e=ln();return y(()=>{const{[vn.author]:t}=n.value;return t?sa(t):t===!1?[]:sa(e.value.author,!1)})},ig=n=>{const e=xa();return y(()=>Ic(n.value[vn.category]).map(t=>({name:t,path:e.value.map[t].path})))},cg=n=>{const e=La();return y(()=>Pc(n.value[vn.tag]).map(t=>({name:t,path:e.value.map[t].path})))},rg=n=>y(()=>{const{[vn.date]:e}=n.value;return ql(e)}),ug=n=>{const e=Ot(n,"info"),t=Aa(),a=pg(e),s=ig(e),l=cg(e),o=rg(e),i=Rc(),c=y(()=>({author:a.value,category:s.value,date:o.value,localizedDate:e.value[vn.localizedDate]||"",tag:l.value,isOriginal:e.value[vn.isOriginal]||!1,readingTime:e.value[vn.readingTime]||null,readingTimeLocale:e.value[vn.readingTime]&&i.value?Dc(e.value[vn.readingTime],i.value):null,pageview:n.path})),r=y(()=>t.value.articleInfo);return{info:c,items:r}},_r=Symbol(""),Sa=()=>{const n=wn(_r);if(!n)throw new Error("useArticles() is called without provider.");return n},dg=()=>{const n=gs("article");ie(_r,n)},yr=Symbol(""),Yl=()=>{const n=wn(yr);if(!n)throw new Error("useStars() is called without provider.");return n},hg=()=>{const n=gs("star");ie(yr,n)},br=Symbol(""),Ql=()=>{const n=wn(br);if(!n)throw new Error("useTimelines() is called without provider.");return n},kg=()=>{const n=gs("timeline"),e=y(()=>{const t=[];return n.value.items.forEach(({info:a,path:s})=>{const l=ql(a[vn.date]);if(l){const o=l.getFullYear(),i=l.getMonth()+1,c=l.getDate();(!t[0]||t[0].year!==o)&&t.unshift({year:o,items:[]}),t[0].items.push({date:`${i}/${c}`,info:a,path:s})}}),{...n.value,config:t.reverse()}});ie(br,e)},gg=()=>{dg(),lg(),hg(),og(),kg()};var mg=V({name:"SocialMedia",setup(){const n=Aa(),e=it(),t=y(()=>{const a=n.value.medias;return a?ot(a).map(([s,l])=>({name:s,icon:eg[s],url:l})):[]});return()=>t.value.length?p("div",{class:"vp-social-medias"},t.value.map(({name:a,icon:s,url:l})=>p("a",{class:"vp-social-media",href:l,rel:"noopener noreferrer",target:"_blank","aria-label":a,...e.value?{}:{"data-balloon-pos":"up"},innerHTML:s}))):null}}),Zl=V({name:"BloggerInfo",setup(){const n=Aa(),e=va(),t=ln(),a=Sa(),s=xa(),l=La(),o=Ql(),i=ba(),c=y(()=>{var k;return n.value.name||((k=sa(t.value.author)[0])==null?void 0:k.name)||e.value.title}),r=y(()=>n.value.avatar||t.value.logo),u=y(()=>t.value.blogLocales),d=y(()=>n.value.intro);return()=>{const{article:k,category:g,tag:_,timeline:b}=u.value,E=[[a.value.path,a.value.items.length,k],[s.value.path,re(s.value.map).length,g],[l.value.path,re(l.value.map).length,_],[o.value.path,o.value.items.length,b]];return p("div",{class:"vp-blogger-info",vocab:"https://schema.org/",typeof:"Person"},[p("div",{class:"vp-blogger",...d.value?{style:{cursor:"pointer"},"aria-label":u.value.intro,"data-balloon-pos":"down",role:"link",onClick:()=>i(d.value)}:{}},[r.value?p("img",{class:["vp-blogger-avatar",{round:n.value.roundAvatar}],src:xn(r.value),property:"image",alt:"Blogger Avatar",loading:"lazy"}):null,c.value?p("div",{class:"vp-blogger-name",property:"name"},c.value):null,n.value.description?p("div",{class:"vp-blogger-description",innerHTML:n.value.description}):null,d.value?p("meta",{property:"url",content:xn(d.value)}):null]),p("div",{class:"vp-blog-counts"},E.map(([w,L,f])=>p(Tn,{class:"vp-blog-count",to:w},()=>[p("div",{class:"count"},L),p("div",f)]))),p(mg)])}}});const no=()=>p(un,{name:"category"},()=>p("path",{d:"M148.41 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H148.41c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.311-40.31zM147.556 553.478H429.73c22.263 0 40.311 18.048 40.311 40.31v282.176c0 22.263-18.048 40.312-40.31 40.312H147.555c-22.263 0-40.311-18.049-40.311-40.312V593.79c0-22.263 18.048-40.311 40.31-40.311zM593.927 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H593.927c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.31-40.31zM730.22 920.502H623.926c-40.925 0-74.22-33.388-74.22-74.425V623.992c0-41.038 33.387-74.424 74.425-74.424h222.085c41.038 0 74.424 33.226 74.424 74.067v114.233c0 10.244-8.304 18.548-18.547 18.548s-18.548-8.304-18.548-18.548V623.635c0-20.388-16.746-36.974-37.33-36.974H624.13c-20.585 0-37.331 16.747-37.331 37.33v222.086c0 20.585 16.654 37.331 37.126 37.331H730.22c10.243 0 18.547 8.304 18.547 18.547 0 10.244-8.304 18.547-18.547 18.547z"}));no.displayName="CategoryIcon";const eo=()=>p(un,{name:"tag"},()=>p("path",{d:"M939.902 458.563L910.17 144.567c-1.507-16.272-14.465-29.13-30.737-30.737L565.438 84.098h-.402c-3.215 0-5.726 1.005-7.634 2.913l-470.39 470.39a10.004 10.004 0 000 14.164l365.423 365.424c1.909 1.908 4.42 2.913 7.132 2.913s5.223-1.005 7.132-2.913l470.39-470.39c2.01-2.11 3.014-5.023 2.813-8.036zm-240.067-72.121c-35.458 0-64.286-28.828-64.286-64.286s28.828-64.285 64.286-64.285 64.286 28.828 64.286 64.285-28.829 64.286-64.286 64.286z"}));eo.displayName="TagIcon";const to=()=>p(un,{name:"timeline"},()=>p("path",{d:"M511.997 70.568c-243.797 0-441.429 197.633-441.429 441.435 0 243.797 197.632 441.429 441.43 441.429S953.431 755.8 953.431 512.002c0-243.796-197.637-441.434-441.435-441.434zm150.158 609.093-15.605 15.61c-8.621 8.615-22.596 8.615-31.215 0L472.197 552.126c-4.95-4.944-4.34-14.888-4.34-24.677V247.14c0-12.19 9.882-22.07 22.07-22.07h22.07c12.19 0 22.07 9.882 22.07 22.07v273.218l128.088 128.088c8.62 8.62 8.62 22.595 0 31.215zm0 0"}));to.displayName="TimelineIcon";const wr=()=>p(un,{name:"slides"},()=>p("path",{d:"M896 170.667v426.666a85.333 85.333 0 0 1-85.333 85.334h-256v61.184l192.597 115.584-43.861 73.13-148.736-89.173v95.275h-85.334v-95.318l-148.736 89.216-43.861-73.13 192.597-115.627v-61.141h-256A85.333 85.333 0 0 1 128 597.333V170.667H85.333V85.333h853.334v85.334H896zm-682.667 0v426.666h597.334V170.667H213.333zM426.667 512h-85.334V341.333h85.334V512zm128 0h-85.334V256h85.334v256zm128 0h-85.334V384h85.334v128z"}));wr.displayName="SlideIcon";const Er=()=>p(un,{name:"sticky"},()=>[p("path",{d:"m381.3 733.8l-161.9 118c-5.9 4.5-13.2 6.6-20.1 6.6-8.7 0-17.7-3.4-24.3-10-12.2-12.2-13.9-31.3-3.5-45.2l144.5-195.5-113.6-112.9c-11.1-11.1-13.2-28.4-5.5-42 5.5-8.7 52.1-76.4 155.5-51 1.8 0.3 3.5 0.3 5.6 0.7 4.2 0.3 9 0.7 14.2 1.7 21.9 3.5 60.8-13.9 94.5-42.7 32.3-27.5 53.1-59.4 53.1-81.6 0-5.2 0-10.8-0.3-16-0.7-20.8-2.1-52.8 21.5-76.4 28.1-28.1 72.9-30.6 103.9-5.2 0.6 0.3 1 1 1.7 1.7 16.7 16.3 187.5 187.2 189.3 188.9 14.5 14.6 22.9 34.4 22.9 55.3 0 20.8-8 40.2-22.9 54.8-23.7 23.6-56 22.6-77.1 21.6-4.9 0-10.5-0.4-15.7-0.4-20.8 0-45.8 14.6-70.5 41.3-34.3 37.5-55.5 85.8-53.8 107.7 0.7 6.9 2.1 19.1 2.4 20.8 25 101.4-42.7 147.6-50.7 152.8-13.9 8.4-31.6 6.3-42.7-4.8l-112.1-112.2z"})]);Er.displayName="StickyIcon";const ms=()=>p(un,{name:"article"},()=>p("path",{d:"M853.333 938.667H170.667A42.667 42.667 0 0 1 128 896V128a42.667 42.667 0 0 1 42.667-42.667h682.666A42.667 42.667 0 0 1 896 128v768a42.667 42.667 0 0 1-42.667 42.667zm-42.666-85.334V170.667H213.333v682.666h597.334zM298.667 256h170.666v170.667H298.667V256zm0 256h426.666v85.333H298.667V512zm0 170.667h426.666V768H298.667v-85.333zm256-384h170.666V384H554.667v-85.333z"}));ms.displayName="ArticleIcon";const xr=()=>p(un,{name:"book"},()=>p("path",{d:"M256 853.333h426.667A85.333 85.333 0 0 0 768 768V256a85.333 85.333 0 0 0-85.333-85.333H469.333a42.667 42.667 0 0 1 0-85.334h213.334A170.667 170.667 0 0 1 853.333 256v512a170.667 170.667 0 0 1-170.666 170.667H213.333A42.667 42.667 0 0 1 170.667 896V128a42.667 42.667 0 0 1 42.666-42.667h128A42.667 42.667 0 0 1 384 128v304.256l61.653-41.088a42.667 42.667 0 0 1 47.36 0l61.654 41.045V256A42.667 42.667 0 0 1 640 256v256a42.667 42.667 0 0 1-66.347 35.499l-104.32-69.547-104.32 69.547A42.667 42.667 0 0 1 298.667 512V170.667H256v682.666z"}));xr.displayName="BookIcon";const Ar=()=>p(un,{name:"link"},()=>p("path",{d:"M460.8 584.533c17.067 17.067 17.067 42.667 0 59.734-17.067 17.066-42.667 17.066-59.733 0-85.334-85.334-85.334-217.6 0-302.934L554.667 192C640 110.933 776.533 110.933 857.6 196.267c81.067 81.066 81.067 213.333 0 294.4l-68.267 64c0-34.134-4.266-68.267-17.066-102.4l21.333-21.334c51.2-46.933 55.467-128 4.267-179.2s-128-55.466-179.2-4.266c-4.267 0-4.267 4.266-4.267 4.266L465.067 401.067c-51.2 51.2-51.2 132.266-4.267 183.466m123.733-183.466C601.6 384 627.2 384 644.267 401.067c85.333 85.333 85.333 217.6 0 302.933l-153.6 149.333C405.333 934.4 268.8 934.4 187.733 849.067c-81.066-81.067-81.066-213.334 0-294.4l68.267-64c0 34.133 4.267 72.533 17.067 102.4L251.733 614.4C204.8 665.6 204.8 746.667 256 793.6c51.2 46.933 123.733 46.933 174.933 0l149.334-149.333c51.2-51.2 51.2-128 0-179.2-12.8-17.067-17.067-46.934 4.266-64z"}));Ar.displayName="LinkIcon";const Lr=()=>p(un,{name:"project"},()=>p("path",{d:"M987.456 425.152H864V295.296a36.48 36.48 0 0 0-36.544-36.544h-360l-134.08-128.256A9.344 9.344 0 0 0 327.04 128H36.48A36.48 36.48 0 0 0 0 164.544v676.608a36.48 36.48 0 0 0 36.544 36.544h797.76a36.672 36.672 0 0 0 33.92-22.848L1021.44 475.52a36.48 36.48 0 0 0-33.92-50.304zM82.304 210.304h215.424l136.64 130.752h347.328v84.096H198.848A36.672 36.672 0 0 0 164.928 448L82.304 652.8V210.304zM808.32 795.456H108.544l118.08-292.608h699.904L808.32 795.52z"}));Lr.displayName="ProjectIcon";const Sr=()=>p(un,{name:"friend"},()=>p("path",{d:"M860.16 213.333A268.373 268.373 0 0 0 512 186.027a267.52 267.52 0 0 0-348.16 404.48L428.8 855.893a118.613 118.613 0 0 0 166.4 0l264.96-265.386a267.52 267.52 0 0 0 0-377.174zM800 531.627l-264.96 264.96a32.427 32.427 0 0 1-46.08 0L224 530.347a183.04 183.04 0 0 1 0-256 182.187 182.187 0 0 1 256 0 42.667 42.667 0 0 0 60.587 0 182.187 182.187 0 0 1 256 0 183.04 183.04 0 0 1 3.413 256z"}));Sr.displayName="FriendIcon";const rl=()=>p(un,{name:"slide-down"},()=>p("path",{d:"M108.775 312.23c13.553 0 27.106 3.734 39.153 11.806l375.205 250.338 363.641-252.808c32.587-21.624 76.499-12.83 98.123 19.757 21.685 32.467 12.95 76.56-19.576 98.184l-402.854 278.89c-23.733 15.901-54.694 15.962-78.547.12L69.501 442.097c-32.647-21.685-41.441-65.777-19.817-98.304 13.734-20.54 36.201-31.563 59.09-31.563Z"}));rl.displayName="SlideDownIcon";const Cr=()=>p("svg",{xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink",class:"empty-icon",viewBox:"0 0 1024 1024",innerHTML:''});Cr.displayName="EmptyIcon";var vg=V({name:"ArticleItem",props:{info:{type:Object,required:!0},path:{type:String,required:!0}},slots:Object,setup(n,{slots:e}){const t=Ot(n,"info"),{info:a,items:s}=ug(n);return()=>{var k,g,_;const{[vn.title]:l,[vn.type]:o,[vn.isEncrypted]:i=!1,[vn.cover]:c,[vn.excerpt]:r,[vn.sticky]:u}=t.value,d=a.value;return p("div",{class:"vp-article-wrapper"},p("article",{class:"vp-article-item",vocab:"https://schema.org/",typeof:"Article"},[((k=e.cover)==null?void 0:k.call(e,{cover:c}))||(c?[p("img",{class:"vp-article-cover",src:xn(c),loading:"lazy"}),p("meta",{property:"image",content:xn(c)})]:[]),u?p(Er):null,p(Tn,{to:n.path},()=>{var b;return((b=e.title)==null?void 0:b.call(e,{title:l,isEncrypted:i,type:o}))||p("header",{class:"vp-article-title"},[i?p(Xl):null,o===Vc.slide?p(wr):null,p("span",{property:"headline"},l)])}),((g=e.excerpt)==null?void 0:g.call(e,{excerpt:r}))||(r?p("div",{class:"vp-article-excerpt",innerHTML:r}):null),p("hr",{class:"vp-article-hr"}),((_=e.info)==null?void 0:_.call(e,{info:d}))||p(ir,{info:d,...s.value?{items:s.value}:{}})]))}}}),fg=V({name:"Pagination",props:{total:{type:Number,default:10},perPage:{type:Number,default:10},current:{type:Number,default:1}},emits:["updateCurrentPage"],setup(n,{emit:e}){let t;const a=ln(),s=K(""),l=y(()=>a.value.paginationLocales),o=y(()=>Math.ceil(n.total/n.perPage)),i=y(()=>!!o.value&&o.value!==1),c=y(()=>o.value<7?!1:n.current>4),r=y(()=>o.value<7?!1:n.current{const{current:g}=n;let _=1,b=o.value;const E=[];o.value>=7&&(g<=4&&g4&&g>=o.value-3?(b=o.value,_=o.value-4):o.value>7&&(_=g-2,b=g+2));for(let w=_;w<=b;w++)E.push(w);return E}),d=g=>e("updateCurrentPage",g),k=g=>{const _=parseInt(g,10);_<=o.value&&_>0?d(_):t.pop(`${l.value.errorText.replace(/\$page/gu,o.value.toString())}`)};return En(()=>{t=new z1}),()=>p("div",{class:"vp-pagination"},i.value?p("nav",{class:"vp-pagination-list"},[p("div",{class:"vp-pagination-number "},[n.current>1?p("div",{class:"prev",role:"navigation",unselectable:"on",onClick:()=>d(n.current-1)},l.value.prev):null,c.value?[p("div",{role:"navigation",onClick:()=>d(1)},1),p("div",{class:"ellipsis"},"...")]:null,u.value.map(g=>p("div",{key:g,class:{active:n.current===g},role:"navigation",onClick:()=>d(g)},g)),r.value?[p("div",{class:"ellipsis"},"..."),p("div",{role:"navigation",onClick:()=>d(o.value)},o.value)]:null,n.currentd(n.current+1)},l.value.next):null]),p("div",{class:"vp-pagination-nav"},[p("label",{for:"navigation-text"},`${l.value.navigate}: `),p("input",{id:"navigation-text",value:s.value,onInput:({target:g})=>{s.value=g.value},onKeydown:g=>{g.key==="Enter"&&(g.preventDefault(),k(s.value))}}),p("button",{class:"vp-pagination-button",role:"navigation",title:l.value.action,onClick:()=>k(s.value)},l.value.action)])]):[])}}),ao=V({name:"ArticleList",props:{items:{type:Array,default:()=>[]}},setup(n){const e=Ge(),t=me(),a=Aa(),s=K(1),l=y(()=>a.value.articlePerPage||10),o=y(()=>n.items.slice((s.value-1)*l.value,s.value*l.value)),i=async c=>{s.value=c;const r={...e.query};!(r.page===c.toString()||c===1&&!r.page)&&(c===1?delete r.page:r.page=c.toString(),await t.push({path:e.path,query:r}))};return En(()=>{const{page:c}=e.query;i(c?Number(c):1),dn(s,()=>{const r=document.querySelector("#article-list").getBoundingClientRect().top+window.scrollY;setTimeout(()=>{window.scrollTo(0,r)},100)})}),()=>p("div",{id:"article-list",class:"vp-article-list",role:"feed"},o.value.length?[...o.value.map(({info:c,path:r},u)=>p(rn,{appear:!0,delay:u*.04},()=>p(vg,{key:r,info:c,path:r}))),p(fg,{current:s.value,perPage:l.value,total:n.items.length,onUpdateCurrentPage:i})]:p(Cr))}}),Tr=V({name:"CategoryList",setup(){const n=gn(),e=xa();return()=>p("ul",{class:"vp-category-list"},ot(e.value.map).sort(([,t],[,a])=>a.items.length-t.items.length).map(([t,{path:a,items:s}])=>p("li",{class:["vp-category",`vp-category${ks(t,9)}`,{active:a===n.value.path}]},p(Tn,{to:a},()=>[t,p("span",{class:"count"},s.length)]))))}}),Or=V({name:"TagList",setup(){const n=fn(),e=La(),t=a=>{var s;return a===((s=n.value.blog)==null?void 0:s.name)};return()=>p("ul",{class:"tag-list-wrapper"},ot(e.value.map).sort(([,a],[,s])=>s.items.length-a.items.length).map(([a,{path:s,items:l}])=>p("li",{class:["tag",`tag${ks(a,9)}`,{active:t(a)}]},p(Tn,{to:s},()=>[a,p("span",{class:"tag-num"},l.length)]))))}}),_g=V({name:"TimelineList",setup(){const n=ln(),e=Ql(),t=ba(),a=y(()=>n.value.blogLocales.timeline);return()=>p("div",{class:"timeline-list-wrapper"},[p("div",{class:"timeline-list-title",onClick:()=>t(e.value.path)},[p(to),p("span",{class:"num"},e.value.items.length),a.value]),p("hr"),p("div",{class:"timeline-content"},p("ul",{class:"timeline-list"},e.value.config.map(({year:s,items:l},o)=>p(rn,{appear:!0,delay:.08*(o+1)},()=>p("li",[p("h3",{class:"timeline-year"},s),p("ul",{class:"timeline-year-wrapper"},l.map(({date:i,info:c,path:r})=>p("li",{class:"timeline-item"},[p("span",{class:"timeline-date"},i),p(Tn,{class:"timeline-title",to:r},()=>c[vn.title])])))])))))])}});const yg={article:ms,category:no,tag:eo,timeline:to};var Ir=V({name:"InfoList",setup(){const n=ln(),e=Sa(),t=xa(),a=y(()=>re(t.value.map).length),s=Yl(),l=La(),o=y(()=>re(l.value.map).length),i=ba(),c=K("article"),r=y(()=>n.value.blogLocales);return()=>p("div",{class:"vp-blog-infos"},[p("div",{class:"vp-blog-type-switcher"},ot(yg).map(([u,d])=>p("button",{type:"button",class:"vp-blog-type-button",onClick:()=>{c.value=u}},p("div",{class:["icon-wrapper",{active:c.value===u}],"aria-label":r.value[u],"data-balloon-pos":"up"},p(d))))),p(rn,()=>c.value==="article"?p("div",{class:"vp-star-article-wrapper"},[p("div",{class:"title",onClick:()=>i(e.value.path)},[p(ms),p("span",{class:"num"},e.value.items.length),r.value.article]),p("hr"),s.value.items.length?p("ul",{class:"vp-star-articles"},s.value.items.map(({info:u,path:d},k)=>p(rn,{appear:!0,delay:.08*(k+1)},()=>p("li",{class:"vp-star-article"},p(Tn,{to:d},()=>u[vn.title]))))):p("div",{class:"vp-star-article-empty"},r.value.empty.replace("$text",r.value.star))]):c.value==="category"?p("div",{class:"vp-category-wrapper"},[a.value?[p("div",{class:"title",onClick:()=>i(t.value.path)},[p(no),p("span",{class:"num"},a.value),r.value.category]),p("hr"),p(rn,{delay:.04},()=>p(Tr))]:p("div",{class:"vp-category-empty"},r.value.empty.replace("$text",r.value.category))]):c.value==="tag"?p("div",{class:"vp-tag-wrapper"},[o.value?[p("div",{class:"title",onClick:()=>i(l.value.path)},[p(eo),p("span",{class:"num"},o.value),r.value.tag]),p("hr"),p(rn,{delay:.04},()=>p(Or))]:p("div",{class:"vp-tag-empty"},r.value.empty.replace("$text",r.value.tag))]):p(rn,()=>p(_g)))])}}),vs=V({name:"BlogWrapper",slots:Object,setup(n,{slots:e}){const{isMobile:t}=wa();return()=>[p(Kl),p(Gl,{noSidebar:!0,noToc:!0},{default:()=>e.default(),navScreenBottom:()=>p(Zl),...t.value?{sidebar:()=>p(Ir)}:{}})]}});const Pr=()=>p("aside",{class:"vp-blog-info-wrapper"},[p(rn,()=>p(Zl)),p(rn,{delay:.04},()=>p(Ir))]);Pr.displayName="InfoPanel";var fs=Pr,bg=V({name:"BlogPage",setup(){const n=gn(),e=fn(),t=xa(),a=La();return()=>{const{key:s="",name:l=""}=e.value.blog||{},o=l?s==="category"?t.value.map[l].items:s==="tag"?a.value.map[l].items:[]:[];return p(vs,()=>p("div",{class:"vp-page vp-blog"},p("div",{class:"blog-page-wrapper"},[p("main",{id:"main-content",class:"vp-blog-main"},[p(rn,()=>s==="category"?p(Tr):s==="tag"?p(Or):null),l?p(rn,{appear:!0,delay:.24},()=>p(ao,{key:n.value.path,items:o})):null]),p(rn,{delay:.16},()=>p(fs,{key:"blog"}))])))}}});const wg="//theme-hope-assets.vuejs.press/hero/default.jpg";var Eg=V({name:"BlogHero",slots:Object,setup(n,{slots:e}){const t=fn(),a=va(),s=Nn(),l=y(()=>t.value.heroFullScreen??!1),o=y(()=>{const{heroText:c,heroImage:r,heroImageDark:u,heroAlt:d,heroImageStyle:k,tagline:g}=t.value;return{text:c??a.value.title??"Hello",tagline:g??"",image:r?xn(r):null,imageDark:u?xn(u):null,alt:d||c||"",style:k,isFullScreen:l.value}}),i=y(()=>{const{bgImage:c,bgImageDark:r,bgImageStyle:u}=t.value;return{image:An(c)?xn(c):c===!1?null:wg,imageDark:An(r)?xn(r):null,bgStyle:u,isFullScreen:l.value}});return()=>{var c,r;return t.value.hero===!1?null:p("div",{ref:s,class:["vp-blog-hero",{fullscreen:l.value,"no-bg":!i.value.image}]},[((c=e.heroBg)==null?void 0:c.call(e,i.value))||[i.value.image?p("div",{class:["vp-blog-mask",{light:i.value.imageDark}],style:[{background:`url(${i.value.image}) center/cover no-repeat`},i.value.bgStyle]}):null,i.value.imageDark?p("div",{class:"vp-blog-mask dark",style:[{background:`url(${i.value.imageDark}) center/cover no-repeat`},i.value.bgStyle]}):null],((r=e.heroInfo)==null?void 0:r.call(e,o.value))||[p(rn,{appear:!0,type:"group",delay:.04},()=>[o.value.image?p("img",{key:"light",class:["vp-blog-hero-image",{light:o.value.imageDark}],style:o.value.style,src:o.value.image,alt:o.value.alt}):null,o.value.imageDark?p("img",{key:"dark",class:"vp-blog-hero-image dark",style:o.value.style,src:o.value.imageDark,alt:o.value.alt}):null]),p(rn,{appear:!0,delay:.08},()=>o.value.text?p("h1",{class:"vp-blog-hero-title"},o.value.text):null),p(rn,{appear:!0,delay:.12},()=>o.value.tagline?p("p",{class:"vp-blog-hero-description",innerHTML:o.value.tagline}):null)],o.value.isFullScreen?p("button",{type:"button",class:"slide-down-button",onClick:()=>{window.scrollTo({top:s.value.clientHeight,behavior:"smooth"})}},[p(rl),p(rl)]):null])}}});const xg=["link","article","book","project","friend"];var Ag=V({name:"ProjectPanel",components:{ArticleIcon:ms,BookIcon:xr,FriendIcon:Sr,LinkIcon:Ar,ProjectIcon:Lr},props:{items:{type:Array,required:!0}},setup(n){const e=it(),t=ba(),a=(s="",l="icon")=>xg.includes(s)?p(ee(`${s}-icon`)):Je(s)?p("img",{class:"vp-project-image",src:s,alt:l}):us(s)?p("img",{class:"vp-project-image",src:xn(s),alt:l}):p(Vn,{icon:s});return()=>p("div",{class:"vp-project-panel"},n.items.map(({icon:s,link:l,name:o,desc:i},c)=>p("div",{class:["vp-project-card",{[`project${c%9}`]:!e.value}],onClick:()=>t(l)},[a(s,o),p("div",{class:"vp-project-name"},o),p("div",{class:"vp-project-desc"},i)])))}}),Lg=V({name:"BlogHome",setup(){const n=Sa(),e=fn(),t=y(()=>e.value.projects??[]);return()=>p("div",{class:"vp-page vp-blog"},[p(Eg),p("div",{class:"blog-page-wrapper"},[p("main",{id:"main-content",class:"vp-blog-main"},[t.value.length?p(rn,{appear:!0,delay:.16},()=>p(Ag,{items:t.value})):null,p(rn,{appear:!0,delay:.24},()=>p(ao,{items:n.value.items}))]),p(rn,{appear:!0,delay:.16},()=>p(fs,{key:"blog"}))]),p(rn,{appear:!0,delay:.28},()=>p(Wl))])}});const jr=()=>p(vs,()=>p(Lg));jr.displayName="BlogHomeLayout";var Sg=jr,Cg=V({name:"ArticleType",setup(){const n=gn(),e=fe(),t=ln(),a=Sa(),s=Yl(),l=y(()=>{const o=t.value.blogLocales;return[{text:o.all,path:a.value.path},{text:o.star,path:s.value.path},...sg.map(({key:i,path:c})=>({text:o[i],path:c.replace(/^\//,e.value)}))]});return()=>p("ul",{class:"vp-article-type-wrapper"},l.value.map(o=>p("li",{class:["vp-article-type",{active:o.path===n.value.path}]},p(Tn,{to:o.path},()=>o.text))))}}),Tg=V({name:"BlogPage",setup(){const n=gs(),e=fn(),t=gn(),a=Sa(),s=Yl(),l=y(()=>{const{key:o="",type:i}=e.value.blog||{};return o==="star"?s.value.items:i==="type"&&o?n.value.items:a.value.items});return()=>p(vs,()=>p("div",{class:"vp-page vp-blog"},p("div",{class:"blog-page-wrapper"},[p("main",{id:"main-content",class:"vp-blog-main"},[p(rn,()=>p(Cg)),p(rn,{appear:!0,delay:.24},()=>p(ao,{key:t.value.path,items:l.value}))]),p(rn,{delay:.16},()=>p(fs,{key:"blog"}))])))}}),Og=V({name:"TimelineItems",setup(){const n=Aa(),e=ln(),t=Ql(),a=y(()=>n.value.timeline||e.value.blogLocales.timelineTitle),s=y(()=>t.value.config.map(({year:l})=>({title:l.toString(),level:2,slug:l.toString(),children:[]})));return()=>p("div",{class:"timeline-wrapper"},p("ul",{class:"timeline-content"},[p(rn,()=>p("li",{class:"motto"},a.value)),p(cr,{items:s.value}),t.value.config.map(({year:l,items:o},i)=>p(rn,{appear:!0,delay:.08*(i+1),type:"group"},()=>[p("h3",{key:"title",id:l,class:"timeline-year-title"},p("span",l)),p("li",{key:"content",class:"timeline-year-list"},[p("ul",{class:"timeline-year-wrapper"},o.map(({date:c,info:r,path:u})=>p("li",{class:"timeline-item"},[p("span",{class:"timeline-date"},c),p(Tn,{class:"timeline-title",to:u},()=>r[vn.title])])))])]))]))}});const Dr=()=>p(vs,()=>p("div",{class:"vp-page vp-blog"},p("div",{class:"blog-page-wrapper"},[p("main",{id:"main-content",class:"vp-blog-main"},[p(rn,{appear:!0,delay:.24},()=>p(Og))]),p(rn,{delay:.16},()=>p(fs,{key:"blog"}))])));Dr.displayName="Timeline";var Ig=Dr,Pg={};const dt="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),qs=Array.from({length:64},(n,e)=>e),Na=n=>Array(n).fill(-1),Pe=[...Na(46),0,1,...qs.slice(54,64),...Na(7),...qs.slice(2,28),...Na(6),...qs.slice(28,54),...Na(5)],Sp=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],Cp=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971e4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409e3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],Rr=[1332899944,1700884034,1701343084,1684370003,1668446532,1869963892],ul=(n,e)=>{if(e<=0||e>n.length)throw Error(`Illegal len: ${e}`);let t=0,a,s;const l=[];for(;t>2&63]),a=(a&3)<<4,t>=e){l.push(dt[a&63]);break}if(s=n[t++]&255,a|=s>>4&15,l.push(dt[a&63]),a=(s&15)<<2,t>=e){l.push(dt[a&63]);break}s=n[t++]&255,a|=s>>6&3,l.push(dt[a&63]),l.push(dt[s&63])}return l.join("")},jg=(n,e)=>{const t=n.length;let a=0,s=0,l,o,i,c,r,u;const d=[];for(;a>>0,r|=(o&48)>>4,d.push(String.fromCharCode(r)),++s>=e||a>=t)||(u=n.charCodeAt(a++),i=u>>0,r|=(i&60)>>2,d.push(String.fromCharCode(r)),++s>=e||a>=t)));)u=n.charCodeAt(a++),c=u>>0,r|=c,d.push(String.fromCharCode(r)),++s;return d.map(k=>k.charCodeAt(0))},Dg=(n,e)=>{let t=null;for(typeof n=="number"&&(t=n,n=()=>null);t!==null||(t=n())!==null;)t<128?e(t&127):t<2048?(e(t>>6&31|192),e(t&63|128)):t<65536?(e(t>>12&15|224),e(t>>6&63|128),e(t&63|128)):(e(t>>18&7|240),e(t>>12&63|128),e(t>>6&63|128),e(t&63|128)),t=null},Rg=(n,e)=>{let t,a=null;for(;(t=a!==null?a:n())!==null;){if(t>=55296&&t<=57343&&(a=n())!==null&&a>=56320&&a<=57343){e((t-55296)*1024+a-56320+65536),a=null;continue}e(t)}a!==null&&e(a)},Fg=(n,e)=>{Rg(n,function(t){Dg(t,e)})},Bg=typeof process=="object"&&Pg.NEXT_RUNTIME==="edge"?setTimeout:typeof setImmediate=="function"?setImmediate:typeof process=="object"&&typeof process.nextTick=="function"?process.nextTick:setTimeout,Mg=n=>{const e=[];let t=0;return Fg(()=>t>=n.length?null:n.charCodeAt(t++),a=>{e.push(a)}),e},oa=(n,e,t,a)=>{let s,l=n[e],o=n[e+1];return l^=t[0],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[1],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[2],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[3],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[4],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[5],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[6],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[7],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[8],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[9],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[10],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[11],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[12],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[13],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[14],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[15],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[16],n[e]=o^t[17],n[e+1]=l,n},kt=(n,e)=>{let t=0;for(let a=0;a<4;++a)t=t<<8|n[e]&255,e=(e+1)%n.length;return{key:t,offp:e}},Tp=(n,e,t)=>{const a=e.length,s=t.length;let l=0,o=[0,0],i;for(let c=0;c{const s=t.length,l=a.length;let o=0,i=[0,0],c;for(let r=0;r{const l=Rr.slice(),o=l.length;if(t<4||t>31){const k=new Error(`Illegal number of rounds (4-31): ${t}`);if(a===!1)return Promise.reject(k);throw k}if(e.length!==16){const k=new Error(`Illegal salt length: ${e.length} != 16`);if(a===!1)return Promise.reject(k);throw k}t=1<>>0;let i,c,r=0,u;Int32Array?(i=new Int32Array(Sp),c=new Int32Array(Cp)):(i=Sp.slice(),c=Cp.slice()),Vg(e,n,i,c);const d=()=>{if(r100)););}else{for(r=0;r<64;r++)for(u=0;u>1;u++)oa(l,u<<1,i,c);const k=[];for(r=0;r>24&255)>>>0),k.push((l[r]>>16&255)>>>0),k.push((l[r]>>8&255)>>>0),k.push((l[r]&255)>>>0);return a===!1?Promise.resolve(k):k}if(a===!1)return new Promise(k=>Bg(()=>{d().then(k)}))};if(a===!1)return d();{let k;for(;;)if(typeof(k=d())<"u")return k||[]}},qg=n=>{try{let e;typeof window<"u"?e=window.crypto??window.msCrypto:e=globalThis.crypto;const t=new Uint32Array(n);return e==null||e.getRandomValues(t),Array.from(t)}catch{throw Error("WebCryptoAPI is not available")}},Ng=(n=10)=>{if(typeof n!="number")throw Error("Illegal arguments: "+typeof n);n<4?n=4:n>31&&(n=31);const e=[];return e.push("$2a$"),n<10&&e.push("0"),e.push(n.toString()),e.push("$"),e.push(ul(qg(16),16)),e.join("")};function Hg(n,e,t,a){if(typeof n!="string"||typeof e!="string"){const g=new Error("Invalid string / salt: Not a string");if(t===!1)return Promise.reject(g);throw g}let s,l;if(e.charAt(0)!=="$"||e.charAt(1)!=="2"){const g=new Error("Invalid salt version: "+e.substring(0,2));if(t===!1)return Promise.reject(g);throw g}if(e.charAt(2)==="$")s="\0",l=3;else{if(s=e.charAt(2),s!=="a"&&s!=="b"&&s!=="y"||e.charAt(3)!=="$"){const g=Error("Invalid salt revision: "+e.substring(2,4));if(t===!1)return Promise.reject(g);throw g}l=4}if(e.charAt(l+2)>"$"){const g=new Error("Missing salt rounds");if(t===!1)return Promise.reject(g);throw g}const o=parseInt(e.substring(l,l+1),10)*10,i=parseInt(e.substring(l+1,l+2),10),c=o+i,r=e.substring(l+3,l+25);n+=s>="a"?"\0":"";const u=Mg(n),d=jg(r,16),k=g=>{const _=[];return _.push("$2"),s>="a"&&_.push(s),_.push("$"),c<10&&_.push("0"),_.push(c.toString()),_.push("$"),_.push(ul(d,d.length)),_.push(ul(g,Rr.length*4-1)),_.join("")};return t===!1?Op(u,d,c,!1).then(g=>k(g)):k(Op(u,d,c,!0))}const $g=(n,e=10)=>{if(typeof e=="number"&&(e=Ng(e)),typeof n!="string"||typeof e!="string")throw Error("Illegal arguments: "+typeof n+", "+typeof e);return Hg(n,e,!0)},dl=(n,e)=>{if(typeof n!="string"||typeof e!="string")throw Error("Illegal arguments: "+typeof n+", "+typeof e);return e.length!==60?!1:$g(n,e.substring(0,e.length-31))===e};var Fr=V({name:"PasswordModal",props:{full:Boolean},emits:["verify"],setup(n,{emit:e}){const t=fn(),a=ln(),s=K(""),l=K(!1),o=K(!1),i=y(()=>a.value.encryptLocales);let c=null;const r=()=>{c&&clearTimeout(c),l.value=!1,e("verify",s.value,o.value),Ue().then(()=>{l.value=!0,c=setTimeout(()=>{l.value=!1},1e3)})};return()=>p("div",{class:["vp-decrypt-layer",{expand:n.full||t.value.home}]},p("div",{class:"vp-decrypt-modal"},[p("div",{class:["vp-decrypt-hint",{tried:l.value}]},l.value?i.value.errorHint:p(Xl,{"aria-label":i.value.iconLabel})),p("div",{class:"vp-decrypt-input"},[p("input",{type:"password",value:s.value,placeholder:i.value.placeholder,onInput:({target:u})=>{s.value=u.value},onKeydown:({key:u})=>{u==="Enter"&&r()}})]),p("div",{class:"vp-remember-password"},[p("input",{type:"checkbox",value:o.value,onChange:()=>o.value=!o.value}),i.value.remember]),p("button",{type:"button",class:"vp-decrypt-submit",onClick:()=>r()},"OK")]))}});const Br=()=>{const n=Se();return y(()=>n.value.encrypt||{})},Ip="VUEPRESS_HOPE_GLOBAL_TOKEN",zg=()=>{const n=Br(),e=hs(Ip,""),t=rc(Ip,""),a=y(()=>{const{global:l=!1,admin:o=[]}=n.value;return l&&o.length>0}),s=y(()=>{if(a.value){if(e.value)return n.value.admin.some(l=>dl(e.value,l));if(t.value)return n.value.admin.some(l=>dl(t.value,l))}return!1});return{isEncrypted:a,isDecrypted:s,validate:(l,o=!1)=>{(o?e:t).value=l}}},Ns=(n="",e)=>!!n&&dl(n,e),Pp="VUEPRESS_HOPE_PATH_TOKEN",Ug=()=>{const n=gn(),e=Br(),t=hs(Pp,{}),a=rc(Pp,{}),s=o=>ma(e.value.config)?re(e.value.config).filter(i=>St(decodeURI(o),i)).sort((i,c)=>c.length-i.length):[],l=o=>{const i=s(o);if(i.length>0){const{config:c={}}=e.value;return{isEncrypted:!0,isDecrypted:i.some(r=>t.value[r]&&c[r].some(u=>Ns(t.value[r],u))||a.value[r]&&c[r].some(u=>Ns(a.value[r],u)))}}return{isDecrypted:!1,isEncrypted:!1}};return{status:y(()=>l(n.value.path)),getStatus:l,validate:(o,i=!1)=>{const{config:c={}}=e.value,r=s(n.value.path);for(const u of r)if(c[u].filter(d=>Ns(o,d))){(i?t:a).value[u]=o;break}}}};var Jg=V({name:"GlobalEncrypt",slots:Object,setup(n,{slots:e}){const{isDecrypted:t,isEncrypted:a,validate:s}=zg(),l=K(!1);return En(()=>{l.value=!0}),()=>p(ur,()=>a.value?l.value?t.value?e.default():p(Fr,{full:!0,onVerify:s}):null:e.default())}}),Gg=V({name:"LocalEncrypt",slots:Object,setup(n,{slots:e}){const{status:t,validate:a}=Ug(),s=K(!1);return En(()=>{s.value=!0}),()=>{const{isEncrypted:l,isDecrypted:o}=t.value;return l?s.value?o?e.default():p(Fr,{full:!0,onVerify:a}):null:e.default()}}});P2(n=>{const e=n.t,t=n.I!==!1,a=n.i;return t?{title:e,content:a?()=>[p(Vn,{icon:a}),e]:null,order:n.O,index:n.I}:null});const Wg=ae({enhance:({app:n,router:e})=>{const{scrollBehavior:t}=e.options;e.options.scrollBehavior=async(...a)=>(await rr().wait(),t(...a)),s0(n),n.component("HopeIcon",Vn),n.component("BloggerInfo",Zl),n.component("GlobalEncrypt",Jg),n.component("LocalEncrypt",Gg)},setup:()=>{l0(),i0(),gg()},layouts:{Layout:Q0,NotFound:ng,BlogCategory:bg,BlogHome:Sg,BlogType:Tg,Timeline:Ig}}),Ha=[Uk,A2,I2,B2,q2,z2,W2,Z2,r1,d1,L1,$1,Wg],Kg=JSON.parse(`{"base":"/","lang":"zh-CN","title":"ChenSino","description":"ChenSino's Blog","head":[],"locales":{}}`);var Bt=Nn(Kg),Xg=ck,Yg=()=>{const n=Rk({history:Xg(Fl("/")),routes:[{name:"vuepress-route",path:"/:catchAll(.*)",components:{}}],scrollBehavior:(e,t,a)=>a||(e.hash?{el:e.hash}:{top:0})});return n.beforeResolve(async(e,t)=>{if(e.path!==t.path||t===be){const a=It(e.path);if(a.path!==e.path)return a.path;const s=await a.loader();e.meta={...a.meta,_pageChunk:s}}else e.path===t.path&&(e.meta=t.meta)}),n},Qg=n=>{n.component("ClientOnly",rs),n.component("Content",ec),n.component("RouteLink",Tn)},Zg=(n,e,t)=>{const a=y(()=>e.currentRoute.value.path),s=Sl((E,w)=>({get(){return E(),e.currentRoute.value.meta._pageChunk},set(L){e.currentRoute.value.meta._pageChunk=L,w()}})),l=y(()=>Ye.resolveLayouts(t)),o=y(()=>Ye.resolveRouteLocale(Bt.value.locales,a.value)),i=y(()=>Ye.resolveSiteLocaleData(Bt.value,o.value)),c=y(()=>s.value.comp),r=y(()=>s.value.data),u=y(()=>r.value.frontmatter),d=y(()=>Ye.resolvePageHeadTitle(r.value,i.value)),k=y(()=>Ye.resolvePageHead(d.value,u.value,i.value)),g=y(()=>Ye.resolvePageLang(r.value,i.value)),_=y(()=>Ye.resolvePageLayout(r.value,l.value)),b={layouts:l,pageData:r,pageComponent:c,pageFrontmatter:u,pageHead:k,pageHeadTitle:d,pageLang:g,pageLayout:_,redirects:Zi,routeLocale:o,routePath:a,routes:ta,siteData:Bt,siteLocaleData:i};return n.provide(Vl,b),Object.defineProperties(n.config.globalProperties,{$frontmatter:{get:()=>u.value},$head:{get:()=>k.value},$headTitle:{get:()=>d.value},$lang:{get:()=>g.value},$page:{get:()=>r.value},$routeLocale:{get:()=>o.value},$site:{get:()=>Bt.value},$siteLocale:{get:()=>i.value},$withBase:{get:()=>xn}}),b},nm=()=>{const n=Mk(),e=Yi();let t=[];const a=()=>{n.value.forEach(o=>{const i=em(o);i&&t.push(i)})},s=()=>{const o=[];return n.value.forEach(i=>{const c=tm(i);c&&o.push(c)}),o},l=()=>{document.documentElement.lang=e.value;const o=s();t.forEach((i,c)=>{const r=o.findIndex(u=>i.isEqualNode(u));r===-1?(i.remove(),delete t[c]):o.splice(r,1)}),o.forEach(i=>document.head.appendChild(i)),t=[...t.filter(i=>!!i),...o]};ie(Nk,l),En(()=>{a(),dn(n,l,{immediate:!1})})},em=([n,e,t=""])=>{const a=Object.entries(e).map(([i,c])=>An(c)?`[${i}=${JSON.stringify(c)}]`:c===!0?`[${i}]`:"").join(""),s=`head > ${n}${a}`;return Array.from(document.querySelectorAll(s)).find(i=>i.innerText===t)||null},tm=([n,e,t])=>{if(!An(n))return null;const a=document.createElement(n);return ma(e)&&Object.entries(e).forEach(([s,l])=>{An(l)?a.setAttribute(s,l):l===!0&&a.setAttribute(s,"")}),An(t)&&a.appendChild(document.createTextNode(t)),a},am=fh,sm=async()=>{var t;const n=am({name:"Vuepress",setup(){var l;nm();for(const o of Ha)(l=o.setup)==null||l.call(o);const a=Ha.flatMap(({rootComponents:o=[]})=>o.map(i=>p(i))),s=Vk();return()=>[p(s.value),a]}}),e=Yg();Qg(n),Zg(n,e,Ha);for(const a of Ha)await((t=a.enhance)==null?void 0:t.call(a,{app:n,router:e,siteData:Bt}));return n.use(e),{app:n,router:e}};sm().then(({app:n,router:e})=>{e.isReady().then(()=>{n.mount("#app")})});export{Li as a,Si as b,om as c,sm as createVueApp,In as d,pm as e,lm as o,ee as r,Fu as w}; + */const pn={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
    '},status:null,set:n=>{const e=pn.isStarted();n=Fs(n,pn.settings.minimum,1),pn.status=n===1?null:n;const t=pn.render(!e),a=t.querySelector(pn.settings.barSelector),s=pn.settings.speed,l=pn.settings.easing;return t.offsetWidth,U2(o=>{qa(a,{transform:"translate3d("+gp(n)+"%,0,0)",transition:"all "+s+"ms "+l}),n===1?(qa(t,{transition:"none",opacity:"1"}),t.offsetWidth,setTimeout(function(){qa(t,{transition:"all "+s+"ms linear",opacity:"0"}),setTimeout(function(){pn.remove(),o()},s)},s)):setTimeout(()=>o(),s)}),pn},isStarted:()=>typeof pn.status=="number",start:()=>{pn.status||pn.set(0);const n=()=>{setTimeout(()=>{pn.status&&(pn.trickle(),n())},pn.settings.trickleSpeed)};return pn.settings.trickle&&n(),pn},done:n=>!n&&!pn.status?pn:pn.inc(.3+.5*Math.random()).set(1),inc:n=>{let e=pn.status;return e?(typeof n!="number"&&(n=(1-e)*Fs(Math.random()*e,.1,.95)),e=Fs(e+n,0,.994),pn.set(e)):pn.start()},trickle:()=>pn.inc(Math.random()*pn.settings.trickleRate),render:n=>{if(pn.isRendered())return document.getElementById("nprogress");mp(document.documentElement,"nprogress-busy");const e=document.createElement("div");e.id="nprogress",e.innerHTML=pn.settings.template;const t=e.querySelector(pn.settings.barSelector),a=n?"-100":gp(pn.status||0),s=document.querySelector(pn.settings.parent);return qa(t,{transition:"all 0 linear",transform:"translate3d("+a+"%,0,0)"}),s!==document.body&&mp(s,"nprogress-custom-parent"),s==null||s.appendChild(e),e},remove:()=>{vp(document.documentElement,"nprogress-busy"),vp(document.querySelector(pn.settings.parent),"nprogress-custom-parent");const n=document.getElementById("nprogress");n&&J2(n)},isRendered:()=>!!document.getElementById("nprogress")},Fs=(n,e,t)=>nt?t:n,gp=n=>(-1+n)*100,U2=function(){const n=[];function e(){const t=n.shift();t&&t(e)}return function(t){n.push(t),n.length===1&&e()}}(),qa=function(){const n=["Webkit","O","Moz","ms"],e={};function t(o){return o.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(i,c){return c.toUpperCase()})}function a(o){const i=document.body.style;if(o in i)return o;let c=n.length;const r=o.charAt(0).toUpperCase()+o.slice(1);let u;for(;c--;)if(u=n[c]+r,u in i)return u;return o}function s(o){return o=t(o),e[o]??(e[o]=a(o))}function l(o,i,c){i=s(i),o.style[i]=c}return function(o,i){for(const c in i){const r=i[c];r!==void 0&&Object.prototype.hasOwnProperty.call(i,c)&&l(o,c,r)}}}(),gc=(n,e)=>(typeof n=="string"?n:$l(n)).indexOf(" "+e+" ")>=0,mp=(n,e)=>{const t=$l(n),a=t+e;gc(t,e)||(n.className=a.substring(1))},vp=(n,e)=>{const t=$l(n);if(!gc(n,e))return;const a=t.replace(" "+e+" "," ");n.className=a.substring(1,a.length-1)},$l=n=>(" "+(n.className||"")+" ").replace(/\s+/gi," "),J2=n=>{n&&n.parentNode&&n.parentNode.removeChild(n)},G2=()=>{En(()=>{const n=me(),e=new Set;e.add(n.currentRoute.value.path),n.beforeEach(t=>{e.has(t.path)||pn.start()}),n.afterEach(t=>{e.add(t.path),pn.done()})})},W2=ae({setup(){G2()}}),K2=JSON.parse('{"encrypt":{"config":{"/guide/encrypt.html":["$2a$10$jpVnxNMXKfCo.XhkHr1/EOGRp7ulzvTIp7i.Donb2QFOYopTVV59i"]}},"fullscreen":true,"author":{"name":"ChenSino","url":"https://ChenSino.github.io"},"logo":"/logo.svg","repo":"ChenSino/ChenSino.github.io","docsDir":"docs","docsBranch":"dev","footer":"鄂ICP备2024079959号-1","copyright":"","displayFooter":true,"themeColor":true,"pageInfo":["Author","Original","Date","Category","Tag","ReadingTime"],"blog":{"name":"ChenSino","avatar":"https://ddns.chensina.cn:29000/afatpig/blog/20220802180305.png","roundAvatar":true,"description":"洛星星的爸爸","intro":"https://chensina.cn/","medias":{"Baidu":"https://example.com","Weibo":"https://example.com","Zhihu":"https://example.com"}},"locales":{"/":{"lang":"zh-CN","navbarLocales":{"langName":"简体中文","selectLangAriaLabel":"选择语言"},"metaLocales":{"author":"作者","date":"写作日期","origin":"原创","views":"访问量","category":"分类","tag":"标签","readingTime":"阅读时间","words":"字数","toc":"此页内容","prev":"上一页","next":"下一页","lastUpdated":"上次编辑于","contributors":"贡献者","editLink":"编辑此页","print":"打印"},"blogLocales":{"article":"文章","articleList":"文章列表","category":"分类","tag":"标签","timeline":"时间轴","timelineTitle":"昨日不在","all":"全部","intro":"个人介绍","star":"星标","empty":"$text 为空"},"paginationLocales":{"prev":"上一页","next":"下一页","navigate":"跳转到","action":"前往","errorText":"请输入 1 到 $page 之前的页码!"},"outlookLocales":{"themeColor":"主题色","darkmode":"外观","fullscreen":"全屏"},"encryptLocales":{"iconLabel":"文章已加密","placeholder":"输入密码","remember":"记住密码","errorHint":"请输入正确的密码"},"routeLocales":{"skipToContent":"跳至主要內容","notFoundTitle":"页面不存在","notFoundMsg":["这里什么也没有","我们是怎么来到这儿的?","这 是 四 零 四 !","看起来你访问了一个失效的链接"],"back":"返回上一页","home":"带我回家","openInNewWindow":"Open in new window"},"navbar":["/home","/",{"text":"Java","icon":"java","prefix":"/java/","children":[{"text":"Java基础","icon":"java","link":"base/Serialization"},{"text":"Java进阶","icon":"java","link":"advance/ProxyInJava"},{"text":"Java虚拟机","icon":"java","link":"jvm/NewObject"},{"text":"Java框架","icon":"java","prefix":"framework/","children":[{"text":"Spring","link":"spring/"},{"text":"Security","link":"security/"},{"text":"SpringBoot","link":"springboot/"},{"text":"Mybatis","link":"mybatis/"}]},{"text":"其他","icon":"other","prefix":"other/","children":[{"text":"Maven","link":"maven/"},{"text":"Gradle","icon":"java","link":"gradle/"},{"text":"Java问题定位","icon":"java","link":"locateproblem/"},{"text":"Java版本","icon":"java","link":"JdkVersion.md"}]}]},{"text":"前端","icon":"javascript","prefix":"/frontweb/","children":[{"text":"Vue","icon":"vue","link":"vue/"},{"text":"Vite","icon":"vue","link":"vite/"},{"text":"ES5","icon":"javascript","link":"es5/"},{"text":"ES6","icon":"javascript","link":"es6/"},{"text":"TypeScript","icon":"javascript","link":"typeScript/"},{"text":"NodeJS","icon":"nodejs","link":"nodejs/"}]},{"text":"设计模式","icon":"java","link":"/designpattern/"},{"text":"前后分离项目搭建","icon":"app","link":"/other/web/README.md"},{"text":"C++学习","icon":"app","link":"/cpp/study/README.md"},{"text":"家庭服务器","icon":"app","link":"/myserver/README.md"},{"text":"其他","icon":"others","prefix":"/other/","children":[{"text":"Web","icon":"vue","link":"web/"},{"text":"Git","icon":"git","link":"git/GitCommands"},{"text":"Linux","icon":"linux","link":"linux/CommonUsedCMD"},{"text":"Docker","icon":"ubuntu","link":"docker/Docker.md"},{"text":"DataBase","icon":"java","link":"database/CPUOverLoad"},{"text":"MarkDown","icon":"markdown","link":"markdown/"},{"text":"工具软件","icon":"software","link":"tools/idea"},{"text":"小组分享","icon":"share","link":"training/CloudServiceTraining"},{"text":"随笔杂记","icon":"activity","link":"essay/2022-04-12"},{"text":"电子书资源","icon":"app","link":"books/ebooks"},{"text":"分布式微服务","icon":"class","link":"distributeservice/DistributeLock"},{"text":"OAuth2.0","icon":"class","link":"oauth2/"},{"text":"PVE虚拟机","icon":"class","link":"pve/"}]}],"sidebar":{"/java/":[{"text":"Java 基础","icon":"discover","prefix":"base/","collapsible":true,"children":"structure"},{"text":"Java 进阶","icon":"blog","prefix":"advance/","collapsible":true,"children":"structure"},{"text":"Java 虚拟机","icon":"write","prefix":"jvm/","collapsible":true,"children":"structure"},{"text":"Java框架","icon":"write","prefix":"framework/","collapsible":true,"children":"structure"}],"/java/other/":["JdkVersion",{"text":"Maven","icon":"discover","prefix":"maven/","collapsible":true,"children":"structure"},{"text":"Gradle","icon":"discover","prefix":"gradle/","collapsible":true,"children":"structure"},{"text":"Java问题定位","icon":"java","prefix":"locateproblem/","collapsible":true,"children":"structure"}],"/frontweb/":[{"text":"Vue","icon":"vue","prefix":"vue/","collapsible":true,"children":"structure"},{"text":"Vite","icon":"vue","prefix":"vite/","collapsible":true,"children":[]},{"text":"ECMAScript 5","icon":"write","prefix":"es5/","collapsible":true,"children":"structure"},{"text":"ECMAScript 6","icon":"blog","prefix":"es6/","collapsible":true,"children":"structure"},{"text":"TypeScript","icon":"typescript","prefix":"typeScript/","collapsible":true,"children":"structure"},{"text":"NodeJS","icon":"nodejs","prefix":"nodejs/","collapsible":true,"children":"structure"}],"/designpattern/":"structure","/cpp/":[{"text":"基础语法学习","icon":"app","prefix":"study/","collapsible":true,"children":"structure"},{"text":"其他","icon":"app","prefix":"other/","collapsible":true,"children":"structure"}],"/myserver/":"structure","/other/":[{"text":"Git","icon":"git","prefix":"git/","collapsible":true,"children":"structure"},{"text":"Linux","icon":"linux","prefix":"linux/","collapsible":true,"children":"structure"},{"text":"数据库","icon":"repo","prefix":"database/","collapsible":true,"children":"structure"},{"text":"MarkDown","icon":"markdown","prefix":"markdown/","collapsible":true,"children":"structure"},{"text":"工具软件","icon":"software","prefix":"tools/","collapsible":true,"children":"structure"},{"text":"小组分享","icon":"share","prefix":"training/","collapsible":true,"children":"structure"},{"text":"随笔分享","icon":"share","prefix":"essay/","collapsible":true,"children":"structure"},{"text":"web","icon":"share","prefix":"web/","collapsible":true,"children":"structure"},{"text":"分布式微服务","icon":"share","prefix":"distributeservice/","collapsible":true,"children":"structure"},{"text":"OAuth2.0","icon":"share","prefix":"oauth2/","collapsible":true,"children":"structure"},{"text":"Docker","icon":"ubuntu","prefix":"docker/","collapsible":true,"children":"structure"},{"text":"PVE","icon":"class","prefix":"pve/","collapsible":true,"children":"structure"}]}}}}'),X2=K(K2),mc=()=>X2,vc=Symbol(""),Y2=()=>{const n=wn(vc);if(!n)throw new Error("useThemeLocaleData() is called without provider.");return n},Q2=(n,e)=>{const{locales:t,...a}=n;return{...a,...t==null?void 0:t[e]}},Z2=ae({enhance({app:n}){const e=mc(),t=n._context.provides[Vl],a=y(()=>Q2(e.value,t.routeLocale.value));n.provide(vc,a),Object.defineProperties(n.config.globalProperties,{$theme:{get(){return e.value}},$themeLocale:{get(){return a.value}}})}}),n1=/\b(?:Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini)/i,e1=()=>typeof window<"u"&&window.navigator&&"userAgent"in window.navigator&&n1.test(navigator.userAgent),t1=({delay:n=500,duration:e=2e3,locales:t,selector:a,showInMobile:s})=>{const{copy:l,copied:o}=r2({legacy:!0,copiedDuring:e}),i=fa(t),c=gn(),r=k=>{if(!k.hasAttribute("copy-code-registered")){const g=document.createElement("button");g.type="button",g.classList.add("vp-copy-code-button"),g.innerHTML='
    ',g.setAttribute("aria-label",i.value.copy),g.setAttribute("data-copied",i.value.copied),k.parentElement&&k.parentElement.insertBefore(g,k),k.setAttribute("copy-code-registered","")}},u=()=>{Ue().then(()=>tc(n)).then(()=>{a.forEach(k=>{document.querySelectorAll(k).forEach(r)})})},d=(k,g,_)=>{let{innerText:b=""}=g;/language-(shellscript|shell|bash|sh|zsh)/.test(k.classList.toString())&&(b=b.replace(/^ *(\$|>) /gm,"")),l(b).then(()=>{_.classList.add("copied"),dn(o,()=>{_.classList.remove("copied"),_.blur()},{once:!0})})};En(()=>{const k=!e1()||s;k&&u(),Fn("click",g=>{const _=g.target;if(_.matches('div[class*="language-"] > button.copy')){const b=_.parentElement,E=_.nextElementSibling;E&&d(b,E,_)}else if(_.matches('div[class*="language-"] div.vp-copy-icon')){const b=_.parentElement,E=b.parentElement,w=b.nextElementSibling;w&&d(E,w,b)}}),dn(()=>c.value.path,()=>{k&&u()})})};var a1={"/":{copy:"复制代码",copied:"已复制"}},s1=['.theme-hope-content div[class*="language-"] pre'];const l1=500,o1=2e3,p1=a1,i1=s1,c1=!1,r1=ae({setup:()=>{t1({selector:i1,locales:p1,duration:o1,delay:l1,showInMobile:c1})}}),u1=()=>{Fn("beforeprint",()=>{document.querySelectorAll("details").forEach(n=>{n.open=!0})})},d1=ae({enhance:({app:n})=>{},setup:()=>{u1()}});let h1={};const fc=Symbol(""),k1=()=>wn(fc),g1=n=>{n.provide(fc,h1)},m1='
    ',v1=n=>An(n)?Array.from(document.querySelectorAll(n)):n.map(e=>Array.from(document.querySelectorAll(e))).flat(),_c=n=>new Promise((e,t)=>{n.complete?e({type:"image",element:n,src:n.src,width:n.naturalWidth,height:n.naturalHeight,alt:n.alt,msrc:n.src}):(n.onload=()=>e(_c(n)),n.onerror=a=>t(a))}),f1=n=>{const{isSupported:e,toggle:t}=Hl();n.on("uiRegister",()=>{e.value&&n.ui.registerElement({name:"fullscreen",order:7,isButton:!0,html:'',onClick:()=>{t()}}),n.ui.registerElement({name:"download",order:8,isButton:!0,tagName:"a",html:{isCustomSVG:!0,inner:'',outlineID:"pswp__icn-download"},onInit:(a,s)=>{a.setAttribute("download",""),a.setAttribute("target","_blank"),a.setAttribute("rel","noopener"),s.on("change",()=>{a.setAttribute("href",s.currSlide.data.src)})}}),n.ui.registerElement({name:"bulletsIndicator",className:"photo-swipe-bullets-indicator",appendTo:"wrapper",onInit:(a,s)=>{const l=[];let o=-1;for(let i=0;i{s.goTo(l.indexOf(r.target))},l.push(c),a.appendChild(c)}s.on("change",()=>{o>=0&&l[o].classList.remove("active"),l[s.currIndex].classList.add("active"),o=s.currIndex})}})})},_1=(n,e,t=!0)=>h(()=>import("./photoswipe.esm-GXRgw7eJ.js"),__vite__mapDeps([])).then(({default:a})=>{let s=null;const l=n.map(o=>({html:m1,element:o,msrc:o.src}));return n.forEach((o,i)=>{const c=()=>{s==null||s.destroy(),s=new a({preloaderDelay:0,showHideAnimationType:"zoom",...e,dataSource:l,index:i,...t?{closeOnVerticalDrag:!0,wheelToZoom:!1}:{}}),f1(s),s.addFilter("thumbEl",()=>o),s.addFilter("placeholderSrc",()=>o.src),s.init()};o.getAttribute("photo-swipe")||(o.style.cursor="zoom-in",o.addEventListener("click",()=>{c()}),o.addEventListener("keypress",({key:r})=>{r==="Enter"&&c()}),o.setAttribute("photo-swipe","")),_c(o).then(r=>{l.splice(i,1,r),s==null||s.refreshSlideContent(i)})}),t?Fn("wheel",()=>{s==null||s.close()}):()=>{}}),y1=({selector:n,locales:e,delay:t=500,scrollToClose:a=!0})=>{const s=k1(),l=fa(e),o=gn(),i=fn();let c=null;const r=()=>{const{photoSwipe:u}=i.value;u!==!1&&Ue().then(()=>tc(t)).then(async()=>{const d=An(u)?u:n;c=await _1(v1(d),{...s,...l.value},a)})};En(()=>{r(),dn(()=>o.value.path,()=>{c==null||c(),r()})}),da(()=>{c==null||c()})};var b1={"/":{closeTitle:"关闭",downloadTitle:"下载图片",fullscreenTitle:"切换全屏",zoomTitle:"缩放",arrowPrevTitle:"上一个 (左箭头)",arrowNextTitle:"下一个 (右箭头)"}};const w1=".theme-hope-content :not(a) > img:not([no-view])",E1=b1,x1=800,A1=!0,L1=ae({enhance:({app:n})=>{g1(n)},setup:()=>{y1({selector:w1,delay:x1,locales:E1,scrollToClose:A1})}}),S1=n=>n instanceof Element?document.activeElement===n&&(["TEXTAREA","SELECT","INPUT"].includes(n.tagName)||n.hasAttribute("contenteditable")):!1,C1=(n,e)=>e.some(t=>{if(An(t))return t===n.key;const{key:a,ctrl:s=!1,shift:l=!1,alt:o=!1}=t;return a===n.key&&s===n.ctrlKey&&l===n.shiftKey&&o===n.altKey}),T1=/[^\x00-\x7F]/,O1=n=>n.split(/\s+/g).map(e=>e.trim()).filter(e=>!!e),fp=n=>n.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),_p=(n,e)=>{const t=e.join(" "),a=O1(n);if(T1.test(n))return a.some(o=>t.toLowerCase().indexOf(o)>-1);const s=n.endsWith(" ");return new RegExp(a.map((o,i)=>a.length===i+1&&!s?`(?=.*\\b${fp(o)})`:`(?=.*\\b${fp(o)}\\b)`).join("")+".+","gi").test(t)},I1=({input:n,hotKeys:e})=>{if(e.value.length===0)return;const t=a=>{n.value&&C1(a,e.value)&&!S1(a.target)&&(a.preventDefault(),n.value.focus())};En(()=>{document.addEventListener("keydown",t)}),Il(()=>{document.removeEventListener("keydown",t)})},P1=[{title:"ChenSino",headers:[],path:"/",pathLocale:"/",extraFields:[]},{title:"Home",headers:[],path:"/home.html",pathLocale:"/",extraFields:[]},{title:"幻灯片页",headers:[{level:2,title:"幻灯片演示",slug:"幻灯片演示",link:"#幻灯片演示",children:[]},{level:2,title:"标注幻灯片",slug:"标注幻灯片",link:"#标注幻灯片",children:[]},{level:2,title:"标注幻灯片",slug:"标注幻灯片-1",link:"#标注幻灯片-1",children:[]},{level:2,title:"Markdown",slug:"markdown",link:"#markdown",children:[]},{level:2,title:"Markdown",slug:"markdown-1",link:"#markdown-1",children:[{level:3,title:"这是一个 H3",slug:"这是一个-h3",link:"#这是一个-h3",children:[]}]},{level:2,title:"Markdown",slug:"markdown-2",link:"#markdown-2",children:[]},{level:2,title:"Markdown",slug:"markdown-3",link:"#markdown-3",children:[]},{level:2,title:"Markdown",slug:"markdown-4",link:"#markdown-4",children:[]},{level:2,title:"Markdown",slug:"markdown-5",link:"#markdown-5",children:[]},{level:2,title:"布局",slug:"布局",link:"#布局",children:[]},{level:2,title:"布局",slug:"布局-1",link:"#布局-1",children:[]},{level:2,title:"布局",slug:"布局-2",link:"#布局-2",children:[]},{level:2,title:"布局",slug:"布局-3",link:"#布局-3",children:[{level:3,title:"背景",slug:"背景",link:"#背景",children:[]}]},{level:2,title:"动画片段",slug:"动画片段",link:"#动画片段",children:[]},{level:2,title:"动画片段",slug:"动画片段-1",link:"#动画片段-1",children:[]},{level:2,title:"动画片段",slug:"动画片段-2",link:"#动画片段-2",children:[{level:3,title:"动画 class",slug:"动画-class",link:"#动画-class",children:[]}]},{level:2,title:"动画片段",slug:"动画片段-3",link:"#动画片段-3",children:[{level:3,title:"动画 class",slug:"动画-class-1",link:"#动画-class-1",children:[]}]},{level:2,title:"动画片段",slug:"动画片段-4",link:"#动画片段-4",children:[{level:3,title:"多个动画片段",slug:"多个动画片段",link:"#多个动画片段",children:[]}]},{level:2,title:"动画片段",slug:"动画片段-5",link:"#动画片段-5",children:[{level:3,title:"顺序",slug:"顺序",link:"#顺序",children:[]}]},{level:2,title:"渐变",slug:"渐变",link:"#渐变",children:[]},{level:2,title:"渐变",slug:"渐变-1",link:"#渐变-1",children:[]},{level:2,title:"渐变",slug:"渐变-2",link:"#渐变-2",children:[{level:3,title:"过渡动画",slug:"过渡动画",link:"#过渡动画",children:[]}]},{level:2,title:"功能",slug:"功能",link:"#功能",children:[]},{level:2,title:"功能",slug:"功能-1",link:"#功能-1",children:[{level:3,title:"代码",slug:"代码",link:"#代码",children:[]}]},{level:2,title:"功能",slug:"功能-2",link:"#功能-2",children:[{level:3,title:"预览模式",slug:"预览模式",link:"#预览模式",children:[]}]},{level:2,title:"功能",slug:"功能-3",link:"#功能-3",children:[{level:3,title:"全屏模式",slug:"全屏模式",link:"#全屏模式",children:[]}]},{level:2,title:"功能",slug:"功能-4",link:"#功能-4",children:[{level:3,title:"缩放",slug:"缩放",link:"#缩放",children:[]}]},{level:2,title:"结束",slug:"结束",link:"#结束",children:[]}],path:"/slide.html",pathLocale:"/",extraFields:[]},{title:"JavaScript成神之路",headers:[],path:"/frontweb/",pathLocale:"/",extraFields:[]},{title:"README",headers:[{level:2,title:"说明",slug:"说明",link:"#说明",children:[]}],path:"/designpattern/",pathLocale:"/",extraFields:[]},{title:"Builder Pattern",headers:[{level:2,title:"建造者模式",slug:"建造者模式",link:"#建造者模式",children:[]},{level:2,title:"mybatis中",slug:"mybatis中",link:"#mybatis中",children:[]}],path:"/designpattern/%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"Template Method",headers:[{level:2,title:"定义",slug:"定义",link:"#定义",children:[]},{level:2,title:"例子",slug:"例子",link:"#例子",children:[]}],path:"/designpattern/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"Strategy Pattern",headers:[{level:3,title:"策略模式【CHATGPT回答】",slug:"策略模式【chatgpt回答】",link:"#策略模式【chatgpt回答】",children:[]},{level:3,title:"Spring中策略模式的使用",slug:"spring中策略模式的使用",link:"#spring中策略模式的使用",children:[]},{level:3,title:"我的工作中用到的策略模式",slug:"我的工作中用到的策略模式",link:"#我的工作中用到的策略模式",children:[]}],path:"/designpattern/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"Decorator Design Pattern",headers:[{level:2,title:"定义",slug:"定义",link:"#定义",children:[]},{level:2,title:"框架中使用",slug:"框架中使用",link:"#框架中使用",children:[]}],path:"/designpattern/%E8%A3%85%E9%A5%B0%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"Chain of Responsibility Pattern",headers:[{level:3,title:"定义",slug:"定义",link:"#定义",children:[]},{level:3,title:"Spring中的责任链",slug:"spring中的责任链",link:"#spring中的责任链",children:[]}],path:"/designpattern/%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"主要功能与配置演示",headers:[{level:2,title:"目录",slug:"目录",link:"#目录",children:[]}],path:"/guide/",pathLocale:"/",extraFields:[]},{title:"布局与功能禁用",headers:[],path:"/guide/disable.html",pathLocale:"/",extraFields:[]},{title:"Markdown 展示",headers:[{level:2,title:"Markdown 介绍",slug:"markdown-介绍",link:"#markdown-介绍",children:[]},{level:2,title:"Markdown 配置",slug:"markdown-配置",link:"#markdown-配置",children:[]},{level:2,title:"Markdown 扩展",slug:"markdown-扩展",link:"#markdown-扩展",children:[{level:3,title:"VuePress 扩展",slug:"vuepress-扩展",link:"#vuepress-扩展",children:[]},{level:3,title:"主题扩展",slug:"主题扩展",link:"#主题扩展",children:[]},{level:3,title:"图片增强",slug:"图片增强",link:"#图片增强",children:[]}]},{level:2,title:"幻灯片 1",slug:"幻灯片-1",link:"#幻灯片-1",children:[]},{level:2,title:"幻灯片 2",slug:"幻灯片-2",link:"#幻灯片-2",children:[]},{level:2,title:"幻灯片 3.1",slug:"幻灯片-3-1",link:"#幻灯片-3-1",children:[]},{level:2,title:"幻灯片 3.2",slug:"幻灯片-3-2",link:"#幻灯片-3-2",children:[]}],path:"/guide/markdown.html",pathLocale:"/",extraFields:[]},{title:"页面配置",headers:[{level:2,title:"页面信息",slug:"页面信息",link:"#页面信息",children:[]},{level:2,title:"页面内容",slug:"页面内容",link:"#页面内容",children:[]},{level:2,title:"页面结构",slug:"页面结构",link:"#页面结构",children:[]}],path:"/guide/page.html",pathLocale:"/",extraFields:[]},{title:"Java入门到放弃",headers:[],path:"/java/",pathLocale:"/",extraFields:[]},{title:"jellyfin搭建",headers:[{level:2,title:"家庭影院",slug:"家庭影院",link:"#家庭影院",children:[]}],path:"/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html",pathLocale:"/",extraFields:[]},{title:"个人服务器",headers:[{level:3,title:"个人服务器搭建",slug:"个人服务器搭建",link:"#个人服务器搭建",children:[]},{level:3,title:"搭建家庭影院",slug:"搭建家庭影院",link:"#搭建家庭影院",children:[]}],path:"/myserver/",pathLocale:"/",extraFields:[]},{title:"旁路由网关",headers:[{level:2,title:"vmware中使用openwrt做旁路由网关",slug:"vmware中使用openwrt做旁路由网关",link:"#vmware中使用openwrt做旁路由网关",children:[]},{level:2,title:"参考",slug:"参考",link:"#参考",children:[]}],path:"/myserver/x86_openwrt.html",pathLocale:"/",extraFields:[]},{title:"旁路由网关",headers:[{level:3,title:"问题1. 内网主机把网关指向旁路由无法通过主路由端口转发",slug:"问题1-内网主机把网关指向旁路由无法通过主路由端口转发",link:"#问题1-内网主机把网关指向旁路由无法通过主路由端口转发",children:[]}],path:"/myserver/%E6%97%81%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3.html",pathLocale:"/",extraFields:[]},{title:"网络设置",headers:[{level:2,title:"家庭组网",slug:"家庭组网",link:"#家庭组网",children:[{level:3,title:"为什么要组网",slug:"为什么要组网",link:"#为什么要组网",children:[]},{level:3,title:"家庭网线图",slug:"家庭网线图",link:"#家庭网线图",children:[]}]},{level:2,title:"桥接拨号还是光猫拨号",slug:"桥接拨号还是光猫拨号",link:"#桥接拨号还是光猫拨号",children:[]},{level:2,title:"开通公网",slug:"开通公网",link:"#开通公网",children:[{level:3,title:"ipv4玩法",slug:"ipv4玩法",link:"#ipv4玩法",children:[]},{level:3,title:"ipv6玩法",slug:"ipv6玩法",link:"#ipv6玩法",children:[]}]}],path:"/myserver/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE.html",pathLocale:"/",extraFields:[]},{title:"自建nas",headers:[{level:2,title:"nas介绍",slug:"nas介绍",link:"#nas介绍",children:[]}],path:"/myserver/%E8%87%AA%E5%BB%BAnas.html",pathLocale:"/",extraFields:[]},{title:"其他",headers:[],path:"/other/",pathLocale:"/",extraFields:[]},{title:"c++中使用的编译工具介绍",headers:[{level:3,title:"gcc",slug:"gcc",link:"#gcc",children:[]},{level:3,title:"make",slug:"make",link:"#make",children:[]},{level:3,title:"makefile",slug:"makefile",link:"#makefile",children:[]},{level:3,title:"cmake",slug:"cmake",link:"#cmake",children:[]},{level:3,title:"CMakeList.txt",slug:"cmakelist-txt",link:"#cmakelist-txt",children:[]},{level:3,title:"nmake[1]",slug:"nmake-1",link:"#nmake-1",children:[]}],path:"/cpp/other/1.html",pathLocale:"/",extraFields:[]},{title:"使用Clion搭建jdk源码调试环境",headers:[{level:2,title:"占坑",slug:"占坑",link:"#占坑",children:[]}],path:"/cpp/other/2.html",pathLocale:"/",extraFields:[]},{title:"index",headers:[{level:2,title:"c++基础学习",slug:"c-基础学习",link:"#c-基础学习",children:[]}],path:"/cpp/study/",pathLocale:"/",extraFields:[]},{title:"ECMAScript 5",headers:[{level:2,title:"目录",slug:"目录",link:"#目录",children:[]}],path:"/frontweb/es5/",pathLocale:"/",extraFields:[]},{title:"异步async函数",headers:[{level:2,title:"一,async函数的定义",slug:"一-async函数的定义",link:"#一-async函数的定义",children:[]},{level:2,title:"二,async函数的返回值",slug:"二-async函数的返回值",link:"#二-async函数的返回值",children:[]},{level:2,title:"三, await命令",slug:"三-await命令",link:"#三-await命令",children:[]},{level:2,title:"五,错误处理",slug:"五-错误处理",link:"#五-错误处理",children:[]},{level:2,title:"六,总结",slug:"六-总结",link:"#六-总结",children:[]}],path:"/frontweb/es5/aboutAsync.html",pathLocale:"/",extraFields:[]},{title:"JS原生事件",headers:[{level:2,title:"一,事件注册的三种方式",slug:"一-事件注册的三种方式",link:"#一-事件注册的三种方式",children:[]},{level:2,title:"二,解绑事件",slug:"二-解绑事件",link:"#二-解绑事件",children:[]},{level:2,title:"三,事件流",slug:"三-事件流",link:"#三-事件流",children:[]},{level:2,title:"四,事件委托",slug:"四-事件委托",link:"#四-事件委托",children:[]},{level:2,title:"五、事件方法",slug:"五、事件方法",link:"#五、事件方法",children:[]}],path:"/frontweb/es5/aboutEvent.html",pathLocale:"/",extraFields:[]},{title:"this指向问题",headers:[{level:2,title:"一,函数内部的this指向",slug:"一-函数内部的this指向",link:"#一-函数内部的this指向",children:[]},{level:2,title:"二,setTimeout & setInterval",slug:"二-settimeout-setinterval",link:"#二-settimeout-setinterval",children:[]},{level:2,title:"三,箭头函数中的this",slug:"三-箭头函数中的this",link:"#三-箭头函数中的this",children:[]},{level:2,title:"四,关于promise中的this指向问题",slug:"四-关于promise中的this指向问题",link:"#四-关于promise中的this指向问题",children:[]}],path:"/frontweb/es5/aboutThis.html",pathLocale:"/",extraFields:[]},{title:"关于async/await的异常捕获",headers:[{level:2,title:"一,为什么要捕获异常",slug:"一-为什么要捕获异常",link:"#一-为什么要捕获异常",children:[]},{level:2,title:"二,相关案例",slug:"二-相关案例",link:"#二-相关案例",children:[]}],path:"/frontweb/es5/asyncError.html",pathLocale:"/",extraFields:[]},{title:"前端跨域(一)之proxy配置",headers:[{level:3,title:"一,同源策略",slug:"一-同源策略",link:"#一-同源策略",children:[]},{level:3,title:"二,什么是跨域",slug:"二-什么是跨域",link:"#二-什么是跨域",children:[]},{level:3,title:"三,跨域的解决方案",slug:"三-跨域的解决方案",link:"#三-跨域的解决方案",children:[]}],path:"/frontweb/es5/crossDomain.html",pathLocale:"/",extraFields:[]},{title:"前端跨域(二)之JSONP跨域",headers:[{level:3,title:"一,什么是JSONP",slug:"一-什么是jsonp",link:"#一-什么是jsonp",children:[]},{level:3,title:"二,JSONP的原理",slug:"二-jsonp的原理",link:"#二-jsonp的原理",children:[]},{level:3,title:"三,JSONP的具体实现",slug:"三-jsonp的具体实现",link:"#三-jsonp的具体实现",children:[]},{level:3,title:"四,总结",slug:"四-总结",link:"#四-总结",children:[]}],path:"/frontweb/es5/crossDomain2.html",pathLocale:"/",extraFields:[]},{title:"图片懒加载",headers:[{level:3,title:"一,为什么需要图片懒加载",slug:"一-为什么需要图片懒加载",link:"#一-为什么需要图片懒加载",children:[]},{level:3,title:"二,图片懒加载原理",slug:"二-图片懒加载原理",link:"#二-图片懒加载原理",children:[]},{level:3,title:"三,实战",slug:"三-实战",link:"#三-实战",children:[]}],path:"/frontweb/es5/lazyLoad.html",pathLocale:"/",extraFields:[]},{title:"节流与防抖",headers:[{level:2,title:"一,节流概念(Throttle)",slug:"一-节流概念-throttle",link:"#一-节流概念-throttle",children:[]},{level:2,title:"二,节流实现",slug:"二-节流实现",link:"#二-节流实现",children:[]},{level:2,title:"",slug:"",link:"#",children:[]},{level:2,title:"三,防抖概念(Debounce)",slug:"三-防抖概念-debounce",link:"#三-防抖概念-debounce",children:[]},{level:2,title:"四,防抖实现",slug:"四-防抖实现",link:"#四-防抖实现",children:[]}],path:"/frontweb/es5/throttle.html",pathLocale:"/",extraFields:[]},{title:"ECMAScript 6",headers:[],path:"/frontweb/es6/",pathLocale:"/",extraFields:[]},{title:"js能识别最大的正整数",headers:[{level:2,title:"背景",slug:"背景",link:"#背景",children:[]},{level:2,title:"原因,参考gpt回答",slug:"原因-参考gpt回答",link:"#原因-参考gpt回答",children:[]},{level:2,title:"解决方法",slug:"解决方法",link:"#解决方法",children:[]}],path:"/frontweb/es6/js%E4%B8%AD%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.html",pathLocale:"/",extraFields:[]},{title:"Promise介绍",headers:[{level:3,title:"1. Promise介绍",slug:"_1-promise介绍",link:"#_1-promise介绍",children:[]},{level:3,title:"2. 使用",slug:"_2-使用",link:"#_2-使用",children:[]},{level:3,title:"3. promise中使用then和await的区别",slug:"_3-promise中使用then和await的区别",link:"#_3-promise中使用then和await的区别",children:[]}],path:"/frontweb/es6/promise.html",pathLocale:"/",extraFields:[]},{title:"前端模块化",headers:[{level:2,title:"一,什么是前端模块化",slug:"一-什么是前端模块化",link:"#一-什么是前端模块化",children:[]},{level:2,title:"二,CommonJS",slug:"二-commonjs",link:"#二-commonjs",children:[]},{level:2,title:"三,AMD(异步模块定义)",slug:"三-amd-异步模块定义",link:"#三-amd-异步模块定义",children:[]},{level:2,title:"四, CMD(公共模块定义)",slug:"四-cmd-公共模块定义",link:"#四-cmd-公共模块定义",children:[]},{level:2,title:"五,UMD",slug:"五-umd",link:"#五-umd",children:[]},{level:2,title:"六,ES6模块化",slug:"六-es6模块化",link:"#六-es6模块化",children:[{level:3,title:"6.1 在浏览器中使用 ES 模块化",slug:"_6-1-在浏览器中使用-es-模块化",link:"#_6-1-在浏览器中使用-es-模块化",children:[]},{level:3,title:"6.2 在 Node.js 中使用 ES 模块化",slug:"_6-2-在-node-js-中使用-es-模块化",link:"#_6-2-在-node-js-中使用-es-模块化",children:[]},{level:3,title:"其他",slug:"其他",link:"#其他",children:[]}]}],path:"/frontweb/es6/useModule.html",pathLocale:"/",extraFields:[]},{title:"搞懂npm与cnpm",headers:[{level:2,title:"一,什么是npm",slug:"一-什么是npm",link:"#一-什么是npm",children:[]},{level:2,title:"二,什么时候使用cnpm?",slug:"二-什么时候使用cnpm",link:"#二-什么时候使用cnpm",children:[{level:3,title:"【混淆系列】三问:npx、npm、cnpm、pnpm区别你搞清楚了吗?",slug:"【混淆系列】三问-npx、npm、cnpm、pnpm区别你搞清楚了吗",link:"#【混淆系列】三问-npx、npm、cnpm、pnpm区别你搞清楚了吗",children:[]}]}],path:"/frontweb/es6/useNpm.html",pathLocale:"/",extraFields:[]},{title:"搞懂npm与pnpm",headers:[],path:"/frontweb/es6/usePnpm.html",pathLocale:"/",extraFields:[]},{title:"npm与yarn的区别",headers:[{level:2,title:"一,yarn简介",slug:"一-yarn简介",link:"#一-yarn简介",children:[]},{level:2,title:"二,npm的缺陷",slug:"二-npm的缺陷",link:"#二-npm的缺陷",children:[]},{level:2,title:"三,yarn优点",slug:"三-yarn优点",link:"#三-yarn优点",children:[]}],path:"/frontweb/es6/useYarn.html",pathLocale:"/",extraFields:[]},{title:"NodeJS",headers:[{level:2,title:"",slug:"",link:"#",children:[]}],path:"/frontweb/nodejs/",pathLocale:"/",extraFields:[]},{title:"CentOS7安装node18",headers:[{level:3,title:"背景",slug:"背景",link:"#背景",children:[]},{level:3,title:"安装非官方的node",slug:"安装非官方的node",link:"#安装非官方的node",children:[]}],path:"/frontweb/nodejs/%E5%AE%89%E8%A3%85%E9%97%AE%E9%A2%98.html",pathLocale:"/",extraFields:[]},{title:"typeScript学习资料",headers:[{level:2,title:"学习typeScript",slug:"学习typescript",link:"#学习typescript",children:[]}],path:"/frontweb/typeScript/",pathLocale:"/",extraFields:[]},{title:"typeScript项目实战",headers:[{level:2,title:"一,利用typeScript实现新增,删除一行数据",slug:"一-利用typescript实现新增-删除一行数据",link:"#一-利用typescript实现新增-删除一行数据",children:[]}],path:"/frontweb/typeScript/action-usage.html",pathLocale:"/",extraFields:[]},{title:"typeScript中使用axios",headers:[{level:2,title:"项目一",slug:"项目一",link:"#项目一",children:[]}],path:"/frontweb/typeScript/axios.html",pathLocale:"/",extraFields:[]},{title:"typeScript在vue项目中常见用法",headers:[{level:3,title:"一,模板中的 TypeScript",slug:"一-模板中的-typescript",link:"#一-模板中的-typescript",children:[]},{level:3,title:"二,为组件的prop标注类型",slug:"二-为组件的prop标注类型",link:"#二-为组件的prop标注类型",children:[]},{level:3,title:"三,为组件的 emit 标注类型",slug:"三-为组件的-emit-标注类型",link:"#三-为组件的-emit-标注类型",children:[]}],path:"/frontweb/typeScript/basic-usage.html",pathLocale:"/",extraFields:[]},{title:"typeScript中的泛型",headers:[{level:2,title:"一,泛型",slug:"一-泛型",link:"#一-泛型",children:[]},{level:2,title:"二,定义泛型接口",slug:"二-定义泛型接口",link:"#二-定义泛型接口",children:[]},{level:2,title:"三 对象字面量泛型",slug:"三-对象字面量泛型",link:"#三-对象字面量泛型",children:[]},{level:2,title:"四,泛型接口",slug:"四-泛型接口",link:"#四-泛型接口",children:[]},{level:2,title:",泛型约束",slug:"泛型约束",link:"#泛型约束",children:[]},{level:2,title:"五,泛型参数默认值",slug:"五-泛型参数默认值",link:"#五-泛型参数默认值",children:[]}],path:"/frontweb/typeScript/fanType.html",pathLocale:"/",extraFields:[]},{title:"typeScript在vue3中的实战",headers:[{level:2,title:"ts在表单中的应用",slug:"ts在表单中的应用",link:"#ts在表单中的应用",children:[]}],path:"/frontweb/typeScript/tsAndvue3.html",pathLocale:"/",extraFields:[]},{title:"Vite",headers:[],path:"/frontweb/vite/",pathLocale:"/",extraFields:[]},{title:"Vue",headers:[],path:"/frontweb/vue/",pathLocale:"/",extraFields:[]},{title:"ElementUI表单自定义校验",headers:[{level:3,title:"常规",slug:"常规",link:"#常规",children:[]},{level:3,title:"动态获取参数",slug:"动态获取参数",link:"#动态获取参数",children:[]}],path:"/frontweb/vue/elementui%E8%A1%A8%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C.html",pathLocale:"/",extraFields:[]},{title:"事件总线Mitt",headers:[],path:"/frontweb/vue/eventBus.html",pathLocale:"/",extraFields:[]},{title:"vue自定义指令控制按钮级别权限",headers:[{level:2,title:"一,自定义指令介绍",slug:"一-自定义指令介绍",link:"#一-自定义指令介绍",children:[]},{level:2,title:"二,注册自定义指令",slug:"二-注册自定义指令",link:"#二-注册自定义指令",children:[]},{level:2,title:"三,指令钩子",slug:"三-指令钩子",link:"#三-指令钩子",children:[]},{level:2,title:"四,钩子函数的参数",slug:"四-钩子函数的参数",link:"#四-钩子函数的参数",children:[]},{level:2,title:"五,实际使用",slug:"五-实际使用",link:"#五-实际使用",children:[]}],path:"/frontweb/vue/vue-Direactive.html",pathLocale:"/",extraFields:[]},{title:"vue中权限相关的问题",headers:[{level:2,title:"前端主要有哪些权限控制?",slug:"前端主要有哪些权限控制",link:"#前端主要有哪些权限控制",children:[{level:3,title:"一,接口访问权限",slug:"一-接口访问权限",link:"#一-接口访问权限",children:[]},{level:3,title:"二,菜单权限",slug:"二-菜单权限",link:"#二-菜单权限",children:[]},{level:3,title:"三,按钮权限",slug:"三-按钮权限",link:"#三-按钮权限",children:[]}]}],path:"/frontweb/vue/vue-authority.html",pathLocale:"/",extraFields:[]},{title:"利用vue-cli搭建项目",headers:[{level:3,title:"一、创建一个新项目",slug:"一、创建一个新项目",link:"#一、创建一个新项目",children:[]},{level:3,title:"二、创建vue.config.js",slug:"二、创建vue-config-js",link:"#二、创建vue-config-js",children:[]}],path:"/frontweb/vue/vue-in-action.html",pathLocale:"/",extraFields:[]},{title:"关于vue-nextTick",headers:[{level:2,title:"一,什么是nextTick",slug:"一-什么是nexttick",link:"#一-什么是nexttick",children:[]},{level:2,title:"二,nextTick应用场景",slug:"二-nexttick应用场景",link:"#二-nexttick应用场景",children:[]},{level:2,title:"三,Vue.nextTick(callback) 使用原理",slug:"三-vue-nexttick-callback-使用原理",link:"#三-vue-nexttick-callback-使用原理",children:[]}],path:"/frontweb/vue/vue-nextTick.html",pathLocale:"/",extraFields:[]},{title:"vue图片路径问题",headers:[{level:3,title:"一,在vue中静态导入相对路径",slug:"一-在vue中静态导入相对路径",link:"#一-在vue中静态导入相对路径",children:[]},{level:3,title:"二,静态导入绝对路径",slug:"二-静态导入绝对路径",link:"#二-静态导入绝对路径",children:[]},{level:3,title:"三,动态导入相对路径",slug:"三-动态导入相对路径",link:"#三-动态导入相对路径",children:[]}],path:"/frontweb/vue/vue-pic.html",pathLocale:"/",extraFields:[]},{title:"vue-router4.0的基本使用",headers:[{level:3,title:"一,安装",slug:"一-安装",link:"#一-安装",children:[]},{level:3,title:"二,基本用法",slug:"二-基本用法",link:"#二-基本用法",children:[]},{level:3,title:"三,路由的访问",slug:"三-路由的访问",link:"#三-路由的访问",children:[]}],path:"/frontweb/vue/vue-router1.html",pathLocale:"/",extraFields:[]},{title:"vue-router源码浅析",headers:[{level:2,title:"一, 路由的本质",slug:"一-路由的本质",link:"#一-路由的本质",children:[]},{level:2,title:"二, 路由的区别",slug:"二-路由的区别",link:"#二-路由的区别",children:[]},{level:2,title:"三,路由简单实现",slug:"三-路由简单实现",link:"#三-路由简单实现",children:[{level:3,title:"3.1 hash模式",slug:"_3-1-hash模式",link:"#_3-1-hash模式",children:[]},{level:3,title:"3.2 history模式",slug:"_3-2-history模式",link:"#_3-2-history模式",children:[]}]},{level:2,title:"四,原生js实现前端路由",slug:"四-原生js实现前端路由",link:"#四-原生js实现前端路由",children:[]},{level:2,title:"五,剖析Vue-router",slug:"五-剖析vue-router",link:"#五-剖析vue-router",children:[]},{level:2,title:"六,分析Vue.use",slug:"六-分析vue-use",link:"#六-分析vue-use",children:[]},{level:2,title:"七、 完善install方法",slug:"七、-完善install方法",link:"#七、-完善install方法",children:[]},{level:2,title:"八,完善VueRouter类",slug:"八-完善vuerouter类",link:"#八-完善vuerouter类",children:[]},{level:2,title:"九,完善$route",slug:"九-完善-route",link:"#九-完善-route",children:[]},{level:2,title:"十,完善router-view组件",slug:"十-完善router-view组件",link:"#十-完善router-view组件",children:[]},{level:2,title:"十一、完善router-link组件",slug:"十一、完善router-link组件",link:"#十一、完善router-link组件",children:[]}],path:"/frontweb/vue/vue-router2.html",pathLocale:"/",extraFields:[]},{title:"vue3使用emit进行父子组件传值",headers:[{level:2,title:"vue3 使用组合式api时如何进行父子组件通信",slug:"vue3-使用组合式api时如何进行父子组件通信",link:"#vue3-使用组合式api时如何进行父子组件通信",children:[]}],path:"/frontweb/vue/vue3Emit.html",pathLocale:"/",extraFields:[]},{title:"vue中组件的生命周期",headers:[{level:2,title:"一,vue生命周期钩子函数",slug:"一-vue生命周期钩子函数",link:"#一-vue生命周期钩子函数",children:[{level:3,title:"1.1 beforeCreate",slug:"_1-1-beforecreate",link:"#_1-1-beforecreate",children:[]},{level:3,title:"1.2 created",slug:"_1-2-created",link:"#_1-2-created",children:[]},{level:3,title:"1.3 beforeMount",slug:"_1-3-beforemount",link:"#_1-3-beforemount",children:[]},{level:3,title:"1.4 mounted",slug:"_1-4-mounted",link:"#_1-4-mounted",children:[]},{level:3,title:"1.5 beforeUpdate",slug:"_1-5-beforeupdate",link:"#_1-5-beforeupdate",children:[]},{level:3,title:"1.6 updated",slug:"_1-6-updated",link:"#_1-6-updated",children:[]},{level:3,title:"1.7 beforeDestroy",slug:"_1-7-beforedestroy",link:"#_1-7-beforedestroy",children:[]},{level:3,title:"1.8 destroyed",slug:"_1-8-destroyed",link:"#_1-8-destroyed",children:[]},{level:3,title:"1.9 activated",slug:"_1-9-activated",link:"#_1-9-activated",children:[]},{level:3,title:"1.10 deactivated",slug:"_1-10-deactivated",link:"#_1-10-deactivated",children:[]},{level:3,title:"1.11 errorCaptured",slug:"_1-11-errorcaptured",link:"#_1-11-errorcaptured",children:[]}]},{level:2,title:"二、 父子组件生命周期执行顺序",slug:"二、-父子组件生命周期执行顺序",link:"#二、-父子组件生命周期执行顺序",children:[]}],path:"/frontweb/vue/vue3LifeTime.html",pathLocale:"/",extraFields:[]},{title:"利用Vue.extend定义全局组件",headers:[{level:2,title:"一,vue.extend基本概念和用法",slug:"一-vue-extend基本概念和用法",link:"#一-vue-extend基本概念和用法",children:[]},{level:2,title:"二,为什么要使用extend",slug:"二-为什么要使用extend",link:"#二-为什么要使用extend",children:[]}],path:"/frontweb/vue/vueExtend.html",pathLocale:"/",extraFields:[]},{title:"使用自定义指令实现高亮",headers:[{level:3,title:"需求",slug:"需求",link:"#需求",children:[]},{level:3,title:"实现方案",slug:"实现方案",link:"#实现方案",children:[]},{level:3,title:"代码",slug:"代码",link:"#代码",children:[]},{level:3,title:"扩展",slug:"扩展",link:"#扩展",children:[]}],path:"/frontweb/vue/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4%E7%9A%84%E5%AE%9E%E8%B7%B5.html",pathLocale:"/",extraFields:[]},{title:"使用Arthas定位线上问题",headers:[{level:2,title:"1、使用arthas来跟踪",slug:"_1、使用arthas来跟踪",link:"#_1、使用arthas来跟踪",children:[]}],path:"/java/advance/Arthas.html",pathLocale:"/",extraFields:[]},{title:"java集合",headers:[{level:2,title:"1、Collection",slug:"_1、collection",link:"#_1、collection",children:[]}],path:"/java/advance/Collection.html",pathLocale:"/",extraFields:[]},{title:"在Manjaro中编译JDK11",headers:[{level:2,title:"1 下载源码",slug:"_1-下载源码",link:"#_1-下载源码",children:[]},{level:2,title:"2 编译",slug:"_2-编译",link:"#_2-编译",children:[{level:3,title:"2.1 configure",slug:"_2-1-configure",link:"#_2-1-configure",children:[]},{level:3,title:"2.2 执行编译make images",slug:"_2-2-执行编译make-images",link:"#_2-2-执行编译make-images",children:[]},{level:3,title:"2.3 解决编译器gcc版本问题",slug:"_2-3-解决编译器gcc版本问题",link:"#_2-3-解决编译器gcc版本问题",children:[]}]},{level:2,title:"3、jdk源码目录结构",slug:"_3、jdk源码目录结构",link:"#_3、jdk源码目录结构",children:[]}],path:"/java/advance/CompileJdk11.html",pathLocale:"/",extraFields:[]},{title:"并发问题",headers:[{level:2,title:"1、背景",slug:"_1、背景",link:"#_1、背景",children:[]},{level:2,title:"解决方法",slug:"解决方法",link:"#解决方法",children:[]},{level:2,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/java/advance/Concurrent.html",pathLocale:"/",extraFields:[]},{title:"多线程中的Future",headers:[{level:2,title:"1、Future的作用",slug:"_1、future的作用",link:"#_1、future的作用",children:[]}],path:"/java/advance/Future.html",pathLocale:"/",extraFields:[]},{title:"100%搞懂5中I/O模型",headers:[{level:2,title:"1、从TCP发送数据的流程说起",slug:"_1、从tcp发送数据的流程说起",link:"#_1、从tcp发送数据的流程说起",children:[]},{level:2,title:"2、阻塞IO |非阻塞IO",slug:"_2、阻塞io-非阻塞io",link:"#_2、阻塞io-非阻塞io",children:[{level:3,title:"2.1 什么是阻塞IO",slug:"_2-1-什么是阻塞io",link:"#_2-1-什么是阻塞io",children:[]},{level:3,title:"2.2 什么是非阻塞IO",slug:"_2-2-什么是非阻塞io",link:"#_2-2-什么是非阻塞io",children:[]}]},{level:2,title:"3、IO复用模型",slug:"_3、io复用模型",link:"#_3、io复用模型",children:[{level:3,title:"3.1 IO多路复用模型扩展",slug:"_3-1-io多路复用模型扩展",link:"#_3-1-io多路复用模型扩展",children:[]}]},{level:2,title:"4、信号驱动IO模型",slug:"_4、信号驱动io模型",link:"#_4、信号驱动io模型",children:[]},{level:2,title:"5、异步IO",slug:"_5、异步io",link:"#_5、异步io",children:[]},{level:2,title:"6、再谈IO模型里面的同步异步",slug:"_6、再谈io模型里面的同步异步",link:"#_6、再谈io模型里面的同步异步",children:[]},{level:2,title:"7、tomcat中的IO模型",slug:"_7、tomcat中的io模型",link:"#_7、tomcat中的io模型",children:[]},{level:2,title:"8、Redis中的IO模型",slug:"_8、redis中的io模型",link:"#_8、redis中的io模型",children:[]},{level:2,title:"9、Nginx中的IO模型",slug:"_9、nginx中的io模型",link:"#_9、nginx中的io模型",children:[]},{level:2,title:"10、java中的IO模型",slug:"_10、java中的io模型",link:"#_10、java中的io模型",children:[]},{level:2,title:"10、netty中的io模型",slug:"_10、netty中的io模型",link:"#_10、netty中的io模型",children:[]}],path:"/java/advance/IO-Model.html",pathLocale:"/",extraFields:[]},{title:"I/O模型总结",headers:[{level:2,title:"1 BIO",slug:"_1-bio",link:"#_1-bio",children:[]},{level:2,title:"2 NIO",slug:"_2-nio",link:"#_2-nio",children:[]},{level:2,title:"3 IO多路复用",slug:"_3-io多路复用",link:"#_3-io多路复用",children:[]},{level:2,title:"4 异步IO",slug:"_4-异步io",link:"#_4-异步io",children:[]},{level:2,title:"5 事件驱动的io",slug:"_5-事件驱动的io",link:"#_5-事件驱动的io",children:[]},{level:2,title:"6 reactor线程模型",slug:"_6-reactor线程模型",link:"#_6-reactor线程模型",children:[{level:3,title:"6.1 单reactor单线程",slug:"_6-1-单reactor单线程",link:"#_6-1-单reactor单线程",children:[]},{level:3,title:"6.2 单reactor多线程",slug:"_6-2-单reactor多线程",link:"#_6-2-单reactor多线程",children:[]},{level:3,title:"6.3 主从(多)reactor多线程",slug:"_6-3-主从-多-reactor多线程",link:"#_6-3-主从-多-reactor多线程",children:[]}]}],path:"/java/advance/IO-model1.html",pathLocale:"/",extraFields:[]},{title:"子类和父类(或者父接口)实现同一个接口",headers:[{level:2,title:"1、背景",slug:"_1、背景",link:"#_1、背景",children:[]},{level:2,title:"2、探索",slug:"_2、探索",link:"#_2、探索",children:[{level:3,title:"2.1 demo验证",slug:"_2-1-demo验证",link:"#_2-1-demo验证",children:[]},{level:3,title:"2.2 回到security源码验证",slug:"_2-2-回到security源码验证",link:"#_2-2-回到security源码验证",children:[]}]},{level:2,title:"3、结论",slug:"_3、结论",link:"#_3、结论",children:[]}],path:"/java/advance/ImplementSameInterface.html",pathLocale:"/",extraFields:[]},{title:"mysql8搭建主从复制",headers:[{level:3,title:"1、mysql主从复制",slug:"_1、mysql主从复制",link:"#_1、mysql主从复制",children:[]},{level:3,title:"2、使用mybatis-plus实现读写分离(mysql的主从复制是实现读写分离的基础)",slug:"_2、使用mybatis-plus实现读写分离-mysql的主从复制是实现读写分离的基础",link:"#_2、使用mybatis-plus实现读写分离-mysql的主从复制是实现读写分离的基础",children:[]}],path:"/java/advance/MysqlMasterSlave.html",pathLocale:"/",extraFields:[]},{title:"自定义native方法",headers:[{level:2,title:"1、native方法",slug:"_1、native方法",link:"#_1、native方法",children:[]},{level:2,title:"2、自定义实现native方法",slug:"_2、自定义实现native方法",link:"#_2、自定义实现native方法",children:[{level:3,title:"2.1 定义java源文件",slug:"_2-1-定义java源文件",link:"#_2-1-定义java源文件",children:[]},{level:3,title:"2.2 使用jdk自带工具生成c++头文件",slug:"_2-2-使用jdk自带工具生成c-头文件",link:"#_2-2-使用jdk自带工具生成c-头文件",children:[]},{level:3,title:"2.3 添加cpp文件",slug:"_2-3-添加cpp文件",link:"#_2-3-添加cpp文件",children:[]},{level:3,title:"2.4 编译C++源码为动态库",slug:"_2-4-编译c-源码为动态库",link:"#_2-4-编译c-源码为动态库",children:[]},{level:3,title:"2.5 执行java程序验证",slug:"_2-5-执行java程序验证",link:"#_2-5-执行java程序验证",children:[]}]},{level:2,title:"3、总结",slug:"_3、总结",link:"#_3、总结",children:[]}],path:"/java/advance/NativeMethod.html",pathLocale:"/",extraFields:[]},{title:"证明SPI打破双亲委派模式",headers:[{level:2,title:"1、什么是双亲委派?",slug:"_1、什么是双亲委派",link:"#_1、什么是双亲委派",children:[]},{level:2,title:"2、什么是SPI",slug:"_2、什么是spi",link:"#_2、什么是spi",children:[{level:3,title:"2.1 定义",slug:"_2-1-定义",link:"#_2-1-定义",children:[]},{level:3,title:"2.2 使用场景",slug:"_2-2-使用场景",link:"#_2-2-使用场景",children:[]},{level:3,title:"2.3 自己写一个SPI模拟jdbc的spi",slug:"_2-3-自己写一个spi模拟jdbc的spi",link:"#_2-3-自己写一个spi模拟jdbc的spi",children:[]}]},{level:2,title:"3、为什么SPI打破了双亲委派",slug:"_3、为什么spi打破了双亲委派",link:"#_3、为什么spi打破了双亲委派",children:[{level:3,title:"3.1 ContextClassLoader",slug:"_3-1-contextclassloader",link:"#_3-1-contextclassloader",children:[]},{level:3,title:"4.1 jdbc介绍",slug:"_4-1-jdbc介绍",link:"#_4-1-jdbc介绍",children:[]},{level:3,title:"4.2 jdbc一定打破双亲委派吗?",slug:"_4-2-jdbc一定打破双亲委派吗",link:"#_4-2-jdbc一定打破双亲委派吗",children:[]},{level:3,title:"4.3 调试jdbc4.0+、mysql5.1.6+版本的spi打破双亲委派",slug:"_4-3-调试jdbc4-0-、mysql5-1-6-版本的spi打破双亲委派",link:"#_4-3-调试jdbc4-0-、mysql5-1-6-版本的spi打破双亲委派",children:[]}]}],path:"/java/advance/ParentDelegationClassLoader.html",pathLocale:"/",extraFields:[]},{title:"彻底理清Java中的几种代理",headers:[{level:3,title:"1、代理的分类",slug:"_1、代理的分类",link:"#_1、代理的分类",children:[]},{level:3,title:"2、各种代理的区别",slug:"_2、各种代理的区别",link:"#_2、各种代理的区别",children:[]},{level:3,title:"3、Spring中提到的AOP和AspectJ属于什么代理",slug:"_3、spring中提到的aop和aspectj属于什么代理",link:"#_3、spring中提到的aop和aspectj属于什么代理",children:[]},{level:3,title:"4、比较几种代理",slug:"_4、比较几种代理",link:"#_4、比较几种代理",children:[]},{level:3,title:"5、各种代理的详细介绍以及示例代码",slug:"_5、各种代理的详细介绍以及示例代码",link:"#_5、各种代理的详细介绍以及示例代码",children:[]},{level:3,title:"6、参考",slug:"_6、参考",link:"#_6、参考",children:[]}],path:"/java/advance/ProxyInJava.html",pathLocale:"/",extraFields:[]},{title:"Java进阶",headers:[],path:"/java/advance/",pathLocale:"/",extraFields:[]},{title:"synchronized实现 原理",headers:[{level:2,title:"1、",slug:"_1、",link:"#_1、",children:[]}],path:"/java/advance/Synchronized.html",pathLocale:"/",extraFields:[]},{title:"ThreadLocal",headers:[{level:2,title:"1、介绍",slug:"_1、介绍",link:"#_1、介绍",children:[]},{level:2,title:"2、使用",slug:"_2、使用",link:"#_2、使用",children:[]},{level:2,title:"3、 使用反射在当前线程获取所有的ThreadLocal",slug:"_3、-使用反射在当前线程获取所有的threadlocal",link:"#_3、-使用反射在当前线程获取所有的threadlocal",children:[]}],path:"/java/advance/ThreadLocal.html",pathLocale:"/",extraFields:[]},{title:"线程池总结",headers:[{level:2,title:"1、先上测试代码",slug:"_1、先上测试代码",link:"#_1、先上测试代码",children:[]},{level:2,title:"2、 源码解析",slug:"_2、-源码解析",link:"#_2、-源码解析",children:[{level:3,title:"2.1 修改测试代码继续测试",slug:"_2-1-修改测试代码继续测试",link:"#_2-1-修改测试代码继续测试",children:[]},{level:3,title:"2.2 测试带返回值的多线程任务",slug:"_2-2-测试带返回值的多线程任务",link:"#_2-2-测试带返回值的多线程任务",children:[]}]},{level:2,title:"3、线程池的总结",slug:"_3、线程池的总结",link:"#_3、线程池的总结",children:[{level:3,title:"3.1 不建议使用官方工具类直接创建",slug:"_3-1-不建议使用官方工具类直接创建",link:"#_3-1-不建议使用官方工具类直接创建",children:[]},{level:3,title:"3.2 java.util.concurrent.ThreadPoolExecutor",slug:"_3-2-java-util-concurrent-threadpoolexecutor",link:"#_3-2-java-util-concurrent-threadpoolexecutor",children:[]},{level:3,title:"3.3 线程池中的异常处理",slug:"_3-3-线程池中的异常处理",link:"#_3-3-线程池中的异常处理",children:[]},{level:3,title:"3.4 补充说明",slug:"_3-4-补充说明",link:"#_3-4-补充说明",children:[]}]}],path:"/java/advance/ThreadPool.html",pathLocale:"/",extraFields:[]},{title:"I/O模型",headers:[{level:2,title:"1、参考",slug:"_1、参考",link:"#_1、参考",children:[]},{level:2,title:"2、模型分类",slug:"_2、模型分类",link:"#_2、模型分类",children:[]},{level:2,title:"3、形象生活例子",slug:"_3、形象生活例子",link:"#_3、形象生活例子",children:[]},{level:2,title:"4、同步阻塞I/O(传统BIO)",slug:"_4、同步阻塞i-o-传统bio",link:"#_4、同步阻塞i-o-传统bio",children:[{level:3,title:"4.1 介绍",slug:"_4-1-介绍",link:"#_4-1-介绍",children:[]},{level:3,title:"4.2 传统BIO存在的问题",slug:"_4-2-传统bio存在的问题",link:"#_4-2-传统bio存在的问题",children:[]},{level:3,title:"4.3 使用多线程改进BIO",slug:"_4-3-使用多线程改进bio",link:"#_4-3-使用多线程改进bio",children:[]},{level:3,title:"4.4 验证上下文切换带来的CPU消耗巨大",slug:"_4-4-验证上下文切换带来的cpu消耗巨大",link:"#_4-4-验证上下文切换带来的cpu消耗巨大",children:[]},{level:3,title:"4.5 NIO",slug:"_4-5-nio",link:"#_4-5-nio",children:[]}]}],path:"/java/advance/io.html",pathLocale:"/",extraFields:[]},{title:"ConstantPool",headers:[{level:3,title:"1. Integer常量池默认的范围",slug:"_1-integer常量池默认的范围",link:"#_1-integer常量池默认的范围",children:[]},{level:3,title:"2、测试",slug:"_2、测试",link:"#_2、测试",children:[]}],path:"/java/base/ConstantPool.html",pathLocale:"/",extraFields:[]},{title:"自定义LRU实现",headers:[{level:2,title:"LRU介绍",slug:"lru介绍",link:"#lru介绍",children:[]},{level:2,title:"自定义lru算法",slug:"自定义lru算法",link:"#自定义lru算法",children:[{level:3,title:"第一次测试",slug:"第一次测试",link:"#第一次测试",children:[]},{level:3,title:"第二次测试",slug:"第二次测试",link:"#第二次测试",children:[]}]}],path:"/java/base/CustomLRU.html",pathLocale:"/",extraFields:[]},{title:"Integer常量池",headers:[{level:3,title:"1. Integer常量池默认的范围",slug:"_1-integer常量池默认的范围",link:"#_1-integer常量池默认的范围",children:[]},{level:3,title:"2、测试",slug:"_2、测试",link:"#_2、测试",children:[]}],path:"/java/base/IntegerConstantPool.html",pathLocale:"/",extraFields:[]},{title:"Java基础",headers:[],path:"/java/base/",pathLocale:"/",extraFields:[]},{title:"序列化",headers:[],path:"/java/base/Serialization.html",pathLocale:"/",extraFields:[]},{title:"字符串设计",headers:[],path:"/java/base/String.html",pathLocale:"/",extraFields:[]},{title:"自定义类加载器",headers:[{level:2,title:"1、自定义类加载器",slug:"_1、自定义类加载器",link:"#_1、自定义类加载器",children:[]}],path:"/java/jvm/ClassLoader.html",pathLocale:"/",extraFields:[]},{title:"内存模型",headers:[{level:2,title:"1、元空间",slug:"_1、元空间",link:"#_1、元空间",children:[]}],path:"/java/jvm/MemoryModel.html",pathLocale:"/",extraFields:[]},{title:"对象创建过程",headers:[{level:3,title:"1、在类中本地变量引用自身类,会引发的问题",slug:"_1、在类中本地变量引用自身类-会引发的问题",link:"#_1、在类中本地变量引用自身类-会引发的问题",children:[]}],path:"/java/jvm/NewObject.html",pathLocale:"/",extraFields:[]},{title:"对象引用类型",headers:[{level:2,title:"1、强引用",slug:"_1、强引用",link:"#_1、强引用",children:[]},{level:2,title:"2、软引用",slug:"_2、软引用",link:"#_2、软引用",children:[]},{level:2,title:"3、弱引用",slug:"_3、弱引用",link:"#_3、弱引用",children:[]},{level:2,title:"4、虚引用",slug:"_4、虚引用",link:"#_4、虚引用",children:[]}],path:"/java/jvm/ObjectReference.html",pathLocale:"/",extraFields:[]},{title:"JVM",headers:[],path:"/java/jvm/",pathLocale:"/",extraFields:[]},{title:"给对象设置null的意义",headers:[{level:3,title:"1、开始写代码测试(所有测试都要加上以下指令)",slug:"_1、开始写代码测试-所有测试都要加上以下指令",link:"#_1、开始写代码测试-所有测试都要加上以下指令",children:[]},{level:3,title:"2、结论",slug:"_2、结论",link:"#_2、结论",children:[]},{level:3,title:"3、参考",slug:"_3、参考",link:"#_3、参考",children:[]}],path:"/java/jvm/SetObjectNull.html",pathLocale:"/",extraFields:[]},{title:"通过反汇编来看String的拼接",headers:[],path:"/java/jvm/StringAdd.html",pathLocale:"/",extraFields:[]},{title:"volatile关键字",headers:[{level:2,title:"1、双重检查的单例模式是否真的线程安全?",slug:"_1、双重检查的单例模式是否真的线程安全",link:"#_1、双重检查的单例模式是否真的线程安全",children:[]},{level:2,title:"2、问题剖析",slug:"_2、问题剖析",link:"#_2、问题剖析",children:[{level:3,title:"2.1 另一个例子",slug:"_2-1-另一个例子",link:"#_2-1-另一个例子",children:[]}]},{level:2,title:"3、正确的双重检查单例模式写法",slug:"_3、正确的双重检查单例模式写法",link:"#_3、正确的双重检查单例模式写法",children:[]},{level:2,title:"4、参考",slug:"_4、参考",link:"#_4、参考",children:[]}],path:"/java/jvm/volatile.html",pathLocale:"/",extraFields:[]},{title:"名词解释",headers:[{level:3,title:"vm中说的字面量和符号引用是什么",slug:"vm中说的字面量和符号引用是什么",link:"#vm中说的字面量和符号引用是什么",children:[]}],path:"/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html",pathLocale:"/",extraFields:[]},{title:"Jdk版本",headers:[{level:2,title:"1、Jdk11对比jdk1.8",slug:"_1、jdk11对比jdk1-8",link:"#_1、jdk11对比jdk1-8",children:[{level:3,title:"1.1 Java9",slug:"_1-1-java9",link:"#_1-1-java9",children:[]},{level:3,title:"1.2 Java10",slug:"_1-2-java10",link:"#_1-2-java10",children:[]},{level:3,title:"1.3 Java11(LTS)",slug:"_1-3-java11-lts",link:"#_1-3-java11-lts",children:[]}]}],path:"/java/other/JdkVersion.html",pathLocale:"/",extraFields:[]},{title:"其他",headers:[{level:2,title:"其他分类",slug:"其他分类",link:"#其他分类",children:[]}],path:"/java/other/",pathLocale:"/",extraFields:[]},{title:"电子书资源",headers:[{level:2,title:"目录",slug:"目录",link:"#目录",children:[]}],path:"/other/books/",pathLocale:"/",extraFields:[]},{title:"电子书资源汇总",headers:[{level:3,title:"1、springboot",slug:"_1、springboot",link:"#_1、springboot",children:[]},{level:3,title:"2、Nacos原理",slug:"_2、nacos原理",link:"#_2、nacos原理",children:[]}],path:"/other/books/ebooks.html",pathLocale:"/",extraFields:[]},{title:"深入理解TCP/IP",headers:[{level:2,title:"1、TCP/IP与OSI的关系",slug:"_1、tcp-ip与osi的关系",link:"#_1、tcp-ip与osi的关系",children:[]},{level:2,title:"2、TCP/IP",slug:"_2、tcp-ip",link:"#_2、tcp-ip",children:[{level:3,title:"2.1 应用层",slug:"_2-1-应用层",link:"#_2-1-应用层",children:[]},{level:3,title:"2.2 传输控制层(TCP,不讲UDP)",slug:"_2-2-传输控制层-tcp-不讲udp",link:"#_2-2-传输控制层-tcp-不讲udp",children:[]},{level:3,title:"2.3 网络层",slug:"_2-3-网络层",link:"#_2-3-网络层",children:[]},{level:3,title:"2.4 数据链路层",slug:"_2-4-数据链路层",link:"#_2-4-数据链路层",children:[]}]},{level:2,title:"3、参考资料",slug:"_3、参考资料",link:"#_3、参考资料",children:[]}],path:"/other/computerprinciple/TCP-IP.html",pathLocale:"/",extraFields:[]},{title:"Mysql CPU负载过高",headers:[{level:3,title:"问题",slug:"问题",link:"#问题",children:[]},{level:3,title:"分析原因",slug:"分析原因",link:"#分析原因",children:[]},{level:3,title:"结论",slug:"结论",link:"#结论",children:[]}],path:"/other/database/CPUOverLoad.html",pathLocale:"/",extraFields:[]},{title:"Mysql中的Collate和charset",headers:[{level:3,title:"COLLATE是用来做什么的?",slug:"collate是用来做什么的",link:"#collate是用来做什么的",children:[]},{level:3,title:"各种COLLATE的区别",slug:"各种collate的区别",link:"#各种collate的区别",children:[]},{level:3,title:"COLLATE设置级别及其优先级",slug:"collate设置级别及其优先级",link:"#collate设置级别及其优先级",children:[]}],path:"/other/database/MysqlCollate.html",pathLocale:"/",extraFields:[]},{title:"Mysql知识点记录",headers:[{level:3,title:"1、批量插入速度慢",slug:"_1、批量插入速度慢",link:"#_1、批量插入速度慢",children:[]},{level:3,title:"2、唯一索引",slug:"_2、唯一索引",link:"#_2、唯一索引",children:[]}],path:"/other/database/MysqlNote.html",pathLocale:"/",extraFields:[]},{title:"Mysql开启远程连接权限",headers:[],path:"/other/database/MysqlRemoteConnect.html",pathLocale:"/",extraFields:[]},{title:"数据库",headers:[],path:"/other/database/",pathLocale:"/",extraFields:[]},{title:"递归下钻",headers:[{level:2,title:"1、递归下钻",slug:"_1、递归下钻",link:"#_1、递归下钻",children:[]},{level:2,title:"2、示例",slug:"_2、示例",link:"#_2、示例",children:[]}],path:"/other/database/Recurse.html",pathLocale:"/",extraFields:[]},{title:"联合查询sql优化",headers:[{level:3,title:"1、改造后的语句如下",slug:"_1、改造后的语句如下",link:"#_1、改造后的语句如下",children:[]},{level:3,title:"2、原因分析",slug:"_2、原因分析",link:"#_2、原因分析",children:[]}],path:"/other/database/SQLOptimization.html",pathLocale:"/",extraFields:[]},{title:"定时备份数据库",headers:[{level:3,title:"1.备份脚本",slug:"_1-备份脚本",link:"#_1-备份脚本",children:[]},{level:3,title:"2. 给脚本添加可执行权限",slug:"_2-给脚本添加可执行权限",link:"#_2-给脚本添加可执行权限",children:[]},{level:3,title:"3. 设置crontab定时任务(每天0点执行一次)",slug:"_3-设置crontab定时任务-每天0点执行一次",link:"#_3-设置crontab定时任务-每天0点执行一次",children:[]}],path:"/other/database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD.html",pathLocale:"/",extraFields:[]},{title:"分布式锁",headers:[],path:"/other/distributeservice/DistributeLock.html",pathLocale:"/",extraFields:[]},{title:"Nacos学习",headers:[{level:2,title:"1、配置管理",slug:"_1、配置管理",link:"#_1、配置管理",children:[]}],path:"/other/distributeservice/Nacos.html",pathLocale:"/",extraFields:[]},{title:"分布式微服务",headers:[],path:"/other/distributeservice/",pathLocale:"/",extraFields:[]},{title:"Docker常用命令",headers:[{level:2,title:"1 命令速查",slug:"_1-命令速查",link:"#_1-命令速查",children:[]}],path:"/other/docker/Docker.html",pathLocale:"/",extraFields:[]},{title:"使用docker安装常见服务",headers:[{level:2,title:"1. docker安装Redis",slug:"_1-docker安装redis",link:"#_1-docker安装redis",children:[]}],path:"/other/docker/ServiceInstall.html",pathLocale:"/",extraFields:[]},{title:"2022-04-02",headers:[{level:2,title:"1、库A导出",slug:"_1、库a导出",link:"#_1、库a导出",children:[]},{level:2,title:"2、使用source导入",slug:"_2、使用source导入",link:"#_2、使用source导入",children:[]}],path:"/other/essay/2022-04-12.html",pathLocale:"/",extraFields:[]},{title:"二叉树",headers:[{level:2,title:"二叉树进化图",slug:"二叉树进化图",link:"#二叉树进化图",children:[]}],path:"/other/essay/BTree.html",pathLocale:"/",extraFields:[]},{title:"CDN静态资源加速",headers:[],path:"/other/essay/CDN.html",pathLocale:"/",extraFields:[]},{title:"ChromeDevTools学习",headers:[],path:"/other/essay/ChromeDevTools.html",pathLocale:"/",extraFields:[]},{title:"云服务问题分析及总结",headers:[{level:2,title:"1、我的云服务使用场景",slug:"_1、我的云服务使用场景",link:"#_1、我的云服务使用场景",children:[]},{level:2,title:"2、遇到的问题",slug:"_2、遇到的问题",link:"#_2、遇到的问题",children:[]},{level:2,title:"3、问题分析",slug:"_3、问题分析",link:"#_3、问题分析",children:[{level:3,title:"3.1 使用cdn 第一次访问慢原因分析",slug:"_3-1-使用cdn-第一次访问慢原因分析",link:"#_3-1-使用cdn-第一次访问慢原因分析",children:[]},{level:3,title:"3.2 解决第一次访问慢的问题",slug:"_3-2-解决第一次访问慢的问题",link:"#_3-2-解决第一次访问慢的问题",children:[]},{level:3,title:"3.3、问题2分析",slug:"_3-3、问题2分析",link:"#_3-3、问题2分析",children:[]},{level:3,title:"3.4、问题解决",slug:"_3-4、问题解决",link:"#_3-4、问题解决",children:[]}]}],path:"/other/essay/CloudService.html",pathLocale:"/",extraFields:[]},{title:"如何在github部署静态网站",headers:[{level:2,title:"1、 GitHubPage介绍",slug:"_1、-githubpage介绍",link:"#_1、-githubpage介绍",children:[{level:3,title:"1.1 ok",slug:"_1-1-ok",link:"#_1-1-ok",children:[]},{level:3,title:"1.2 搭建个人githubpage",slug:"_1-2-搭建个人githubpage",link:"#_1-2-搭建个人githubpage",children:[]}]},{level:2,title:"2、配合github的Action实现自动化部署",slug:"_2、配合github的action实现自动化部署",link:"#_2、配合github的action实现自动化部署",children:[{level:3,title:"2.1 自动部署脚本",slug:"_2-1-自动部署脚本",link:"#_2-1-自动部署脚本",children:[]}]}],path:"/other/essay/DeployGithubPage.html",pathLocale:"/",extraFields:[]},{title:"IM即时通信选型",headers:[{level:2,title:"1. 需求",slug:"_1-需求",link:"#_1-需求",children:[]},{level:2,title:"2. 原有方案评估",slug:"_2-原有方案评估",link:"#_2-原有方案评估",children:[{level:3,title:"2.1 webrtc即时通信",slug:"_2-1-webrtc即时通信",link:"#_2-1-webrtc即时通信",children:[]}]},{level:2,title:"3. IM通信的难点",slug:"_3-im通信的难点",link:"#_3-im通信的难点",children:[{level:3,title:"3.1 消息传递可靠性问题",slug:"_3-1-消息传递可靠性问题",link:"#_3-1-消息传递可靠性问题",children:[]},{level:3,title:"3.2 消息重复问题",slug:"_3-2-消息重复问题",link:"#_3-2-消息重复问题",children:[]},{level:3,title:"3.3 消息时序问题",slug:"_3-3-消息时序问题",link:"#_3-3-消息时序问题",children:[]},{level:3,title:"3.4 音视频QoS问题",slug:"_3-4-音视频qos问题",link:"#_3-4-音视频qos问题",children:[]},{level:3,title:"3.5 消息推送问题",slug:"_3-5-消息推送问题",link:"#_3-5-消息推送问题",children:[]},{level:3,title:"3.6 技术栈问题",slug:"_3-6-技术栈问题",link:"#_3-6-技术栈问题",children:[]}]},{level:2,title:"4. 技术选型目标",slug:"_4-技术选型目标",link:"#_4-技术选型目标",children:[]},{level:2,title:"4. 候选方案",slug:"_4-候选方案",link:"#_4-候选方案",children:[{level:3,title:"4.1 uni-im",slug:"_4-1-uni-im",link:"#_4-1-uni-im",children:[]},{level:3,title:"4.1.1 简介",slug:"_4-1-1-简介",link:"#_4-1-1-简介",children:[]},{level:3,title:"4.1.2 优势",slug:"_4-1-2-优势",link:"#_4-1-2-优势",children:[]},{level:3,title:"4.1.3 劣势",slug:"_4-1-3-劣势",link:"#_4-1-3-劣势",children:[]},{level:3,title:"4.1.4 费用",slug:"_4-1-4-费用",link:"#_4-1-4-费用",children:[]},{level:3,title:"4.2 J-IM",slug:"_4-2-j-im",link:"#_4-2-j-im",children:[]},{level:3,title:"4.3 cim",slug:"_4-3-cim",link:"#_4-3-cim",children:[]},{level:3,title:"4.4 V-IM",slug:"_4-4-v-im",link:"#_4-4-v-im",children:[]},{level:3,title:"4.5 MobileIMSDK",slug:"_4-5-mobileimsdk",link:"#_4-5-mobileimsdk",children:[]},{level:3,title:"4.6 野火IM/im-server",slug:"_4-6-野火im-im-server",link:"#_4-6-野火im-im-server",children:[]}]},{level:2,title:"5. 总结",slug:"_5-总结",link:"#_5-总结",children:[]}],path:"/other/essay/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B.html",pathLocale:"/",extraFields:[]},{title:"jenkins部署及使用",headers:[{level:2,title:"1、jenkins插件更新报错",slug:"_1、jenkins插件更新报错",link:"#_1、jenkins插件更新报错",children:[{level:3,title:"1.1 报错如下,ssl证书问题",slug:"_1-1-报错如下-ssl证书问题",link:"#_1-1-报错如下-ssl证书问题",children:[]},{level:3,title:"1.2 解决办法",slug:"_1-2-解决办法",link:"#_1-2-解决办法",children:[]}]},{level:2,title:"2、gitlab通过webhook调用jenkins",slug:"_2、gitlab通过webhook调用jenkins",link:"#_2、gitlab通过webhook调用jenkins",children:[{level:3,title:"2.1 jenkins端配置",slug:"_2-1-jenkins端配置",link:"#_2-1-jenkins端配置",children:[]},{level:3,title:"2.1 、gitlab服务端配置",slug:"_2-1-、gitlab服务端配置",link:"#_2-1-、gitlab服务端配置",children:[]}]},{level:2,title:"3、使用部署springboot项目滚动显示日志",slug:"_3、使用部署springboot项目滚动显示日志",link:"#_3、使用部署springboot项目滚动显示日志",children:[]}],path:"/other/essay/Jenkins.html",pathLocale:"/",extraFields:[]},{title:"随笔分享",headers:[],path:"/other/essay/",pathLocale:"/",extraFields:[]},{title:"在Typora中使用Picgo",headers:[{level:3,title:"前言",slug:"前言",link:"#前言",children:[]},{level:3,title:"1、搭建Typora + PicGo + gitee",slug:"_1、搭建typora-picgo-gitee",link:"#_1、搭建typora-picgo-gitee",children:[]},{level:3,title:"2、给typora中picgo设置代理",slug:"_2、给typora中picgo设置代理",link:"#_2、给typora中picgo设置代理",children:[]}],path:"/other/essay/TyporaPicgo.html",pathLocale:"/",extraFields:[]},{title:"elasticsearch操作",headers:[{level:3,title:"1.索引库操作",slug:"_1-索引库操作",link:"#_1-索引库操作",children:[]},{level:3,title:"2.文档操作",slug:"_2-文档操作",link:"#_2-文档操作",children:[]},{level:3,title:"3. ES搜索引擎",slug:"_3-es搜索引擎",link:"#_3-es搜索引擎",children:[]},{level:3,title:"4.客户端",slug:"_4-客户端",link:"#_4-客户端",children:[]}],path:"/other/essay/elasticSearch%E6%93%8D%E4%BD%9C.html",pathLocale:"/",extraFields:[]},{title:"ELK 部署",headers:[{level:3,title:"1. 目录结构",slug:"_1-目录结构",link:"#_1-目录结构",children:[]},{level:3,title:"2. docker-compose",slug:"_2-docker-compose",link:"#_2-docker-compose",children:[]},{level:3,title:"3. 初始化密码",slug:"_3-初始化密码",link:"#_3-初始化密码",children:[]},{level:3,title:"4. 安装IK中文分词器",slug:"_4-安装ik中文分词器",link:"#_4-安装ik中文分词器",children:[]},{level:3,title:"5. 安装拼音分词器",slug:"_5-安装拼音分词器",link:"#_5-安装拼音分词器",children:[]}],path:"/other/essay/elk%E9%83%A8%E7%BD%B2.html",pathLocale:"/",extraFields:[]},{title:"即时通信软件需求",headers:[{level:2,title:"1. 概述",slug:"_1-概述",link:"#_1-概述",children:[{level:3,title:"1.1 项目背景",slug:"_1-1-项目背景",link:"#_1-1-项目背景",children:[]},{level:3,title:"1.2 项目目标",slug:"_1-2-项目目标",link:"#_1-2-项目目标",children:[]},{level:3,title:"1.3 用户群体",slug:"_1-3-用户群体",link:"#_1-3-用户群体",children:[]}]},{level:2,title:"2. 功能需求",slug:"_2-功能需求",link:"#_2-功能需求",children:[{level:3,title:"2.1 基础功能",slug:"_2-1-基础功能",link:"#_2-1-基础功能",children:[]},{level:3,title:"2.2 自动回复",slug:"_2-2-自动回复",link:"#_2-2-自动回复",children:[]},{level:3,title:"2.3 安全与合规",slug:"_2-3-安全与合规",link:"#_2-3-安全与合规",children:[]},{level:3,title:"2.4 高级功能",slug:"_2-4-高级功能",link:"#_2-4-高级功能",children:[]},{level:3,title:"2.5 跨平台支持",slug:"_2-5-跨平台支持",link:"#_2-5-跨平台支持",children:[]},{level:3,title:"2.6 其他需求",slug:"_2-6-其他需求",link:"#_2-6-其他需求",children:[]}]},{level:2,title:"3. 非功能需求",slug:"_3-非功能需求",link:"#_3-非功能需求",children:[{level:3,title:"3.1 性能要求",slug:"_3-1-性能要求",link:"#_3-1-性能要求",children:[]},{level:3,title:"3.2 安全要求",slug:"_3-2-安全要求",link:"#_3-2-安全要求",children:[]},{level:3,title:"3.3 可维护性",slug:"_3-3-可维护性",link:"#_3-3-可维护性",children:[]}]},{level:2,title:"4. 风险管理",slug:"_4-风险管理",link:"#_4-风险管理",children:[]}],path:"/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html",pathLocale:"/",extraFields:[]},{title:"windows下服务注册",headers:[{level:2,title:"1.需求",slug:"_1-需求",link:"#_1-需求",children:[]},{level:2,title:"2. 实现方式",slug:"_2-实现方式",link:"#_2-实现方式",children:[{level:3,title:"2.1 使用nssm",slug:"_2-1-使用nssm",link:"#_2-1-使用nssm",children:[]},{level:3,title:"2.2 使用winsw",slug:"_2-2-使用winsw",link:"#_2-2-使用winsw",children:[]}]}],path:"/other/essay/windows%E4%B8%8B%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C.html",pathLocale:"/",extraFields:[]},{title:"系统引导基本名词BIOS/EFI/MBR/GPT/GRUB",headers:[{level:3,title:"对应关系",slug:"对应关系",link:"#对应关系",children:[]}],path:"/other/essay/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%BC%95%E5%AF%BC.html",pathLocale:"/",extraFields:[]},{title:"批量删除github仓库",headers:[{level:2,title:"背景",slug:"背景",link:"#背景",children:[]},{level:2,title:"方法",slug:"方法",link:"#方法",children:[]}],path:"/other/git/BatchDeleteGitHubRepo.html",pathLocale:"/",extraFields:[]},{title:"git命令",headers:[{level:2,title:"一、善用手册",slug:"一、善用手册",link:"#一、善用手册",children:[]},{level:2,title:"二、 本地基本命令使用",slug:"二、-本地基本命令使用",link:"#二、-本地基本命令使用",children:[{level:3,title:"2.1 分支操作",slug:"_2-1-分支操作",link:"#_2-1-分支操作",children:[]},{level:3,title:"2.2、撤销修改",slug:"_2-2、撤销修改",link:"#_2-2、撤销修改",children:[]},{level:3,title:"2.3、使用git reflog在各个commit中穿梭",slug:"_2-3、使用git-reflog在各个commit中穿梭",link:"#_2-3、使用git-reflog在各个commit中穿梭",children:[]},{level:3,title:"2.4、git rm",slug:"_2-4、git-rm",link:"#_2-4、git-rm",children:[]}]},{level:2,title:"三、 远程命令使用",slug:"三、-远程命令使用",link:"#三、-远程命令使用",children:[{level:3,title:"1、把一个本地仓库关联到远程",slug:"_1、把一个本地仓库关联到远程",link:"#_1、把一个本地仓库关联到远程",children:[]},{level:3,title:"2、查看本地和远程分支关联关系",slug:"_2、查看本地和远程分支关联关系",link:"#_2、查看本地和远程分支关联关系",children:[]},{level:3,title:"3、解除和远程仓库关联",slug:"_3、解除和远程仓库关联",link:"#_3、解除和远程仓库关联",children:[]},{level:3,title:"4、git pull",slug:"_4、git-pull",link:"#_4、git-pull",children:[]},{level:3,title:"5、git rebase",slug:"_5、git-rebase",link:"#_5、git-rebase",children:[]}]}],path:"/other/git/GitCommands.html",pathLocale:"/",extraFields:[]},{title:"Git",headers:[],path:"/other/git/",pathLocale:"/",extraFields:[]},{title:"git 拉取远程分支到本地",headers:[{level:3,title:"1,查看远程分支",slug:"_1-查看远程分支",link:"#_1-查看远程分支",children:[]},{level:3,title:"2, 查看本地分支",slug:"_2-查看本地分支",link:"#_2-查看本地分支",children:[]},{level:3,title:"3,拉取远程分支",slug:"_3-拉取远程分支",link:"#_3-拉取远程分支",children:[]},{level:3,title:"4,新建本地dev分支,并关联远程dev分支",slug:"_4-新建本地dev分支-并关联远程dev分支",link:"#_4-新建本地dev分支-并关联远程dev分支",children:[]}],path:"/other/git/branch01.html",pathLocale:"/",extraFields:[]},{title:"git分支操作",headers:[{level:3,title:"一,推送本地分支到远程",slug:"一-推送本地分支到远程",link:"#一-推送本地分支到远程",children:[]},{level:3,title:"二,从远程拉取分支到d本地",slug:"二-从远程拉取分支到d本地",link:"#二-从远程拉取分支到d本地",children:[]},{level:3,title:"三,删除本地分支",slug:"三-删除本地分支",link:"#三-删除本地分支",children:[]},{level:3,title:"四,合并分支到master上",slug:"四-合并分支到master上",link:"#四-合并分支到master上",children:[]}],path:"/other/git/branch02.html",pathLocale:"/",extraFields:[]},{title:"Git克隆出现连接错误",headers:[{level:2,title:"如何解决 fatal: unable to access...的问题",slug:"如何解决-fatal-unable-to-access-的问题",link:"#如何解决-fatal-unable-to-access-的问题",children:[{level:3,title:"现象",slug:"现象",link:"#现象",children:[]},{level:3,title:"确定是否是因为代理问题",slug:"确定是否是因为代理问题",link:"#确定是否是因为代理问题",children:[]},{level:3,title:"解决问题",slug:"解决问题",link:"#解决问题",children:[]}]}],path:"/other/git/fatal.html",pathLocale:"/",extraFields:[]},{title:"git冲突出现的原因",headers:[{level:2,title:"一,git冲突出现的原因及解决方案",slug:"一-git冲突出现的原因及解决方案",link:"#一-git冲突出现的原因及解决方案",children:[]}],path:"/other/git/gitConflict.html",pathLocale:"/",extraFields:[]},{title:"git rebase的使用",headers:[{level:3,title:"一,用于合并当前分支的多个commit记录",slug:"一-用于合并当前分支的多个commit记录",link:"#一-用于合并当前分支的多个commit记录",children:[]},{level:3,title:"",slug:"",link:"#",children:[]},{level:3,title:"二、使用rebase替代merge合并分支",slug:"二、使用rebase替代merge合并分支",link:"#二、使用rebase替代merge合并分支",children:[]}],path:"/other/git/gitRebase.html",pathLocale:"/",extraFields:[]},{title:"git工作区、暂存区、和版本库",headers:[{level:3,title:"一,基本概念",slug:"一-基本概念",link:"#一-基本概念",children:[]}],path:"/other/git/gitwork.html",pathLocale:"/",extraFields:[]},{title:"git pull产生临时Merge branch的问题",headers:[{level:3,title:"一,问题",slug:"一-问题",link:"#一-问题",children:[]},{level:3,title:"二,产生的原因",slug:"二-产生的原因",link:"#二-产生的原因",children:[]},{level:3,title:"三,解决方法",slug:"三-解决方法",link:"#三-解决方法",children:[]}],path:"/other/git/mergeBranch.html",pathLocale:"/",extraFields:[]},{title:"git代理",headers:[{level:2,title:"1、全局代理",slug:"_1、全局代理",link:"#_1、全局代理",children:[]},{level:2,title:"2、部分代理",slug:"_2、部分代理",link:"#_2、部分代理",children:[]},{level:2,title:"3、参考",slug:"_3、参考",link:"#_3、参考",children:[]}],path:"/other/git/proxy.html",pathLocale:"/",extraFields:[]},{title:"git rebase与merge的区别",headers:[],path:"/other/git/rebaseAndMerge.html",pathLocale:"/",extraFields:[]},{title:"git reset命令使用",headers:[{level:3,title:"1、几种reset介绍",slug:"_1、几种reset介绍",link:"#_1、几种reset介绍",children:[]}],path:"/other/git/reset.html",pathLocale:"/",extraFields:[]},{title:"git stash 暂存",headers:[{level:2,title:"一,使用场景",slug:"一-使用场景",link:"#一-使用场景",children:[]},{level:2,title:"二,stash的作用",slug:"二-stash的作用",link:"#二-stash的作用",children:[]},{level:2,title:"三,使用命令",slug:"三-使用命令",link:"#三-使用命令",children:[]},{level:2,title:"四,本地解决冲突",slug:"四-本地解决冲突",link:"#四-本地解决冲突",children:[]}],path:"/other/git/stash.html",pathLocale:"/",extraFields:[]},{title:"cpu介绍",headers:[{level:2,title:"intel cpu型号",slug:"intel-cpu型号",link:"#intel-cpu型号",children:[{level:3,title:"cpu型号命名",slug:"cpu型号命名",link:"#cpu型号命名",children:[]},{level:3,title:"cpu 后缀",slug:"cpu-后缀",link:"#cpu-后缀",children:[]}]}],path:"/other/hardware/CPU.html",pathLocale:"/",extraFields:[]},{title:"RedHat系",headers:[{level:2,title:"1、安装JDK11",slug:"_1、安装jdk11",link:"#_1、安装jdk11",children:[]},{level:2,title:"2、多版本JDK切换",slug:"_2、多版本jdk切换",link:"#_2、多版本jdk切换",children:[]}],path:"/other/linux/CentOS.html",pathLocale:"/",extraFields:[]},{title:"常用命令",headers:[{level:3,title:"1、查找多个文件中是否包含字符串",slug:"_1、查找多个文件中是否包含字符串",link:"#_1、查找多个文件中是否包含字符串",children:[]},{level:3,title:"2、高亮关键字",slug:"_2、高亮关键字",link:"#_2、高亮关键字",children:[]},{level:3,title:"3、查询大文件",slug:"_3、查询大文件",link:"#_3、查询大文件",children:[]},{level:3,title:"4、查看文件被哪个进程占用",slug:"_4、查看文件被哪个进程占用",link:"#_4、查看文件被哪个进程占用",children:[]},{level:3,title:"5、看开机启动项启动耗时",slug:"_5、看开机启动项启动耗时",link:"#_5、看开机启动项启动耗时",children:[]},{level:3,title:"6、Curl命令",slug:"_6、curl命令",link:"#_6、curl命令",children:[]},{level:3,title:"7、strace命令",slug:"_7、strace命令",link:"#_7、strace命令",children:[]},{level:3,title:"8、 nc命令",slug:"_8、-nc命令",link:"#_8、-nc命令",children:[]},{level:3,title:"9、tcpdump抓包",slug:"_9、tcpdump抓包",link:"#_9、tcpdump抓包",children:[]}],path:"/other/linux/CommonUsedCMD.html",pathLocale:"/",extraFields:[]},{title:"Curl命令",headers:[{level:2,title:"1、使用CURL分析接口请求耗时",slug:"_1、使用curl分析接口请求耗时",link:"#_1、使用curl分析接口请求耗时",children:[{level:3,title:"1.1 构造curl命令",slug:"_1-1-构造curl命令",link:"#_1-1-构造curl命令",children:[]},{level:3,title:"1.2、分析耗时",slug:"_1-2、分析耗时",link:"#_1-2、分析耗时",children:[]}]}],path:"/other/linux/Curl.html",pathLocale:"/",extraFields:[]},{title:"docker安装mysql",headers:[{level:2,title:"1. 不要用太新的版本",slug:"_1-不要用太新的版本",link:"#_1-不要用太新的版本",children:[]},{level:2,title:"2 安装",slug:"_2-安装",link:"#_2-安装",children:[]},{level:2,title:"3 报错",slug:"_3-报错",link:"#_3-报错",children:[]}],path:"/other/linux/InstallMysqlWithDocker.html",pathLocale:"/",extraFields:[]},{title:"Manjaro问题搜集",headers:[{level:3,title:"1、降级软件包",slug:"_1、降级软件包",link:"#_1、降级软件包",children:[]},{level:3,title:"2、开机报错failed to start rotate log files",slug:"_2、开机报错failed-to-start-rotate-log-files",link:"#_2、开机报错failed-to-start-rotate-log-files",children:[]},{level:3,title:"linux下输入法无法输入中文中括号问题",slug:"linux下输入法无法输入中文中括号问题",link:"#linux下输入法无法输入中文中括号问题",children:[]},{level:3,title:"3、美化",slug:"_3、美化",link:"#_3、美化",children:[]},{level:3,title:"4、设置yakuake提示没有权限修改文件",slug:"_4、设置yakuake提示没有权限修改文件",link:"#_4、设置yakuake提示没有权限修改文件",children:[]},{level:3,title:"4、Manjaro不支持Mysql",slug:"_4、manjaro不支持mysql",link:"#_4、manjaro不支持mysql",children:[]},{level:3,title:"5、Manjaro设置DNS",slug:"_5、manjaro设置dns",link:"#_5、manjaro设置dns",children:[]},{level:3,title:"6、Manjaro fcitx5中文大括号问题",slug:"_6、manjaro-fcitx5中文大括号问题",link:"#_6、manjaro-fcitx5中文大括号问题",children:[]},{level:3,title:"7、分支管理",slug:"_7、分支管理",link:"#_7、分支管理",children:[]},{level:3,title:"9、解压缩中文乱码",slug:"_9、解压缩中文乱码",link:"#_9、解压缩中文乱码",children:[]},{level:3,title:"10、vmware使用问题",slug:"_10、vmware使用问题",link:"#_10、vmware使用问题",children:[]},{level:3,title:"11、vnc",slug:"_11、vnc",link:"#_11、vnc",children:[]},{level:3,title:"12、Remmina远程windows字体发虚",slug:"_12、remmina远程windows字体发虚",link:"#_12、remmina远程windows字体发虚",children:[]},{level:3,title:"13、manjaro-kde下idea新版本UI问题",slug:"_13、manjaro-kde下idea新版本ui问题",link:"#_13、manjaro-kde下idea新版本ui问题",children:[]},{level:3,title:"12、GTK3主题设置",slug:"_12、gtk3主题设置",link:"#_12、gtk3主题设置",children:[]}],path:"/other/linux/Manjaro.html",pathLocale:"/",extraFields:[]},{title:"系统挂载磁盘",headers:[{level:2,title:"参考",slug:"参考",link:"#参考",children:[]},{level:2,title:"挂载",slug:"挂载",link:"#挂载",children:[{level:3,title:"划分分区并挂载磁盘",slug:"划分分区并挂载磁盘",link:"#划分分区并挂载磁盘",children:[]},{level:3,title:"设置开机自动挂载磁盘分区",slug:"设置开机自动挂载磁盘分区",link:"#设置开机自动挂载磁盘分区",children:[]}]}],path:"/other/linux/MountDisk.html",pathLocale:"/",extraFields:[]},{title:"双网卡问题",headers:[{level:2,title:"1.1 kde桌面双网卡内外网设置",slug:"_1-1-kde桌面双网卡内外网设置",link:"#_1-1-kde桌面双网卡内外网设置",children:[{level:3,title:"1.1 内网设置",slug:"_1-1-内网设置",link:"#_1-1-内网设置",children:[]},{level:3,title:"1.2 外网(无线)设置(保留默认即可)",slug:"_1-2-外网-无线-设置-保留默认即可",link:"#_1-2-外网-无线-设置-保留默认即可",children:[]}]},{level:2,title:"2、双网卡DNS解析",slug:"_2、双网卡dns解析",link:"#_2、双网卡dns解析",children:[{level:3,title:"3、搜索域",slug:"_3、搜索域",link:"#_3、搜索域",children:[]}]}],path:"/other/linux/MultiNetworkCard.html",pathLocale:"/",extraFields:[]},{title:"Linux",headers:[],path:"/other/linux/",pathLocale:"/",extraFields:[]},{title:"部署Samba",headers:[{level:2,title:"1、安装过程省略",slug:"_1、安装过程省略",link:"#_1、安装过程省略",children:[]},{level:2,title:"2、配置",slug:"_2、配置",link:"#_2、配置",children:[{level:3,title:"2.1 配置文件",slug:"_2-1-配置文件",link:"#_2-1-配置文件",children:[]},{level:3,title:"2.2 创建samba用户",slug:"_2-2-创建samba用户",link:"#_2-2-创建samba用户",children:[]},{level:3,title:"2.3 windows连接samba",slug:"_2-3-windows连接samba",link:"#_2-3-windows连接samba",children:[]}]}],path:"/other/linux/Samba.html",pathLocale:"/",extraFields:[]},{title:"Linux挂载windows共享目录",headers:[{level:3,title:"1、在windows设置共享目录",slug:"_1、在windows设置共享目录",link:"#_1、在windows设置共享目录",children:[]},{level:3,title:"2、在linux下挂载",slug:"_2、在linux下挂载",link:"#_2、在linux下挂载",children:[]},{level:3,title:"参考博客",slug:"参考博客",link:"#参考博客",children:[]}],path:"/other/linux/ShareBetweenWindowsAndLinux.html",pathLocale:"/",extraFields:[]},{title:"tcpdump抓包",headers:[{level:3,title:"1、所用抓报命令",slug:"_1、所用抓报命令",link:"#_1、所用抓报命令",children:[]},{level:3,title:"2、抓包后的处理",slug:"_2、抓包后的处理",link:"#_2、抓包后的处理",children:[]},{level:3,title:"3、其他使用实例",slug:"_3、其他使用实例",link:"#_3、其他使用实例",children:[]}],path:"/other/linux/TcpDump.html",pathLocale:"/",extraFields:[]},{title:"Linux下加装wifi模块",headers:[{level:2,title:"1、需求场景",slug:"_1、需求场景",link:"#_1、需求场景",children:[]},{level:2,title:"2、解决方案——使用板载无线网卡",slug:"_2、解决方案——使用板载无线网卡",link:"#_2、解决方案——使用板载无线网卡",children:[]}],path:"/other/linux/Wifi.html",pathLocale:"/",extraFields:[]},{title:"linux中防火墙",headers:[{level:2,title:"集中常用的防火墙配置工具",slug:"集中常用的防火墙配置工具",link:"#集中常用的防火墙配置工具",children:[]},{level:2,title:"关系图",slug:"关系图",link:"#关系图",children:[]},{level:2,title:"chatgpt中的回答",slug:"chatgpt中的回答",link:"#chatgpt中的回答",children:[]},{level:2,title:"结论",slug:"结论",link:"#结论",children:[]},{level:2,title:"参考",slug:"参考",link:"#参考",children:[]}],path:"/other/linux/firewall.html",pathLocale:"/",extraFields:[]},{title:"windows子系统wsl",headers:[{level:2,title:"2.1 更换国内镜像源",slug:"_2-1-更换国内镜像源",link:"#_2-1-更换国内镜像源",children:[]},{level:2,title:"2.2 从windows进入wsl",slug:"_2-2-从windows进入wsl",link:"#_2-2-从windows进入wsl",children:[]},{level:2,title:"2.3 wsl域名解析慢的问题",slug:"_2-3-wsl域名解析慢的问题",link:"#_2-3-wsl域名解析慢的问题",children:[]}],path:"/other/linux/wsl.html",pathLocale:"/",extraFields:[]},{title:"使用Flameshot截图",headers:[{level:2,title:"manjaro-kde为flameshot设置快捷截图",slug:"manjaro-kde为flameshot设置快捷截图",link:"#manjaro-kde为flameshot设置快捷截图",children:[]}],path:"/other/linux/%E6%88%AA%E5%9B%BE.html",pathLocale:"/",extraFields:[]},{title:"MarkDown资源",headers:[{level:2,title:"Emoji",slug:"emoji",link:"#emoji",children:[]},{level:2,title:"vue-press-theme-hope Icon",slug:"vue-press-theme-hope-icon",link:"#vue-press-theme-hope-icon",children:[]}],path:"/other/markdown/",pathLocale:"/",extraFields:[]},{title:"01 | OAuth 2.0是要通过什么方式解决什么问题?",headers:[{level:3,title:"OAuth 2.0 是什么?",slug:"oauth-2-0-是什么",link:"#oauth-2-0-是什么",children:[]},{level:3,title:"为什么用 OAuth 2.0?",slug:"为什么用-oauth-2-0",link:"#为什么用-oauth-2-0",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/01.html",pathLocale:"/",extraFields:[]},{title:"02 | 授权码许可类型中,为什么一定要有授权码?",headers:[{level:3,title:"为什么需要授权码?",slug:"为什么需要授权码",link:"#为什么需要授权码",children:[]},{level:3,title:"授权码许可类型的通信过程",slug:"授权码许可类型的通信过程",link:"#授权码许可类型的通信过程",children:[]},{level:3,title:"直接通信",slug:"直接通信",link:"#直接通信",children:[]},{level:3,title:"两个 “一伙”",slug:"两个-一伙",link:"#两个-一伙",children:[]},{level:3,title:"一定要有浏览器吗?",slug:"一定要有浏览器吗",link:"#一定要有浏览器吗",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/02.html",pathLocale:"/",extraFields:[]},{title:"03 | 授权服务:授权码和访问令牌的颁发流程是怎样",headers:[{level:3,title:"授权服务的工作过程",slug:"授权服务的工作过程",link:"#授权服务的工作过程",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/03.html",pathLocale:"/",extraFields:[]},{title:"04 | 在OAuth 2.0中,如何使用JWT结构化令牌?",headers:[{level:3,title:"JWT 结构化令牌",slug:"jwt-结构化令牌",link:"#jwt-结构化令牌",children:[]},{level:3,title:"令牌内检",slug:"令牌内检",link:"#令牌内检",children:[]},{level:3,title:"JWT 是如何被使用的?",slug:"jwt-是如何被使用的",link:"#jwt-是如何被使用的",children:[]},{level:3,title:"为什么要使用 JWT 令牌?",slug:"为什么要使用-jwt-令牌",link:"#为什么要使用-jwt-令牌",children:[]},{level:3,title:"令牌的生命周期",slug:"令牌的生命周期",link:"#令牌的生命周期",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/04.html",pathLocale:"/",extraFields:[]},{title:"05 | 如何安全、快速地接入OAuth 2.0?",headers:[{level:3,title:"构建第三方软件应用",slug:"构建第三方软件应用",link:"#构建第三方软件应用",children:[]},{level:3,title:"服务市场中的第三方应用软件",slug:"服务市场中的第三方应用软件",link:"#服务市场中的第三方应用软件",children:[]},{level:3,title:"构建受保护资源服务",slug:"构建受保护资源服务",link:"#构建受保护资源服务",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/05.html",pathLocale:"/",extraFields:[]},{title:"06 | 除了授权码许可类型,OAuth 2.0还支持什么授权流程?",headers:[{level:3,title:"资源拥有者凭据许可",slug:"资源拥有者凭据许可",link:"#资源拥有者凭据许可",children:[]},{level:3,title:"客户端凭据许可",slug:"客户端凭据许可",link:"#客户端凭据许可",children:[]},{level:3,title:"隐式许可",slug:"隐式许可",link:"#隐式许可",children:[]},{level:3,title:"如何选择?",slug:"如何选择",link:"#如何选择",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/06.html",pathLocale:"/",extraFields:[]},{title:"07 | 如何在移动App中使用OAuth 2.0?",headers:[{level:3,title:"没有 Server 端的 App",slug:"没有-server-端的-app",link:"#没有-server-端的-app",children:[]},{level:3,title:"有 Server 端的 App",slug:"有-server-端的-app",link:"#有-server-端的-app",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/07.html",pathLocale:"/",extraFields:[]},{title:"08 | 实践OAuth 2.0时,使用不当可能会导致哪些安全漏洞?",headers:[{level:3,title:"CSRF 攻击",slug:"csrf-攻击",link:"#csrf-攻击",children:[]},{level:3,title:"XSS 攻击",slug:"xss-攻击",link:"#xss-攻击",children:[]},{level:3,title:"水平越权",slug:"水平越权",link:"#水平越权",children:[]},{level:3,title:"授权码失窃",slug:"授权码失窃",link:"#授权码失窃",children:[]},{level:3,title:"重定向 URI 被篡改",slug:"重定向-uri-被篡改",link:"#重定向-uri-被篡改",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/08.html",pathLocale:"/",extraFields:[]},{title:"09 | 实战:利用OAuth 2.0实现一个OpenID Connect用户身份认证协议",headers:[{level:3,title:"OIDC 是什么?",slug:"oidc-是什么",link:"#oidc-是什么",children:[]},{level:3,title:"OIDC 和 OAuth 2.0 的角色对应关系",slug:"oidc-和-oauth-2-0-的角色对应关系",link:"#oidc-和-oauth-2-0-的角色对应关系",children:[]},{level:3,title:"OIDC 和 OAuth 2.0 的关键区别",slug:"oidc-和-oauth-2-0-的关键区别",link:"#oidc-和-oauth-2-0-的关键区别",children:[]},{level:3,title:"OIDC 中的 ID 令牌生成和解析方法",slug:"oidc-中的-id-令牌生成和解析方法",link:"#oidc-中的-id-令牌生成和解析方法",children:[]},{level:3,title:"用访问令牌获取 ID 令牌之外的信息",slug:"用访问令牌获取-id-令牌之外的信息",link:"#用访问令牌获取-id-令牌之外的信息",children:[]},{level:3,title:"单点登录",slug:"单点登录",link:"#单点登录",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/09.html",pathLocale:"/",extraFields:[]},{title:"10 | 串讲:OAuth 2.0的工作流程与安全问题",headers:[{level:3,title:"OAuth 2.0 安全问题串讲",slug:"oauth-2-0-安全问题串讲",link:"#oauth-2-0-安全问题串讲",children:[]},{level:3,title:"再强调都不为过的安全意识",slug:"再强调都不为过的安全意识",link:"#再强调都不为过的安全意识",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]},{level:3,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/10.html",pathLocale:"/",extraFields:[]},{title:"11 | 实战案例:使用Spring Security搭建一套基于JWT的OAuth 2.0架构",headers:[{level:3,title:"项目准备工作",slug:"项目准备工作",link:"#项目准备工作",children:[]},{level:3,title:"搭建授权服务器",slug:"搭建授权服务器",link:"#搭建授权服务器",children:[]},{level:3,title:"搭建受保护资源服务器",slug:"搭建受保护资源服务器",link:"#搭建受保护资源服务器",children:[]},{level:3,title:"初始化数据配置",slug:"初始化数据配置",link:"#初始化数据配置",children:[]},{level:3,title:"演示三种授权许可类型",slug:"演示三种授权许可类型",link:"#演示三种授权许可类型",children:[]},{level:3,title:"资源拥有者凭据许可类型",slug:"资源拥有者凭据许可类型",link:"#资源拥有者凭据许可类型",children:[]},{level:3,title:"客户端授权许可类型",slug:"客户端授权许可类型",link:"#客户端授权许可类型",children:[]},{level:3,title:"授权码许可类型",slug:"授权码许可类型",link:"#授权码许可类型",children:[]},{level:3,title:"演示权限控制",slug:"演示权限控制",link:"#演示权限控制",children:[]},{level:3,title:"搭建客户端程序",slug:"搭建客户端程序",link:"#搭建客户端程序",children:[]},{level:3,title:"演示单点登录",slug:"演示单点登录",link:"#演示单点登录",children:[]},{level:3,title:"演示客户端请求资源服务器资源",slug:"演示客户端请求资源服务器资源",link:"#演示客户端请求资源服务器资源",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/11.html",pathLocale:"/",extraFields:[]},{title:"12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构",headers:[{level:2,title:"微服务分层架构",slug:"微服务分层架构",link:"#微服务分层架构",children:[{level:3,title:"Nginx 反向代理层",slug:"nginx-反向代理层",link:"#nginx-反向代理层",children:[]},{level:3,title:"Web 应用层",slug:"web-应用层",link:"#web-应用层",children:[]},{level:3,title:"Gateway 网关层",slug:"gateway-网关层",link:"#gateway-网关层",children:[]},{level:3,title:"IDP 服务",slug:"idp-服务",link:"#idp-服务",children:[]},{level:3,title:"BFF 层",slug:"bff-层",link:"#bff-层",children:[]},{level:3,title:"领域服务层",slug:"领域服务层",link:"#领域服务层",children:[]}]},{level:2,title:"OAuth 2.0/JWT 如何与微服务进行集成?",slug:"oauth-2-0-jwt-如何与微服务进行集成",link:"#oauth-2-0-jwt-如何与微服务进行集成",children:[{level:3,title:"场景 1:第一方 Web 应用 + 资源拥有者凭据模式",slug:"场景-1-第一方-web-应用-资源拥有者凭据模式",link:"#场景-1-第一方-web-应用-资源拥有者凭据模式",children:[]},{level:3,title:"场景 2:第一方移动应用 + 授权码许可模式",slug:"场景-2-第一方移动应用-授权码许可模式",link:"#场景-2-第一方移动应用-授权码许可模式",children:[]},{level:3,title:"场景 3:第三方 Web 应用 + 授权码模式",slug:"场景-3-第三方-web-应用-授权码模式",link:"#场景-3-第三方-web-应用-授权码模式",children:[]}]},{level:2,title:"小结",slug:"小结",link:"#小结",children:[]},{level:2,title:"思考题",slug:"思考题",link:"#思考题",children:[]}],path:"/other/oauth2/12.html",pathLocale:"/",extraFields:[]},{title:"13 | 各大开放平台是如何使用OAuth 2.0的?",headers:[{level:3,title:"开放平台体系是什么样子的?",slug:"开放平台体系是什么样子的",link:"#开放平台体系是什么样子的",children:[]},{level:3,title:"各大开放平台授权流程",slug:"各大开放平台授权流程",link:"#各大开放平台授权流程",children:[]},{level:3,title:"授权码流程中的参数说明",slug:"授权码流程中的参数说明",link:"#授权码流程中的参数说明",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/13.html",pathLocale:"/",extraFields:[]},{title:"14 | 查漏补缺:OAuth 2.0 常见问题答疑",headers:[{level:3,title:"发明 OAuth 的目的到底是什么?",slug:"发明-oauth-的目的到底是什么",link:"#发明-oauth-的目的到底是什么",children:[]},{level:3,title:"OAuth 2.0 是身份认证协议吗?",slug:"oauth-2-0-是身份认证协议吗",link:"#oauth-2-0-是身份认证协议吗",children:[]},{level:3,title:"有了刷新令牌,是不是就可以让访问令牌一直有效了?",slug:"有了刷新令牌-是不是就可以让访问令牌一直有效了",link:"#有了刷新令牌-是不是就可以让访问令牌一直有效了",children:[]},{level:3,title:"使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?",slug:"使用了-https-是不是就能确保-jwt-格式令牌的数据安全",link:"#使用了-https-是不是就能确保-jwt-格式令牌的数据安全",children:[]},{level:3,title:"ID 令牌和访问令牌之间有联系吗?",slug:"id-令牌和访问令牌之间有联系吗",link:"#id-令牌和访问令牌之间有联系吗",children:[]},{level:3,title:"PKCE 协议到底解决的是什么问题?",slug:"pkce-协议到底解决的是什么问题",link:"#pkce-协议到底解决的是什么问题",children:[]},{level:3,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/other/oauth2/14.html",pathLocale:"/",extraFields:[]},{title:"开篇词 | 为什么要学OAuth 2.0?",headers:[{level:3,title:"OAuth 2.0是什么?",slug:"oauth-2-0是什么",link:"#oauth-2-0是什么",children:[]},{level:3,title:"为什么会有这门课?",slug:"为什么会有这门课",link:"#为什么会有这门课",children:[]},{level:3,title:"这门课是怎么设计的?",slug:"这门课是怎么设计的",link:"#这门课是怎么设计的",children:[]}],path:"/other/oauth2/",pathLocale:"/",extraFields:[]},{title:"玩转PVE虚拟机",headers:[{level:3,title:"pve",slug:"pve",link:"#pve",children:[]}],path:"/other/pve/",pathLocale:"/",extraFields:[]},{title:"ddns证书",headers:[{level:2,title:"1. 申请动态域名(省略)",slug:"_1-申请动态域名-省略",link:"#_1-申请动态域名-省略",children:[]},{level:2,title:"2. 使用acme申请免费的",slug:"_2-使用acme申请免费的",link:"#_2-使用acme申请免费的",children:[{level:3,title:"2.1 使用acme申请证书",slug:"_2-1-使用acme申请证书",link:"#_2-1-使用acme申请证书",children:[]},{level:3,title:"2.2 使用openssl创建密钥对",slug:"_2-2-使用openssl创建密钥对",link:"#_2-2-使用openssl创建密钥对",children:[]},{level:3,title:"2.3 上传公钥到dynv6",slug:"_2-3-上传公钥到dynv6",link:"#_2-3-上传公钥到dynv6",children:[]},{level:3,title:"2.4 执行申请证书",slug:"_2-4-执行申请证书",link:"#_2-4-执行申请证书",children:[]}]},{level:2,title:"3.给pve的web管理配置证书",slug:"_3-给pve的web管理配置证书",link:"#_3-给pve的web管理配置证书",children:[]},{level:2,title:"4. 自动续签",slug:"_4-自动续签",link:"#_4-自动续签",children:[]},{level:2,title:"pve的webui界面自带的acme插件",slug:"pve的webui界面自带的acme插件",link:"#pve的webui界面自带的acme插件",children:[]}],path:"/other/pve/ddns%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6.html",pathLocale:"/",extraFields:[]},{title:"pve防火墙",headers:[{level:3,title:"一、防火墙简介",slug:"一、防火墙简介",link:"#一、防火墙简介",children:[]},{level:3,title:"二: 防火墙配置文件",slug:"二-防火墙配置文件",link:"#二-防火墙配置文件",children:[]},{level:3,title:"三、实际配置",slug:"三、实际配置",link:"#三、实际配置",children:[]}],path:"/other/pve/firewall.html",pathLocale:"/",extraFields:[]},{title:"rabbitmq",headers:[{level:3,title:"从入门到放弃",slug:"从入门到放弃",link:"#从入门到放弃",children:[]}],path:"/other/rabbitmq/",pathLocale:"/",extraFields:[]},{title:"软件激活",headers:[{level:3,title:"smartsvn",slug:"smartsvn",link:"#smartsvn",children:[]}],path:"/other/software/%E7%A0%B4%E8%A7%A3%E8%BD%AF%E4%BB%B6.html",pathLocale:"/",extraFields:[]},{title:"客户端对接用户中心",headers:[{level:2,title:"1、基础环境搭建",slug:"_1、基础环境搭建",link:"#_1、基础环境搭建",children:[{level:3,title:"1.1 引入依赖",slug:"_1-1-引入依赖",link:"#_1-1-引入依赖",children:[]},{level:3,title:"1.2 配置",slug:"_1-2-配置",link:"#_1-2-配置",children:[]}]},{level:2,title:"2、用户中心用户转本地用户",slug:"_2、用户中心用户转本地用户",link:"#_2、用户中心用户转本地用户",children:[{level:3,title:"2.1 过滤器进行拦截处理",slug:"_2-1-过滤器进行拦截处理",link:"#_2-1-过滤器进行拦截处理",children:[]}]}],path:"/other/sono/SUC.html",pathLocale:"/",extraFields:[]},{title:"Idea",headers:[{level:2,title:"1、多线程debug遇到的问题",slug:"_1、多线程debug遇到的问题",link:"#_1、多线程debug遇到的问题",children:[]},{level:2,title:"2、Idea指定jdk启动",slug:"_2、idea指定jdk启动",link:"#_2、idea指定jdk启动",children:[]},{level:2,title:"3、idea中git操作",slug:"_3、idea中git操作",link:"#_3、idea中git操作",children:[{level:3,title:"3.1 对比任意非连续的两次提交文件差异",slug:"_3-1-对比任意非连续的两次提交文件差异",link:"#_3-1-对比任意非连续的两次提交文件差异",children:[]}]},{level:2,title:"4、debug无法进入jdk源码",slug:"_4、debug无法进入jdk源码",link:"#_4、debug无法进入jdk源码",children:[]}],path:"/other/tools/Idea.html",pathLocale:"/",extraFields:[]},{title:"软件工具",headers:[],path:"/other/tools/",pathLocale:"/",extraFields:[]},{title:"软件分享",headers:[{level:3,title:"1、Beyond Compare3",slug:"_1、beyond-compare3",link:"#_1、beyond-compare3",children:[]}],path:"/other/tools/SoftWare.html",pathLocale:"/",extraFields:[]},{title:"vscode配置",headers:[{level:3,title:"1. vscode终端无法打开",slug:"_1-vscode终端无法打开",link:"#_1-vscode终端无法打开",children:[]},{level:3,title:"2、在vscode中使用git-bash作为默认终端",slug:"_2、在vscode中使用git-bash作为默认终端",link:"#_2、在vscode中使用git-bash作为默认终端",children:[]},{level:3,title:"3. vscode代码片段",slug:"_3-vscode代码片段",link:"#_3-vscode代码片段",children:[]},{level:3,title:"4. 设置jdk环境",slug:"_4-设置jdk环境",link:"#_4-设置jdk环境",children:[]}],path:"/other/tools/VsCode.html",pathLocale:"/",extraFields:[]},{title:"小组分享-云服务",headers:[{level:2,title:"1.1 什么是DNS",slug:"_1-1-什么是dns",link:"#_1-1-什么是dns",children:[]},{level:2,title:"1.1 A(Address)记录",slug:"_1-1-a-address-记录",link:"#_1-1-a-address-记录",children:[]},{level:2,title:"1.2 CNAME(Canonical Name)",slug:"_1-2-cname-canonical-name",link:"#_1-2-cname-canonical-name",children:[{level:3,title:"1.2.1 定义",slug:"_1-2-1-定义",link:"#_1-2-1-定义",children:[]},{level:3,title:"1.2.2 参考博客",slug:"_1-2-2-参考博客",link:"#_1-2-2-参考博客",children:[]},{level:3,title:"1.2.2 场景",slug:"_1-2-2-场景",link:"#_1-2-2-场景",children:[]}]},{level:2,title:"1.3 NS(Name Server)记录",slug:"_1-3-ns-name-server-记录",link:"#_1-3-ns-name-server-记录",children:[]},{level:2,title:"1.4 DNS分析实战",slug:"_1-4-dns分析实战",link:"#_1-4-dns分析实战",children:[]},{level:2,title:"2.1 什么是CDN",slug:"_2-1-什么是cdn",link:"#_2-1-什么是cdn",children:[]},{level:2,title:"2.2 CDN原理",slug:"_2-2-cdn原理",link:"#_2-2-cdn原理",children:[]},{level:2,title:"2.3 CDN是如何使用CNAME",slug:"_2-3-cdn是如何使用cname",link:"#_2-3-cdn是如何使用cname",children:[]},{level:2,title:"2.4 CNAME为什么不直接解析到ip,而是域名",slug:"_2-4-cname为什么不直接解析到ip-而是域名",link:"#_2-4-cname为什么不直接解析到ip-而是域名",children:[]},{level:2,title:"3.1 什么是对象存储",slug:"_3-1-什么是对象存储",link:"#_3-1-什么是对象存储",children:[]},{level:2,title:"3.2 华为SDK使用",slug:"_3-2-华为sdk使用",link:"#_3-2-华为sdk使用",children:[]},{level:2,title:"3.3 S3协议sdk",slug:"_3-3-s3协议sdk",link:"#_3-3-s3协议sdk",children:[]},{level:2,title:"3.3 CDN加速OBS中存储的图片.",slug:"_3-3-cdn加速obs中存储的图片",link:"#_3-3-cdn加速obs中存储的图片",children:[]},{level:2,title:"4.1 一个域名能解析多少个IP?",slug:"_4-1-一个域名能解析多少个ip",link:"#_4-1-一个域名能解析多少个ip",children:[]},{level:2,title:"4.2 请求一个地址,加www和不加是否有区别?",slug:"_4-2-请求一个地址-加www和不加是否有区别",link:"#_4-2-请求一个地址-加www和不加是否有区别",children:[]}],path:"/other/training/CloudServiceTraining.html",pathLocale:"/",extraFields:[]},{title:"小组分享",headers:[],path:"/other/training/",pathLocale:"/",extraFields:[]},{title:"OAuth2分享",headers:[{level:2,title:"1、SSO",slug:"_1、sso",link:"#_1、sso",children:[{level:3,title:"1.1 SSO介绍",slug:"_1-1-sso介绍",link:"#_1-1-sso介绍",children:[]},{level:3,title:"1.2 SSO使用场景(解决了什么问题)",slug:"_1-2-sso使用场景-解决了什么问题",link:"#_1-2-sso使用场景-解决了什么问题",children:[]},{level:3,title:"1.3 使用SSO的好处",slug:"_1-3-使用sso的好处",link:"#_1-3-使用sso的好处",children:[]}]},{level:2,title:"2、SSO常见实现方案",slug:"_2、sso常见实现方案",link:"#_2、sso常见实现方案",children:[{level:3,title:"2.1 基于 Cookie 的单点登录",slug:"_2-1-基于-cookie-的单点登录",link:"#_2-1-基于-cookie-的单点登录",children:[]},{level:3,title:"2.2 分布式 Session 实现单点登录",slug:"_2-2-分布式-session-实现单点登录",link:"#_2-2-分布式-session-实现单点登录",children:[]},{level:3,title:"2.3 统一认证授权方式实现单点登录",slug:"_2-3-统一认证授权方式实现单点登录",link:"#_2-3-统一认证授权方式实现单点登录",children:[]}]},{level:2,title:"3、实际场景中SSO实现",slug:"_3、实际场景中sso实现",link:"#_3、实际场景中sso实现",children:[{level:3,title:"3.1 JWT(完全跨域方案)",slug:"_3-1-jwt-完全跨域方案",link:"#_3-1-jwt-完全跨域方案",children:[]},{level:3,title:"3.2 使用 OAuth2.0 实现单点登录",slug:"_3-2-使用-oauth2-0-实现单点登录",link:"#_3-2-使用-oauth2-0-实现单点登录",children:[]}]},{level:2,title:"4、参考资料",slug:"_4、参考资料",link:"#_4、参考资料",children:[]}],path:"/other/training/SSO.html",pathLocale:"/",extraFields:[]},{title:"画图工具",headers:[{level:2,title:"1 示例",slug:"_1-示例",link:"#_1-示例",children:[]},{level:2,title:"2 制图工具",slug:"_2-制图工具",link:"#_2-制图工具",children:[]},{level:2,title:"3 工具选择",slug:"_3-工具选择",link:"#_3-工具选择",children:[]}],path:"/other/training/draw.html",pathLocale:"/",extraFields:[]},{title:"前后分离项目搭建",headers:[{level:2,title:"1、后端篇",slug:"_1、后端篇",link:"#_1、后端篇",children:[{level:3,title:"1.1 初始化springboot项目",slug:"_1-1-初始化springboot项目",link:"#_1-1-初始化springboot项目",children:[]},{level:3,title:"1.2 全局异常处理",slug:"_1-2-全局异常处理",link:"#_1-2-全局异常处理",children:[]},{level:3,title:"1.3 aop统一日志处理",slug:"_1-3-aop统一日志处理",link:"#_1-3-aop统一日志处理",children:[]},{level:3,title:"1.4 添加Mybatis-plus",slug:"_1-4-添加mybatis-plus",link:"#_1-4-添加mybatis-plus",children:[]},{level:3,title:"1.5 添加Redis",slug:"_1-5-添加redis",link:"#_1-5-添加redis",children:[]},{level:3,title:"1.6 添加swagger",slug:"_1-6-添加swagger",link:"#_1-6-添加swagger",children:[]},{level:3,title:"1.7 添加Security",slug:"_1-7-添加security",link:"#_1-7-添加security",children:[]},{level:3,title:"1.8 角色权限控制",slug:"_1-8-角色权限控制",link:"#_1-8-角色权限控制",children:[]},{level:3,title:"1.9 自定义token认证(前后分离)",slug:"_1-9-自定义token认证-前后分离",link:"#_1-9-自定义token认证-前后分离",children:[]},{level:3,title:"1.9.3 放行登录接口,去掉表单登录",slug:"_1-9-3-放行登录接口-去掉表单登录",link:"#_1-9-3-放行登录接口-去掉表单登录",children:[]},{level:3,title:"next 菜单权限",slug:"next-菜单权限",link:"#next-菜单权限",children:[]}]},{level:2,title:"2、前端篇",slug:"_2、前端篇",link:"#_2、前端篇",children:[{level:3,title:"2.1 初始化vue2",slug:"_2-1-初始化vue2",link:"#_2-1-初始化vue2",children:[]},{level:3,title:"2.2 axios统一处理",slug:"_2-2-axios统一处理",link:"#_2-2-axios统一处理",children:[]}]}],path:"/other/web/BuildWebProject.html",pathLocale:"/",extraFields:[]},{title:"Cookie",headers:[{level:2,title:"Cookie的作用域domain",slug:"cookie的作用域domain",link:"#cookie的作用域domain",children:[]},{level:2,title:"Cookie的path",slug:"cookie的path",link:"#cookie的path",children:[]},{level:2,title:"实例",slug:"实例",link:"#实例",children:[{level:3,title:"域名可以看到当前域以及父域名下的cookie,看不到其子域名下的cookie",slug:"域名可以看到当前域以及父域名下的cookie-看不到其子域名下的cookie",link:"#域名可以看到当前域以及父域名下的cookie-看不到其子域名下的cookie",children:[]},{level:3,title:"设置了path,要同时满足url中有指定path才能看到",slug:"设置了path-要同时满足url中有指定path才能看到",link:"#设置了path-要同时满足url中有指定path才能看到",children:[]}]},{level:2,title:"浏览器请求时会自动携带其所有能看到的cookie发送到后端",slug:"浏览器请求时会自动携带其所有能看到的cookie发送到后端",link:"#浏览器请求时会自动携带其所有能看到的cookie发送到后端",children:[]},{level:2,title:"java中Session和Cookie交互",slug:"java中session和cookie交互",link:"#java中session和cookie交互",children:[]}],path:"/other/web/Cookie.html",pathLocale:"/",extraFields:[]},{title:"Http",headers:[{level:2,title:"一、状态码",slug:"一、状态码",link:"#一、状态码",children:[{level:3,title:"1.1 3xx",slug:"_1-1-3xx",link:"#_1-1-3xx",children:[]},{level:3,title:"1.1.1 304",slug:"_1-1-1-304",link:"#_1-1-1-304",children:[]}]},{level:2,title:"二、http请求",slug:"二、http请求",link:"#二、http请求",children:[{level:3,title:"2.1 302重定向",slug:"_2-1-302重定向",link:"#_2-1-302重定向",children:[]}]},{level:2,title:"二、http协议",slug:"二、http协议",link:"#二、http协议",children:[{level:3,title:"2.1 http协议是什么",slug:"_2-1-http协议是什么",link:"#_2-1-http协议是什么",children:[]}]},{level:2,title:"三、Headers",slug:"三、headers",link:"#三、headers",children:[{level:3,title:"3.1 中文header",slug:"_3-1-中文header",link:"#_3-1-中文header",children:[]}]}],path:"/other/web/Http.html",pathLocale:"/",extraFields:[]},{title:"jwt",headers:[{level:2,title:"1、jwt在服务端如何校验的?",slug:"_1、jwt在服务端如何校验的",link:"#_1、jwt在服务端如何校验的",children:[]},{level:2,title:"2、为什么相同用户每次登陆得到的jwt token不一样?",slug:"_2、为什么相同用户每次登陆得到的jwt-token不一样",link:"#_2、为什么相同用户每次登陆得到的jwt-token不一样",children:[]},{level:2,title:"3、Jwt VS 普通token",slug:"_3、jwt-vs-普通token",link:"#_3、jwt-vs-普通token",children:[]}],path:"/other/web/Jwt.html",pathLocale:"/",extraFields:[]},{title:"oauth第三方登录",headers:[{level:2,title:"1 oauth2",slug:"_1-oauth2",link:"#_1-oauth2",children:[{level:3,title:"1.1 典型应用场景",slug:"_1-1-典型应用场景",link:"#_1-1-典型应用场景",children:[]},{level:3,title:"1.2 完整时序图",slug:"_1-2-完整时序图",link:"#_1-2-完整时序图",children:[]}]},{level:2,title:"2 GitHub配置",slug:"_2-github配置",link:"#_2-github配置",children:[]},{level:2,title:"3 前后分离项目前后端交互",slug:"_3-前后分离项目前后端交互",link:"#_3-前后分离项目前后端交互",children:[]},{level:2,title:"4 前端代码",slug:"_4-前端代码",link:"#_4-前端代码",children:[]},{level:2,title:"5 后端处理",slug:"_5-后端处理",link:"#_5-后端处理",children:[{level:3,title:"5.1 处理github授权请求",slug:"_5-1-处理github授权请求",link:"#_5-1-处理github授权请求",children:[]},{level:3,title:"5.2 重定向后的处理",slug:"_5-2-重定向后的处理",link:"#_5-2-重定向后的处理",children:[]},{level:3,title:"5.3 前后交互数据问题",slug:"_5-3-前后交互数据问题",link:"#_5-3-前后交互数据问题",children:[]}]}],path:"/other/web/OAUTH_LOGIN.html",pathLocale:"/",extraFields:[]},{title:"web开发通用知识",headers:[],path:"/other/web/",pathLocale:"/",extraFields:[]},{title:"refresh_token",headers:[{level:2,title:"1、 refresh_token介绍",slug:"_1、-refresh-token介绍",link:"#_1、-refresh-token介绍",children:[]},{level:2,title:"2、token过期处理",slug:"_2、token过期处理",link:"#_2、token过期处理",children:[{level:3,title:"2.1 方式一,重新登录",slug:"_2-1-方式一-重新登录",link:"#_2-1-方式一-重新登录",children:[]},{level:3,title:"2.2 方式二,续签(刷新)token",slug:"_2-2-方式二-续签-刷新-token",link:"#_2-2-方式二-续签-刷新-token",children:[]}]}],path:"/other/web/RefreshToken.html",pathLocale:"/",extraFields:[]},{title:"HTTP Restful",headers:[{level:2,title:"一、参考",slug:"一、参考",link:"#一、参考",children:[]}],path:"/other/web/Restful.html",pathLocale:"/",extraFields:[]},{title:"wsl问题",headers:[{level:2,title:"1. wsl系统设置桥接网络",slug:"_1-wsl系统设置桥接网络",link:"#_1-wsl系统设置桥接网络",children:[{level:3,title:"1.1 开启hyper-v",slug:"_1-1-开启hyper-v",link:"#_1-1-开启hyper-v",children:[]},{level:3,title:"1.2 桥接网络",slug:"_1-2-桥接网络",link:"#_1-2-桥接网络",children:[]},{level:3,title:"1.3 修改wsl",slug:"_1-3-修改wsl",link:"#_1-3-修改wsl",children:[]},{level:3,title:"1.4 取消桥接",slug:"_1-4-取消桥接",link:"#_1-4-取消桥接",children:[]}]}],path:"/other/windows/WSL.html",pathLocale:"/",extraFields:[]},{title:"MybatisPlus多线程数据源切换问题",headers:[{level:2,title:"1、问题的背景",slug:"_1、问题的背景",link:"#_1、问题的背景",children:[]},{level:2,title:"2、问题产生的原因",slug:"_2、问题产生的原因",link:"#_2、问题产生的原因",children:[]},{level:2,title:"3、解决方法",slug:"_3、解决方法",link:"#_3、解决方法",children:[]},{level:2,title:"4、MybatisPlus多线程数据源切换的源码分析",slug:"_4、mybatisplus多线程数据源切换的源码分析",link:"#_4、mybatisplus多线程数据源切换的源码分析",children:[]}],path:"/java/framework/mybatis/MybatisPlusDataSource.html",pathLocale:"/",extraFields:[]},{title:"Mybatis",headers:[],path:"/java/framework/mybatis/",pathLocale:"/",extraFields:[]},{title:"Mybatis使用",headers:[{level:2,title:"mybatis缓存",slug:"mybatis缓存",link:"#mybatis缓存",children:[]}],path:"/java/framework/mybatis/mybatis.html",pathLocale:"/",extraFields:[]},{title:"权限校验原理",headers:[{level:2,title:"1、权限说明",slug:"_1、权限说明",link:"#_1、权限说明",children:[]},{level:2,title:"2、Security中负责权限校验的类结构图",slug:"_2、security中负责权限校验的类结构图",link:"#_2、security中负责权限校验的类结构图",children:[]}],path:"/java/framework/security/Authorization.html",pathLocale:"/",extraFields:[]},{title:"Security扩展自定义登录方式",headers:[{level:2,title:"1、需求",slug:"_1、需求",link:"#_1、需求",children:[]},{level:2,title:"2、实现方法",slug:"_2、实现方法",link:"#_2、实现方法",children:[{level:3,title:"2.1 自定义AuthenticationProvider",slug:"_2-1-自定义authenticationprovider",link:"#_2-1-自定义authenticationprovider",children:[]},{level:3,title:"2.2 自定义AuthenticationToken",slug:"_2-2-自定义authenticationtoken",link:"#_2-2-自定义authenticationtoken",children:[]},{level:3,title:"2.3 自定义登录接口",slug:"_2-3-自定义登录接口",link:"#_2-3-自定义登录接口",children:[]},{level:3,title:"2.4 配置自定义的AuthenticationProvider",slug:"_2-4-配置自定义的authenticationprovider",link:"#_2-4-配置自定义的authenticationprovider",children:[]}]}],path:"/java/framework/security/CustomAuthenticationProvider.html",pathLocale:"/",extraFields:[]},{title:"自定义登陆页面",headers:[{level:2,title:"1、修改自定义的登陆页面以及登陆请求校验",slug:"_1、修改自定义的登陆页面以及登陆请求校验",link:"#_1、修改自定义的登陆页面以及登陆请求校验",children:[{level:3,title:"1.1 Secutity配置",slug:"_1-1-secutity配置",link:"#_1-1-secutity配置",children:[]},{level:3,title:"1.2 自定义登陆页面",slug:"_1-2-自定义登陆页面",link:"#_1-2-自定义登陆页面",children:[]}]},{level:2,title:"2、开启表单认证",slug:"_2、开启表单认证",link:"#_2、开启表单认证",children:[]},{level:2,title:"3、关闭表单认证",slug:"_3、关闭表单认证",link:"#_3、关闭表单认证",children:[]}],path:"/java/framework/security/CustomLoginPage.html",pathLocale:"/",extraFields:[]},{title:"前后分离项目自定义token认证",headers:[{level:2,title:"1、Security配置注意事项",slug:"_1、security配置注意事项",link:"#_1、security配置注意事项",children:[]},{level:2,title:"3、自定义过滤器",slug:"_3、自定义过滤器",link:"#_3、自定义过滤器",children:[]}],path:"/java/framework/security/CustomTokenAuthentication.html",pathLocale:"/",extraFields:[]},{title:"DelegatingFilterProxy介绍",headers:[{level:3,title:"作用",slug:"作用",link:"#作用",children:[]}],path:"/java/framework/security/DelegatingFilterProxy.html",pathLocale:"/",extraFields:[]},{title:"Ruoyi使用oauth对接pig",headers:[{level:2,title:"1、时序图",slug:"_1、时序图",link:"#_1、时序图",children:[]},{level:2,title:"2、流程解析",slug:"_2、流程解析",link:"#_2、流程解析",children:[{level:3,title:"2.1 第1步",slug:"_2-1-第1步",link:"#_2-1-第1步",children:[]},{level:3,title:"2.2 第2步",slug:"_2-2-第2步",link:"#_2-2-第2步",children:[]},{level:3,title:"2.3 第3步",slug:"_2-3-第3步",link:"#_2-3-第3步",children:[]},{level:3,title:"2.4 第4步",slug:"_2-4-第4步",link:"#_2-4-第4步",children:[]},{level:3,title:"2.5 第5步",slug:"_2-5-第5步",link:"#_2-5-第5步",children:[]},{level:3,title:"2.6 第6步",slug:"_2-6-第6步",link:"#_2-6-第6步",children:[]},{level:3,title:"2.7 第7步",slug:"_2-7-第7步",link:"#_2-7-第7步",children:[]},{level:3,title:"2.8 第8步",slug:"_2-8-第8步",link:"#_2-8-第8步",children:[]},{level:3,title:"2.9 第9步",slug:"_2-9-第9步",link:"#_2-9-第9步",children:[]},{level:3,title:"2.10 第10步",slug:"_2-10-第10步",link:"#_2-10-第10步",children:[]},{level:3,title:"2.11 第11步",slug:"_2-11-第11步",link:"#_2-11-第11步",children:[]}]}],path:"/java/framework/security/OAuth2Authentication.html",pathLocale:"/",extraFields:[]},{title:"OnecePerRequestFilter",headers:[{level:2,title:"1、OnecePerRequestFilter初识",slug:"_1、oneceperrequestfilter初识",link:"#_1、oneceperrequestfilter初识",children:[{level:3,title:"1.1 源码doc",slug:"_1-1-源码doc",link:"#_1-1-源码doc",children:[]}]},{level:2,title:"2、 源码剖析",slug:"_2、-源码剖析",link:"#_2、-源码剖析",children:[]},{level:2,title:"3、总结",slug:"_3、总结",link:"#_3、总结",children:[]}],path:"/java/framework/security/OncePerRequestFilter.html",pathLocale:"/",extraFields:[]},{title:"PreAuthorize注解",headers:[{level:2,title:"1、使用方式",slug:"_1、使用方式",link:"#_1、使用方式",children:[]},{level:2,title:"2、@PreAuthorize注解是何时生效的?",slug:"_2、-preauthorize注解是何时生效的",link:"#_2、-preauthorize注解是何时生效的",children:[{level:3,title:"2.1",slug:"_2-1",link:"#_2-1",children:[]}]}],path:"/java/framework/security/PreAuthorize.html",pathLocale:"/",extraFields:[]},{title:"Security学习",headers:[],path:"/java/framework/security/",pathLocale:"/",extraFields:[]},{title:"SSO协议",headers:[{level:2,title:"1、写在前面",slug:"_1、写在前面",link:"#_1、写在前面",children:[]},{level:2,title:"2、SSO和OAUTH2介绍",slug:"_2、sso和oauth2介绍",link:"#_2、sso和oauth2介绍",children:[]},{level:2,title:"3、SSO实现",slug:"_3、sso实现",link:"#_3、sso实现",children:[{level:3,title:"3.1 CAS",slug:"_3-1-cas",link:"#_3-1-cas",children:[]}]},{level:2,title:"4、各种协议对比",slug:"_4、各种协议对比",link:"#_4、各种协议对比",children:[]}],path:"/java/framework/security/SSO.html",pathLocale:"/",extraFields:[]},{title:"Security配置类",headers:[{level:2,title:"1、配置类",slug:"_1、配置类",link:"#_1、配置类",children:[]},{level:2,title:"2、在配置类配置和在Controller方法注解上使用@PreAuthorized有何区别",slug:"_2、在配置类配置和在controller方法注解上使用-preauthorized有何区别",link:"#_2、在配置类配置和在controller方法注解上使用-preauthorized有何区别",children:[{level:3,title:"2.1 在配置配给资源配置权限",slug:"_2-1-在配置配给资源配置权限",link:"#_2-1-在配置配给资源配置权限",children:[]},{level:3,title:"2.2 使用注解@PreAuthorized",slug:"_2-2-使用注解-preauthorized",link:"#_2-2-使用注解-preauthorized",children:[]}]},{level:2,title:"3、新版本配置",slug:"_3、新版本配置",link:"#_3、新版本配置",children:[]}],path:"/java/framework/security/SecurityFilterChain.html",pathLocale:"/",extraFields:[]},{title:"Security中的Session",headers:[{level:2,title:"1、背景(问题复现)",slug:"_1、背景-问题复现",link:"#_1、背景-问题复现",children:[]},{level:2,title:"2、sessionid变化的原因",slug:"_2、sessionid变化的原因",link:"#_2、sessionid变化的原因",children:[]},{level:2,title:"3、源码查看",slug:"_3、源码查看",link:"#_3、源码查看",children:[]},{level:2,title:"4、基于Session的认证",slug:"_4、基于session的认证",link:"#_4、基于session的认证",children:[]}],path:"/java/framework/security/Session.html",pathLocale:"/",extraFields:[]},{title:"Security入门笔记",headers:[{level:2,title:"1、加密解密流程",slug:"_1、加密解密流程",link:"#_1、加密解密流程",children:[]},{level:2,title:"2、HttpSecurity和WebSecurity区别",slug:"_2、httpsecurity和websecurity区别",link:"#_2、httpsecurity和websecurity区别",children:[]},{level:2,title:"3、SpringSecurity中User实体中Lock和enabled区别",slug:"_3、springsecurity中user实体中lock和enabled区别",link:"#_3、springsecurity中user实体中lock和enabled区别",children:[]},{level:2,title:"4、开启方法级别的权限控制",slug:"_4、开启方法级别的权限控制",link:"#_4、开启方法级别的权限控制",children:[]}],path:"/java/framework/security/note.html",pathLocale:"/",extraFields:[]},{title:"spring-security-oauth2-authorization-server",headers:[{level:2,title:"介绍",slug:"介绍",link:"#介绍",children:[]},{level:2,title:"/oauth2/token",slug:"oauth2-token",link:"#oauth2-token",children:[]}],path:"/java/framework/security/spring-security-oauth2-authorization-server.html",pathLocale:"/",extraFields:[]},{title:"注解",headers:[{level:2,title:"1、依赖注入",slug:"_1、依赖注入",link:"#_1、依赖注入",children:[]},{level:2,title:"2、关于Spring不建议使用@Autowired在字段上",slug:"_2、关于spring不建议使用-autowired在字段上",link:"#_2、关于spring不建议使用-autowired在字段上",children:[{level:3,title:"2.1 如果使用@Autowoired在字段上,可能带来的问题",slug:"_2-1-如果使用-autowoired在字段上-可能带来的问题",link:"#_2-1-如果使用-autowoired在字段上-可能带来的问题",children:[]}]}],path:"/java/framework/spring/Annotation.html",pathLocale:"/",extraFields:[]},{title:"BeanPostProcessor介绍",headers:[{level:3,title:"1. BeanPostProcessor介绍",slug:"_1-beanpostprocessor介绍",link:"#_1-beanpostprocessor介绍",children:[]},{level:3,title:"2. 再看看BeanPostProcessor能做什么,有什么使用场景",slug:"_2-再看看beanpostprocessor能做什么-有什么使用场景",link:"#_2-再看看beanpostprocessor能做什么-有什么使用场景",children:[]},{level:3,title:"3. bean实例化过程",slug:"_3-bean实例化过程",link:"#_3-bean实例化过程",children:[]}],path:"/java/framework/spring/BeanPostProcessor.html",pathLocale:"/",extraFields:[]},{title:"Spring循环依赖",headers:[{level:2,title:"1、循环依赖的产生",slug:"_1、循环依赖的产生",link:"#_1、循环依赖的产生",children:[]},{level:2,title:"2、Spring是如何解决的循环依赖问题",slug:"_2、spring是如何解决的循环依赖问题",link:"#_2、spring是如何解决的循环依赖问题",children:[{level:3,title:"2.1 Bean的生命周期",slug:"_2-1-bean的生命周期",link:"#_2-1-bean的生命周期",children:[]},{level:3,title:"2.2 依赖注入的过程",slug:"_2-2-依赖注入的过程",link:"#_2-2-依赖注入的过程",children:[]}]}],path:"/java/framework/spring/CircularDependency.html",pathLocale:"/",extraFields:[]},{title:"Spring中的设计模式",headers:[{level:3,title:"1、工厂模式",slug:"_1、工厂模式",link:"#_1、工厂模式",children:[]},{level:3,title:"2、单例模式",slug:"_2、单例模式",link:"#_2、单例模式",children:[]},{level:3,title:"3、观察者模式",slug:"_3、观察者模式",link:"#_3、观察者模式",children:[]},{level:3,title:"4、适配器模式",slug:"_4、适配器模式",link:"#_4、适配器模式",children:[]},{level:3,title:"5、代理模式",slug:"_5、代理模式",link:"#_5、代理模式",children:[]},{level:3,title:"6、模板方法模式",slug:"_6、模板方法模式",link:"#_6、模板方法模式",children:[]},{level:3,title:"7、包装模式(装饰者模式)",slug:"_7、包装模式-装饰者模式",link:"#_7、包装模式-装饰者模式",children:[]},{level:3,title:"8、原型模式",slug:"_8、原型模式",link:"#_8、原型模式",children:[]},{level:3,title:"9、建造者模式",slug:"_9、建造者模式",link:"#_9、建造者模式",children:[]}],path:"/java/framework/spring/DesignPatternInSpring.html",pathLocale:"/",extraFields:[]},{title:"OncePerRequestFilter",headers:[{level:2,title:"1、OncePerRequestFilter",slug:"_1、onceperrequestfilter",link:"#_1、onceperrequestfilter",children:[]},{level:2,title:"2、参考",slug:"_2、参考",link:"#_2、参考",children:[]}],path:"/java/framework/spring/OncePerRequestFilter.html",pathLocale:"/",extraFields:[]},{title:"Spring",headers:[],path:"/java/framework/spring/",pathLocale:"/",extraFields:[]},{title:"SpringAOP",headers:[{level:2,title:"1、SpringAOP",slug:"_1、springaop",link:"#_1、springaop",children:[]},{level:2,title:"2、SpringAOP - 非注解实现",slug:"_2、springaop-非注解实现",link:"#_2、springaop-非注解实现",children:[{level:3,title:"2.1 演示1(有接口实现)",slug:"_2-1-演示1-有接口实现",link:"#_2-1-演示1-有接口实现",children:[]},{level:3,title:"2.2 演示2(无接口实现)",slug:"_2-2-演示2-无接口实现",link:"#_2-2-演示2-无接口实现",children:[]}]},{level:2,title:"3、SpringAOP注解实现",slug:"_3、springaop注解实现",link:"#_3、springaop注解实现",children:[]},{level:2,title:"4、 SpringAOP实现原理",slug:"_4、-springaop实现原理",link:"#_4、-springaop实现原理",children:[{level:3,title:"4.1 SpringAOP是如何产生代理类的",slug:"_4-1-springaop是如何产生代理类的",link:"#_4-1-springaop是如何产生代理类的",children:[]},{level:3,title:"4.2 源码层面查看SpringAOP是使用JDK动态代理还是CGlib",slug:"_4-2-源码层面查看springaop是使用jdk动态代理还是cglib",link:"#_4-2-源码层面查看springaop是使用jdk动态代理还是cglib",children:[]},{level:3,title:"4.3 时序图",slug:"_4-3-时序图",link:"#_4-3-时序图",children:[]}]}],path:"/java/framework/spring/SpringAOP.html",pathLocale:"/",extraFields:[]},{title:"Spring缓存",headers:[{level:3,title:"1、背景",slug:"_1、背景",link:"#_1、背景",children:[]},{level:3,title:"2、spring-cache介绍",slug:"_2、spring-cache介绍",link:"#_2、spring-cache介绍",children:[]},{level:3,title:"3、springcache的实现",slug:"_3、springcache的实现",link:"#_3、springcache的实现",children:[]},{level:3,title:"4、springcache的注解使用",slug:"_4、springcache的注解使用",link:"#_4、springcache的注解使用",children:[]}],path:"/java/framework/spring/SpringCache.html",pathLocale:"/",extraFields:[]},{title:"Spring框架扩展点",headers:[{level:3,title:"1. spring生命周期",slug:"_1-spring生命周期",link:"#_1-spring生命周期",children:[]},{level:3,title:"2. 扩展点介绍",slug:"_2-扩展点介绍",link:"#_2-扩展点介绍",children:[]}],path:"/java/framework/spring/SpringExtensionPoint.html",pathLocale:"/",extraFields:[]},{title:"Spring IOC 容器源码分析",headers:[{level:2,title:"引言",slug:"引言",link:"#引言",children:[]},{level:2,title:"BeanFactory 简介",slug:"beanfactory-简介",link:"#beanfactory-简介",children:[]},{level:2,title:"启动过程分析",slug:"启动过程分析",link:"#启动过程分析",children:[{level:3,title:"创建 Bean 容器前的准备工作",slug:"创建-bean-容器前的准备工作",link:"#创建-bean-容器前的准备工作",children:[]},{level:3,title:"创建 Bean 容器,加载并注册 Bean",slug:"创建-bean-容器-加载并注册-bean",link:"#创建-bean-容器-加载并注册-bean",children:[]},{level:3,title:"Bean 容器实例化完成后",slug:"bean-容器实例化完成后",link:"#bean-容器实例化完成后",children:[]},{level:3,title:"准备 Bean 容器: prepareBeanFactory",slug:"准备-bean-容器-preparebeanfactory",link:"#准备-bean-容器-preparebeanfactory",children:[]},{level:3,title:"初始化所有的 singleton beans",slug:"初始化所有的-singleton-beans",link:"#初始化所有的-singleton-beans",children:[]}]},{level:2,title:"附录",slug:"附录",link:"#附录",children:[{level:3,title:"id 和 name",slug:"id-和-name",link:"#id-和-name",children:[]},{level:3,title:"配置是否允许 Bean 覆盖、是否允许循环依赖",slug:"配置是否允许-bean-覆盖、是否允许循环依赖",link:"#配置是否允许-bean-覆盖、是否允许循环依赖",children:[]},{level:3,title:"profile",slug:"profile",link:"#profile",children:[]},{level:3,title:"工厂模式生成 Bean",slug:"工厂模式生成-bean",link:"#工厂模式生成-bean",children:[]},{level:3,title:"FactoryBean",slug:"factorybean",link:"#factorybean",children:[]},{level:3,title:"初始化 Bean 的回调",slug:"初始化-bean-的回调",link:"#初始化-bean-的回调",children:[]},{level:3,title:"销毁 Bean 的回调",slug:"销毁-bean-的回调",link:"#销毁-bean-的回调",children:[]},{level:3,title:"ConversionService",slug:"conversionservice",link:"#conversionservice",children:[]},{level:3,title:"Bean 继承",slug:"bean-继承",link:"#bean-继承",children:[]},{level:3,title:"方法注入",slug:"方法注入",link:"#方法注入",children:[]},{level:3,title:"BeanPostProcessor",slug:"beanpostprocessor",link:"#beanpostprocessor",children:[]}]},{level:2,title:"总结",slug:"总结",link:"#总结",children:[]}],path:"/java/framework/spring/SpringIOC.html",pathLocale:"/",extraFields:[]},{title:"SpringMVC基本原理",headers:[{level:2,title:"SpringMVC处理请求的流程",slug:"springmvc处理请求的流程",link:"#springmvc处理请求的流程",children:[]},{level:2,title:"前端控制器源码",slug:"前端控制器源码",link:"#前端控制器源码",children:[{level:3,title:"步骤1 客户端发起请求,请求到达前端控制器",slug:"步骤1-客户端发起请求-请求到达前端控制器",link:"#步骤1-客户端发起请求-请求到达前端控制器",children:[]},{level:3,title:"步骤2. 根据请求获取HandlerMethod",slug:"步骤2-根据请求获取handlermethod",link:"#步骤2-根据请求获取handlermethod",children:[]}]},{level:2,title:"步骤3 获取HandlerAdapter",slug:"步骤3-获取handleradapter",link:"#步骤3-获取handleradapter",children:[]},{level:2,title:"步骤4 通过HandlerAdapter真正调用业务处理逻辑",slug:"步骤4-通过handleradapter真正调用业务处理逻辑",link:"#步骤4-通过handleradapter真正调用业务处理逻辑",children:[]},{level:2,title:"步骤5 渲染页面",slug:"步骤5-渲染页面",link:"#步骤5-渲染页面",children:[]}],path:"/java/framework/spring/SpringMVC.html",pathLocale:"/",extraFields:[]},{title:"SpringIOC源码分析",headers:[],path:"/java/framework/spring/SpringSourceAnalize.html",pathLocale:"/",extraFields:[]},{title:"自定义validator分组检验",headers:[{level:2,title:"自定义校验",slug:"自定义校验",link:"#自定义校验",children:[]},{level:2,title:"调用",slug:"调用",link:"#调用",children:[]}],path:"/java/framework/spring/Validator.html",pathLocale:"/",extraFields:[]},{title:"使用aop记录请求log",headers:[],path:"/java/framework/springboot/AOPLog.html",pathLocale:"/",extraFields:[]},{title:"注入集合",headers:[{level:2,title:"1、测试",slug:"_1、测试",link:"#_1、测试",children:[]},{level:2,title:"2、实现方法",slug:"_2、实现方法",link:"#_2、实现方法",children:[]},{level:2,title:"3、以上方法如何保证只注入部分的实现类呢?",slug:"_3、以上方法如何保证只注入部分的实现类呢",link:"#_3、以上方法如何保证只注入部分的实现类呢",children:[]}],path:"/java/framework/springboot/CollectionInject.html",pathLocale:"/",extraFields:[]},{title:"springboot开启http2.0",headers:[{level:2,title:"1、http2.0优势",slug:"_1、http2-0优势",link:"#_1、http2-0优势",children:[]},{level:2,title:"2、springboot开启http2.0",slug:"_2、springboot开启http2-0",link:"#_2、springboot开启http2-0",children:[]}],path:"/java/framework/springboot/Http2.html",pathLocale:"/",extraFields:[]},{title:"SpringBoot",headers:[],path:"/java/framework/springboot/",pathLocale:"/",extraFields:[]},{title:"springboot自动配置原理",headers:[],path:"/java/framework/springboot/SpringBootAutoConfiguration.html",pathLocale:"/",extraFields:[]},{title:"Swagger",headers:[{level:2,title:"Swagger和SpringFox关系",slug:"swagger和springfox关系",link:"#swagger和springfox关系",children:[]}],path:"/java/framework/springboot/Swagger.html",pathLocale:"/",extraFields:[]},{title:"springboot部署",headers:[{level:3,title:"参考",slug:"参考",link:"#参考",children:[]}],path:"/java/framework/springboot/%E9%83%A8%E7%BD%B2.html",pathLocale:"/",extraFields:[]},{title:"网关路由失效",headers:[{level:2,title:"问题描述",slug:"问题描述",link:"#问题描述",children:[]},{level:2,title:"原因",slug:"原因",link:"#原因",children:[]},{level:2,title:"针对本次问题的解决方法",slug:"针对本次问题的解决方法",link:"#针对本次问题的解决方法",children:[{level:3,title:"方法1",slug:"方法1",link:"#方法1",children:[]},{level:3,title:"方法2",slug:"方法2",link:"#方法2",children:[]}]}],path:"/java/framework/springcloud/SpringCloudGateway.html",pathLocale:"/",extraFields:[]},{title:"Gradle",headers:[{level:2,title:"1. Gradle的放弃之路",slug:"_1-gradle的放弃之路",link:"#_1-gradle的放弃之路",children:[]}],path:"/java/other/gradle/",pathLocale:"/",extraFields:[]},{title:"Wrapper",headers:[],path:"/java/other/gradle/wrapper.html",pathLocale:"/",extraFields:[]},{title:"线上问题定位",headers:[{level:2,title:"此分类专门定位线上问题",slug:"此分类专门定位线上问题",link:"#此分类专门定位线上问题",children:[]}],path:"/java/other/locateproblem/",pathLocale:"/",extraFields:[]},{title:"TooManyOpenFiles",headers:[{level:2,title:"Linux服务器打开文件过多",slug:"linux服务器打开文件过多",link:"#linux服务器打开文件过多",children:[{level:3,title:"线上异常",slug:"线上异常",link:"#线上异常",children:[]},{level:3,title:"问题原因分析",slug:"问题原因分析",link:"#问题原因分析",children:[]}]}],path:"/java/other/locateproblem/TooManyOpenFiles.html",pathLocale:"/",extraFields:[]},{title:"undertow.xxx not found",headers:[{level:2,title:"背景",slug:"背景",link:"#背景",children:[]},{level:2,title:"原因",slug:"原因",link:"#原因",children:[]},{level:2,title:"解决方法",slug:"解决方法",link:"#解决方法",children:[{level:3,title:"方法一",slug:"方法一",link:"#方法一",children:[]},{level:3,title:"方法二",slug:"方法二",link:"#方法二",children:[]},{level:3,title:"方法三",slug:"方法三",link:"#方法三",children:[]}]},{level:2,title:"验证是否生效",slug:"验证是否生效",link:"#验证是否生效",children:[{level:3,title:"方式1",slug:"方式1",link:"#方式1",children:[]},{level:3,title:"方式2",slug:"方式2",link:"#方式2",children:[]}]}],path:"/java/other/locateproblem/Undertow.xxxNotFount.html",pathLocale:"/",extraFields:[]},{title:"各种版本的坑",headers:[{level:2,title:"1、Spring系列",slug:"_1、spring系列",link:"#_1、spring系列",children:[{level:3,title:"1.1 坑1 spring.factories使用方式变更",slug:"_1-1-坑1-spring-factories使用方式变更",link:"#_1-1-坑1-spring-factories使用方式变更",children:[]},{level:3,title:"1.2 坑2 Spring Security OAuth2已被新版本security弃用",slug:"_1-2-坑2-spring-security-oauth2已被新版本security弃用",link:"#_1-2-坑2-spring-security-oauth2已被新版本security弃用",children:[]}]},{level:2,title:"2、Oauth2.0",slug:"_2、oauth2-0",link:"#_2、oauth2-0",children:[{level:3,title:"2.1 移除password授权类型",slug:"_2-1-移除password授权类型",link:"#_2-1-移除password授权类型",children:[]}]}],path:"/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html",pathLocale:"/",extraFields:[]},{title:"开发中遇到的各种问题",headers:[{level:3,title:"1. springcloudgateway负载均衡配置不生效",slug:"_1-springcloudgateway负载均衡配置不生效",link:"#_1-springcloudgateway负载均衡配置不生效",children:[]}],path:"/java/other/locateproblem/%E5%90%84%E7%A7%8D%E9%97%AE%E9%A2%98.html",pathLocale:"/",extraFields:[]},{title:"LogBack",headers:[{level:2,title:"配置",slug:"配置",link:"#配置",children:[]},{level:2,title:"分解配置",slug:"分解配置",link:"#分解配置",children:[{level:3,title:"logger",slug:"logger",link:"#logger",children:[]},{level:3,title:"appender",slug:"appender",link:"#appender",children:[]}]},{level:2,title:"在logger 标签已经定义了level为什么在appender下的filter中还要定义?",slug:"在logger-标签已经定义了level为什么在appender下的filter中还要定义",link:"#在logger-标签已经定义了level为什么在appender下的filter中还要定义",children:[]}],path:"/java/other/log/logback.html",pathLocale:"/",extraFields:[]},{title:"maven",headers:[{level:2,title:"1. maven的放弃之路",slug:"_1-maven的放弃之路",link:"#_1-maven的放弃之路",children:[]}],path:"/java/other/maven/",pathLocale:"/",extraFields:[]},{title:"build标签",headers:[{level:2,title:"配置解释",slug:"配置解释",link:"#配置解释",children:[]}],path:"/java/other/maven/build%E6%A0%87%E7%AD%BE.html",pathLocale:"/",extraFields:[]},{title:"import使用",headers:[{level:2,title:"1. import介绍",slug:"_1-import介绍",link:"#_1-import介绍",children:[]},{level:2,title:"2. 使用方式",slug:"_2-使用方式",link:"#_2-使用方式",children:[]},{level:2,title:"3.使用parent",slug:"_3-使用parent",link:"#_3-使用parent",children:[]}],path:"/java/other/maven/import.html",pathLocale:"/",extraFields:[]},{title:"多模块maven项目的搭建",headers:[{level:2,title:"1、一个真实的多模块maven项目",slug:"_1、一个真实的多模块maven项目",link:"#_1、一个真实的多模块maven项目",children:[]}],path:"/java/other/maven/multiModule.html",pathLocale:"/",extraFields:[]},{title:"使用问题记录",headers:[{level:2,title:"1、继承",slug:"_1、继承",link:"#_1、继承",children:[]},{level:2,title:"2、聚合",slug:"_2、聚合",link:"#_2、聚合",children:[]},{level:2,title:"3、依赖",slug:"_3、依赖",link:"#_3、依赖",children:[]},{level:2,title:"4、dependencies vs dependencyManagement",slug:"_4、dependencies-vs-dependencymanagement",link:"#_4、dependencies-vs-dependencymanagement",children:[]},{level:2,title:"5、springboot 找不到包",slug:"_5、springboot-找不到包",link:"#_5、springboot-找不到包",children:[]}],path:"/java/other/maven/problem.html",pathLocale:"/",extraFields:[]},{title:"Maven的生命周期",headers:[{level:2,title:"Maven的生命周期",slug:"maven的生命周期",link:"#maven的生命周期",children:[]},{level:2,title:"各个生命周期包含的phase",slug:"各个生命周期包含的phase",link:"#各个生命周期包含的phase",children:[{level:3,title:"default lifecycle包含的phase",slug:"default-lifecycle包含的phase",link:"#default-lifecycle包含的phase",children:[]},{level:3,title:"clean lifecycle生命周期所包含的phase",slug:"clean-lifecycle生命周期所包含的phase",link:"#clean-lifecycle生命周期所包含的phase",children:[]},{level:3,title:"site lificycle生命周期包含的phase",slug:"site-lificycle生命周期包含的phase",link:"#site-lificycle生命周期包含的phase",children:[]}]},{level:2,title:"关于默认绑定的插件",slug:"关于默认绑定的插件",link:"#关于默认绑定的插件",children:[{level:3,title:"packaging类型pom 类型默认绑定插件",slug:"packaging类型pom-类型默认绑定插件",link:"#packaging类型pom-类型默认绑定插件",children:[]},{level:3,title:"packaging类型为jar,绑定的插件",slug:"packaging类型为jar-绑定的插件",link:"#packaging类型为jar-绑定的插件",children:[]}]},{level:2,title:"我对maven生命周期的理解",slug:"我对maven生命周期的理解",link:"#我对maven生命周期的理解",children:[]},{level:2,title:"写在最后",slug:"写在最后",link:"#写在最后",children:[]}],path:"/java/other/maven/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html",pathLocale:"/",extraFields:[]},{title:"",headers:[],path:"/404.html",pathLocale:"/",extraFields:[]},{title:"Other",headers:[],path:"/cpp/other/",pathLocale:"/",extraFields:[]},{title:"Cpp",headers:[],path:"/cpp/",pathLocale:"/",extraFields:[]},{title:"Computerprinciple",headers:[],path:"/other/computerprinciple/",pathLocale:"/",extraFields:[]},{title:"Docker",headers:[],path:"/other/docker/",pathLocale:"/",extraFields:[]},{title:"Hardware",headers:[],path:"/other/hardware/",pathLocale:"/",extraFields:[]},{title:"Software",headers:[],path:"/other/software/",pathLocale:"/",extraFields:[]},{title:"Sono",headers:[],path:"/other/sono/",pathLocale:"/",extraFields:[]},{title:"Windows",headers:[],path:"/other/windows/",pathLocale:"/",extraFields:[]},{title:"Framework",headers:[],path:"/java/framework/",pathLocale:"/",extraFields:[]},{title:"Springcloud",headers:[],path:"/java/framework/springcloud/",pathLocale:"/",extraFields:[]},{title:"Log",headers:[],path:"/java/other/log/",pathLocale:"/",extraFields:[]},{title:"分类",headers:[],path:"/category/",pathLocale:"/",extraFields:[]},{title:"设计模式 分类",headers:[],path:"/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/",pathLocale:"/",extraFields:[]},{title:"使用指南 分类",headers:[],path:"/category/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/",pathLocale:"/",extraFields:[]},{title:"vue知识点 分类",headers:[],path:"/category/vue%E7%9F%A5%E8%AF%86%E7%82%B9/",pathLocale:"/",extraFields:[]},{title:"js基础 分类",headers:[],path:"/category/js%E5%9F%BA%E7%A1%80/",pathLocale:"/",extraFields:[]},{title:"npm知识点 分类",headers:[],path:"/category/npm%E7%9F%A5%E8%AF%86%E7%82%B9/",pathLocale:"/",extraFields:[]},{title:"问题定位 分类",headers:[],path:"/category/%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/",pathLocale:"/",extraFields:[]},{title:"vite 分类",headers:[],path:"/category/vite/",pathLocale:"/",extraFields:[]},{title:"集合 分类",headers:[],path:"/category/%E9%9B%86%E5%90%88/",pathLocale:"/",extraFields:[]},{title:"jdk 分类",headers:[],path:"/category/jdk/",pathLocale:"/",extraFields:[]},{title:"线程池 分类",headers:[],path:"/category/%E7%BA%BF%E7%A8%8B%E6%B1%A0/",pathLocale:"/",extraFields:[]},{title:"多线程 分类",headers:[],path:"/category/%E5%A4%9A%E7%BA%BF%E7%A8%8B/",pathLocale:"/",extraFields:[]},{title:"java 分类",headers:[],path:"/category/java/",pathLocale:"/",extraFields:[]},{title:"对象锁 分类",headers:[],path:"/category/%E5%AF%B9%E8%B1%A1%E9%94%81/",pathLocale:"/",extraFields:[]},{title:"java基础 分类",headers:[],path:"/category/java%E5%9F%BA%E7%A1%80/",pathLocale:"/",extraFields:[]},{title:"jvm 分类",headers:[],path:"/category/jvm/",pathLocale:"/",extraFields:[]},{title:"电子书 分类",headers:[],path:"/category/%E7%94%B5%E5%AD%90%E4%B9%A6/",pathLocale:"/",extraFields:[]},{title:"数据库 分类",headers:[],path:"/category/%E6%95%B0%E6%8D%AE%E5%BA%93/",pathLocale:"/",extraFields:[]},{title:"docker 分类",headers:[],path:"/category/docker/",pathLocale:"/",extraFields:[]},{title:"运维 分类",headers:[],path:"/category/%E8%BF%90%E7%BB%B4/",pathLocale:"/",extraFields:[]},{title:"git 操作 分类",headers:[],path:"/category/git-%E6%93%8D%E4%BD%9C/",pathLocale:"/",extraFields:[]},{title:"必会 分类",headers:[],path:"/category/%E5%BF%85%E4%BC%9A/",pathLocale:"/",extraFields:[]},{title:"linux 分类",headers:[],path:"/category/linux/",pathLocale:"/",extraFields:[]},{title:"markdown 分类",headers:[],path:"/category/markdown/",pathLocale:"/",extraFields:[]},{title:"公司业务 分类",headers:[],path:"/category/%E5%85%AC%E5%8F%B8%E4%B8%9A%E5%8A%A1/",pathLocale:"/",extraFields:[]},{title:"小组分享 分类",headers:[],path:"/category/%E5%B0%8F%E7%BB%84%E5%88%86%E4%BA%AB/",pathLocale:"/",extraFields:[]},{title:"web 分类",headers:[],path:"/category/web/",pathLocale:"/",extraFields:[]},{title:"oauth 分类",headers:[],path:"/category/oauth/",pathLocale:"/",extraFields:[]},{title:"框架 分类",headers:[],path:"/category/%E6%A1%86%E6%9E%B6/",pathLocale:"/",extraFields:[]},{title:"security 分类",headers:[],path:"/category/security/",pathLocale:"/",extraFields:[]},{title:"Security 分类",headers:[],path:"/category/security/",pathLocale:"/",extraFields:[]},{title:"OAuth 分类",headers:[],path:"/category/oauth/",pathLocale:"/",extraFields:[]},{title:"Spring 分类",headers:[],path:"/category/spring/",pathLocale:"/",extraFields:[]},{title:"open-source 分类",headers:[],path:"/category/open-source/",pathLocale:"/",extraFields:[]},{title:"maven 分类",headers:[],path:"/category/maven/",pathLocale:"/",extraFields:[]},{title:"标签",headers:[],path:"/tag/",pathLocale:"/",extraFields:[]},{title:"标签: 禁用",headers:[],path:"/tag/%E7%A6%81%E7%94%A8/",pathLocale:"/",extraFields:[]},{title:"标签: 文章加密",headers:[],path:"/tag/%E6%96%87%E7%AB%A0%E5%8A%A0%E5%AF%86/",pathLocale:"/",extraFields:[]},{title:"标签: Markdown",headers:[],path:"/tag/markdown/",pathLocale:"/",extraFields:[]},{title:"标签: 页面配置",headers:[],path:"/tag/%E9%A1%B5%E9%9D%A2%E9%85%8D%E7%BD%AE/",pathLocale:"/",extraFields:[]},{title:"标签: 使用指南",headers:[],path:"/tag/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/",pathLocale:"/",extraFields:[]},{title:"标签: 你所不了解的JavaScript",headers:[],path:"/tag/%E4%BD%A0%E6%89%80%E4%B8%8D%E4%BA%86%E8%A7%A3%E7%9A%84javascript/",pathLocale:"/",extraFields:[]},{title:"标签: 必会",headers:[],path:"/tag/%E5%BF%85%E4%BC%9A/",pathLocale:"/",extraFields:[]},{title:"标签: vue中的 TypeScript",headers:[],path:"/tag/vue%E4%B8%AD%E7%9A%84-typescript/",pathLocale:"/",extraFields:[]},{title:"标签: vite",headers:[],path:"/tag/vite/",pathLocale:"/",extraFields:[]},{title:"标签: ts",headers:[],path:"/tag/ts/",pathLocale:"/",extraFields:[]},{title:"标签: vue3",headers:[],path:"/tag/vue3/",pathLocale:"/",extraFields:[]},{title:"标签: 集合",headers:[],path:"/tag/%E9%9B%86%E5%90%88/",pathLocale:"/",extraFields:[]},{title:"标签: -- 并发",headers:[],path:"/tag/---%E5%B9%B6%E5%8F%91/",pathLocale:"/",extraFields:[]},{title:"标签: 多线程",headers:[],path:"/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/",pathLocale:"/",extraFields:[]},{title:"标签: 线程池",headers:[],path:"/tag/%E7%BA%BF%E7%A8%8B%E6%B1%A0/",pathLocale:"/",extraFields:[]},{title:"标签: oauth",headers:[],path:"/tag/oauth/",pathLocale:"/",extraFields:[]},{title:"标签: sso",headers:[],path:"/tag/sso/",pathLocale:"/",extraFields:[]},{title:"标签: 部署搭建",headers:[],path:"/tag/%E9%83%A8%E7%BD%B2%E6%90%AD%E5%BB%BA/",pathLocale:"/",extraFields:[]},{title:"标签: 字节码",headers:[],path:"/tag/%E5%AD%97%E8%8A%82%E7%A0%81/",pathLocale:"/",extraFields:[]},{title:"标签: 反汇编",headers:[],path:"/tag/%E5%8F%8D%E6%B1%87%E7%BC%96/",pathLocale:"/",extraFields:[]},{title:"标签: mysql",headers:[],path:"/tag/mysql/",pathLocale:"/",extraFields:[]},{title:"标签: 数据库",headers:[],path:"/tag/%E6%95%B0%E6%8D%AE%E5%BA%93/",pathLocale:"/",extraFields:[]},{title:"标签: 数据结构",headers:[],path:"/tag/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/",pathLocale:"/",extraFields:[]},{title:"标签: 二叉树",headers:[],path:"/tag/%E4%BA%8C%E5%8F%89%E6%A0%91/",pathLocale:"/",extraFields:[]},{title:"标签: 运维",headers:[],path:"/tag/%E8%BF%90%E7%BB%B4/",pathLocale:"/",extraFields:[]},{title:"标签: 工具使用",headers:[],path:"/tag/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/",pathLocale:"/",extraFields:[]},{title:"标签: linux",headers:[],path:"/tag/linux/",pathLocale:"/",extraFields:[]},{title:"标签: markdown",headers:[],path:"/tag/markdown/",pathLocale:"/",extraFields:[]},{title:"标签: web",headers:[],path:"/tag/web/",pathLocale:"/",extraFields:[]},{title:"标签: springboot",headers:[],path:"/tag/springboot/",pathLocale:"/",extraFields:[]},{title:"标签: 框架",headers:[],path:"/tag/%E6%A1%86%E6%9E%B6/",pathLocale:"/",extraFields:[]},{title:"标签: 日志",headers:[],path:"/tag/%E6%97%A5%E5%BF%97/",pathLocale:"/",extraFields:[]},{title:"文章",headers:[],path:"/article/",pathLocale:"/",extraFields:[]},{title:"星标",headers:[],path:"/star/",pathLocale:"/",extraFields:[]},{title:"时间轴",headers:[],path:"/timeline/",pathLocale:"/",extraFields:[]}],j1=K(P1),D1=()=>j1,R1=({searchIndex:n,routeLocale:e,query:t,maxSuggestions:a})=>{const s=y(()=>n.value.filter(l=>l.pathLocale===e.value));return y(()=>{const l=t.value.trim().toLowerCase();if(!l)return[];const o=[],i=(c,r)=>{_p(l,[r.title])&&o.push({link:`${c.path}#${r.slug}`,title:c.title,header:r.title});for(const u of r.children){if(o.length>=a.value)return;i(c,u)}};for(const c of s.value){if(o.length>=a.value)break;if(_p(l,[c.title,...c.extraFields])){o.push({link:c.path,title:c.title});continue}for(const r of c.headers){if(o.length>=a.value)break;i(c,r)}}return o})},F1=n=>{const e=K(0);return{focusIndex:e,focusNext:()=>{e.value{e.value>0?e.value-=1:e.value=n.value.length-1}}},B1=V({name:"SearchBox",props:{locales:{type:Object,required:!1,default:()=>({})},hotKeys:{type:Array,required:!1,default:()=>[]},maxSuggestions:{type:Number,required:!1,default:5}},setup(n){const{locales:e,hotKeys:t,maxSuggestions:a}=Su(n),s=me(),l=fe(),o=D1(),i=K(null),c=K(!1),r=K(""),u=y(()=>e.value[l.value]??{}),d=R1({searchIndex:o,routeLocale:l,query:r,maxSuggestions:a}),{focusIndex:k,focusNext:g,focusPrev:_}=F1(d);I1({input:i,hotKeys:t});const b=y(()=>c.value&&!!d.value.length),E=()=>{b.value&&_()},w=()=>{b.value&&g()},L=f=>{if(!b.value)return;const S=d.value[f];S&&s.push(S.link).then(()=>{r.value="",k.value=0})};return()=>p("form",{class:"search-box",role:"search"},[p("input",{ref:i,type:"search",placeholder:u.value.placeholder,autocomplete:"off",spellcheck:!1,value:r.value,onFocus:()=>c.value=!0,onBlur:()=>c.value=!1,onInput:f=>r.value=f.target.value,onKeydown:f=>{switch(f.key){case"ArrowUp":{E();break}case"ArrowDown":{w();break}case"Enter":{f.preventDefault(),L(k.value);break}}}}),b.value&&p("ul",{class:"suggestions",onMouseleave:()=>k.value=-1},d.value.map(({link:f,title:S,header:F},A)=>p("li",{class:["suggestion",{focus:k.value===A}],onMouseenter:()=>k.value=A,onMousedown:()=>L(A)},p("a",{href:f,onClick:H=>H.preventDefault()},[p("span",{class:"page-title"},S),F&&p("span",{class:"page-header"},`> ${F}`)]))))])}});var M1=["s","/"],V1={"/":{placeholder:"Search"},"/zh/":{placeholder:"搜索"}};const q1=V1,N1=M1,H1=5,$1=ae({enhance({app:n}){n.component("SearchBox",e=>p(B1,{locales:q1,hotKeys:N1,maxSuggestions:H1,...e}))}}),un=({name:n="",color:e="currentColor"},{slots:t})=>{var a;return p("svg",{xmlns:"http://www.w3.org/2000/svg",class:["icon",`${n}-icon`],viewBox:"0 0 1024 1024",fill:e,"aria-label":`${n} icon`},(a=t.default)==null?void 0:a.call(t))};un.displayName="IconBase";const yc=(n,{slots:e})=>{var t;return(t=e.default)==null?void 0:t.call(e)},bc=()=>p(un,{name:"github"},()=>p("path",{d:"M511.957 21.333C241.024 21.333 21.333 240.981 21.333 512c0 216.832 140.544 400.725 335.574 465.664 24.49 4.395 32.256-10.07 32.256-23.083 0-11.69.256-44.245 0-85.205-136.448 29.61-164.736-64.64-164.736-64.64-22.315-56.704-54.4-71.765-54.4-71.765-44.587-30.464 3.285-29.824 3.285-29.824 49.195 3.413 75.179 50.517 75.179 50.517 43.776 75.008 114.816 53.333 142.762 40.79 4.523-31.66 17.152-53.377 31.19-65.537-108.971-12.458-223.488-54.485-223.488-242.602 0-53.547 19.114-97.323 50.517-131.67-5.035-12.33-21.93-62.293 4.779-129.834 0 0 41.258-13.184 134.912 50.346a469.803 469.803 0 0 1 122.88-16.554c41.642.213 83.626 5.632 122.88 16.554 93.653-63.488 134.784-50.346 134.784-50.346 26.752 67.541 9.898 117.504 4.864 129.834 31.402 34.347 50.474 78.123 50.474 131.67 0 188.586-114.73 230.016-224.042 242.09 17.578 15.232 33.578 44.672 33.578 90.454v135.85c0 13.142 7.936 27.606 32.854 22.87C862.25 912.597 1002.667 728.747 1002.667 512c0-271.019-219.648-490.667-490.71-490.667z"}));bc.displayName="GitHubIcon";const wc=()=>p(un,{name:"gitlab"},()=>p("path",{d:"M229.333 78.688C223.52 62 199.895 62 193.895 78.688L87.958 406.438h247.5c-.188 0-106.125-327.75-106.125-327.75zM33.77 571.438c-4.875 15 .563 31.687 13.313 41.25l464.812 345L87.77 406.438zm301.5-165 176.813 551.25 176.812-551.25zm655.125 165-54-165-424.312 551.25 464.812-345c12.938-9.563 18.188-26.25 13.5-41.25zM830.27 78.688c-5.812-16.688-29.437-16.688-35.437 0l-106.125 327.75h247.5z"}));wc.displayName="GitLabIcon";const Ec=()=>p(un,{name:"gitee"},()=>p("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm242.97-533.34H482.39a23.7 23.7 0 0 0-23.7 23.7l-.03 59.28c0 13.08 10.59 23.7 23.7 23.7h165.96a23.7 23.7 0 0 1 23.7 23.7v11.85a71.1 71.1 0 0 1-71.1 71.1H375.71a23.7 23.7 0 0 1-23.7-23.7V423.11a71.1 71.1 0 0 1 71.1-71.1h331.8a23.7 23.7 0 0 0 23.7-23.7l.06-59.25a23.73 23.73 0 0 0-23.7-23.73H423.11a177.78 177.78 0 0 0-177.78 177.75v331.83c0 13.08 10.62 23.7 23.7 23.7h349.62a159.99 159.99 0 0 0 159.99-159.99V482.33a23.7 23.7 0 0 0-23.7-23.7z"}));Ec.displayName="GiteeIcon";const xc=()=>p(un,{name:"bitbucket"},()=>p("path",{d:"M575.256 490.862c6.29 47.981-52.005 85.723-92.563 61.147-45.714-20.004-45.714-92.562-1.133-113.152 38.29-23.442 93.696 7.424 93.696 52.005zm63.451-11.996c-10.276-81.152-102.29-134.839-177.152-101.156-47.433 21.138-79.433 71.424-77.129 124.562 2.853 69.705 69.157 126.866 138.862 120.576S647.3 548.571 638.708 478.83zm136.558-309.723c-25.161-33.134-67.986-38.839-105.728-45.13-106.862-17.151-216.576-17.7-323.438 1.134-35.438 5.706-75.447 11.996-97.719 43.996 36.572 34.304 88.576 39.424 135.424 45.129 84.553 10.862 171.447 11.447 256 .585 47.433-5.705 99.987-10.276 135.424-45.714zm32.585 591.433c-16.018 55.99-6.839 131.438-66.304 163.986-102.29 56.576-226.304 62.867-338.87 42.862-59.43-10.862-129.135-29.696-161.72-85.723-14.3-54.858-23.442-110.848-32.585-166.84l3.438-9.142 10.276-5.157c170.277 112.567 408.576 112.567 579.438 0 26.844 8.01 6.84 40.558 6.29 60.014zm103.424-549.157c-19.42 125.148-41.728 249.71-63.415 374.272-6.29 36.572-41.728 57.162-71.424 72.558-106.862 53.724-231.424 62.866-348.562 50.286-79.433-8.558-160.585-29.696-225.134-79.433-30.28-23.443-30.28-63.415-35.986-97.134-20.005-117.138-42.862-234.277-57.161-352.585 6.839-51.42 64.585-73.728 107.447-89.71 57.16-21.138 118.272-30.866 178.87-36.571 129.134-12.58 261.157-8.01 386.304 28.562 44.581 13.13 92.563 31.415 122.844 69.705 13.714 17.7 9.143 40.01 6.29 60.014z"}));xc.displayName="BitbucketIcon";const Ac=()=>p(un,{name:"source"},()=>p("path",{d:"M601.92 475.2c0 76.428-8.91 83.754-28.512 99.594-14.652 11.88-43.956 14.058-78.012 16.434-18.81 1.386-40.392 2.97-62.172 6.534-18.612 2.97-36.432 9.306-53.064 17.424V299.772c37.818-21.978 63.36-62.766 63.36-109.692 0-69.894-56.826-126.72-126.72-126.72S190.08 120.186 190.08 190.08c0 46.926 25.542 87.714 63.36 109.692v414.216c-37.818 21.978-63.36 62.766-63.36 109.692 0 69.894 56.826 126.72 126.72 126.72s126.72-56.826 126.72-126.72c0-31.086-11.286-59.598-29.7-81.576 13.266-9.504 27.522-17.226 39.996-19.206 16.038-2.574 32.868-3.762 50.688-5.148 48.312-3.366 103.158-7.326 148.896-44.55 61.182-49.698 74.25-103.158 75.24-187.902V475.2h-126.72zM316.8 126.72c34.848 0 63.36 28.512 63.36 63.36s-28.512 63.36-63.36 63.36-63.36-28.512-63.36-63.36 28.512-63.36 63.36-63.36zm0 760.32c-34.848 0-63.36-28.512-63.36-63.36s28.512-63.36 63.36-63.36 63.36 28.512 63.36 63.36-28.512 63.36-63.36 63.36zM823.68 158.4h-95.04V63.36h-126.72v95.04h-95.04v126.72h95.04v95.04h126.72v-95.04h95.04z"}));Ac.displayName="SourceIcon";const ks=(n,e)=>{let t=1;for(let a=0;a>6;return t+=t<<3,t^=t>>11,t%e};let z1=class{constructor(){this.messageElements={};const e="message-container",t=document.getElementById(e);t?this.containerElement=t:(this.containerElement=document.createElement("div"),this.containerElement.id=e,document.body.appendChild(this.containerElement))}pop(e,t=2e3){const a=document.createElement("div"),s=Date.now();return a.className="message move-in",a.innerHTML=e,this.containerElement.appendChild(a),this.messageElements[s]=a,t>0&&setTimeout(()=>{this.close(s)},t),s}close(e){if(e){const t=this.messageElements[e];t.classList.remove("move-in"),t.classList.add("move-out"),t.addEventListener("animationend",()=>{t.remove(),delete this.messageElements[e]})}else re(this.messageElements).forEach(t=>this.close(Number(t)))}destroy(){document.body.removeChild(this.containerElement)}};const Lc=/#.*$/u,U1=n=>{const e=Lc.exec(n);return e?e[0]:""},yp=n=>decodeURI(n).replace(Lc,"").replace(/\/index\.html$/iu,"/").replace(/\.html$/iu,"").replace(/(README|index)?\.md$/iu,""),Sc=(n,e)=>{if(!Jk(e))return!1;const t=yp(n.path),a=yp(e),s=U1(e);return s?s===n.hash&&(!a||t===a):t===a},J1=n=>Je(n)?n:`https://github.com/${n}`,Cc=n=>!Je(n)||/github\.com/.test(n)?"GitHub":/bitbucket\.org/.test(n)?"Bitbucket":/gitlab\.com/.test(n)?"GitLab":/gitee\.com/.test(n)?"Gitee":null;var G1=n=>Object.prototype.toString.call(n)==="[object Object]",aa=n=>typeof n=="string";const Tc=Array.isArray,bp=n=>G1(n)&&aa(n.name),sa=(n,e=!1)=>n?Tc(n)?n.map(t=>aa(t)?{name:t}:bp(t)?t:null).filter(t=>t!==null):aa(n)?[{name:n}]:bp(n)?[n]:(console.error(`Expect "author" to be \`AuthorInfo[] | AuthorInfo | string[] | string ${e?"":"| false"} | undefined\`, but got`,n),[]):[],Oc=(n,e)=>{if(n){if(Tc(n)&&n.every(aa))return n;if(aa(n))return[n];console.error(`Expect ${e||"value"} to be \`string[] | string | undefined\`, but got`,n)}return[]},Ic=n=>Oc(n,"category"),Pc=n=>Oc(n,"tag"),jc=()=>{const n=gn();return y(()=>n.value.readingTime??null)},Dc=(n,e)=>{const{minutes:t,words:a}=n,{less1Minute:s,word:l,time:o}=e;return{time:t<1?s:o.replace("$time",Math.round(t).toString()),words:l.replace("$word",a.toString())}};var wp={"/":{word:"约 $word 字",less1Minute:"小于 1 分钟",time:"大约 $time 分钟"}};const Ep={words:"",time:""},al=typeof wp>"u"?null:wp,Rc=()=>al?fa(al):y(()=>null),W1=()=>{if(typeof al>"u")return y(()=>Ep);const n=jc(),e=Rc();return y(()=>n.value&&e.value?Dc(n.value,e.value):Ep)},Se=()=>mc(),ln=()=>Y2(),it=()=>{const n=Se();return y(()=>!!n.value.pure)},Bs=()=>null,K1="719px",X1="1440px",Y1="false",zl={mobileBreakPoint:K1,pcBreakPoint:X1,enableThemeColor:Y1},Ul={"/java/base/":["","ConstantPool","IntegerConstantPool","String","Serialization","CustomLRU"],"/java/advance/":["","IO-Model","io","IO-model1","Collection","MysqlMasterSlave","Synchronized","ThreadLocal","Arthas","CompileJdk11","Future","ImplementSameInterface","Concurrent","ProxyInJava","ThreadPool","NativeMethod","ParentDelegationClassLoader"],"/java/jvm/":["","volatile","MemoryModel","名词解释","NewObject","ObjectReference","SetObjectNull","ClassLoader","StringAdd"],"/java/framework/":[{text:"Mybatis",prefix:"mybatis/",collapsible:!0,children:["","MybatisPlusDataSource","mybatis"]},{text:"Security学习",prefix:"security/",collapsible:!0,children:["","DelegatingFilterProxy","OncePerRequestFilter","PreAuthorize","OAuth2Authentication","Session","note","CustomAuthenticationProvider","SecurityFilterChain","spring-security-oauth2-authorization-server","SSO","CustomTokenAuthentication","Authorization","CustomLoginPage"]},{text:"Spring",prefix:"spring/",collapsible:!0,children:["","BeanPostProcessor","OncePerRequestFilter","SpringIOC","SpringAOP","SpringSourceAnalize","SpringMVC","DesignPatternInSpring","CircularDependency","SpringExtensionPoint","SpringCache","Annotation","Validator"]},{text:"SpringBoot",prefix:"springboot/",collapsible:!0,children:["","Http2","SpringBootAutoConfiguration","部署","Swagger","AOPLog","CollectionInject"]},{text:"Springcloud",prefix:"springcloud/",collapsible:!0,children:["SpringCloudGateway"]}],"/java/other/maven/":["","build标签","import","生命周期","problem","multiModule"],"/java/other/gradle/":["","wrapper"],"/java/other/locateproblem/":["","TooManyOpenFiles","Undertow.xxxNotFount","各种框架版本的坑","各种问题"],"/frontweb/vue/":["","elementui表单自定义校验","vue-router1","vue-router2","vue3Emit","vue-authority","vue3LifeTime","vue-pic","vue-Direactive","eventBus","自定义指令的实践","vue-nextTick","vue-in-action","vueExtend"],"/frontweb/es5/":["","aboutEvent","aboutThis","asyncError","crossDomain","crossDomain2","lazyLoad","aboutAsync","throttle"],"/frontweb/es6/":["","js中整数的最大值","useYarn","promise","useModule","useNpm","usePnpm"],"/frontweb/typeScript/":["","axios","fanType","tsAndvue3","basic-usage","action-usage"],"/frontweb/nodejs/":["readme","安装问题"],"/designpattern/":["","建造者模式","责任链模式","装饰模式","策略模式","模板方法模式"],"/cpp/study/":[""],"/cpp/other/":["1","2"],"/myserver/":["","Jellyfin搭建","x86_openwrt","旁路由网关","网络设置","自建nas"],"/other/git/":["","mergeBranch","rebaseAndMerge","gitRebase","reset","stash","branch01","proxy","fatal","gitConflict","branch02","GitCommands","gitwork","BatchDeleteGitHubRepo"],"/other/linux/":["","Curl","InstallMysqlWithDocker","Wifi","firewall","ShareBetweenWindowsAndLinux","Manjaro","CentOS","TcpDump","wsl","截图","MultiNetworkCard","CommonUsedCMD","MountDisk","Samba"],"/other/database/":["","CPUOverLoad","MysqlCollate","MysqlRemoteConnect","MysqlNote","数据库备份","SQLOptimization","Recurse"],"/other/markdown/":[""],"/other/tools/":["","Idea","VsCode","SoftWare"],"/other/training/":["","SSO","CloudServiceTraining","draw"],"/other/essay/":["","2022-04-12","CDN","ChromeDevTools","elasticSearch操作","elk部署","IM即时通信技术选型","Jenkins","windows下服务注册","BTree","CloudService","im即时通信的需求","TyporaPicgo","DeployGithubPage","操作系统引导"],"/other/web/":["","Cookie","Http","Restful","Jwt","OAUTH_LOGIN","RefreshToken","BuildWebProject"],"/other/distributeservice/":["","Nacos","DistributeLock"],"/other/oauth2/":["","01","02","03","04","05","06","07","08","09","10","11","12","13","14"],"/other/docker/":["Docker","ServiceInstall"],"/other/pve/":["","ddns申请证书","firewall"]},Fc=n=>{const{icon:e="",color:t,size:a}=n,s=t||a?{}:null;return t&&(s.color=t),a&&(s.height=Number.isNaN(Number(a))?a:`${a}px`),Je(e)?p("img",{class:"icon",src:e,alt:"","no-view":"",style:s}):us(e)?p("img",{class:"icon",src:xn(e),alt:"","aria-hidden":"","no-view":"",style:s}):p(ee("FontIcon"),n)};Fc.displayName="HopeIcon";var Vn=Fc;const ba=()=>{const n=me(),e=Ge();return t=>{if(t)if(us(t))e.path!==t&&n.push(t);else if(Mi(t))window&&window.open(t);else{const a=e.path.slice(0,e.path.lastIndexOf("/"));n.push(`${a}/${encodeURI(t)}`)}}},Bc=()=>{const n=ln(),e=fn();return y(()=>{const{author:t}=e.value;return t?sa(t):t===!1?[]:sa(n.value.author,!1)})},Q1=()=>{const n=fn(),e=wn(Symbol.for("categoryMap"));return y(()=>Ic(n.value.category).map(t=>{var a;return{name:t,path:((a=e==null?void 0:e.value.map[t])==null?void 0:a.path)||""}}))},Z1=()=>{const n=fn(),e=wn(Symbol.for("tagMap"));return y(()=>Pc(n.value.tag).map(t=>{var a;return{name:t,path:((a=e==null?void 0:e.value.map[t])==null?void 0:a.path)||""}}))},n0=()=>{const n=fn(),e=gn();return y(()=>{const t=ql(n.value.date);if(t)return t;const{createdTime:a}=e.value.git||{};return a?new Date(a):null})},e0=()=>{const n=ln(),e=gn(),t=fn(),a=Bc(),s=Q1(),l=Z1(),o=n0(),i=jc(),c=W1(),r=y(()=>({author:a.value,category:s.value,date:o.value,localizedDate:e.value.localizedDate,tag:l.value,isOriginal:t.value.isOriginal||!1,readingTime:i.value,readingTimeLocale:c.value,pageview:"pageview"in t.value?t.value.pageview:!0})),u=y(()=>"pageInfo"in t.value?t.value.pageInfo:"pageInfo"in n.value?n.value.pageInfo:null);return{info:r,items:u}},{mobileBreakPoint:t0,pcBreakPoint:a0}=zl,xp=n=>n.endsWith("px")?Number(n.slice(0,-2)):null,wa=()=>{const n=K(!1),e=K(!1),t=()=>{n.value=window.innerWidth<=(xp(t0)??719),e.value=window.innerWidth>=(xp(a0)??1440)};return En(()=>{t(),Fn("resize",t,!1),Fn("orientationchange",t,!1)}),{isMobile:n,isPC:e}},Mc=Symbol(""),Ea=()=>{const n=wn(Mc);if(!n)throw new Error("useDarkmode() is called without provider.");return n},s0=n=>{const e=Se(),t=m2(),a=y(()=>e.value.darkmode||"switch"),s=hs("vuepress-theme-hope-scheme","auto"),l=y(()=>{const i=a.value;return i==="disable"?!1:i==="enable"?!0:i==="auto"?t.value:i==="toggle"?s.value==="dark":s.value==="dark"||s.value==="auto"&&t.value}),o=y(()=>{const i=a.value;return i==="switch"||i==="toggle"});n.provide(Mc,{canToggle:o,config:a,isDarkmode:l,status:s}),Object.defineProperties(n.config.globalProperties,{$isDarkmode:{get:()=>l.value}})},l0=()=>{const{config:n,isDarkmode:e,status:t}=Ea();ii(()=>{n.value==="disable"?t.value="light":n.value==="enable"?t.value="dark":n.value==="toggle"&&t.value==="auto"&&(t.value="light")}),En(()=>{dn(e,a=>document.documentElement.setAttribute("data-theme",a?"dark":"light"),{immediate:!0})})};var vn=(n=>(n.type="y",n.title="t",n.shortTitle="s",n.icon="i",n.author="a",n.date="d",n.localizedDate="l",n.category="c",n.tag="g",n.isEncrypted="n",n.isOriginal="o",n.readingTime="r",n.excerpt="e",n.sticky="u",n.cover="v",n.index="I",n.order="O",n))(vn||{}),Vc=(n=>(n.article="a",n.home="h",n.slide="s",n.page="p",n))(Vc||{});const nt=(n="",e="")=>us(e)?e:`${Bi(n)}${e}`,yt=(n,e=!1)=>{const{meta:t,path:a,notFound:s}=It(n);return s?{text:a,link:a}:{text:!e&&t[vn.shortTitle]?t[vn.shortTitle]:t[vn.title]||a,link:a,...t[vn.icon]?{icon:t[vn.icon]}:{}}},sl=({config:n,prefix:e=""})=>{const t=(a,s=e)=>{const l=An(a)?yt(nt(s,a)):a.link?{...a,...Zt(a.link)?{}:{link:yt(nt(s,a.link)).link}}:a;if("children"in l){const o=nt(s,l.prefix),i=l.children==="structure"?Ul[o]:l.children;return{type:"group",...l,prefix:o,children:i.map(c=>t(c,o))}}return{type:"page",...l}};return n.map(a=>t(a))},o0=({config:n,page:e,headerDepth:t})=>{const a=re(n).sort((s,l)=>l.length-s.length);for(const s of a)if(St(decodeURI(e.path),s)){const l=n[s];return l?sl({config:l==="structure"?Ul[s]:l,page:e,headerDepth:t,prefix:s}):[]}return console.warn(`${e.path} is missing sidebar config.`),[]},p0=({config:n,routeLocale:e,page:t,headerDepth:a})=>n==="structure"?sl({config:Ul[e],page:t,headerDepth:a,prefix:e}):el(n)?sl({config:n,page:t,headerDepth:a}):ma(n)?o0({config:n,page:t,headerDepth:a}):[],qc=Symbol(""),i0=()=>{const n=fn(),e=ln(),t=gn(),a=fe(),s=y(()=>n.value.home?!1:n.value.sidebar??e.value.sidebar??"structure"),l=y(()=>n.value.headerDepth??e.value.headerDepth??2),o=ac(()=>[s.value,l.value,t.value.path,null],()=>p0({config:s.value,routeLocale:a.value,page:t.value,headerDepth:l.value}));ie(qc,o)},Jl=()=>{const n=wn(qc);if(!n)throw new Error("useSidebarItems() is called without provider.");return n};var c0=V({name:"PageFooter",setup(){const n=Se(),e=ln(),t=fn(),a=Bc(),s=y(()=>{const{copyright:r,footer:u}=t.value;return u!==!1&&!!(r||u||e.value.displayFooter)}),l=y(()=>{const{footer:r}=t.value;return r===!1?!1:An(r)?r:e.value.footer||""}),o=y(()=>a.value.map(({name:r})=>r).join(", ")),i=r=>`Copyright © ${new Date().getFullYear()} ${o.value} ${r?`${r} Licensed`:""}`,c=y(()=>{const{copyright:r,license:u=""}=t.value,{license:d}=n.value,{copyright:k}=e.value;return r??(u?i(u):An(k)?k:o.value||d?i(d):!1)});return()=>s.value?p("footer",{class:"vp-footer-wrapper"},[l.value?p("div",{class:"vp-footer",innerHTML:l.value}):null,c.value?p("div",{class:"vp-copyright",innerHTML:c.value}):null]):null}}),Un=V({name:"AutoLink",inheritAttrs:!1,props:{config:{type:Object,required:!0},exact:Boolean,noExternalLinkIcon:Boolean},emits:["focusout"],slots:Object,setup(n,{attrs:e,emit:t,slots:a}){const s=Ge(),l=Qi(),o=Ot(n,"config"),i=y(()=>Je(o.value.link)),c=y(()=>!i.value&&Mi(o.value.link)),r=y(()=>o.value.target||(i.value?"_blank":void 0)),u=y(()=>r.value==="_blank"),d=y(()=>!i.value&&!c.value&&!u.value),k=y(()=>o.value.rel||(u.value?"noopener noreferrer":null)),g=y(()=>o.value.ariaLabel||o.value.text),_=y(()=>{if(n.exact)return!1;const E=re(l.value.locales);return E.length?E.every(w=>w!==o.value.link):o.value.link!=="/"}),b=y(()=>d.value?o.value.activeMatch?new RegExp(o.value.activeMatch,"u").test(s.path):_.value?St(s.path,o.value.link):s.path===o.value.link:!1);return()=>{const{before:E,after:w,default:L}=a,{text:f,icon:S,link:F}=o.value;return d.value?p(Tn,{to:F,"aria-label":g.value,...e,class:["nav-link",{active:b.value},e.class],onFocusout:()=>t("focusout")},()=>L?L():[E?E():p(Vn,{icon:S}),f,w==null?void 0:w()]):p("a",{href:F,rel:k.value,target:r.value,"aria-label":g.value,...e,class:["nav-link",e.class],onFocusout:()=>t("focusout")},L?L():[E?E():p(Vn,{icon:S}),f,n.noExternalLinkIcon?null:p(kc),w==null?void 0:w()])}}}),r0=V({name:"NavbarDropdownLink",props:{config:{type:Object,required:!0}},slots:Object,setup(n,{slots:e}){const t=gn(),a=Ot(n,"config"),s=y(()=>a.value.ariaLabel||a.value.text),l=K(!1);dn(()=>t.value.path,()=>{l.value=!1});const o=i=>{i.detail===0&&(l.value=!l.value)};return()=>{var i;return p("div",{class:["dropdown-wrapper",{open:l.value}]},[p("button",{type:"button",class:"dropdown-title","aria-label":s.value,onClick:o},[((i=e.title)==null?void 0:i.call(e))||p("span",{class:"title"},[p(Vn,{icon:a.value.icon}),n.config.text]),p("span",{class:"arrow"}),p("ul",{class:"nav-dropdown"},a.value.children.map((c,r)=>{const u=r===a.value.children.length-1;return p("li",{class:"dropdown-item"},"children"in c?[p("h4",{class:"dropdown-subtitle"},c.link?p(Un,{config:c,onFocusout:()=>{c.children.length===0&&u&&(l.value=!1)}}):p("span",c.text)),p("ul",{class:"dropdown-subitem-wrapper"},c.children.map((d,k)=>p("li",{class:"dropdown-subitem"},p(Un,{config:d,onFocusout:()=>{k===c.children.length-1&&u&&(l.value=!1)}}))))]:p(Un,{config:c,onFocusout:()=>{u&&(l.value=!1)}}))}))])])}}});const Nc=(n,e="")=>An(n)?yt(nt(e,n)):"children"in n?{...n,...n.link&&!Zt(n.link)?yt(nt(e,n.link)):{},children:n.children.map(t=>Nc(t,nt(e,n.prefix)))}:{...n,link:Zt(n.link)?n.link:yt(nt(e,n.link)).link},Hc=()=>{const n=ln(),e=()=>(n.value.navbar||[]).map(t=>Nc(t));return ac(()=>n.value.navbar,()=>e())},u0=()=>{const n=ln(),e=y(()=>n.value.repo||null),t=y(()=>e.value?J1(e.value):null),a=y(()=>e.value?Cc(e.value):null),s=y(()=>t.value?n.value.repoLabel??(a.value===null?"Source":a.value):null);return y(()=>!t.value||!s.value||n.value.repoDisplay===!1?null:{type:a.value||"Source",label:s.value,link:t.value})};var d0=V({name:"NavScreenDropdown",props:{config:{type:Object,required:!0}},setup(n){const e=gn(),t=Ot(n,"config"),a=y(()=>t.value.ariaLabel||t.value.text),s=K(!1);dn(()=>e.value.path,()=>{s.value=!1});const l=(o,i)=>i[i.length-1]===o;return()=>[p("button",{type:"button",class:["nav-screen-dropdown-title",{active:s.value}],"aria-label":a.value,onClick:()=>{s.value=!s.value}},[p("span",{class:"title"},[p(Vn,{icon:t.value.icon}),n.config.text]),p("span",{class:["arrow",s.value?"down":"end"]})]),p("ul",{class:["nav-screen-dropdown",{hide:!s.value}]},t.value.children.map(o=>p("li",{class:"dropdown-item"},"children"in o?[p("h4",{class:"dropdown-subtitle"},o.link?p(Un,{config:o,onFocusout:()=>{l(o,t.value.children)&&o.children.length===0&&(s.value=!1)}}):p("span",o.text)),p("ul",{class:"dropdown-subitem-wrapper"},o.children.map(i=>p("li",{class:"dropdown-subitem"},p(Un,{config:i,onFocusout:()=>{l(i,o.children)&&l(o,t.value.children)&&(s.value=!1)}}))))]:p(Un,{config:o,onFocusout:()=>{l(o,t.value.children)&&(s.value=!1)}}))))]}}),h0=V({name:"NavScreenLinks",setup(){const n=Hc();return()=>n.value.length?p("nav",{class:"nav-screen-links"},n.value.map(e=>p("div",{class:"navbar-links-item"},"children"in e?p(d0,{config:e}):p(Un,{config:e})))):null}});const $c=()=>p(un,{name:"dark"},()=>p("path",{d:"M524.8 938.667h-4.267a439.893 439.893 0 0 1-313.173-134.4 446.293 446.293 0 0 1-11.093-597.334A432.213 432.213 0 0 1 366.933 90.027a42.667 42.667 0 0 1 45.227 9.386 42.667 42.667 0 0 1 10.24 42.667 358.4 358.4 0 0 0 82.773 375.893 361.387 361.387 0 0 0 376.747 82.774 42.667 42.667 0 0 1 54.187 55.04 433.493 433.493 0 0 1-99.84 154.88 438.613 438.613 0 0 1-311.467 128z"}));$c.displayName="DarkIcon";const zc=()=>p(un,{name:"light"},()=>p("path",{d:"M952 552h-80a40 40 0 0 1 0-80h80a40 40 0 0 1 0 80zM801.88 280.08a41 41 0 0 1-57.96-57.96l57.96-58a41.04 41.04 0 0 1 58 58l-58 57.96zM512 752a240 240 0 1 1 0-480 240 240 0 0 1 0 480zm0-560a40 40 0 0 1-40-40V72a40 40 0 0 1 80 0v80a40 40 0 0 1-40 40zm-289.88 88.08-58-57.96a41.04 41.04 0 0 1 58-58l57.96 58a41 41 0 0 1-57.96 57.96zM192 512a40 40 0 0 1-40 40H72a40 40 0 0 1 0-80h80a40 40 0 0 1 40 40zm30.12 231.92a41 41 0 0 1 57.96 57.96l-57.96 58a41.04 41.04 0 0 1-58-58l58-57.96zM512 832a40 40 0 0 1 40 40v80a40 40 0 0 1-80 0v-80a40 40 0 0 1 40-40zm289.88-88.08 58 57.96a41.04 41.04 0 0 1-58 58l-57.96-58a41 41 0 0 1 57.96-57.96z"}));zc.displayName="LightIcon";const Uc=()=>p(un,{name:"auto"},()=>p("path",{d:"M512 992C246.92 992 32 777.08 32 512S246.92 32 512 32s480 214.92 480 480-214.92 480-480 480zm0-840c-198.78 0-360 161.22-360 360 0 198.84 161.22 360 360 360s360-161.16 360-360c0-198.78-161.22-360-360-360zm0 660V212c165.72 0 300 134.34 300 300 0 165.72-134.28 300-300 300z"}));Uc.displayName="AutoIcon";const Jc=()=>p(un,{name:"enter-fullscreen"},()=>p("path",{d:"M762.773 90.24h-497.28c-96.106 0-174.4 78.293-174.4 174.4v497.28c0 96.107 78.294 174.4 174.4 174.4h497.28c96.107 0 175.04-78.293 174.4-174.4V264.64c0-96.213-78.186-174.4-174.4-174.4zm-387.2 761.173H215.04c-21.867 0-40.427-17.92-41.067-41.066V649.92c0-22.507 17.92-40.427 40.427-40.427 11.307 0 21.227 4.694 28.48 11.947 7.253 7.253 11.947 17.92 11.947 28.48v62.293l145.28-145.28c15.893-15.893 41.813-15.893 57.706 0 15.894 15.894 15.894 41.814 0 57.707l-145.28 145.28h62.294c22.506 0 40.426 17.92 40.426 40.427s-17.173 41.066-39.68 41.066zM650.24 165.76h160.427c21.866 0 40.426 17.92 41.066 41.067v160.426c0 22.507-17.92 40.427-40.426 40.427-11.307 0-21.227-4.693-28.48-11.947-7.254-7.253-11.947-17.92-11.947-28.48v-62.186L625.6 450.347c-15.893 15.893-41.813 15.893-57.707 0-15.893-15.894-15.893-41.814 0-57.707l145.28-145.28H650.88c-22.507 0-40.427-17.92-40.427-40.427s17.174-41.173 39.787-41.173z"}));Jc.displayName="EnterFullScreenIcon";const Gc=()=>p(un,{name:"cancel-fullscreen"},()=>p("path",{d:"M778.468 78.62H247.922c-102.514 0-186.027 83.513-186.027 186.027V795.08c0 102.514 83.513 186.027 186.027 186.027h530.432c102.514 0 186.71-83.513 186.026-186.027V264.647C964.494 162.02 880.981 78.62 778.468 78.62zM250.88 574.35h171.122c23.324 0 43.122 19.115 43.804 43.805v171.121c0 24.008-19.114 43.122-43.122 43.122-12.06 0-22.641-5.006-30.378-12.743s-12.743-19.115-12.743-30.379V722.83L224.597 877.91c-16.953 16.952-44.6 16.952-61.553 0-16.953-16.954-16.953-44.602 0-61.554L318.009 661.39h-66.446c-24.007 0-43.122-19.114-43.122-43.122 0-24.12 18.432-43.918 42.439-43.918zm521.899-98.873H601.657c-23.325 0-43.122-19.114-43.805-43.804V260.55c0-24.007 19.115-43.122 43.122-43.122 12.06 0 22.642 5.007 30.379 12.743s12.743 19.115 12.743 30.38v66.445l154.965-154.965c16.953-16.953 44.601-16.953 61.554 0 16.953 16.953 16.953 44.6 0 61.554L705.536 388.55h66.446c24.007 0 43.122 19.115 43.122 43.122.114 24.007-18.318 43.804-42.325 43.804z"}));Gc.displayName="CancelFullScreenIcon";const Wc=()=>p(un,{name:"outlook"},()=>[p("path",{d:"M224 800c0 9.6 3.2 44.8 6.4 54.4 6.4 48-48 76.8-48 76.8s80 41.6 147.2 0 134.4-134.4 38.4-195.2c-22.4-12.8-41.6-19.2-57.6-19.2C259.2 716.8 227.2 761.6 224 800zM560 675.2l-32 51.2c-51.2 51.2-83.2 32-83.2 32 25.6 67.2 0 112-12.8 128 25.6 6.4 51.2 9.6 80 9.6 54.4 0 102.4-9.6 150.4-32l0 0c3.2 0 3.2-3.2 3.2-3.2 22.4-16 12.8-35.2 6.4-44.8-9.6-12.8-12.8-25.6-12.8-41.6 0-54.4 60.8-99.2 137.6-99.2 6.4 0 12.8 0 22.4 0 12.8 0 38.4 9.6 48-25.6 0-3.2 0-3.2 3.2-6.4 0-3.2 3.2-6.4 3.2-6.4 6.4-16 6.4-16 6.4-19.2 9.6-35.2 16-73.6 16-115.2 0-105.6-41.6-198.4-108.8-268.8C704 396.8 560 675.2 560 675.2zM224 419.2c0-28.8 22.4-51.2 51.2-51.2 28.8 0 51.2 22.4 51.2 51.2 0 28.8-22.4 51.2-51.2 51.2C246.4 470.4 224 448 224 419.2zM320 284.8c0-22.4 19.2-41.6 41.6-41.6 22.4 0 41.6 19.2 41.6 41.6 0 22.4-19.2 41.6-41.6 41.6C339.2 326.4 320 307.2 320 284.8zM457.6 208c0-12.8 12.8-25.6 25.6-25.6 12.8 0 25.6 12.8 25.6 25.6 0 12.8-12.8 25.6-25.6 25.6C470.4 233.6 457.6 220.8 457.6 208zM128 505.6C128 592 153.6 672 201.6 736c28.8-60.8 112-60.8 124.8-60.8-16-51.2 16-99.2 16-99.2l316.8-422.4c-48-19.2-99.2-32-150.4-32C297.6 118.4 128 291.2 128 505.6zM764.8 86.4c-22.4 19.2-390.4 518.4-390.4 518.4-22.4 28.8-12.8 76.8 22.4 99.2l9.6 6.4c35.2 22.4 80 12.8 99.2-25.6 0 0 6.4-12.8 9.6-19.2 54.4-105.6 275.2-524.8 288-553.6 6.4-19.2-3.2-32-19.2-32C777.6 76.8 771.2 80 764.8 86.4z"})]);Wc.displayName="OutlookIcon";var Kc=V({name:"AppearanceSwitch",setup(){const{config:n,isDarkmode:e,status:t}=Ea(),a=it(),s=()=>{n.value==="switch"?t.value={light:"dark",dark:"auto",auto:"light"}[t.value]:t.value=t.value==="light"?"dark":"light"},l=async o=>{if(!(document.startViewTransition&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches&&!a.value)||!o){s();return}const i=o.clientX,c=o.clientY,r=Math.hypot(Math.max(i,innerWidth-i),Math.max(c,innerHeight-c)),u=e.value;await document.startViewTransition(async()=>{s(),await Ue()}).ready,e.value!==u&&document.documentElement.animate({clipPath:e.value?[`circle(${r}px at ${i}px ${c}px)`,`circle(0px at ${i}px ${c}px)`]:[`circle(0px at ${i}px ${c}px)`,`circle(${r}px at ${i}px ${c}px)`]},{duration:400,pseudoElement:e.value?"::view-transition-old(root)":"::view-transition-new(root)"})};return()=>p("button",{type:"button",id:"appearance-switch",onClick:l},[p(Uc,{style:{display:t.value==="auto"?"block":"none"}}),p($c,{style:{display:t.value==="dark"?"block":"none"}}),p(zc,{style:{display:t.value==="light"?"block":"none"}})])}}),k0=V({name:"AppearanceMode",setup(){const n=ln(),{canToggle:e}=Ea(),t=y(()=>n.value.outlookLocales.darkmode);return()=>e.value?p("div",{class:"appearance-wrapper"},[p("label",{class:"appearance-title",for:"appearance-switch"},t.value),p(Kc)]):null}});const Ms="VUEPRESS_THEME_COLOR";var g0=V({name:"ThemeColorPicker",props:{themeColor:{type:Object,required:!0}},setup(n){const e=(t="")=>{const a=document.documentElement.classList,s=re(n.themeColor);if(!t){localStorage.removeItem(Ms),a.remove(...s);return}a.remove(...s.filter(l=>l!==t)),a.add(t),localStorage.setItem(Ms,t)};return En(()=>{const t=localStorage.getItem(Ms);t&&e(t)}),()=>p("ul",{id:"theme-color-picker"},[p("li",p("span",{class:"theme-color",onClick:()=>e()})),ot(n.themeColor).map(([t,a])=>p("li",p("span",{style:{background:a},onClick:()=>e(t)})))])}});const bt=zl.enableThemeColor==="true",m0=bt?Wk(ot(zl).filter(([n])=>n.startsWith("theme-"))):{};var v0=V({name:"ThemeColor",setup(){const n=ln(),e=y(()=>n.value.outlookLocales.themeColor);return()=>bt?p("div",{class:"theme-color-wrapper"},[p("label",{class:"theme-color-title",for:"theme-color-picker"},e.value),p(g0,{themeColor:m0})]):null}}),Xc=V({name:"ToggleFullScreenButton",setup(){const n=ln(),{isSupported:e,isFullscreen:t,toggle:a}=Hl(),s=y(()=>n.value.outlookLocales.fullscreen);return()=>e?p("div",{class:"full-screen-wrapper"},[p("label",{class:"full-screen-title",for:"full-screen-switch"},s.value),p("button",{type:"button",id:"full-screen-switch",class:"full-screen",ariaPressed:t.value,onClick:()=>a()},t.value?p(Gc):p(Jc))]):null}}),Yc=V({name:"OutlookSettings",setup(){const n=Se(),e=it(),t=y(()=>!e.value&&n.value.fullscreen);return()=>p(rs,()=>[bt?p(v0):null,p(k0),t.value?p(Xc):null])}}),f0=V({name:"NavScreen",props:{show:Boolean},emits:["close"],slots:Object,setup(n,{emit:e,slots:t}){const a=gn(),{isMobile:s}=wa(),l=Nn(),o=cc(l);return En(()=>{l.value=document.body,dn(s,i=>{!i&&n.show&&(o.value=!1,e("close"))}),dn(()=>a.value.path,()=>{o.value=!1,e("close")})}),da(()=>{o.value=!1}),()=>p($e,{name:"fade",onEnter:()=>{o.value=!0},onAfterLeave:()=>{o.value=!1}},()=>{var i,c;return n.show?p("div",{id:"nav-screen"},p("div",{class:"vp-nav-screen-container"},[(i=t.before)==null?void 0:i.call(t),p(h0),p("div",{class:"vp-outlook-wrapper"},p(Yc)),(c=t.after)==null?void 0:c.call(t)])):null})}}),_0=V({name:"NavbarBrand",setup(){const n=fe(),e=va(),t=ln(),a=y(()=>t.value.home||n.value),s=y(()=>e.value.title),l=y(()=>t.value.navTitle??s.value),o=y(()=>t.value.logo?xn(t.value.logo):null),i=y(()=>t.value.logoDark?xn(t.value.logoDark):null);return()=>p(Tn,{to:a.value,class:"vp-brand"},()=>[o.value?p("img",{class:["vp-nav-logo",{light:!!i.value}],src:o.value,alt:""}):null,i.value?p("img",{class:["vp-nav-logo dark"],src:i.value,alt:""}):null,l.value?p("span",{class:["vp-site-name",{"hide-in-pad":o.value&&t.value.hideSiteNameOnMobile!==!1}]},l.value):null])}}),y0=V({name:"NavbarLinks",setup(){const n=Hc();return()=>n.value.length?p("nav",{class:"vp-nav-links"},n.value.map(e=>p("div",{class:"nav-item hide-in-mobile"},"children"in e?p(r0,{config:e}):p(Un,{config:e})))):null}}),b0=V({name:"RepoLink",components:{BitbucketIcon:xc,GiteeIcon:Ec,GitHubIcon:bc,GitLabIcon:wc,SourceIcon:Ac},setup(){const n=u0();return()=>n.value?p("div",{class:"nav-item vp-repo"},p("a",{class:"vp-repo-link",href:n.value.link,target:"_blank",rel:"noopener noreferrer","aria-label":n.value.label},p(ee(`${n.value.type}Icon`),{style:{width:"1.25rem",height:"1.25rem",verticalAlign:"middle"}}))):null}});const Qc=({active:n=!1},{emit:e})=>p("button",{type:"button",class:["vp-toggle-navbar-button",{"is-active":n}],"aria-label":"Toggle Navbar","aria-expanded":n,"aria-controls":"nav-screen",onClick:()=>e("toggle")},p("span",[p("span",{class:"vp-top"}),p("span",{class:"vp-middle"}),p("span",{class:"vp-bottom"})]));Qc.displayName="ToggleNavbarButton";var w0=Qc;const ll=(n,{emit:e})=>p("button",{type:"button",class:"vp-toggle-sidebar-button",title:"Toggle Sidebar",onClick:()=>e("toggle")},p("span",{class:"icon"}));ll.displayName="ToggleSidebarButton",ll.emits=["toggle"];var E0=ll,x0=V({name:"OutlookButton",setup(){const{isSupported:n}=Hl(),e=Se(),t=it(),a=gn(),{canToggle:s}=Ea(),l=K(!1),o=y(()=>!t.value&&e.value.fullscreen&&n);return dn(()=>a.value.path,()=>{l.value=!1}),()=>s.value||o.value||bt?p("div",{class:"nav-item hide-in-mobile"},s.value&&!o.value&&!bt?p(Kc):o.value&&!s.value&&!bt?p(Xc):p("button",{type:"button",class:["outlook-button",{open:l.value}],tabindex:"-1","aria-hidden":!0},[p(Wc),p("div",{class:"outlook-dropdown"},p(Yc))])):null}}),A0=V({name:"NavBar",emits:["toggleSidebar"],slots:Object,setup(n,{emit:e,slots:t}){const a=ln(),{isMobile:s}=wa(),l=K(!1),o=y(()=>{const{navbarAutoHide:u="mobile"}=a.value;return u!=="none"&&(u==="always"||s.value)}),i=y(()=>a.value.navbarLayout||{start:["Brand"],center:["Links"],end:["Language","Repo","Outlook","Search"]}),c={Brand:_0,Language:Bs,Links:y0,Repo:b0,Outlook:x0,Search:xe("Docsearch")?ee("Docsearch"):xe("SearchBox")?ee("SearchBox"):Bs},r=u=>c[u]??(xe(u)?ee(u):Bs);return()=>{var u,d,k,g,_,b;return[p("header",{key:"navbar",id:"navbar",class:["vp-navbar",{"auto-hide":o.value,"hide-icon":a.value.navbarIcon===!1}]},[p("div",{class:"vp-navbar-start"},[p(E0,{onToggle:()=>{l.value&&(l.value=!1),e("toggleSidebar")}}),(u=t.startBefore)==null?void 0:u.call(t),(i.value.start||[]).map(E=>p(r(E))),(d=t.startAfter)==null?void 0:d.call(t)]),p("div",{class:"vp-navbar-center"},[(k=t.centerBefore)==null?void 0:k.call(t),(i.value.center||[]).map(E=>p(r(E))),(g=t.centerAfter)==null?void 0:g.call(t)]),p("div",{class:"vp-navbar-end"},[(_=t.endBefore)==null?void 0:_.call(t),(i.value.end||[]).map(E=>p(r(E))),(b=t.endAfter)==null?void 0:b.call(t),p(w0,{active:l.value,onToggle:()=>{l.value=!l.value}})])]),p(f0,{show:l.value,onClose:()=>{l.value=!1}},{before:()=>{var E;return(E=t.screenTop)==null?void 0:E.call(t)},after:()=>{var E;return(E=t.screenBottom)==null?void 0:E.call(t)}})]}}});const la=(n,e,t=!1)=>"activeMatch"in e?new RegExp(e.activeMatch,"u").test(n.path):Sc(n,e.link)?!0:"children"in e&&!t?e.children.some(a=>la(n,a)):!1,Zc=(n,e)=>e.type==="group"?e.children.some(t=>t.type==="group"?Zc(n,t):t.type==="page"&&la(n,t,!0))||"prefix"in e&&Sc(n,e.prefix):!1;var L0=V({name:"SidebarChild",props:{config:{type:Object,required:!0}},setup(n){const e=Ge();return()=>An(n.config.link)?p(Un,{class:["vp-sidebar-link","vp-sidebar-page",{active:la(e,n.config,!0)}],exact:!0,config:n.config}):p("p",n,[p(Vn,{icon:n.config.icon}),n.config.text])}}),S0=V({name:"SidebarGroup",props:{config:{type:Object,required:!0},open:{type:Boolean,required:!0}},emits:["toggle"],setup(n,{emit:e}){const t=Ge(),a=y(()=>la(t,n.config)),s=y(()=>la(t,n.config,!0));return()=>{const{collapsible:l,children:o=[],icon:i,prefix:c,link:r,text:u}=n.config;return p("section",{class:"vp-sidebar-group"},[p(l?"button":"p",{class:["vp-sidebar-header",{clickable:l||r,exact:s.value,active:a.value}],...l?{type:"button",onClick:()=>e("toggle"),onKeydown:d=>{d.key==="Enter"&&e("toggle")}}:{}},[p(Vn,{icon:i}),r?p(Un,{class:"vp-sidebar-title",config:{text:u,link:r},noExternalLinkIcon:!0}):p("span",{class:"vp-sidebar-title"},u),l?p("span",{class:["vp-arrow",n.open?"down":"end"]}):null]),n.open||!l?p(nr,{key:c,config:o}):null])}}}),nr=V({name:"SidebarLinks",props:{config:{type:Array,required:!0}},setup(n){const e=Ge(),t=K(-1),a=s=>{t.value=s===t.value?-1:s};return dn(()=>e.path,()=>{const s=n.config.findIndex(l=>Zc(e,l));t.value=s},{immediate:!0,flush:"post"}),()=>p("ul",{class:"vp-sidebar-links"},n.config.map((s,l)=>p("li",s.type==="group"?p(S0,{config:s,open:l===t.value,onToggle:()=>a(l)}):p(L0,{config:s}))))}}),C0=V({name:"SideBar",slots:Object,setup(n,{slots:e}){const t=Ge(),a=ln(),s=Jl(),l=Nn();return En(()=>{dn(()=>t.hash,o=>{const i=document.querySelector(`.vp-sidebar a.vp-sidebar-link[href="${t.path}${o}"]`);if(!i)return;const{top:c,height:r}=l.value.getBoundingClientRect(),{top:u,height:d}=i.getBoundingClientRect();uc+r&&i.scrollIntoView(!1)},{immediate:!0})}),()=>{var o,i,c;return p("aside",{ref:l,id:"sidebar",class:["vp-sidebar",{"hide-icon":a.value.sidebarIcon===!1}],key:"sidebar"},[(o=e.top)==null?void 0:o.call(e),((i=e.default)==null?void 0:i.call(e))||p(nr,{config:s.value}),(c=e.bottom)==null?void 0:c.call(e)])}}}),Gl=V({name:"CommonWrapper",props:{containerClass:{type:String,default:""},noNavbar:Boolean,noSidebar:Boolean,noToc:Boolean},slots:Object,setup(n,{slots:e}){const t=me(),a=gn(),s=fn(),l=ln(),{isMobile:o,isPC:i}=wa(),[c,r]=tl(!1),[u,d]=tl(!1),k=Jl(),g=K(!1),_=y(()=>n.noNavbar||s.value.navbar===!1||l.value.navbar===!1?!1:!!(a.value.title||l.value.logo||l.value.repo||l.value.navbar)),b=y(()=>n.noSidebar?!1:s.value.sidebar!==!1&&k.value.length!==0&&!s.value.home),E=y(()=>n.noToc||s.value.home?!1:s.value.toc||l.value.toc!==!1&&s.value.toc!==!1),w={x:0,y:0},L=A=>{w.x=A.changedTouches[0].clientX,w.y=A.changedTouches[0].clientY},f=A=>{const H=A.changedTouches[0].clientX-w.x,M=A.changedTouches[0].clientY-w.y;Math.abs(H)>Math.abs(M)*1.5&&Math.abs(H)>40&&(H>0&&w.x<=80?r(!0):r(!1))},S=()=>window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;let F=0;return Fn("scroll",l2(()=>{const A=S();A<=58||A{A||r(!1)}),En(()=>{const A=cc(document.body);dn(c,M=>{A.value=M});const H=t.afterEach(()=>{r(!1)});da(()=>{A.value=!1,H()})}),()=>p(xe("GlobalEncrypt")?ee("GlobalEncrypt"):yc,()=>p("div",{class:["theme-container",{"no-navbar":!_.value,"no-sidebar":!b.value&&!(e.sidebar||e.sidebarTop||e.sidebarBottom),"has-toc":E.value,"hide-navbar":g.value,"sidebar-collapsed":!o.value&&!i.value&&u.value,"sidebar-open":o.value&&c.value},n.containerClass,s.value.containerClass||""],onTouchStart:L,onTouchEnd:f},[_.value?p(A0,{onToggleSidebar:()=>r()},{startBefore:()=>{var A;return(A=e.navbarStartBefore)==null?void 0:A.call(e)},startAfter:()=>{var A;return(A=e.navbarStartAfter)==null?void 0:A.call(e)},centerBefore:()=>{var A;return(A=e.navbarCenterBefore)==null?void 0:A.call(e)},centerAfter:()=>{var A;return(A=e.navbarCenterAfter)==null?void 0:A.call(e)},endBefore:()=>{var A;return(A=e.navbarEndBefore)==null?void 0:A.call(e)},endAfter:()=>{var A;return(A=e.navbarEndAfter)==null?void 0:A.call(e)},screenTop:()=>{var A;return(A=e.navScreenTop)==null?void 0:A.call(e)},screenBottom:()=>{var A;return(A=e.navScreenBottom)==null?void 0:A.call(e)}}):null,p($e,{name:"fade"},()=>c.value?p("div",{class:"vp-sidebar-mask",onClick:()=>r(!1)}):null),p($e,{name:"fade"},()=>o.value?null:p("div",{class:"toggle-sidebar-wrapper",onClick:()=>d()},p("span",{class:["arrow",u.value?"end":"start"]}))),p(C0,{},{...e.sidebar?{default:()=>e.sidebar()}:{},top:()=>{var A;return(A=e.sidebarTop)==null?void 0:A.call(e)},bottom:()=>{var A;return(A=e.sidebarBottom)==null?void 0:A.call(e)}}),e.default(),p(c0)]))}}),rn=V({name:"DropTransition",props:{type:{type:String,default:"single"},delay:{type:Number,default:0},duration:{type:Number,default:.25},appear:Boolean},slots:Object,setup(n,{slots:e}){const t=s=>{s.style.transition=`transform ${n.duration}s ease-in-out ${n.delay}s, opacity ${n.duration}s ease-in-out ${n.delay}s`,s.style.transform="translateY(-20px)",s.style.opacity="0"},a=s=>{s.style.transform="translateY(0)",s.style.opacity="1"};return()=>p(n.type==="single"?$e:uh,{name:"drop",appear:n.appear,onAppear:t,onAfterAppear:a,onEnter:t,onAfterEnter:a,onBeforeLeave:t},()=>e.default())}});const ol=({custom:n})=>p(ec,{class:["theme-hope-content",{custom:n}]});ol.displayName="MarkdownContent",ol.props={custom:Boolean};var Wl=ol;const er=()=>p(un,{name:"author"},()=>p("path",{d:"M649.6 633.6c86.4-48 147.2-144 147.2-249.6 0-160-128-288-288-288s-288 128-288 288c0 108.8 57.6 201.6 147.2 249.6-121.6 48-214.4 153.6-240 288-3.2 9.6 0 19.2 6.4 25.6 3.2 9.6 12.8 12.8 22.4 12.8h704c9.6 0 19.2-3.2 25.6-12.8 6.4-6.4 9.6-16 6.4-25.6-25.6-134.4-121.6-240-243.2-288z"}));er.displayName="AuthorIcon";const tr=()=>p(un,{name:"calendar"},()=>p("path",{d:"M716.4 110.137c0-18.753-14.72-33.473-33.472-33.473-18.753 0-33.473 14.72-33.473 33.473v33.473h66.993v-33.473zm-334.87 0c0-18.753-14.72-33.473-33.473-33.473s-33.52 14.72-33.52 33.473v33.473h66.993v-33.473zm468.81 33.52H716.4v100.465c0 18.753-14.72 33.473-33.472 33.473a33.145 33.145 0 01-33.473-33.473V143.657H381.53v100.465c0 18.753-14.72 33.473-33.473 33.473a33.145 33.145 0 01-33.473-33.473V143.657H180.6A134.314 134.314 0 0046.66 277.595v535.756A134.314 134.314 0 00180.6 947.289h669.74a134.36 134.36 0 00133.94-133.938V277.595a134.314 134.314 0 00-133.94-133.938zm33.473 267.877H147.126a33.145 33.145 0 01-33.473-33.473c0-18.752 14.72-33.473 33.473-33.473h736.687c18.752 0 33.472 14.72 33.472 33.473a33.145 33.145 0 01-33.472 33.473z"}));tr.displayName="CalendarIcon";const ar=()=>p(un,{name:"category"},()=>p("path",{d:"M148.41 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H148.41c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.311-40.31zM147.556 553.478H429.73c22.263 0 40.311 18.048 40.311 40.31v282.176c0 22.263-18.048 40.312-40.31 40.312H147.555c-22.263 0-40.311-18.049-40.311-40.312V593.79c0-22.263 18.048-40.311 40.31-40.311zM593.927 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H593.927c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.31-40.31zM730.22 920.502H623.926c-40.925 0-74.22-33.388-74.22-74.425V623.992c0-41.038 33.387-74.424 74.425-74.424h222.085c41.038 0 74.424 33.226 74.424 74.067v114.233c0 10.244-8.304 18.548-18.547 18.548s-18.548-8.304-18.548-18.548V623.635c0-20.388-16.746-36.974-37.33-36.974H624.13c-20.585 0-37.331 16.747-37.331 37.33v222.086c0 20.585 16.654 37.331 37.126 37.331H730.22c10.243 0 18.547 8.304 18.547 18.547 0 10.244-8.304 18.547-18.547 18.547z"}));ar.displayName="CategoryIcon";const sr=()=>p(un,{name:"print"},()=>p("path",{d:"M819.2 364.8h-44.8V128c0-17.067-14.933-32-32-32H281.6c-17.067 0-32 14.933-32 32v236.8h-44.8C145.067 364.8 96 413.867 96 473.6v192c0 59.733 49.067 108.8 108.8 108.8h44.8V896c0 17.067 14.933 32 32 32h460.8c17.067 0 32-14.933 32-32V774.4h44.8c59.733 0 108.8-49.067 108.8-108.8v-192c0-59.733-49.067-108.8-108.8-108.8zM313.6 160h396.8v204.8H313.6V160zm396.8 704H313.6V620.8h396.8V864zM864 665.6c0 25.6-19.2 44.8-44.8 44.8h-44.8V588.8c0-17.067-14.933-32-32-32H281.6c-17.067 0-32 14.933-32 32v121.6h-44.8c-25.6 0-44.8-19.2-44.8-44.8v-192c0-25.6 19.2-44.8 44.8-44.8h614.4c25.6 0 44.8 19.2 44.8 44.8v192z"}));sr.displayName="PrintIcon";const lr=()=>p(un,{name:"tag"},()=>p("path",{d:"M939.902 458.563L910.17 144.567c-1.507-16.272-14.465-29.13-30.737-30.737L565.438 84.098h-.402c-3.215 0-5.726 1.005-7.634 2.913l-470.39 470.39a10.004 10.004 0 000 14.164l365.423 365.424c1.909 1.908 4.42 2.913 7.132 2.913s5.223-1.005 7.132-2.913l470.39-470.39c2.01-2.11 3.014-5.023 2.813-8.036zm-240.067-72.121c-35.458 0-64.286-28.828-64.286-64.286s28.828-64.285 64.286-64.285 64.286 28.828 64.286 64.285-28.829 64.286-64.286 64.286z"}));lr.displayName="TagIcon";const or=()=>p(un,{name:"timer"},()=>p("path",{d:"M799.387 122.15c4.402-2.978 7.38-7.897 7.38-13.463v-1.165c0-8.933-7.38-16.312-16.312-16.312H256.33c-8.933 0-16.311 7.38-16.311 16.312v1.165c0 5.825 2.977 10.874 7.637 13.592 4.143 194.44 97.22 354.963 220.201 392.763-122.204 37.542-214.893 196.511-220.2 389.397-4.661 5.049-7.638 11.651-7.638 19.03v5.825h566.49v-5.825c0-7.379-2.849-13.981-7.509-18.9-5.049-193.016-97.867-351.985-220.2-389.527 123.24-37.67 216.446-198.453 220.588-392.892zM531.16 450.445v352.632c117.674 1.553 211.787 40.778 211.787 88.676H304.097c0-48.286 95.149-87.382 213.728-88.676V450.445c-93.077-3.107-167.901-81.297-167.901-177.093 0-8.803 6.99-15.793 15.793-15.793 8.803 0 15.794 6.99 15.794 15.793 0 80.261 63.69 145.635 142.01 145.635s142.011-65.374 142.011-145.635c0-8.803 6.99-15.793 15.794-15.793s15.793 6.99 15.793 15.793c0 95.019-73.789 172.82-165.96 177.093z"}));or.displayName="TimerIcon";const pr=()=>p(un,{name:"word"},()=>[p("path",{d:"M518.217 432.64V73.143A73.143 73.143 0 01603.43 1.097a512 512 0 01419.474 419.474 73.143 73.143 0 01-72.046 85.212H591.36a73.143 73.143 0 01-73.143-73.143z"}),p("path",{d:"M493.714 566.857h340.297a73.143 73.143 0 0173.143 85.577A457.143 457.143 0 11371.566 117.76a73.143 73.143 0 0185.577 73.143v339.383a36.571 36.571 0 0036.571 36.571z"})]);pr.displayName="WordIcon";const We=()=>{const n=ln();return y(()=>n.value.metaLocales)};var T0=V({name:"AuthorInfo",inheritAttrs:!1,props:{author:{type:Array,required:!0},pure:Boolean},setup(n){const e=We();return()=>n.author.length?p("span",{class:"page-author-info","aria-label":`${e.value.author}${n.pure?"":"🖊"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(er),p("span",n.author.map(t=>t.url?p("a",{class:"page-author-item",href:t.url,target:"_blank",rel:"noopener noreferrer"},t.name):p("span",{class:"page-author-item"},t.name))),p("span",{property:"author",content:n.author.map(t=>t.name).join(", ")})]):null}}),O0=V({name:"CategoryInfo",inheritAttrs:!1,props:{category:{type:Array,required:!0},pure:Boolean},setup(n){const e=me(),t=gn(),a=We(),s=(l,o="")=>{o&&t.value.path!==o&&(l.preventDefault(),e.push(o))};return()=>n.category.length?p("span",{class:"page-category-info","aria-label":`${a.value.category}${n.pure?"":"🌈"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(ar),n.category.map(({name:l,path:o})=>p("span",{class:["page-category-item",{[`category${ks(l,9)}`]:!n.pure,clickable:o}],role:o?"navigation":"",onClick:i=>s(i,o)},l)),p("meta",{property:"articleSection",content:n.category.map(({name:l})=>l).join(",")})]):null}}),I0=V({name:"DateInfo",inheritAttrs:!1,props:{date:{type:Object,default:null},localizedDate:{type:String,default:""},pure:Boolean},setup(n){const e=Yi(),t=We();return()=>n.date?p("span",{class:"page-date-info","aria-label":`${t.value.date}${n.pure?"":"📅"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(tr),p("span",p(rs,()=>n.localizedDate||n.date.toLocaleDateString(e.value))),p("meta",{property:"datePublished",content:n.date.toISOString()||""})]):null}}),P0=V({name:"OriginalInfo",inheritAttrs:!1,props:{isOriginal:Boolean},setup(n){const e=We();return()=>n.isOriginal?p("span",{class:"page-original-info"},e.value.origin):null}}),j0=V({name:"ReadingTimeInfo",inheritAttrs:!1,props:{readingTime:{type:Object,default:()=>null},readingTimeLocale:{type:Object,default:()=>null},pure:Boolean},setup(n){const e=We(),t=y(()=>{if(!n.readingTime)return null;const{minutes:a}=n.readingTime;return a<1?"PT1M":`PT${Math.round(a)}M`});return()=>{var a,s;return(a=n.readingTimeLocale)!=null&&a.time?p("span",{class:"page-reading-time-info","aria-label":`${e.value.readingTime}${n.pure?"":"⌛"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(or),p("span",(s=n.readingTimeLocale)==null?void 0:s.time),p("meta",{property:"timeRequired",content:t.value})]):null}}}),D0=V({name:"TagInfo",inheritAttrs:!1,props:{tag:{type:Array,default:()=>[]},pure:Boolean},setup(n){const e=me(),t=gn(),a=We(),s=(l,o="")=>{o&&t.value.path!==o&&(l.preventDefault(),e.push(o))};return()=>n.tag.length?p("span",{class:"page-tag-info","aria-label":`${a.value.tag}${n.pure?"":"🏷"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(lr),n.tag.map(({name:l,path:o})=>p("span",{class:["page-tag-item",{[`tag${ks(l,9)}`]:!n.pure,clickable:o}],role:o?"navigation":"",onClick:i=>s(i,o)},l)),p("meta",{property:"keywords",content:n.tag.map(({name:l})=>l).join(",")})]):null}}),R0=V({name:"ReadTimeInfo",inheritAttrs:!1,props:{readingTime:{type:Object,default:()=>null},readingTimeLocale:{type:Object,default:()=>null},pure:Boolean},setup(n){const e=We();return()=>{var t,a,s;return(t=n.readingTimeLocale)!=null&&t.words?p("span",{class:"page-word-info","aria-label":`${e.value.words}${n.pure?"":"🔠"}`,...n.pure?{}:{"data-balloon-pos":"up"}},[p(pr),p("span",(a=n.readingTimeLocale)==null?void 0:a.words),p("meta",{property:"wordCount",content:(s=n.readingTime)==null?void 0:s.words})]):null}}}),ir=V({name:"PageInfo",components:{AuthorInfo:T0,CategoryInfo:O0,DateInfo:I0,OriginalInfo:P0,PageViewInfo:()=>null,ReadingTimeInfo:j0,TagInfo:D0,WordInfo:R0},props:{items:{type:[Array,Boolean],default:()=>["Author","Original","Date","PageView","ReadingTime","Category","Tag"]},info:{type:Object,required:!0}},setup(n){const e=it();return()=>n.items?p("div",{class:"page-info"},n.items.map(t=>p(ee(`${t}Info`),{...n.info,pure:e.value}))):null}}),F0=V({name:"PrintButton",setup(){const n=Se(),e=ln();return()=>n.value.print===!1?null:p("button",{type:"button",class:"print-button",title:e.value.metaLocales.print,onClick:()=>{window.print()}},p(sr))}}),cr=V({name:"TOC",props:{items:{type:Array,default:()=>[]},headerDepth:{type:Number,default:2}},slots:Object,setup(n,{slots:e}){const t=Ge(),a=gn(),s=We(),[l,o]=tl(),i=Nn(),c=K("-1.7rem"),r=d=>{var k;(k=i.value)==null||k.scrollTo({top:d,behavior:"smooth"})},u=()=>{if(i.value){const d=document.querySelector(".vp-toc-item.active");d?c.value=`${d.getBoundingClientRect().top-i.value.getBoundingClientRect().top+i.value.scrollTop}px`:c.value="-1.7rem"}else c.value="-1.7rem"};return En(()=>{dn(()=>t.hash,d=>{if(i.value){const k=document.querySelector(`#toc a.toc-link[href$="${d}"]`);if(!k)return;const{top:g,height:_}=i.value.getBoundingClientRect(),{top:b,height:E}=k.getBoundingClientRect();bg+_&&r(i.value.scrollTop+b+E-g-_)}}),dn(()=>t.fullPath,u,{flush:"post",immediate:!0})}),()=>{var _,b;const d=({title:E,level:w,slug:L})=>p(Tn,{to:`#${L}`,class:["vp-toc-link",`level${w}`],onClick:()=>{o()}},()=>E),k=(E,w)=>E.length&&w>0?p("ul",{class:"vp-toc-list"},E.map(L=>{const f=k(L.children,w-1);return[p("li",{class:["vp-toc-item",{active:t.hash===`#${L.slug}`}]},d(L)),f?p("li",f):null]})):null,g=n.items.length?k(n.items,n.headerDepth):a.value.headers?k(a.value.headers,n.headerDepth):null;return g?p("div",{class:"vp-toc-placeholder"},[p("aside",{id:"toc"},[(_=e.before)==null?void 0:_.call(e),p("div",{class:"vp-toc-header",onClick:()=>{o()}},[s.value.toc,p(F0),p("div",{class:["arrow",l.value?"down":"end"]})]),p("div",{class:["vp-toc-wrapper",l.value?"open":""],ref:i},[g,p("div",{class:"vp-toc-marker",style:{top:c.value}})]),(b=e.after)==null?void 0:b.call(e)])]):null}}}),Kl=V({name:"SkipLink",props:{content:{type:String,default:"main-content"}},setup(n){const e=gn(),t=ln(),a=Nn(),s=({target:l})=>{const o=document.querySelector(l.hash);if(o){const i=()=>{o.removeAttribute("tabindex"),o.removeEventListener("blur",i)};o.setAttribute("tabindex","-1"),o.addEventListener("blur",i),o.focus(),window.scrollTo(0,0)}};return En(()=>{dn(()=>e.value.path,()=>a.value.focus())}),()=>[p("span",{ref:a,tabindex:"-1"}),p("a",{href:`#${n.content}`,class:"vp-skip-link sr-only",onClick:s},t.value.routeLocales.skipToContent)]}});let Vs=null,Rt=null;const B0={wait:()=>Vs,pending:()=>{Vs=new Promise(n=>{Rt=n})},resolve:()=>{Rt==null||Rt(),Vs=null,Rt=null}},rr=()=>B0;var ur=V({name:"FadeSlideY",slots:Object,setup(n,{slots:e}){const{resolve:t,pending:a}=rr();return()=>p($e,{name:"fade-slide-y",mode:"out-in",onBeforeEnter:t,onBeforeLeave:a},()=>{var s;return(s=e.default)==null?void 0:s.call(e)})}});const M0=(n,e)=>{const t=n.replace(e,"/").split("/"),a=[];let s=Fl(e);return t.forEach((l,o)=>{o!==t.length-1?(s+=`${l}/`,a.push({link:s,name:l||"Home"})):l!==""&&(s+=l,a.push({link:s,name:l}))}),a},dr=(n,{slots:e})=>{var d,k;const{bgImage:t,bgImageDark:a,bgImageStyle:s,color:l,description:o,image:i,imageDark:c,header:r,features:u=[]}=n;return p("div",{class:"vp-feature-wrapper"},[t?p("div",{class:["vp-feature-bg",{light:a}],style:[{"background-image":`url(${t})`},s]}):null,a?p("div",{class:"vp-feature-bg dark",style:[{"background-image":`url(${a})`},s]}):null,p("div",{class:"vp-feature",style:l?{color:l}:{}},[((d=e.image)==null?void 0:d.call(e,n))||[i?p("img",{class:["vp-feature-image",{light:c}],src:xn(i),alt:""}):null,c?p("img",{class:"vp-feature-image dark",src:xn(c),alt:""}):null],((k=e.info)==null?void 0:k.call(e,n))||[r?p("h2",{class:"vp-feature-header"},r):null,o?p("p",{class:"vp-feature-description",innerHTML:o}):null],u.length?p("div",{class:"vp-features"},u.map(({icon:g,title:_,details:b,link:E})=>{const w=[p("h3",{class:"vp-feature-title"},[p(Vn,{icon:g}),p("span",{innerHTML:_})]),p("p",{class:"vp-feature-details",innerHTML:b})];return E?Zt(E)?p("a",{class:"vp-feature-item link",href:E,"aria-label":_,target:"_blank"},w):p(Tn,{class:"vp-feature-item link",to:E,"aria-label":_},()=>w):p("div",{class:"vp-feature-item"},w)})):null])])};dr.displayName="FeaturePanel";var Ap=dr,V0=V({name:"HeroInfo",slots:Object,setup(n,{slots:e}){const t=fn(),a=va(),s=y(()=>t.value.heroFullScreen??!1),l=y(()=>{const{heroText:r,tagline:u}=t.value;return{text:r??a.value.title??"Hello",tagline:u??a.value.description??"",isFullScreen:s.value}}),o=y(()=>{const{heroText:r,heroImage:u,heroImageDark:d,heroAlt:k,heroImageStyle:g}=t.value;return{image:u?xn(u):null,imageDark:d?xn(d):null,style:g,alt:k||r||"",isFullScreen:s.value}}),i=y(()=>{const{bgImage:r,bgImageDark:u,bgImageStyle:d}=t.value;return{image:An(r)?xn(r):null,imageDark:An(u)?xn(u):null,bgStyle:d,isFullScreen:s.value}}),c=y(()=>t.value.actions??[]);return()=>{var r,u,d;return p("header",{class:["vp-hero-info-wrapper",{fullscreen:s.value}]},[((r=e.heroBg)==null?void 0:r.call(e,i.value))||[i.value.image?p("div",{class:["vp-hero-mask",{light:i.value.imageDark}],style:[{"background-image":`url(${i.value.image})`},i.value.bgStyle]}):null,i.value.imageDark?p("div",{class:"vp-hero-mask dark",style:[{"background-image":`url(${i.value.imageDark})`},i.value.bgStyle]}):null],p("div",{class:"vp-hero-info"},[((u=e.heroImage)==null?void 0:u.call(e,o.value))||p(rn,{appear:!0,type:"group"},()=>[o.value.image?p("img",{key:"light",class:["vp-hero-image",{light:o.value.imageDark}],style:o.value.style,src:o.value.image,alt:o.value.alt}):null,o.value.imageDark?p("img",{key:"dark",class:"vp-hero-image dark",style:o.value.style,src:o.value.imageDark,alt:o.value.alt}):null]),((d=e.heroInfo)==null?void 0:d.call(e,l.value))??p("div",{class:"vp-hero-infos"},[l.value.text?p(rn,{appear:!0,delay:.04},()=>p("h1",{id:"main-title"},l.value.text)):null,l.value.tagline?p(rn,{appear:!0,delay:.08},()=>p("p",{id:"main-description",innerHTML:l.value.tagline})):null,c.value.length?p(rn,{appear:!0,delay:.12},()=>p("p",{class:"vp-hero-actions"},c.value.map(k=>p(Un,{class:["vp-hero-action",k.type||"default"],config:k,noExternalLinkIcon:!0},k.icon?{before:()=>p(Vn,{icon:k.icon})}:{})))):null])])])}}});const hr=(n,{slots:e})=>{var k,g,_;const{bgImage:t,bgImageDark:a,bgImageStyle:s,color:l,description:o,image:i,imageDark:c,header:r,highlights:u=[],type:d="un-order"}=n;return p("div",{class:"vp-highlight-wrapper",style:l?{color:l}:{}},[t?p("div",{class:["vp-highlight-bg",{light:a}],style:[{"background-image":`url(${t})`},s]}):null,a?p("div",{class:"vp-highlight-bg dark",style:[{"background-image":`url(${a})`},s]}):null,p("div",{class:"vp-highlight"},[((k=e.image)==null?void 0:k.call(e,n))||[i?p("img",{class:["vp-highlight-image",{light:c}],src:xn(i),alt:""}):null,c?p("img",{class:"vp-highlight-image dark",src:xn(c),alt:""}):null],((g=e.info)==null?void 0:g.call(e,n))||[p("div",{class:"vp-highlight-info-wrapper"},p("div",{class:"vp-highlight-info"},[r?p("h2",{class:"vp-highlight-header",innerHTML:r}):null,o?p("p",{class:"vp-highlight-description",innerHTML:o}):null,((_=e.highlights)==null?void 0:_.call(e,u))||p(d==="order"?"ol":d==="no-order"?"dl":"ul",{class:"vp-highlights"},u.map(({icon:b,title:E,details:w,link:L})=>{const f=[p(d==="no-order"?"dt":"h3",{class:"vp-highlight-title"},[b?p(Vn,{class:"vp-highlight-icon",icon:b}):null,p("span",{innerHTML:E})]),w?p(d==="no-order"?"dd":"p",{class:"vp-highlight-details",innerHTML:w}):null];return p(d==="no-order"?"div":"li",{class:["vp-highlight-item-wrapper",{link:L}]},L?Zt(L)?p("a",{class:"vp-highlight-item link",href:L,"aria-label":E,target:"_blank"},f):p(Tn,{class:"vp-highlight-item link",to:L,"aria-label":E},()=>f):p("div",{class:"vp-highlight-item"},f))}))]))]])])};hr.displayName="HighlightPanel";var q0=hr,N0=V({name:"HomePage",slots:Object,setup(n,{slots:e}){const t=it(),a=fn(),s=y(()=>{const{features:o}=a.value;return el(o)?o:null}),l=y(()=>{const{highlights:o}=a.value;return el(o)?o:null});return()=>{var o,i,c,r;return p("main",{id:"main-content",class:["vp-project-home ",{pure:t.value}],"aria-labelledby":a.value.heroText===null?"":"main-title"},[(o=e.top)==null?void 0:o.call(e),p(V0),((i=l.value)==null?void 0:i.map(u=>"features"in u?p(Ap,u):p(q0,u)))||(s.value?p(rn,{appear:!0,delay:.24},()=>p(Ap,{features:s.value})):null),(c=e.center)==null?void 0:c.call(e),p(rn,{appear:!0,delay:.32},()=>p(Wl)),(r=e.bottom)==null?void 0:r.call(e)])}}}),H0=V({name:"BreadCrumb",setup(){const n=gn(),e=fe(),t=fn(),a=ln(),s=Nn([]),l=y(()=>(t.value.breadcrumb||t.value.breadcrumb!==!1&&a.value.breadcrumb!==!1)&&s.value.length>1),o=y(()=>t.value.breadcrumbIcon||t.value.breadcrumbIcon!==!1&&a.value.breadcrumbIcon!==!1),i=()=>{const c=M0(n.value.path,e.value).map(({link:r,name:u})=>{const{path:d,meta:k,notFound:g}=It(r);return g?null:{title:k[vn.shortTitle]||k[vn.title]||u,icon:k[vn.icon],path:d}}).filter(r=>r!==null);c.length>1&&(s.value=c)};return En(()=>{dn(()=>n.value.path,i,{immediate:!0})}),()=>p("nav",{class:["vp-breadcrumb",{disable:!l.value}]},l.value?p("ol",{vocab:"https://schema.org/",typeof:"BreadcrumbList"},s.value.map((c,r)=>p("li",{class:{"is-active":s.value.length-1===r},property:"itemListElement",typeof:"ListItem"},[p(Tn,{to:c.path,property:"item",typeof:"WebPage"},()=>[o.value?p(Vn,{icon:c.icon}):null,p("span",{property:"name"},c.title||"Unknown")]),p("meta",{property:"position",content:r+1})]))):[])}});const Lp=n=>n===!1||ma(n)?n:An(n)?yt(n,!0):null,pl=(n,e,t)=>{const a=n.findIndex(s=>s.link===e);if(a!==-1){const s=n[a+t];return s!=null&&s.link?s:null}for(const s of n)if("children"in s){const l=pl(s.children,e,t);if(l)return l}return null};var $0=V({name:"PageNav",setup(){const n=ln(),e=fn(),t=Jl(),a=gn(),s=ba(),l=y(()=>{const i=Lp(e.value.prev);return i===!1?null:i||(n.value.prevLink===!1?null:pl(t.value,a.value.path,-1))}),o=y(()=>{const i=Lp(e.value.next);return i===!1?null:i||(n.value.nextLink===!1?null:pl(t.value,a.value.path,1))});return Fn("keydown",i=>{i.altKey&&(i.key==="ArrowRight"?o.value&&(s(o.value.link),i.preventDefault()):i.key==="ArrowLeft"&&l.value&&(s(l.value.link),i.preventDefault()))}),()=>l.value||o.value?p("nav",{class:"vp-page-nav"},[l.value?p(Un,{class:"prev",config:l.value},()=>{var i,c;return[p("div",{class:"hint"},[p("span",{class:"arrow start"}),n.value.metaLocales.prev]),p("div",{class:"link"},[p(Vn,{icon:(i=l.value)==null?void 0:i.icon}),(c=l.value)==null?void 0:c.text])]}):null,o.value?p(Un,{class:"next",config:o.value},()=>{var i,c;return[p("div",{class:"hint"},[n.value.metaLocales.next,p("span",{class:"arrow end"})]),p("div",{class:"link"},[(i=o.value)==null?void 0:i.text,p(Vn,{icon:(c=o.value)==null?void 0:c.icon})])]}):null]):null}});const z0={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},U0=({docsRepo:n,docsBranch:e,docsDir:t,filePathRelative:a,editLinkPattern:s})=>{if(!a)return null;const l=Cc(n);let o;return s?o=s:l!==null&&(o=z0[l]),o?o.replace(/:repo/u,Je(n)?n:`https://github.com/${n}`).replace(/:branch/u,e).replace(/:path/u,Vi(`${Fl(t)}/${a}`)):null},J0=()=>{const n=ln(),e=gn(),t=fn();return y(()=>{const{repo:a,docsRepo:s=a,docsBranch:l="main",docsDir:o="",editLink:i,editLinkPattern:c=""}=n.value;if(!(t.value.editLink??i??!0)||!s)return null;const r=U0({docsRepo:s,docsBranch:l,docsDir:o,editLinkPattern:c,filePathRelative:e.value.filePathRelative});return r?{text:n.value.metaLocales.editLink,link:r}:null})},G0=()=>{const n=va(),e=ln(),t=gn(),a=fn();return y(()=>{var s,l;return!(a.value.lastUpdated??e.value.lastUpdated??!0)||!((s=t.value.git)!=null&&s.updatedTime)?null:new Date((l=t.value.git)==null?void 0:l.updatedTime).toLocaleString(n.value.lang)})},W0=()=>{const n=ln(),e=gn(),t=fn();return y(()=>{var a;return t.value.contributors??n.value.contributors??!0?((a=e.value.git)==null?void 0:a.contributors)??null:null})};var K0=V({name:"PageTitle",setup(){const n=gn(),e=fn(),t=ln(),{info:a,items:s}=e0();return()=>p("div",{class:"vp-page-title"},[p("h1",[t.value.titleIcon===!1?null:p(Vn,{icon:e.value.icon}),n.value.title]),p(ir,{info:a.value,...s.value===null?{}:{items:s.value}}),p("hr")])}});const kr=()=>p(un,{name:"edit"},()=>[p("path",{d:"M430.818 653.65a60.46 60.46 0 0 1-50.96-93.281l71.69-114.012 7.773-10.365L816.038 80.138A60.46 60.46 0 0 1 859.225 62a60.46 60.46 0 0 1 43.186 18.138l43.186 43.186a60.46 60.46 0 0 1 0 86.373L588.879 565.55l-8.637 8.637-117.466 68.234a60.46 60.46 0 0 1-31.958 11.229z"}),p("path",{d:"M728.802 962H252.891A190.883 190.883 0 0 1 62.008 771.98V296.934a190.883 190.883 0 0 1 190.883-192.61h267.754a60.46 60.46 0 0 1 0 120.92H252.891a69.962 69.962 0 0 0-69.098 69.099V771.98a69.962 69.962 0 0 0 69.098 69.098h475.911A69.962 69.962 0 0 0 797.9 771.98V503.363a60.46 60.46 0 1 1 120.922 0V771.98A190.883 190.883 0 0 1 728.802 962z"})]);kr.displayName="EditIcon";var X0=V({name:"PageMeta",setup(){const n=ln(),e=J0(),t=G0(),a=W0();return()=>{const{metaLocales:s}=n.value;return p("footer",{class:"page-meta"},[e.value?p("div",{class:"meta-item edit-link"},p(Un,{class:"label",config:e.value},{before:()=>p(kr)})):null,p("div",{class:"meta-item git-info"},[t.value?p("div",{class:"update-time"},[p("span",{class:"label"},`${s.lastUpdated}: `),p(rs,()=>p("span",{class:"info"},t.value))]):null,a.value&&a.value.length?p("div",{class:"contributors"},[p("span",{class:"label"},`${s.contributors}: `),a.value.map(({email:l,name:o},i)=>[p("span",{class:"contributor",title:`email: ${l}`},o),i!==a.value.length-1?",":""])]):null])])}}}),Y0=V({name:"NormalPage",slots:Object,setup(n,{slots:e}){const t=fn(),{isDarkmode:a}=Ea(),s=ln(),l=y(()=>t.value.toc||t.value.toc!==!1&&s.value.toc!==!1);return()=>p("main",{id:"main-content",class:"vp-page"},p(xe("LocalEncrypt")?ee("LocalEncrypt"):yc,()=>{var o,i,c,r;return[(o=e.top)==null?void 0:o.call(e),t.value.cover?p("div",{class:"page-cover"},p("img",{src:xn(t.value.cover),alt:"","no-view":""})):null,p(H0),p(K0),l.value?p(cr,{headerDepth:t.value.headerDepth??s.value.headerDepth??2},{before:()=>{var u;return(u=e.tocBefore)==null?void 0:u.call(e)},after:()=>{var u;return(u=e.tocAfter)==null?void 0:u.call(e)}}):null,(i=e.contentBefore)==null?void 0:i.call(e),p(Wl),(c=e.contentAfter)==null?void 0:c.call(e),p(X0),p($0),xe("CommentService")?p(ee("CommentService"),{darkmode:a.value}):null,(r=e.bottom)==null?void 0:r.call(e)]}))}}),Q0=V({name:"Layout",slots:Object,setup(n,{slots:e}){const t=Se(),a=ln(),s=gn(),l=fn(),{isMobile:o}=wa(),i=y(()=>{var c,r;return((c=a.value.blog)==null?void 0:c.sidebarDisplay)||((r=t.value.blog)==null?void 0:r.sidebarDisplay)||"mobile"});return()=>[p(Kl),p(Gl,{},{default:()=>{var c;return((c=e.default)==null?void 0:c.call(e))||(l.value.home?p(N0):p(ur,()=>p(Y0,{key:s.value.path},{top:()=>{var r;return(r=e.top)==null?void 0:r.call(e)},bottom:()=>{var r;return(r=e.bottom)==null?void 0:r.call(e)},contentBefore:()=>{var r;return(r=e.contentBefore)==null?void 0:r.call(e)},contentAfter:()=>{var r;return(r=e.contentAfter)==null?void 0:r.call(e)},tocBefore:()=>{var r;return(r=e.tocBefore)==null?void 0:r.call(e)},tocAfter:()=>{var r;return(r=e.tocAfter)==null?void 0:r.call(e)}})))},...i.value==="none"?{}:{navScreenBottom:()=>p(ee("BloggerInfo"))},...!o.value&&i.value==="always"?{sidebar:()=>p(ee("BloggerInfo"))}:{}})]}}),Z0=V({name:"NotFoundHint",setup(){const n=ln(),e=()=>{const t=n.value.routeLocales.notFoundMsg;return t[Math.floor(Math.random()*t.length)]};return()=>p("div",{class:"not-found-hint"},[p("p",{class:"error-code"},"404"),p("h1",{class:"error-title"},n.value.routeLocales.notFoundTitle),p("p",{class:"error-hint"},e())])}}),ng=V({name:"NotFound",slots:Object,setup(n,{slots:e}){const t=me(),a=fe(),s=ln();return()=>[p(Kl),p(Gl,{noSidebar:!0},()=>{var l;return p("main",{id:"main-content",class:"vp-page not-found"},((l=e.default)==null?void 0:l.call(e))||[p(Z0),p("div",{class:"actions"},[p("button",{type:"button",class:"action-button",onClick:()=>{window.history.go(-1)}},s.value.routeLocales.back),p("button",{type:"button",class:"action-button",onClick:()=>{t.push(s.value.home??a.value)}},s.value.routeLocales.home)])])})]}});const eg={Baidu:'',Weibo:'',Zhihu:''},tg={category:{"/":{path:"/category/",map:{设计模式:{path:"/category/设计模式/",indexes:[0,1,2,3,4]},使用指南:{path:"/category/使用指南/",indexes:[5,6,7,8,9]},vue知识点:{path:"/category/vue知识点/",indexes:[10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]},js基础:{path:"/category/js基础/",indexes:[33,34,35,36]},npm知识点:{path:"/category/npm知识点/",indexes:[37]},问题定位:{path:"/category/问题定位/",indexes:[38,39]},vite:{path:"/category/vite/",indexes:[40]},集合:{path:"/category/集合/",indexes:[41]},jdk:{path:"/category/jdk/",indexes:[42]},线程池:{path:"/category/线程池/",indexes:[43,44]},多线程:{path:"/category/多线程/",indexes:[43,44]},java:{path:"/category/java/",indexes:[45,46,47,48]},对象锁:{path:"/category/对象锁/",indexes:[49]},java基础:{path:"/category/java基础/",indexes:[50,51,52,53,54]},jvm:{path:"/category/jvm/",indexes:[45,46]},电子书:{path:"/category/电子书/",indexes:[55]},数据库:{path:"/category/数据库/",indexes:[56,57,58]},docker:{path:"/category/docker/",indexes:[59,60]},运维:{path:"/category/运维/",indexes:[61,62]},"git 操作":{path:"/category/git-操作/",indexes:[63,64,65,66,67,68,69,70,71,72,73]},必会:{path:"/category/必会/",indexes:[73]},linux:{path:"/category/linux/",indexes:[74]},markdown:{path:"/category/markdown/",indexes:[75]},公司业务:{path:"/category/公司业务/",indexes:[76]},小组分享:{path:"/category/小组分享/",indexes:[77,78,79]},web:{path:"/category/web/",indexes:[80,81,82,83]},oauth:{path:"/category/oauth/",indexes:[84]},框架:{path:"/category/框架/",indexes:[85]},security:{path:"/category/security/",indexes:[86,87]},Security:{path:"/category/security/",indexes:[88,89,90,91]},OAuth:{path:"/category/oauth/",indexes:[88]},Spring:{path:"/category/spring/",indexes:[92,93,94]},"open-source":{path:"/category/open-source/",indexes:[95]},maven:{path:"/category/maven/",indexes:[96,97]}}}},tag:{"/":{path:"/tag/",map:{禁用:{path:"/tag/禁用/",indexes:[7]},文章加密:{path:"/tag/文章加密/",indexes:[8]},Markdown:{path:"/tag/markdown/",indexes:[9]},页面配置:{path:"/tag/页面配置/",indexes:[5]},使用指南:{path:"/tag/使用指南/",indexes:[5]},你所不了解的JavaScript:{path:"/tag/你所不了解的javascript/",indexes:[98,99]},必会:{path:"/tag/必会/",indexes:[10,11,100,12,101,13,14,37,15,16,17,18,19,20,21,63,64,65,33,22,23,24,25,26,34,27,28,35,36,50,29,30,66,67,68,69,70,71,72,31,51,32]},"vue中的 TypeScript":{path:"/tag/vue中的-typescript/",indexes:[11,13,18,19,20,21]},vite:{path:"/tag/vite/",indexes:[40]},ts:{path:"/tag/ts/",indexes:[102]},vue3:{path:"/tag/vue3/",indexes:[102]},集合:{path:"/tag/集合/",indexes:[41]},"-- 并发":{path:"/tag/---并发/",indexes:[103]},多线程:{path:"/tag/多线程/",indexes:[43,44]},线程池:{path:"/tag/线程池/",indexes:[43,44]},oauth:{path:"/tag/oauth/",indexes:[84,104,105]},sso:{path:"/tag/sso/",indexes:[104,105]},部署搭建:{path:"/tag/部署搭建/",indexes:[106,48]},字节码:{path:"/tag/字节码/",indexes:[46]},反汇编:{path:"/tag/反汇编/",indexes:[46]},mysql:{path:"/tag/mysql/",indexes:[107]},数据库:{path:"/tag/数据库/",indexes:[58]},数据结构:{path:"/tag/数据结构/",indexes:[108]},二叉树:{path:"/tag/二叉树/",indexes:[108]},运维:{path:"/tag/运维/",indexes:[109,110]},工具使用:{path:"/tag/工具使用/",indexes:[111]},linux:{path:"/tag/linux/",indexes:[74]},markdown:{path:"/tag/markdown/",indexes:[75]},web:{path:"/tag/web/",indexes:[80,81,82,83]},springboot:{path:"/tag/springboot/",indexes:[81]},框架:{path:"/tag/框架/",indexes:[85]},日志:{path:"/tag/日志/",indexes:[112]}}}}},gr=["/designpattern/%E8%A3%85%E9%A5%B0%E6%A8%A1%E5%BC%8F.html","/designpattern/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F.html","/designpattern/%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F.html","/designpattern/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F.html","/designpattern/%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F.html","/guide/page.html","/guide/","/guide/disable.html","/guide/encrypt.html","/guide/markdown.html","/frontweb/es5/asyncError.html","/frontweb/typeScript/axios.html","/frontweb/es6/useModule.html","/frontweb/typeScript/action-usage.html","/frontweb/es6/useNpm.html","/frontweb/es6/useYarn.html","/frontweb/vue/vue-router1.html","/frontweb/vue/vue-router2.html","/frontweb/typeScript/","/frontweb/typeScript/basic-usage.html","/frontweb/typeScript/fanType.html","/frontweb/typeScript/tsAndvue3.html","/frontweb/vue/vue-Direactive.html","/frontweb/vue/vue-authority.html","/frontweb/vue/vue-in-action.html","/frontweb/vue/vue3LifeTime.html","/frontweb/vue/vueExtend.html","/frontweb/es5/crossDomain.html","/frontweb/es5/crossDomain2.html","/frontweb/vue/eventBus.html","/frontweb/vue/vue-pic.html","/frontweb/vue/vue-nextTick.html","/frontweb/es5/aboutAsync.html","/frontweb/es5/aboutEvent.html","/frontweb/es5/aboutThis.html","/frontweb/es5/lazyLoad.html","/frontweb/es5/throttle.html","/frontweb/es6/usePnpm.html","/frontweb/nodejs/","/java/advance/Arthas.html","/frontweb/vite/","/java/advance/Collection.html","/java/advance/CompileJdk11.html","/java/advance/Future.html","/java/advance/ThreadPool.html","/java/jvm/SetObjectNull.html","/java/jvm/StringAdd.html","/java/advance/ProxyInJava.html","/java/advance/MysqlMasterSlave.html","/java/advance/Synchronized.html","/java/base/CustomLRU.html","/java/base/ConstantPool.html","/java/base/IntegerConstantPool.html","/java/base/Serialization.html","/java/base/String.html","/other/books/ebooks.html","/other/database/CPUOverLoad.html","/other/database/SQLOptimization.html","/other/essay/2022-04-12.html","/other/docker/ServiceInstall.html","/other/docker/Docker.html","/other/essay/Jenkins.html","/other/linux/","/other/git/branch01.html","/other/git/branch02.html","/other/git/rebaseAndMerge.html","/other/git/GitCommands.html","/other/git/","/other/git/fatal.html","/other/git/gitConflict.html","/other/git/gitRebase.html","/other/git/gitwork.html","/other/git/mergeBranch.html","/other/git/stash.html","/other/linux/Curl.html","/other/markdown/","/other/sono/SUC.html","/other/training/draw.html","/other/training/SSO.html","/other/training/CloudServiceTraining.html","/other/web/Jwt.html","/other/web/BuildWebProject.html","/other/web/Restful.html","/other/web/","/other/web/OAUTH_LOGIN.html","/java/framework/mybatis/mybatis.html","/java/framework/security/Authorization.html","/java/framework/security/SecurityFilterChain.html","/java/framework/security/OAuth2Authentication.html","/java/framework/security/Session.html","/java/framework/security/CustomAuthenticationProvider.html","/java/framework/security/OncePerRequestFilter.html","/java/framework/springboot/CollectionInject.html","/java/framework/spring/CircularDependency.html","/java/framework/spring/Annotation.html","/java/framework/spring/SpringIOC.html","/java/other/maven/build%E6%A0%87%E7%AD%BE.html","/java/other/maven/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html","/frontweb/es5/","/frontweb/vue/","/other/web/Http.html","/other/web/Cookie.html","/frontweb/vue/vue3Emit.html","/java/advance/Concurrent.html","/java/advance/ImplementSameInterface.html","/java/framework/security/SSO.html","/other/essay/DeployGithubPage.html","/other/database/MysqlCollate.html","/other/essay/BTree.html","/other/essay/CDN.html","/other/essay/CloudService.html","/other/essay/ChromeDevTools.html","/java/other/log/logback.html","/java/framework/spring/SpringCache.html","/java/framework/spring/SpringAOP.html","/java/advance/ParentDelegationClassLoader.html","/other/pve/","/other/pve/ddns%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6.html","/other/pve/firewall.html","/other/windows/WSL.html","/java/framework/security/DelegatingFilterProxy.html","/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html","/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html","/frontweb/nodejs/%E5%AE%89%E8%A3%85%E9%97%AE%E9%A2%98.html","/other/essay/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B.html","/other/essay/elasticSearch%E6%93%8D%E4%BD%9C.html","/other/essay/elk%E9%83%A8%E7%BD%B2.html","/frontweb/vue/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4%E7%9A%84%E5%AE%9E%E8%B7%B5.html","/frontweb/vue/elementui%E8%A1%A8%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C.html","/other/essay/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%BC%95%E5%AF%BC.html","/myserver/%E6%97%81%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3.html","/other/linux/%E6%88%AA%E5%9B%BE.html","/myserver/x86_openwrt.html","/other/essay/windows%E4%B8%8B%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C.html","/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html","/myserver/","/myserver/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE.html","/myserver/%E8%87%AA%E5%BB%BAnas.html","/other/software/%E7%A0%B4%E8%A7%A3%E8%BD%AF%E4%BB%B6.html","/other/linux/ShareBetweenWindowsAndLinux.html","/java/framework/springboot/%E9%83%A8%E7%BD%B2.html","/frontweb/es6/promise.html","/other/hardware/CPU.html","/other/linux/firewall.html","/frontweb/es6/js%E4%B8%AD%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.html","/java/framework/spring/SpringMVC.html","/java/framework/springcloud/SpringCloudGateway.html","/java/framework/security/PreAuthorize.html","/designpattern/","/java/other/locateproblem/%E5%90%84%E7%A7%8D%E9%97%AE%E9%A2%98.html","/java/framework/security/spring-security-oauth2-authorization-server.html","/java/framework/springboot/Swagger.html","/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html","/other/web/RefreshToken.html","/java/framework/spring/Validator.html","/java/other/locateproblem/Undertow.xxxNotFount.html","/other/database/Recurse.html","/other/git/BatchDeleteGitHubRepo.html","/java/framework/spring/SpringSourceAnalize.html","/java/advance/IO-model1.html","/java/jvm/volatile.html","/java/jvm/ClassLoader.html","/java/jvm/MemoryModel.html","/java/jvm/ObjectReference.html","/other/computerprinciple/TCP-IP.html","/java/framework/springboot/Http2.html","/other/linux/MountDisk.html","/cpp/other/1.html","/cpp/other/2.html","/cpp/study/","/java/advance/NativeMethod.html","/java/advance/IO-Model.html","/java/framework/security/CustomTokenAuthentication.html","/other/oauth2/01.html","/other/oauth2/02.html","/other/oauth2/03.html","/other/oauth2/04.html","/other/oauth2/05.html","/other/oauth2/06.html","/other/oauth2/07.html","/other/oauth2/08.html","/other/oauth2/09.html","/other/oauth2/10.html","/other/oauth2/11.html","/other/oauth2/12.html","/other/oauth2/13.html","/other/oauth2/14.html","/other/oauth2/","/other/rabbitmq/","/java/framework/security/CustomLoginPage.html","/java/framework/security/","/java/other/gradle/","/java/other/gradle/wrapper.html","/other/linux/InstallMysqlWithDocker.html","/java/framework/security/note.html","/java/other/maven/import.html","/other/linux/Wifi.html","/other/linux/MultiNetworkCard.html","/java/other/locateproblem/TooManyOpenFiles.html","/java/other/JdkVersion.html","/other/git/proxy.html","/other/linux/CentOS.html","/java/other/","/java/advance/io.html","/other/","/other/books/","/slide.html","/java/advance/ThreadLocal.html","/other/git/reset.html","/other/tools/Idea.html","/java/framework/mybatis/MybatisPlusDataSource.html","/java/other/locateproblem/","/java/other/maven/","/java/other/maven/multiModule.html","/java/other/maven/problem.html","/other/linux/wsl.html","/other/tools/SoftWare.html","/other/tools/VsCode.html","/other/training/","/other/linux/Samba.html","/other/database/MysqlNote.html","/other/database/MysqlRemoteConnect.html","/other/linux/TcpDump.html","/other/database/","/java/jvm/NewObject.html","/other/linux/Manjaro.html","/other/tools/","/java/framework/springboot/AOPLog.html","/other/distributeservice/Nacos.html","/other/distributeservice/","/other/essay/","/other/linux/CommonUsedCMD.html","/frontweb/es6/","/other/essay/TyporaPicgo.html","/java/framework/spring/BeanPostProcessor.html","/java/framework/springboot/SpringBootAutoConfiguration.html","/other/database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD.html","/frontweb/","/java/","/java/advance/","/java/base/","/java/jvm/","/java/framework/mybatis/","/java/framework/spring/OncePerRequestFilter.html","/java/framework/spring/","/java/framework/springboot/","/java/framework/spring/SpringExtensionPoint.html","/java/framework/spring/DesignPatternInSpring.html","/other/distributeservice/DistributeLock.html"],il=Nn(tg);ze(il);const mr=n=>{const e=gn(),t=fn(),a=fe();return y(()=>{var i;const s=n??((i=t.value.blog)==null?void 0:i.key)??"";if(!s)return console.warn("useBlogCategory: key not found"),{path:"/",map:{}};if(!il.value[s])throw new Error(`useBlogCategory: key ${s} is invalid`);const l=il.value[s][a.value],o={path:l.path,map:{}};for(const c in l.map){const r=l.map[c];o.map[c]={path:r.path,items:[]};for(const u of r.indexes){const{path:d,meta:k}=It(gr[u]);o.map[c].items.push({path:d,info:k})}e.value.path===r.path&&(o.currentItems=o.map[c].items)}return o})},ag={article:{"/":{path:"/article/",indexes:[113,114,108,109,106,45,46,5,115,47,116,117,118,119,120,121,122,38,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,96,97,0,1,147,112,148,2,3,4,149,150,151,152,153,154,155,84,102,156,157,103,107,158,159,160,49,161,162,163,76,164,165,166,167,168,169,42,170,104,88,105,89,92,171,93,90,172,94,86,59,77,10,11,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,100,12,101,80,13,191,192,14,37,15,60,78,16,17,193,194,195,196,197,198,199,200,74,201,202,203,18,19,20,21,63,64,204,205,206,6,7,8,9,207,208,209,210,211,212,213,214,65,55,215,216,56,33,217,81,22,79,218,75,82,23,24,25,26,219,111,110,220,221,222,39,223,57,98,34,27,28,35,61,43,58,44,36,99,50,224,225,226,227,228,229,230,231,62,83,29,30,66,67,68,69,70,71,72,73,31,51,87,95,32,232,40,91,233,85,234,235,48,52,53,54,41,236,237,238,239,240,241,242,243,244,245,246,247,248]}},star:{"/":{path:"/star/",indexes:[5]}},timeline:{"/":{path:"/timeline/",indexes:[116,117,118,119,120,121,122,38,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,96,97,0,1,147,112,148,2,3,4,149,150,151,152,153,154,155,84,102,156,157,103,107,158,159,160,49,161,162,163,76,164,165,166,167,168,169,42,170,104,88,105,89,92,171,93,90,172,94,86,59,77,10,11,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,100,12,101,80,13,191,192,14,37,15,60,78,16,17,193,194,195,196,197,198,199,200,74,201,202,203,18,19,20,21,63,64,204,205,206,6,7,8,9,207,208,209,210,211,212,213,214,65,55,215,216,56,33,217,81,22,79,218,75,82,23,24,25,26,219,111,110,220,221,222,39,223,57,98,34,27,28,35,61,43,58,115,44,36,99,50,224,225,226,227,228,229,230,231,62,83,106,47,45,46,29,30,66,67,68,69,70,71,72,73,31,51,87,95,32,232,40,108,91,233,109,5,85,234,235,113,48,52,53,54,41,236,237,238,239,240,241,242,243,244,245,246,247,114,248]}}},cl=Nn(ag);ze(cl);const gs=n=>{const e=fn(),t=fe();return y(()=>{var o;const a=n??((o=e.value.blog)==null?void 0:o.key)??"";if(!a)return console.warn("useBlogType: key not found"),{path:"/",items:[]};if(!cl.value[a])throw new Error(`useBlogType: key ${n} is invalid`);const s=cl.value[a][t.value],l={path:s.path,items:[]};for(const i of s.indexes){const{path:c,meta:r}=It(gr[i]);l.items.push({path:c,info:r})}return l})},Xl=()=>p(un,{name:"lock"},()=>p("path",{d:"M787.168 952.268H236.832c-30.395 0-55.033-24.638-55.033-55.033V429.45c0-30.395 24.638-55.034 55.033-55.034h82.55V264.35c0-106.38 86.238-192.618 192.618-192.618S704.618 157.97 704.618 264.35v110.066h82.55c30.395 0 55.033 24.639 55.033 55.034v467.785c0 30.395-24.639 55.033-55.033 55.033zM484.483 672.046v115.122h55.034V672.046c31.99-11.373 55.033-41.605 55.033-77.496 0-45.592-36.958-82.55-82.55-82.55s-82.55 36.958-82.55 82.55c0 35.89 23.042 66.123 55.033 77.496zM622.067 264.35c0-60.788-49.28-110.067-110.067-110.067s-110.067 49.28-110.067 110.067v110.066h220.135V264.35z"}));Xl.displayName="LockIcon";var sg=[];const vr=Symbol.for("categoryMap"),xa=()=>{const n=wn(vr);if(!n)throw new Error("useCategoryMap() is called without provider.");return n},lg=()=>{const n=mr("category");ie(vr,n)},Aa=()=>{const n=Se(),e=ln();return y(()=>({...n.value.blog,...e.value.blog}))},fr=Symbol.for("tagMap"),La=()=>{const n=wn(fr);if(!n)throw new Error("useTagMap() is called without provider.");return n},og=()=>{const n=mr("tag");ie(fr,n)},pg=n=>{const e=ln();return y(()=>{const{[vn.author]:t}=n.value;return t?sa(t):t===!1?[]:sa(e.value.author,!1)})},ig=n=>{const e=xa();return y(()=>Ic(n.value[vn.category]).map(t=>({name:t,path:e.value.map[t].path})))},cg=n=>{const e=La();return y(()=>Pc(n.value[vn.tag]).map(t=>({name:t,path:e.value.map[t].path})))},rg=n=>y(()=>{const{[vn.date]:e}=n.value;return ql(e)}),ug=n=>{const e=Ot(n,"info"),t=Aa(),a=pg(e),s=ig(e),l=cg(e),o=rg(e),i=Rc(),c=y(()=>({author:a.value,category:s.value,date:o.value,localizedDate:e.value[vn.localizedDate]||"",tag:l.value,isOriginal:e.value[vn.isOriginal]||!1,readingTime:e.value[vn.readingTime]||null,readingTimeLocale:e.value[vn.readingTime]&&i.value?Dc(e.value[vn.readingTime],i.value):null,pageview:n.path})),r=y(()=>t.value.articleInfo);return{info:c,items:r}},_r=Symbol(""),Sa=()=>{const n=wn(_r);if(!n)throw new Error("useArticles() is called without provider.");return n},dg=()=>{const n=gs("article");ie(_r,n)},yr=Symbol(""),Yl=()=>{const n=wn(yr);if(!n)throw new Error("useStars() is called without provider.");return n},hg=()=>{const n=gs("star");ie(yr,n)},br=Symbol(""),Ql=()=>{const n=wn(br);if(!n)throw new Error("useTimelines() is called without provider.");return n},kg=()=>{const n=gs("timeline"),e=y(()=>{const t=[];return n.value.items.forEach(({info:a,path:s})=>{const l=ql(a[vn.date]);if(l){const o=l.getFullYear(),i=l.getMonth()+1,c=l.getDate();(!t[0]||t[0].year!==o)&&t.unshift({year:o,items:[]}),t[0].items.push({date:`${i}/${c}`,info:a,path:s})}}),{...n.value,config:t.reverse()}});ie(br,e)},gg=()=>{dg(),lg(),hg(),og(),kg()};var mg=V({name:"SocialMedia",setup(){const n=Aa(),e=it(),t=y(()=>{const a=n.value.medias;return a?ot(a).map(([s,l])=>({name:s,icon:eg[s],url:l})):[]});return()=>t.value.length?p("div",{class:"vp-social-medias"},t.value.map(({name:a,icon:s,url:l})=>p("a",{class:"vp-social-media",href:l,rel:"noopener noreferrer",target:"_blank","aria-label":a,...e.value?{}:{"data-balloon-pos":"up"},innerHTML:s}))):null}}),Zl=V({name:"BloggerInfo",setup(){const n=Aa(),e=va(),t=ln(),a=Sa(),s=xa(),l=La(),o=Ql(),i=ba(),c=y(()=>{var k;return n.value.name||((k=sa(t.value.author)[0])==null?void 0:k.name)||e.value.title}),r=y(()=>n.value.avatar||t.value.logo),u=y(()=>t.value.blogLocales),d=y(()=>n.value.intro);return()=>{const{article:k,category:g,tag:_,timeline:b}=u.value,E=[[a.value.path,a.value.items.length,k],[s.value.path,re(s.value.map).length,g],[l.value.path,re(l.value.map).length,_],[o.value.path,o.value.items.length,b]];return p("div",{class:"vp-blogger-info",vocab:"https://schema.org/",typeof:"Person"},[p("div",{class:"vp-blogger",...d.value?{style:{cursor:"pointer"},"aria-label":u.value.intro,"data-balloon-pos":"down",role:"link",onClick:()=>i(d.value)}:{}},[r.value?p("img",{class:["vp-blogger-avatar",{round:n.value.roundAvatar}],src:xn(r.value),property:"image",alt:"Blogger Avatar",loading:"lazy"}):null,c.value?p("div",{class:"vp-blogger-name",property:"name"},c.value):null,n.value.description?p("div",{class:"vp-blogger-description",innerHTML:n.value.description}):null,d.value?p("meta",{property:"url",content:xn(d.value)}):null]),p("div",{class:"vp-blog-counts"},E.map(([w,L,f])=>p(Tn,{class:"vp-blog-count",to:w},()=>[p("div",{class:"count"},L),p("div",f)]))),p(mg)])}}});const no=()=>p(un,{name:"category"},()=>p("path",{d:"M148.41 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H148.41c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.311-40.31zM147.556 553.478H429.73c22.263 0 40.311 18.048 40.311 40.31v282.176c0 22.263-18.048 40.312-40.31 40.312H147.555c-22.263 0-40.311-18.049-40.311-40.312V593.79c0-22.263 18.048-40.311 40.31-40.311zM593.927 106.992h282.176c22.263 0 40.31 18.048 40.31 40.31V429.48c0 22.263-18.047 40.31-40.31 40.31H593.927c-22.263 0-40.311-18.047-40.311-40.31V147.302c0-22.263 18.048-40.31 40.31-40.31zM730.22 920.502H623.926c-40.925 0-74.22-33.388-74.22-74.425V623.992c0-41.038 33.387-74.424 74.425-74.424h222.085c41.038 0 74.424 33.226 74.424 74.067v114.233c0 10.244-8.304 18.548-18.547 18.548s-18.548-8.304-18.548-18.548V623.635c0-20.388-16.746-36.974-37.33-36.974H624.13c-20.585 0-37.331 16.747-37.331 37.33v222.086c0 20.585 16.654 37.331 37.126 37.331H730.22c10.243 0 18.547 8.304 18.547 18.547 0 10.244-8.304 18.547-18.547 18.547z"}));no.displayName="CategoryIcon";const eo=()=>p(un,{name:"tag"},()=>p("path",{d:"M939.902 458.563L910.17 144.567c-1.507-16.272-14.465-29.13-30.737-30.737L565.438 84.098h-.402c-3.215 0-5.726 1.005-7.634 2.913l-470.39 470.39a10.004 10.004 0 000 14.164l365.423 365.424c1.909 1.908 4.42 2.913 7.132 2.913s5.223-1.005 7.132-2.913l470.39-470.39c2.01-2.11 3.014-5.023 2.813-8.036zm-240.067-72.121c-35.458 0-64.286-28.828-64.286-64.286s28.828-64.285 64.286-64.285 64.286 28.828 64.286 64.285-28.829 64.286-64.286 64.286z"}));eo.displayName="TagIcon";const to=()=>p(un,{name:"timeline"},()=>p("path",{d:"M511.997 70.568c-243.797 0-441.429 197.633-441.429 441.435 0 243.797 197.632 441.429 441.43 441.429S953.431 755.8 953.431 512.002c0-243.796-197.637-441.434-441.435-441.434zm150.158 609.093-15.605 15.61c-8.621 8.615-22.596 8.615-31.215 0L472.197 552.126c-4.95-4.944-4.34-14.888-4.34-24.677V247.14c0-12.19 9.882-22.07 22.07-22.07h22.07c12.19 0 22.07 9.882 22.07 22.07v273.218l128.088 128.088c8.62 8.62 8.62 22.595 0 31.215zm0 0"}));to.displayName="TimelineIcon";const wr=()=>p(un,{name:"slides"},()=>p("path",{d:"M896 170.667v426.666a85.333 85.333 0 0 1-85.333 85.334h-256v61.184l192.597 115.584-43.861 73.13-148.736-89.173v95.275h-85.334v-95.318l-148.736 89.216-43.861-73.13 192.597-115.627v-61.141h-256A85.333 85.333 0 0 1 128 597.333V170.667H85.333V85.333h853.334v85.334H896zm-682.667 0v426.666h597.334V170.667H213.333zM426.667 512h-85.334V341.333h85.334V512zm128 0h-85.334V256h85.334v256zm128 0h-85.334V384h85.334v128z"}));wr.displayName="SlideIcon";const Er=()=>p(un,{name:"sticky"},()=>[p("path",{d:"m381.3 733.8l-161.9 118c-5.9 4.5-13.2 6.6-20.1 6.6-8.7 0-17.7-3.4-24.3-10-12.2-12.2-13.9-31.3-3.5-45.2l144.5-195.5-113.6-112.9c-11.1-11.1-13.2-28.4-5.5-42 5.5-8.7 52.1-76.4 155.5-51 1.8 0.3 3.5 0.3 5.6 0.7 4.2 0.3 9 0.7 14.2 1.7 21.9 3.5 60.8-13.9 94.5-42.7 32.3-27.5 53.1-59.4 53.1-81.6 0-5.2 0-10.8-0.3-16-0.7-20.8-2.1-52.8 21.5-76.4 28.1-28.1 72.9-30.6 103.9-5.2 0.6 0.3 1 1 1.7 1.7 16.7 16.3 187.5 187.2 189.3 188.9 14.5 14.6 22.9 34.4 22.9 55.3 0 20.8-8 40.2-22.9 54.8-23.7 23.6-56 22.6-77.1 21.6-4.9 0-10.5-0.4-15.7-0.4-20.8 0-45.8 14.6-70.5 41.3-34.3 37.5-55.5 85.8-53.8 107.7 0.7 6.9 2.1 19.1 2.4 20.8 25 101.4-42.7 147.6-50.7 152.8-13.9 8.4-31.6 6.3-42.7-4.8l-112.1-112.2z"})]);Er.displayName="StickyIcon";const ms=()=>p(un,{name:"article"},()=>p("path",{d:"M853.333 938.667H170.667A42.667 42.667 0 0 1 128 896V128a42.667 42.667 0 0 1 42.667-42.667h682.666A42.667 42.667 0 0 1 896 128v768a42.667 42.667 0 0 1-42.667 42.667zm-42.666-85.334V170.667H213.333v682.666h597.334zM298.667 256h170.666v170.667H298.667V256zm0 256h426.666v85.333H298.667V512zm0 170.667h426.666V768H298.667v-85.333zm256-384h170.666V384H554.667v-85.333z"}));ms.displayName="ArticleIcon";const xr=()=>p(un,{name:"book"},()=>p("path",{d:"M256 853.333h426.667A85.333 85.333 0 0 0 768 768V256a85.333 85.333 0 0 0-85.333-85.333H469.333a42.667 42.667 0 0 1 0-85.334h213.334A170.667 170.667 0 0 1 853.333 256v512a170.667 170.667 0 0 1-170.666 170.667H213.333A42.667 42.667 0 0 1 170.667 896V128a42.667 42.667 0 0 1 42.666-42.667h128A42.667 42.667 0 0 1 384 128v304.256l61.653-41.088a42.667 42.667 0 0 1 47.36 0l61.654 41.045V256A42.667 42.667 0 0 1 640 256v256a42.667 42.667 0 0 1-66.347 35.499l-104.32-69.547-104.32 69.547A42.667 42.667 0 0 1 298.667 512V170.667H256v682.666z"}));xr.displayName="BookIcon";const Ar=()=>p(un,{name:"link"},()=>p("path",{d:"M460.8 584.533c17.067 17.067 17.067 42.667 0 59.734-17.067 17.066-42.667 17.066-59.733 0-85.334-85.334-85.334-217.6 0-302.934L554.667 192C640 110.933 776.533 110.933 857.6 196.267c81.067 81.066 81.067 213.333 0 294.4l-68.267 64c0-34.134-4.266-68.267-17.066-102.4l21.333-21.334c51.2-46.933 55.467-128 4.267-179.2s-128-55.466-179.2-4.266c-4.267 0-4.267 4.266-4.267 4.266L465.067 401.067c-51.2 51.2-51.2 132.266-4.267 183.466m123.733-183.466C601.6 384 627.2 384 644.267 401.067c85.333 85.333 85.333 217.6 0 302.933l-153.6 149.333C405.333 934.4 268.8 934.4 187.733 849.067c-81.066-81.067-81.066-213.334 0-294.4l68.267-64c0 34.133 4.267 72.533 17.067 102.4L251.733 614.4C204.8 665.6 204.8 746.667 256 793.6c51.2 46.933 123.733 46.933 174.933 0l149.334-149.333c51.2-51.2 51.2-128 0-179.2-12.8-17.067-17.067-46.934 4.266-64z"}));Ar.displayName="LinkIcon";const Lr=()=>p(un,{name:"project"},()=>p("path",{d:"M987.456 425.152H864V295.296a36.48 36.48 0 0 0-36.544-36.544h-360l-134.08-128.256A9.344 9.344 0 0 0 327.04 128H36.48A36.48 36.48 0 0 0 0 164.544v676.608a36.48 36.48 0 0 0 36.544 36.544h797.76a36.672 36.672 0 0 0 33.92-22.848L1021.44 475.52a36.48 36.48 0 0 0-33.92-50.304zM82.304 210.304h215.424l136.64 130.752h347.328v84.096H198.848A36.672 36.672 0 0 0 164.928 448L82.304 652.8V210.304zM808.32 795.456H108.544l118.08-292.608h699.904L808.32 795.52z"}));Lr.displayName="ProjectIcon";const Sr=()=>p(un,{name:"friend"},()=>p("path",{d:"M860.16 213.333A268.373 268.373 0 0 0 512 186.027a267.52 267.52 0 0 0-348.16 404.48L428.8 855.893a118.613 118.613 0 0 0 166.4 0l264.96-265.386a267.52 267.52 0 0 0 0-377.174zM800 531.627l-264.96 264.96a32.427 32.427 0 0 1-46.08 0L224 530.347a183.04 183.04 0 0 1 0-256 182.187 182.187 0 0 1 256 0 42.667 42.667 0 0 0 60.587 0 182.187 182.187 0 0 1 256 0 183.04 183.04 0 0 1 3.413 256z"}));Sr.displayName="FriendIcon";const rl=()=>p(un,{name:"slide-down"},()=>p("path",{d:"M108.775 312.23c13.553 0 27.106 3.734 39.153 11.806l375.205 250.338 363.641-252.808c32.587-21.624 76.499-12.83 98.123 19.757 21.685 32.467 12.95 76.56-19.576 98.184l-402.854 278.89c-23.733 15.901-54.694 15.962-78.547.12L69.501 442.097c-32.647-21.685-41.441-65.777-19.817-98.304 13.734-20.54 36.201-31.563 59.09-31.563Z"}));rl.displayName="SlideDownIcon";const Cr=()=>p("svg",{xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink",class:"empty-icon",viewBox:"0 0 1024 1024",innerHTML:''});Cr.displayName="EmptyIcon";var vg=V({name:"ArticleItem",props:{info:{type:Object,required:!0},path:{type:String,required:!0}},slots:Object,setup(n,{slots:e}){const t=Ot(n,"info"),{info:a,items:s}=ug(n);return()=>{var k,g,_;const{[vn.title]:l,[vn.type]:o,[vn.isEncrypted]:i=!1,[vn.cover]:c,[vn.excerpt]:r,[vn.sticky]:u}=t.value,d=a.value;return p("div",{class:"vp-article-wrapper"},p("article",{class:"vp-article-item",vocab:"https://schema.org/",typeof:"Article"},[((k=e.cover)==null?void 0:k.call(e,{cover:c}))||(c?[p("img",{class:"vp-article-cover",src:xn(c),loading:"lazy"}),p("meta",{property:"image",content:xn(c)})]:[]),u?p(Er):null,p(Tn,{to:n.path},()=>{var b;return((b=e.title)==null?void 0:b.call(e,{title:l,isEncrypted:i,type:o}))||p("header",{class:"vp-article-title"},[i?p(Xl):null,o===Vc.slide?p(wr):null,p("span",{property:"headline"},l)])}),((g=e.excerpt)==null?void 0:g.call(e,{excerpt:r}))||(r?p("div",{class:"vp-article-excerpt",innerHTML:r}):null),p("hr",{class:"vp-article-hr"}),((_=e.info)==null?void 0:_.call(e,{info:d}))||p(ir,{info:d,...s.value?{items:s.value}:{}})]))}}}),fg=V({name:"Pagination",props:{total:{type:Number,default:10},perPage:{type:Number,default:10},current:{type:Number,default:1}},emits:["updateCurrentPage"],setup(n,{emit:e}){let t;const a=ln(),s=K(""),l=y(()=>a.value.paginationLocales),o=y(()=>Math.ceil(n.total/n.perPage)),i=y(()=>!!o.value&&o.value!==1),c=y(()=>o.value<7?!1:n.current>4),r=y(()=>o.value<7?!1:n.current{const{current:g}=n;let _=1,b=o.value;const E=[];o.value>=7&&(g<=4&&g4&&g>=o.value-3?(b=o.value,_=o.value-4):o.value>7&&(_=g-2,b=g+2));for(let w=_;w<=b;w++)E.push(w);return E}),d=g=>e("updateCurrentPage",g),k=g=>{const _=parseInt(g,10);_<=o.value&&_>0?d(_):t.pop(`${l.value.errorText.replace(/\$page/gu,o.value.toString())}`)};return En(()=>{t=new z1}),()=>p("div",{class:"vp-pagination"},i.value?p("nav",{class:"vp-pagination-list"},[p("div",{class:"vp-pagination-number "},[n.current>1?p("div",{class:"prev",role:"navigation",unselectable:"on",onClick:()=>d(n.current-1)},l.value.prev):null,c.value?[p("div",{role:"navigation",onClick:()=>d(1)},1),p("div",{class:"ellipsis"},"...")]:null,u.value.map(g=>p("div",{key:g,class:{active:n.current===g},role:"navigation",onClick:()=>d(g)},g)),r.value?[p("div",{class:"ellipsis"},"..."),p("div",{role:"navigation",onClick:()=>d(o.value)},o.value)]:null,n.currentd(n.current+1)},l.value.next):null]),p("div",{class:"vp-pagination-nav"},[p("label",{for:"navigation-text"},`${l.value.navigate}: `),p("input",{id:"navigation-text",value:s.value,onInput:({target:g})=>{s.value=g.value},onKeydown:g=>{g.key==="Enter"&&(g.preventDefault(),k(s.value))}}),p("button",{class:"vp-pagination-button",role:"navigation",title:l.value.action,onClick:()=>k(s.value)},l.value.action)])]):[])}}),ao=V({name:"ArticleList",props:{items:{type:Array,default:()=>[]}},setup(n){const e=Ge(),t=me(),a=Aa(),s=K(1),l=y(()=>a.value.articlePerPage||10),o=y(()=>n.items.slice((s.value-1)*l.value,s.value*l.value)),i=async c=>{s.value=c;const r={...e.query};!(r.page===c.toString()||c===1&&!r.page)&&(c===1?delete r.page:r.page=c.toString(),await t.push({path:e.path,query:r}))};return En(()=>{const{page:c}=e.query;i(c?Number(c):1),dn(s,()=>{const r=document.querySelector("#article-list").getBoundingClientRect().top+window.scrollY;setTimeout(()=>{window.scrollTo(0,r)},100)})}),()=>p("div",{id:"article-list",class:"vp-article-list",role:"feed"},o.value.length?[...o.value.map(({info:c,path:r},u)=>p(rn,{appear:!0,delay:u*.04},()=>p(vg,{key:r,info:c,path:r}))),p(fg,{current:s.value,perPage:l.value,total:n.items.length,onUpdateCurrentPage:i})]:p(Cr))}}),Tr=V({name:"CategoryList",setup(){const n=gn(),e=xa();return()=>p("ul",{class:"vp-category-list"},ot(e.value.map).sort(([,t],[,a])=>a.items.length-t.items.length).map(([t,{path:a,items:s}])=>p("li",{class:["vp-category",`vp-category${ks(t,9)}`,{active:a===n.value.path}]},p(Tn,{to:a},()=>[t,p("span",{class:"count"},s.length)]))))}}),Or=V({name:"TagList",setup(){const n=fn(),e=La(),t=a=>{var s;return a===((s=n.value.blog)==null?void 0:s.name)};return()=>p("ul",{class:"tag-list-wrapper"},ot(e.value.map).sort(([,a],[,s])=>s.items.length-a.items.length).map(([a,{path:s,items:l}])=>p("li",{class:["tag",`tag${ks(a,9)}`,{active:t(a)}]},p(Tn,{to:s},()=>[a,p("span",{class:"tag-num"},l.length)]))))}}),_g=V({name:"TimelineList",setup(){const n=ln(),e=Ql(),t=ba(),a=y(()=>n.value.blogLocales.timeline);return()=>p("div",{class:"timeline-list-wrapper"},[p("div",{class:"timeline-list-title",onClick:()=>t(e.value.path)},[p(to),p("span",{class:"num"},e.value.items.length),a.value]),p("hr"),p("div",{class:"timeline-content"},p("ul",{class:"timeline-list"},e.value.config.map(({year:s,items:l},o)=>p(rn,{appear:!0,delay:.08*(o+1)},()=>p("li",[p("h3",{class:"timeline-year"},s),p("ul",{class:"timeline-year-wrapper"},l.map(({date:i,info:c,path:r})=>p("li",{class:"timeline-item"},[p("span",{class:"timeline-date"},i),p(Tn,{class:"timeline-title",to:r},()=>c[vn.title])])))])))))])}});const yg={article:ms,category:no,tag:eo,timeline:to};var Ir=V({name:"InfoList",setup(){const n=ln(),e=Sa(),t=xa(),a=y(()=>re(t.value.map).length),s=Yl(),l=La(),o=y(()=>re(l.value.map).length),i=ba(),c=K("article"),r=y(()=>n.value.blogLocales);return()=>p("div",{class:"vp-blog-infos"},[p("div",{class:"vp-blog-type-switcher"},ot(yg).map(([u,d])=>p("button",{type:"button",class:"vp-blog-type-button",onClick:()=>{c.value=u}},p("div",{class:["icon-wrapper",{active:c.value===u}],"aria-label":r.value[u],"data-balloon-pos":"up"},p(d))))),p(rn,()=>c.value==="article"?p("div",{class:"vp-star-article-wrapper"},[p("div",{class:"title",onClick:()=>i(e.value.path)},[p(ms),p("span",{class:"num"},e.value.items.length),r.value.article]),p("hr"),s.value.items.length?p("ul",{class:"vp-star-articles"},s.value.items.map(({info:u,path:d},k)=>p(rn,{appear:!0,delay:.08*(k+1)},()=>p("li",{class:"vp-star-article"},p(Tn,{to:d},()=>u[vn.title]))))):p("div",{class:"vp-star-article-empty"},r.value.empty.replace("$text",r.value.star))]):c.value==="category"?p("div",{class:"vp-category-wrapper"},[a.value?[p("div",{class:"title",onClick:()=>i(t.value.path)},[p(no),p("span",{class:"num"},a.value),r.value.category]),p("hr"),p(rn,{delay:.04},()=>p(Tr))]:p("div",{class:"vp-category-empty"},r.value.empty.replace("$text",r.value.category))]):c.value==="tag"?p("div",{class:"vp-tag-wrapper"},[o.value?[p("div",{class:"title",onClick:()=>i(l.value.path)},[p(eo),p("span",{class:"num"},o.value),r.value.tag]),p("hr"),p(rn,{delay:.04},()=>p(Or))]:p("div",{class:"vp-tag-empty"},r.value.empty.replace("$text",r.value.tag))]):p(rn,()=>p(_g)))])}}),vs=V({name:"BlogWrapper",slots:Object,setup(n,{slots:e}){const{isMobile:t}=wa();return()=>[p(Kl),p(Gl,{noSidebar:!0,noToc:!0},{default:()=>e.default(),navScreenBottom:()=>p(Zl),...t.value?{sidebar:()=>p(Ir)}:{}})]}});const Pr=()=>p("aside",{class:"vp-blog-info-wrapper"},[p(rn,()=>p(Zl)),p(rn,{delay:.04},()=>p(Ir))]);Pr.displayName="InfoPanel";var fs=Pr,bg=V({name:"BlogPage",setup(){const n=gn(),e=fn(),t=xa(),a=La();return()=>{const{key:s="",name:l=""}=e.value.blog||{},o=l?s==="category"?t.value.map[l].items:s==="tag"?a.value.map[l].items:[]:[];return p(vs,()=>p("div",{class:"vp-page vp-blog"},p("div",{class:"blog-page-wrapper"},[p("main",{id:"main-content",class:"vp-blog-main"},[p(rn,()=>s==="category"?p(Tr):s==="tag"?p(Or):null),l?p(rn,{appear:!0,delay:.24},()=>p(ao,{key:n.value.path,items:o})):null]),p(rn,{delay:.16},()=>p(fs,{key:"blog"}))])))}}});const wg="//theme-hope-assets.vuejs.press/hero/default.jpg";var Eg=V({name:"BlogHero",slots:Object,setup(n,{slots:e}){const t=fn(),a=va(),s=Nn(),l=y(()=>t.value.heroFullScreen??!1),o=y(()=>{const{heroText:c,heroImage:r,heroImageDark:u,heroAlt:d,heroImageStyle:k,tagline:g}=t.value;return{text:c??a.value.title??"Hello",tagline:g??"",image:r?xn(r):null,imageDark:u?xn(u):null,alt:d||c||"",style:k,isFullScreen:l.value}}),i=y(()=>{const{bgImage:c,bgImageDark:r,bgImageStyle:u}=t.value;return{image:An(c)?xn(c):c===!1?null:wg,imageDark:An(r)?xn(r):null,bgStyle:u,isFullScreen:l.value}});return()=>{var c,r;return t.value.hero===!1?null:p("div",{ref:s,class:["vp-blog-hero",{fullscreen:l.value,"no-bg":!i.value.image}]},[((c=e.heroBg)==null?void 0:c.call(e,i.value))||[i.value.image?p("div",{class:["vp-blog-mask",{light:i.value.imageDark}],style:[{background:`url(${i.value.image}) center/cover no-repeat`},i.value.bgStyle]}):null,i.value.imageDark?p("div",{class:"vp-blog-mask dark",style:[{background:`url(${i.value.imageDark}) center/cover no-repeat`},i.value.bgStyle]}):null],((r=e.heroInfo)==null?void 0:r.call(e,o.value))||[p(rn,{appear:!0,type:"group",delay:.04},()=>[o.value.image?p("img",{key:"light",class:["vp-blog-hero-image",{light:o.value.imageDark}],style:o.value.style,src:o.value.image,alt:o.value.alt}):null,o.value.imageDark?p("img",{key:"dark",class:"vp-blog-hero-image dark",style:o.value.style,src:o.value.imageDark,alt:o.value.alt}):null]),p(rn,{appear:!0,delay:.08},()=>o.value.text?p("h1",{class:"vp-blog-hero-title"},o.value.text):null),p(rn,{appear:!0,delay:.12},()=>o.value.tagline?p("p",{class:"vp-blog-hero-description",innerHTML:o.value.tagline}):null)],o.value.isFullScreen?p("button",{type:"button",class:"slide-down-button",onClick:()=>{window.scrollTo({top:s.value.clientHeight,behavior:"smooth"})}},[p(rl),p(rl)]):null])}}});const xg=["link","article","book","project","friend"];var Ag=V({name:"ProjectPanel",components:{ArticleIcon:ms,BookIcon:xr,FriendIcon:Sr,LinkIcon:Ar,ProjectIcon:Lr},props:{items:{type:Array,required:!0}},setup(n){const e=it(),t=ba(),a=(s="",l="icon")=>xg.includes(s)?p(ee(`${s}-icon`)):Je(s)?p("img",{class:"vp-project-image",src:s,alt:l}):us(s)?p("img",{class:"vp-project-image",src:xn(s),alt:l}):p(Vn,{icon:s});return()=>p("div",{class:"vp-project-panel"},n.items.map(({icon:s,link:l,name:o,desc:i},c)=>p("div",{class:["vp-project-card",{[`project${c%9}`]:!e.value}],onClick:()=>t(l)},[a(s,o),p("div",{class:"vp-project-name"},o),p("div",{class:"vp-project-desc"},i)])))}}),Lg=V({name:"BlogHome",setup(){const n=Sa(),e=fn(),t=y(()=>e.value.projects??[]);return()=>p("div",{class:"vp-page vp-blog"},[p(Eg),p("div",{class:"blog-page-wrapper"},[p("main",{id:"main-content",class:"vp-blog-main"},[t.value.length?p(rn,{appear:!0,delay:.16},()=>p(Ag,{items:t.value})):null,p(rn,{appear:!0,delay:.24},()=>p(ao,{items:n.value.items}))]),p(rn,{appear:!0,delay:.16},()=>p(fs,{key:"blog"}))]),p(rn,{appear:!0,delay:.28},()=>p(Wl))])}});const jr=()=>p(vs,()=>p(Lg));jr.displayName="BlogHomeLayout";var Sg=jr,Cg=V({name:"ArticleType",setup(){const n=gn(),e=fe(),t=ln(),a=Sa(),s=Yl(),l=y(()=>{const o=t.value.blogLocales;return[{text:o.all,path:a.value.path},{text:o.star,path:s.value.path},...sg.map(({key:i,path:c})=>({text:o[i],path:c.replace(/^\//,e.value)}))]});return()=>p("ul",{class:"vp-article-type-wrapper"},l.value.map(o=>p("li",{class:["vp-article-type",{active:o.path===n.value.path}]},p(Tn,{to:o.path},()=>o.text))))}}),Tg=V({name:"BlogPage",setup(){const n=gs(),e=fn(),t=gn(),a=Sa(),s=Yl(),l=y(()=>{const{key:o="",type:i}=e.value.blog||{};return o==="star"?s.value.items:i==="type"&&o?n.value.items:a.value.items});return()=>p(vs,()=>p("div",{class:"vp-page vp-blog"},p("div",{class:"blog-page-wrapper"},[p("main",{id:"main-content",class:"vp-blog-main"},[p(rn,()=>p(Cg)),p(rn,{appear:!0,delay:.24},()=>p(ao,{key:t.value.path,items:l.value}))]),p(rn,{delay:.16},()=>p(fs,{key:"blog"}))])))}}),Og=V({name:"TimelineItems",setup(){const n=Aa(),e=ln(),t=Ql(),a=y(()=>n.value.timeline||e.value.blogLocales.timelineTitle),s=y(()=>t.value.config.map(({year:l})=>({title:l.toString(),level:2,slug:l.toString(),children:[]})));return()=>p("div",{class:"timeline-wrapper"},p("ul",{class:"timeline-content"},[p(rn,()=>p("li",{class:"motto"},a.value)),p(cr,{items:s.value}),t.value.config.map(({year:l,items:o},i)=>p(rn,{appear:!0,delay:.08*(i+1),type:"group"},()=>[p("h3",{key:"title",id:l,class:"timeline-year-title"},p("span",l)),p("li",{key:"content",class:"timeline-year-list"},[p("ul",{class:"timeline-year-wrapper"},o.map(({date:c,info:r,path:u})=>p("li",{class:"timeline-item"},[p("span",{class:"timeline-date"},c),p(Tn,{class:"timeline-title",to:u},()=>r[vn.title])])))])]))]))}});const Dr=()=>p(vs,()=>p("div",{class:"vp-page vp-blog"},p("div",{class:"blog-page-wrapper"},[p("main",{id:"main-content",class:"vp-blog-main"},[p(rn,{appear:!0,delay:.24},()=>p(Og))]),p(rn,{delay:.16},()=>p(fs,{key:"blog"}))])));Dr.displayName="Timeline";var Ig=Dr,Pg={};const dt="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),qs=Array.from({length:64},(n,e)=>e),Na=n=>Array(n).fill(-1),Pe=[...Na(46),0,1,...qs.slice(54,64),...Na(7),...qs.slice(2,28),...Na(6),...qs.slice(28,54),...Na(5)],Sp=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],Cp=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971e4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409e3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],Rr=[1332899944,1700884034,1701343084,1684370003,1668446532,1869963892],ul=(n,e)=>{if(e<=0||e>n.length)throw Error(`Illegal len: ${e}`);let t=0,a,s;const l=[];for(;t>2&63]),a=(a&3)<<4,t>=e){l.push(dt[a&63]);break}if(s=n[t++]&255,a|=s>>4&15,l.push(dt[a&63]),a=(s&15)<<2,t>=e){l.push(dt[a&63]);break}s=n[t++]&255,a|=s>>6&3,l.push(dt[a&63]),l.push(dt[s&63])}return l.join("")},jg=(n,e)=>{const t=n.length;let a=0,s=0,l,o,i,c,r,u;const d=[];for(;a>>0,r|=(o&48)>>4,d.push(String.fromCharCode(r)),++s>=e||a>=t)||(u=n.charCodeAt(a++),i=u>>0,r|=(i&60)>>2,d.push(String.fromCharCode(r)),++s>=e||a>=t)));)u=n.charCodeAt(a++),c=u>>0,r|=c,d.push(String.fromCharCode(r)),++s;return d.map(k=>k.charCodeAt(0))},Dg=(n,e)=>{let t=null;for(typeof n=="number"&&(t=n,n=()=>null);t!==null||(t=n())!==null;)t<128?e(t&127):t<2048?(e(t>>6&31|192),e(t&63|128)):t<65536?(e(t>>12&15|224),e(t>>6&63|128),e(t&63|128)):(e(t>>18&7|240),e(t>>12&63|128),e(t>>6&63|128),e(t&63|128)),t=null},Rg=(n,e)=>{let t,a=null;for(;(t=a!==null?a:n())!==null;){if(t>=55296&&t<=57343&&(a=n())!==null&&a>=56320&&a<=57343){e((t-55296)*1024+a-56320+65536),a=null;continue}e(t)}a!==null&&e(a)},Fg=(n,e)=>{Rg(n,function(t){Dg(t,e)})},Bg=typeof process=="object"&&Pg.NEXT_RUNTIME==="edge"?setTimeout:typeof setImmediate=="function"?setImmediate:typeof process=="object"&&typeof process.nextTick=="function"?process.nextTick:setTimeout,Mg=n=>{const e=[];let t=0;return Fg(()=>t>=n.length?null:n.charCodeAt(t++),a=>{e.push(a)}),e},oa=(n,e,t,a)=>{let s,l=n[e],o=n[e+1];return l^=t[0],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[1],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[2],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[3],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[4],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[5],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[6],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[7],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[8],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[9],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[10],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[11],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[12],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[13],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[14],s=a[l>>>24],s+=a[256|l>>16&255],s^=a[512|l>>8&255],s+=a[768|l&255],o^=s^t[15],s=a[o>>>24],s+=a[256|o>>16&255],s^=a[512|o>>8&255],s+=a[768|o&255],l^=s^t[16],n[e]=o^t[17],n[e+1]=l,n},kt=(n,e)=>{let t=0;for(let a=0;a<4;++a)t=t<<8|n[e]&255,e=(e+1)%n.length;return{key:t,offp:e}},Tp=(n,e,t)=>{const a=e.length,s=t.length;let l=0,o=[0,0],i;for(let c=0;c{const s=t.length,l=a.length;let o=0,i=[0,0],c;for(let r=0;r{const l=Rr.slice(),o=l.length;if(t<4||t>31){const k=new Error(`Illegal number of rounds (4-31): ${t}`);if(a===!1)return Promise.reject(k);throw k}if(e.length!==16){const k=new Error(`Illegal salt length: ${e.length} != 16`);if(a===!1)return Promise.reject(k);throw k}t=1<>>0;let i,c,r=0,u;Int32Array?(i=new Int32Array(Sp),c=new Int32Array(Cp)):(i=Sp.slice(),c=Cp.slice()),Vg(e,n,i,c);const d=()=>{if(r100)););}else{for(r=0;r<64;r++)for(u=0;u>1;u++)oa(l,u<<1,i,c);const k=[];for(r=0;r>24&255)>>>0),k.push((l[r]>>16&255)>>>0),k.push((l[r]>>8&255)>>>0),k.push((l[r]&255)>>>0);return a===!1?Promise.resolve(k):k}if(a===!1)return new Promise(k=>Bg(()=>{d().then(k)}))};if(a===!1)return d();{let k;for(;;)if(typeof(k=d())<"u")return k||[]}},qg=n=>{try{let e;typeof window<"u"?e=window.crypto??window.msCrypto:e=globalThis.crypto;const t=new Uint32Array(n);return e==null||e.getRandomValues(t),Array.from(t)}catch{throw Error("WebCryptoAPI is not available")}},Ng=(n=10)=>{if(typeof n!="number")throw Error("Illegal arguments: "+typeof n);n<4?n=4:n>31&&(n=31);const e=[];return e.push("$2a$"),n<10&&e.push("0"),e.push(n.toString()),e.push("$"),e.push(ul(qg(16),16)),e.join("")};function Hg(n,e,t,a){if(typeof n!="string"||typeof e!="string"){const g=new Error("Invalid string / salt: Not a string");if(t===!1)return Promise.reject(g);throw g}let s,l;if(e.charAt(0)!=="$"||e.charAt(1)!=="2"){const g=new Error("Invalid salt version: "+e.substring(0,2));if(t===!1)return Promise.reject(g);throw g}if(e.charAt(2)==="$")s="\0",l=3;else{if(s=e.charAt(2),s!=="a"&&s!=="b"&&s!=="y"||e.charAt(3)!=="$"){const g=Error("Invalid salt revision: "+e.substring(2,4));if(t===!1)return Promise.reject(g);throw g}l=4}if(e.charAt(l+2)>"$"){const g=new Error("Missing salt rounds");if(t===!1)return Promise.reject(g);throw g}const o=parseInt(e.substring(l,l+1),10)*10,i=parseInt(e.substring(l+1,l+2),10),c=o+i,r=e.substring(l+3,l+25);n+=s>="a"?"\0":"";const u=Mg(n),d=jg(r,16),k=g=>{const _=[];return _.push("$2"),s>="a"&&_.push(s),_.push("$"),c<10&&_.push("0"),_.push(c.toString()),_.push("$"),_.push(ul(d,d.length)),_.push(ul(g,Rr.length*4-1)),_.join("")};return t===!1?Op(u,d,c,!1).then(g=>k(g)):k(Op(u,d,c,!0))}const $g=(n,e=10)=>{if(typeof e=="number"&&(e=Ng(e)),typeof n!="string"||typeof e!="string")throw Error("Illegal arguments: "+typeof n+", "+typeof e);return Hg(n,e,!0)},dl=(n,e)=>{if(typeof n!="string"||typeof e!="string")throw Error("Illegal arguments: "+typeof n+", "+typeof e);return e.length!==60?!1:$g(n,e.substring(0,e.length-31))===e};var Fr=V({name:"PasswordModal",props:{full:Boolean},emits:["verify"],setup(n,{emit:e}){const t=fn(),a=ln(),s=K(""),l=K(!1),o=K(!1),i=y(()=>a.value.encryptLocales);let c=null;const r=()=>{c&&clearTimeout(c),l.value=!1,e("verify",s.value,o.value),Ue().then(()=>{l.value=!0,c=setTimeout(()=>{l.value=!1},1e3)})};return()=>p("div",{class:["vp-decrypt-layer",{expand:n.full||t.value.home}]},p("div",{class:"vp-decrypt-modal"},[p("div",{class:["vp-decrypt-hint",{tried:l.value}]},l.value?i.value.errorHint:p(Xl,{"aria-label":i.value.iconLabel})),p("div",{class:"vp-decrypt-input"},[p("input",{type:"password",value:s.value,placeholder:i.value.placeholder,onInput:({target:u})=>{s.value=u.value},onKeydown:({key:u})=>{u==="Enter"&&r()}})]),p("div",{class:"vp-remember-password"},[p("input",{type:"checkbox",value:o.value,onChange:()=>o.value=!o.value}),i.value.remember]),p("button",{type:"button",class:"vp-decrypt-submit",onClick:()=>r()},"OK")]))}});const Br=()=>{const n=Se();return y(()=>n.value.encrypt||{})},Ip="VUEPRESS_HOPE_GLOBAL_TOKEN",zg=()=>{const n=Br(),e=hs(Ip,""),t=rc(Ip,""),a=y(()=>{const{global:l=!1,admin:o=[]}=n.value;return l&&o.length>0}),s=y(()=>{if(a.value){if(e.value)return n.value.admin.some(l=>dl(e.value,l));if(t.value)return n.value.admin.some(l=>dl(t.value,l))}return!1});return{isEncrypted:a,isDecrypted:s,validate:(l,o=!1)=>{(o?e:t).value=l}}},Ns=(n="",e)=>!!n&&dl(n,e),Pp="VUEPRESS_HOPE_PATH_TOKEN",Ug=()=>{const n=gn(),e=Br(),t=hs(Pp,{}),a=rc(Pp,{}),s=o=>ma(e.value.config)?re(e.value.config).filter(i=>St(decodeURI(o),i)).sort((i,c)=>c.length-i.length):[],l=o=>{const i=s(o);if(i.length>0){const{config:c={}}=e.value;return{isEncrypted:!0,isDecrypted:i.some(r=>t.value[r]&&c[r].some(u=>Ns(t.value[r],u))||a.value[r]&&c[r].some(u=>Ns(a.value[r],u)))}}return{isDecrypted:!1,isEncrypted:!1}};return{status:y(()=>l(n.value.path)),getStatus:l,validate:(o,i=!1)=>{const{config:c={}}=e.value,r=s(n.value.path);for(const u of r)if(c[u].filter(d=>Ns(o,d))){(i?t:a).value[u]=o;break}}}};var Jg=V({name:"GlobalEncrypt",slots:Object,setup(n,{slots:e}){const{isDecrypted:t,isEncrypted:a,validate:s}=zg(),l=K(!1);return En(()=>{l.value=!0}),()=>p(ur,()=>a.value?l.value?t.value?e.default():p(Fr,{full:!0,onVerify:s}):null:e.default())}}),Gg=V({name:"LocalEncrypt",slots:Object,setup(n,{slots:e}){const{status:t,validate:a}=Ug(),s=K(!1);return En(()=>{s.value=!0}),()=>{const{isEncrypted:l,isDecrypted:o}=t.value;return l?s.value?o?e.default():p(Fr,{full:!0,onVerify:a}):null:e.default()}}});P2(n=>{const e=n.t,t=n.I!==!1,a=n.i;return t?{title:e,content:a?()=>[p(Vn,{icon:a}),e]:null,order:n.O,index:n.I}:null});const Wg=ae({enhance:({app:n,router:e})=>{const{scrollBehavior:t}=e.options;e.options.scrollBehavior=async(...a)=>(await rr().wait(),t(...a)),s0(n),n.component("HopeIcon",Vn),n.component("BloggerInfo",Zl),n.component("GlobalEncrypt",Jg),n.component("LocalEncrypt",Gg)},setup:()=>{l0(),i0(),gg()},layouts:{Layout:Q0,NotFound:ng,BlogCategory:bg,BlogHome:Sg,BlogType:Tg,Timeline:Ig}}),Ha=[Uk,A2,I2,B2,q2,z2,W2,Z2,r1,d1,L1,$1,Wg],Kg=JSON.parse(`{"base":"/","lang":"zh-CN","title":"ChenSino","description":"ChenSino's Blog","head":[],"locales":{}}`);var Bt=Nn(Kg),Xg=ck,Yg=()=>{const n=Rk({history:Xg(Fl("/")),routes:[{name:"vuepress-route",path:"/:catchAll(.*)",components:{}}],scrollBehavior:(e,t,a)=>a||(e.hash?{el:e.hash}:{top:0})});return n.beforeResolve(async(e,t)=>{if(e.path!==t.path||t===be){const a=It(e.path);if(a.path!==e.path)return a.path;const s=await a.loader();e.meta={...a.meta,_pageChunk:s}}else e.path===t.path&&(e.meta=t.meta)}),n},Qg=n=>{n.component("ClientOnly",rs),n.component("Content",ec),n.component("RouteLink",Tn)},Zg=(n,e,t)=>{const a=y(()=>e.currentRoute.value.path),s=Sl((E,w)=>({get(){return E(),e.currentRoute.value.meta._pageChunk},set(L){e.currentRoute.value.meta._pageChunk=L,w()}})),l=y(()=>Ye.resolveLayouts(t)),o=y(()=>Ye.resolveRouteLocale(Bt.value.locales,a.value)),i=y(()=>Ye.resolveSiteLocaleData(Bt.value,o.value)),c=y(()=>s.value.comp),r=y(()=>s.value.data),u=y(()=>r.value.frontmatter),d=y(()=>Ye.resolvePageHeadTitle(r.value,i.value)),k=y(()=>Ye.resolvePageHead(d.value,u.value,i.value)),g=y(()=>Ye.resolvePageLang(r.value,i.value)),_=y(()=>Ye.resolvePageLayout(r.value,l.value)),b={layouts:l,pageData:r,pageComponent:c,pageFrontmatter:u,pageHead:k,pageHeadTitle:d,pageLang:g,pageLayout:_,redirects:Zi,routeLocale:o,routePath:a,routes:ta,siteData:Bt,siteLocaleData:i};return n.provide(Vl,b),Object.defineProperties(n.config.globalProperties,{$frontmatter:{get:()=>u.value},$head:{get:()=>k.value},$headTitle:{get:()=>d.value},$lang:{get:()=>g.value},$page:{get:()=>r.value},$routeLocale:{get:()=>o.value},$site:{get:()=>Bt.value},$siteLocale:{get:()=>i.value},$withBase:{get:()=>xn}}),b},nm=()=>{const n=Mk(),e=Yi();let t=[];const a=()=>{n.value.forEach(o=>{const i=em(o);i&&t.push(i)})},s=()=>{const o=[];return n.value.forEach(i=>{const c=tm(i);c&&o.push(c)}),o},l=()=>{document.documentElement.lang=e.value;const o=s();t.forEach((i,c)=>{const r=o.findIndex(u=>i.isEqualNode(u));r===-1?(i.remove(),delete t[c]):o.splice(r,1)}),o.forEach(i=>document.head.appendChild(i)),t=[...t.filter(i=>!!i),...o]};ie(Nk,l),En(()=>{a(),dn(n,l,{immediate:!1})})},em=([n,e,t=""])=>{const a=Object.entries(e).map(([i,c])=>An(c)?`[${i}=${JSON.stringify(c)}]`:c===!0?`[${i}]`:"").join(""),s=`head > ${n}${a}`;return Array.from(document.querySelectorAll(s)).find(i=>i.innerText===t)||null},tm=([n,e,t])=>{if(!An(n))return null;const a=document.createElement(n);return ma(e)&&Object.entries(e).forEach(([s,l])=>{An(l)?a.setAttribute(s,l):l===!0&&a.setAttribute(s,"")}),An(t)&&a.appendChild(document.createTextNode(t)),a},am=fh,sm=async()=>{var t;const n=am({name:"Vuepress",setup(){var l;nm();for(const o of Ha)(l=o.setup)==null||l.call(o);const a=Ha.flatMap(({rootComponents:o=[]})=>o.map(i=>p(i))),s=Vk();return()=>[p(s.value),a]}}),e=Yg();Qg(n),Zg(n,e,Ha);for(const a of Ha)await((t=a.enhance)==null?void 0:t.call(a,{app:n,router:e,siteData:Bt}));return n.use(e),{app:n,router:e}};sm().then(({app:n,router:e})=>{e.isReady().then(()=>{n.mount("#app")})});export{Li as a,Si as b,om as c,sm as createVueApp,In as d,pm as e,lm as o,ee as r,Fu as w}; diff --git a/assets/asyncError.html-WxV4KJUL.js b/assets/asyncError.html-npKDZnPq.js similarity index 99% rename from assets/asyncError.html-WxV4KJUL.js rename to assets/asyncError.html-npKDZnPq.js index 27cc2b43af..f9ad722704 100644 --- a/assets/asyncError.html-WxV4KJUL.js +++ b/assets/asyncError.html-npKDZnPq.js @@ -1,4 +1,4 @@ -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o,c,a as s,b as n,d as l,e as a}from"./app-CVMfKeWw.js";const i={},u=a(`

    一,为什么要捕获异常

    //确认提交
    +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o,c,a as s,b as n,d as l,e as a}from"./app-_Oi5YZFn.js";const i={},u=a(`

    一,为什么要捕获异常

    //确认提交
     const submitWorkloadSure = async () => {
       
         let data = await WorkloadSures()
    diff --git a/assets/axios.html-DIiEtqYW.js b/assets/axios.html-BXoNbwVV.js
    similarity index 99%
    rename from assets/axios.html-DIiEtqYW.js
    rename to assets/axios.html-BXoNbwVV.js
    index 7bbc58860c..629b07b35f 100644
    --- a/assets/axios.html-DIiEtqYW.js
    +++ b/assets/axios.html-BXoNbwVV.js
    @@ -1,4 +1,4 @@
    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},p=e(`

    项目一

    根据名称获取动漫列表

    1,定义api.type.ts (定义后台接口返回的类型)

    
    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

    项目一

    根据名称获取动漫列表

    1,定义api.type.ts (定义后台接口返回的类型)

    
     interface ApiFormat<T> {
       /** 状态码 */
       code: number
    diff --git a/assets/basic-usage.html-CEUsK9ry.js b/assets/basic-usage.html-CBqhMzrq.js
    similarity index 99%
    rename from assets/basic-usage.html-CEUsK9ry.js
    rename to assets/basic-usage.html-CBqhMzrq.js
    index c522fe2ba2..1e2364b041 100644
    --- a/assets/basic-usage.html-CEUsK9ry.js
    +++ b/assets/basic-usage.html-CBqhMzrq.js
    @@ -1,4 +1,4 @@
    -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as n,b as s,d as t,e}from"./app-CVMfKeWw.js";const i={},r=e(`

    一,模板中的 TypeScript

    在使用了 <script lang="ts"> 或 <script setup lang="ts"> 后,<template> 在绑定表达式中也支持 TypeScript。

    <script setup lang="ts">
    +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as n,b as s,d as t,e}from"./app-_Oi5YZFn.js";const i={},r=e(`

    一,模板中的 TypeScript

    在使用了 <script lang="ts"> 或 <script setup lang="ts"> 后,<template> 在绑定表达式中也支持 TypeScript。

    <script setup lang="ts">
     let x: string | number = 1
     </script>
     
    diff --git a/assets/branch01.html--1ri9Qlh.js b/assets/branch01.html-CEgmUaaW.js
    similarity index 98%
    rename from assets/branch01.html--1ri9Qlh.js
    rename to assets/branch01.html-CEgmUaaW.js
    index cf76417ff6..2510353eaf 100644
    --- a/assets/branch01.html--1ri9Qlh.js
    +++ b/assets/branch01.html-CEgmUaaW.js
    @@ -1,4 +1,4 @@
    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a,e as n}from"./app-CVMfKeWw.js";const i={},s=n(`

    1,查看远程分支

    可以查看远程分支名,查看要切换的远程分支是否存在。

    git branch -a
    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a,e as n}from"./app-_Oi5YZFn.js";const i={},s=n(`

    1,查看远程分支

    可以查看远程分支名,查看要切换的远程分支是否存在。

    git branch -a
     

    2, 查看本地分支

    查看本地是否已经有了要切换的分支,如果有,可以直接git checkout 分支名来切换分支。

    git branch 
     

    3,拉取远程分支

    比如我把远程dev分支拉下来。

    git fetch origin dev
     

    4,新建本地dev分支,并关联远程dev分支

    git checkout -b dev origin/dev
    diff --git a/assets/branch02.html-CScQPDBP.js b/assets/branch02.html-CkbKOtQo.js
    similarity index 99%
    rename from assets/branch02.html-CScQPDBP.js
    rename to assets/branch02.html-CkbKOtQo.js
    index 1fa2393895..d960a448a2 100644
    --- a/assets/branch02.html-CScQPDBP.js
    +++ b/assets/branch02.html-CkbKOtQo.js
    @@ -1,4 +1,4 @@
    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as n}from"./app-CVMfKeWw.js";const t={},i=n(`

    一,推送本地分支到远程

    昨天我在自家电脑创建了一个分支search_dev.写搜索功能。

    1,在本地创建并切换到search_dev分支

    git checkout -b search_dev
    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as n}from"./app-_Oi5YZFn.js";const t={},i=n(`

    一,推送本地分支到远程

    昨天我在自家电脑创建了一个分支search_dev.写搜索功能。

    1,在本地创建并切换到search_dev分支

    git checkout -b search_dev
     

    2,推送本地search_dev分支到远程

    git push origin search_dev(本地):search_dev(远程)
     

    或者

    git push --set-upstream origin dev(分支名)
     

    二,从远程拉取分支到d本地

    第二天,我到公司写搜索功能,我需要从远程拉取search_dev到本地。

    1,简写命令

    git checkout -b search_dev origin/search_dev //check出远程dev分支到本地
    diff --git "a/assets/build\346\240\207\347\255\276.html-uLUm0P1E.js" "b/assets/build\346\240\207\347\255\276.html-CyXt4kH2.js"
    similarity index 99%
    rename from "assets/build\346\240\207\347\255\276.html-uLUm0P1E.js"
    rename to "assets/build\346\240\207\347\255\276.html-CyXt4kH2.js"
    index b12973556d..870475496a 100644
    --- "a/assets/build\346\240\207\347\255\276.html-uLUm0P1E.js"
    +++ "b/assets/build\346\240\207\347\255\276.html-CyXt4kH2.js"
    @@ -1,4 +1,4 @@
    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

    配置解释

    resource标签的操作是通过maven-resource-plugin插件来实现的,通过配置标签来实现资源文件的过滤替换。这个标签的作用是告诉Maven,在拷贝源文件到目标路径之前,对源文件内容进行参数替换。

        <build>
    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

    配置解释

    resource标签的操作是通过maven-resource-plugin插件来实现的,通过配置标签来实现资源文件的过滤替换。这个标签的作用是告诉Maven,在拷贝源文件到目标路径之前,对源文件内容进行参数替换。

        <build>
             <resources>
                 <resource>
                     <directory>src/main/java</directory>
    diff --git a/assets/crossDomain.html-DCiVSBRz.js b/assets/crossDomain.html-CSDExzG7.js
    similarity index 99%
    rename from assets/crossDomain.html-DCiVSBRz.js
    rename to assets/crossDomain.html-CSDExzG7.js
    index 2bd58329a9..f5c97aa1f5 100644
    --- a/assets/crossDomain.html-DCiVSBRz.js
    +++ b/assets/crossDomain.html-CSDExzG7.js
    @@ -1,4 +1,4 @@
    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},p=e(`

    一,同源策略

    所谓同源是指:当浏览器向后端发送请求时其请求的协议、域名、端口要和当前服务完全一致。比如前端项目的服务位于http://localhost:8080,则其发送的所有请求必须是http://localhost:8080/xxx/xxx这种格式,否则就会被同源策略拦截。

    http://www.test.com:8000/  协议(http)、主域名(test)、子域名(www)、端口号(8000)
    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

    一,同源策略

    所谓同源是指:当浏览器向后端发送请求时其请求的协议、域名、端口要和当前服务完全一致。比如前端项目的服务位于http://localhost:8080,则其发送的所有请求必须是http://localhost:8080/xxx/xxx这种格式,否则就会被同源策略拦截。

    http://www.test.com:8000/  协议(http)、主域名(test)、子域名(www)、端口号(8000)
     

    二,什么是跨域

    跨域就是浏览器从一个域名的网页去请求另一个域名的资源时,出现协议、域名、端口任一不同的情况,没有遵循同源策略。

    例如:浏览器网址localhost:8080 无法向http://www.test.com:8000/发送ajax请求,会跨域

    注意

    发生跨域时,Ajax请求是已经发送出去,并且后端是能收到请求的,只不过后端的Response被同源策略所拦截,同源策略本质是浏览器行为,和后端并无关系。

    三,跨域的解决方案

    1,jsonp

    2,CORS

    3,Node中间件代理

    4,nginx反向代理

    CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案

    JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

    不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。

    日常工作中,生产环境用得比较多的跨域方案是cors和nginx反向代理。

    本地项目中调试时用的最多的就是 node 代理,当然像 nginx、charles(抓包工具)做代理也可以,只要你会配置。

    3.1,Node中间件代理

    ​ 作为前端在写vue项目最常见的遇到的就是在devServer中配置代理,也就是跨域的第3终解决方案,Node中间件代理。

    在vue中使用proxy进行跨域的原理是:将域名发送给本地的服务器(启动vue项目的服务,loclahost:8080),再由 本地的服务器去请求真正的服务器。

    假如我们要在本地 http://localhost:9001 请求接口地址 http://dx.wh.com:9000 那么会存在跨域。

    解决跨域

    例如 http://localhost:9001/api/TokenAuth/GetCodeImage中的域名换成 dx.wh.com:9000

    相当于把请求换成

    http://dx.wh.com:9000/api/TokenAuth/GetCodeImage

    第一种代理方式:

    把请求的url写成 /api/TokenAuth/GetCodeImage (这样调是自动请求的当前域名)

    注意

    请求的地址必须是/api/TokenAuth/GetCodeImage,而不是http://localhost:9001/api/TokenAuth/GetCodeImage,(下面的几种方式也是同样的)因为代理会检查请求开头是否为/api,如果以http开头,则检查不匹配,则不走代理.

    devServer: {
         proxy: {
             '/api': {
    diff --git a/assets/crossDomain2.html-DTphraX3.js b/assets/crossDomain2.html-CA7WfkLl.js
    similarity index 99%
    rename from assets/crossDomain2.html-DTphraX3.js
    rename to assets/crossDomain2.html-CA7WfkLl.js
    index f514865236..50af4fc8d6 100644
    --- a/assets/crossDomain2.html-DTphraX3.js
    +++ b/assets/crossDomain2.html-CA7WfkLl.js
    @@ -1,4 +1,4 @@
    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

    一,什么是JSONP

    Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

    为什么我们从不同的域(网站)访问数据需要一个特殊的技术( JSONP )呢?这是因为同源策略。 同源策略,它是由 Netscape 提出的一个著名的安全策略,现在所有支持 JavaScript 的浏览器都会使用这个策略。

    二,JSONP的原理

    我们知道,在页面上有三种资源是可以与页面本身不同源的。它们是:js脚本,css样式文件,图片。像taobao等大型网站,很定会将这些静态资源放入cdn中,然后在页面上链接。而jsonp就是利用了script标签可以链接到不同源的js脚本,来到达跨域目的。 于是可以判断,当前阶段如果想通过纯web端(ActiveX控件、服务端代理、Web socket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理;

    三,JSONP的具体实现

    JSONP实现跨域请求的具体实现就是动态创建script标签,利用“src”不受同源策略约束的性质来实现跨域获取数据。

    1,服务端JSONP格式数据

    如客户想访问 : https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction。
    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

    一,什么是JSONP

    Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

    为什么我们从不同的域(网站)访问数据需要一个特殊的技术( JSONP )呢?这是因为同源策略。 同源策略,它是由 Netscape 提出的一个著名的安全策略,现在所有支持 JavaScript 的浏览器都会使用这个策略。

    二,JSONP的原理

    我们知道,在页面上有三种资源是可以与页面本身不同源的。它们是:js脚本,css样式文件,图片。像taobao等大型网站,很定会将这些静态资源放入cdn中,然后在页面上链接。而jsonp就是利用了script标签可以链接到不同源的js脚本,来到达跨域目的。 于是可以判断,当前阶段如果想通过纯web端(ActiveX控件、服务端代理、Web socket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理;

    三,JSONP的具体实现

    JSONP实现跨域请求的具体实现就是动态创建script标签,利用“src”不受同源策略约束的性质来实现跨域获取数据。

    1,服务端JSONP格式数据

    如客户想访问 : https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction。
     
     假设客户期望返回数据:["customername1","customername2"]。
     
    diff --git "a/assets/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html-D3MLskiS.js" "b/assets/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html-ykZMVUjH.js"
    similarity index 99%
    rename from "assets/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html-D3MLskiS.js"
    rename to "assets/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html-ykZMVUjH.js"
    index cdd9601f43..a871c222c5 100644
    --- "a/assets/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html-D3MLskiS.js"
    +++ "b/assets/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html-ykZMVUjH.js"
    @@ -1,4 +1,4 @@
    -import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as c,c as o,a as e,b as n,d as s,e as t}from"./app-CVMfKeWw.js";const d={},r={href:"https://github.com/acmesh-official/acme.sh",target:"_blank",rel:"noopener noreferrer"},p={href:"https://pve-doc-cn.readthedocs.io/zh-cn/latest/chapter_system_administration/certmgr.html",target:"_blank",rel:"noopener noreferrer"},h=t('

    1. 申请动态域名(省略)

    2. 使用acme申请免费的

    2.1 使用acme申请证书

    ',3),m={href:"https://github.com/acmesh-official/acme.sh/wiki/Install-in-China",target:"_blank",rel:"noopener noreferrer"},v=t(`
    git clone https://gitee.com/neilpang/acme.sh.git
    +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as c,c as o,a as e,b as n,d as s,e as t}from"./app-_Oi5YZFn.js";const d={},r={href:"https://github.com/acmesh-official/acme.sh",target:"_blank",rel:"noopener noreferrer"},p={href:"https://pve-doc-cn.readthedocs.io/zh-cn/latest/chapter_system_administration/certmgr.html",target:"_blank",rel:"noopener noreferrer"},h=t('

    1. 申请动态域名(省略)

    2. 使用acme申请免费的

    2.1 使用acme申请证书

    ',3),m={href:"https://github.com/acmesh-official/acme.sh/wiki/Install-in-China",target:"_blank",rel:"noopener noreferrer"},v=t(`
    git clone https://gitee.com/neilpang/acme.sh.git
     cd acme.sh
     ./acme.sh --install -m my@example.com
     

    2 当前终端,创建 一个 bash 的 alias, 方便你的使用:

    alias acme.sh=~/.acme.sh/acme.sh
    diff --git a/assets/disable.html-D2cQj4ic.js b/assets/disable.html-D_vnyg33.js
    similarity index 97%
    rename from assets/disable.html-D2cQj4ic.js
    rename to assets/disable.html-D_vnyg33.js
    index f539a2a7bd..63aa6b7ae5 100644
    --- a/assets/disable.html-D2cQj4ic.js
    +++ b/assets/disable.html-D_vnyg33.js
    @@ -1 +1 @@
    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as i,e as o}from"./app-CVMfKeWw.js";const a={},r=o("

    你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。

    本页面就是一个示例,禁用了如下功能:

    • 导航栏
    • 侧边栏
    • 路径导航
    • 页面信息
    • 贡献者
    • 编辑此页链接
    • 更新时间
    • 上一篇/下一篇 链接
    • 评论
    • 页脚
    • 返回顶部按钮
    ",3),n=[r];function l(c,p){return t(),i("div",null,n)}const d=e(a,[["render",l],["__file","disable.html.vue"]]),h=JSON.parse('{"path":"/guide/disable.html","title":"布局与功能禁用","lang":"zh-CN","frontmatter":{"title":"布局与功能禁用","icon":"config","order":3,"category":["使用指南"],"tag":["禁用"],"navbar":false,"sidebar":false,"breadcrumb":false,"pageInfo":false,"contributors":false,"editLink":false,"lastUpdated":false,"prev":false,"next":false,"comment":false,"footer":false,"backtotop":false,"description":"你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。 本页面就是一个示例,禁用了如下功能: 导航栏 侧边栏 路径导航 页面信息 贡献者 编辑此页链接 更新时间 上一篇/下一篇 链接 评论 页脚 返回顶部按钮","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/guide/disable.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"布局与功能禁用"}],["meta",{"property":"og:description","content":"你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。 本页面就是一个示例,禁用了如下功能: 导航栏 侧边栏 路径导航 页面信息 贡献者 编辑此页链接 更新时间 上一篇/下一篇 链接 评论 页脚 返回顶部按钮"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-07-29T09:43:43.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"禁用"}],["meta",{"property":"article:modified_time","content":"2022-07-29T09:43:43.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"布局与功能禁用\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2022-07-29T09:43:43.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659087823000,"updatedTime":1659087823000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.43,"words":128},"filePathRelative":"guide/disable.md","localizedDate":"2022年7月29日","excerpt":"

    你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。

    \\n","autoDesc":true}');export{d as comp,h as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as i,e as o}from"./app-_Oi5YZFn.js";const a={},r=o("

    你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。

    本页面就是一个示例,禁用了如下功能:

    • 导航栏
    • 侧边栏
    • 路径导航
    • 页面信息
    • 贡献者
    • 编辑此页链接
    • 更新时间
    • 上一篇/下一篇 链接
    • 评论
    • 页脚
    • 返回顶部按钮
    ",3),n=[r];function l(c,p){return t(),i("div",null,n)}const d=e(a,[["render",l],["__file","disable.html.vue"]]),h=JSON.parse('{"path":"/guide/disable.html","title":"布局与功能禁用","lang":"zh-CN","frontmatter":{"title":"布局与功能禁用","icon":"config","order":3,"category":["使用指南"],"tag":["禁用"],"navbar":false,"sidebar":false,"breadcrumb":false,"pageInfo":false,"contributors":false,"editLink":false,"lastUpdated":false,"prev":false,"next":false,"comment":false,"footer":false,"backtotop":false,"description":"你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。 本页面就是一个示例,禁用了如下功能: 导航栏 侧边栏 路径导航 页面信息 贡献者 编辑此页链接 更新时间 上一篇/下一篇 链接 评论 页脚 返回顶部按钮","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/guide/disable.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"布局与功能禁用"}],["meta",{"property":"og:description","content":"你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。 本页面就是一个示例,禁用了如下功能: 导航栏 侧边栏 路径导航 页面信息 贡献者 编辑此页链接 更新时间 上一篇/下一篇 链接 评论 页脚 返回顶部按钮"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-07-29T09:43:43.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"禁用"}],["meta",{"property":"article:modified_time","content":"2022-07-29T09:43:43.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"布局与功能禁用\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2022-07-29T09:43:43.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659087823000,"updatedTime":1659087823000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.43,"words":128},"filePathRelative":"guide/disable.md","localizedDate":"2022年7月29日","excerpt":"

    你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。

    \\n","autoDesc":true}');export{d as comp,h as data}; diff --git a/assets/draw.html-3iC56TfH.js b/assets/draw.html-DWXvV2Rt.js similarity index 99% rename from assets/draw.html-3iC56TfH.js rename to assets/draw.html-DWXvV2Rt.js index 2486a67a8b..4a48dc8dca 100644 --- a/assets/draw.html-3iC56TfH.js +++ b/assets/draw.html-DWXvV2Rt.js @@ -1,4 +1,4 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as e,e as n}from"./app-CVMfKeWw.js";const r={},o=n(`

    1 示例

    https://paper.pigx.vip/

    2 制图工具

    名称地址特性推荐指数
    visio可视化制图,单独安装,收费★★
    draw.iohttps://www.draw.io/提供在线网页制图工具、单独安装包、各种IDE集成插件,软件本身也有提供额外的插件支持,可安插件来增强drawio功能。制图采用可视化拖拉拽,简单直观,容易上手。★★★★★
    processonhttps://www.processon.com/网页在线制图,通过拖拉拽制图,免费版有很多限制,不推荐★★
    plantUMLhttp://www.plantuml.com/Java开发的,提供网页在线制图,各种IDE插件,vscode和JetBrains家族都有对应插件。采用标记语言进行制图,图片自动根据标记语言生成★★★★
    mermaidhttps://mermaid-js.github.io/mermaid/#/和PlantUML类似,也提供在线制图,支持markdown渲染,和markdown工具有关,大多md工具都会提供mermaid支持★★★

    3 工具选择

    流程图、架构图推荐使用drawio

    时序图、思维导图推荐使用PlantUML标记语言,自动生成的图比较规整好看

    如果使用markdown,推荐使用mermaid,mermaid语法简单,和PlantUML差不多

    graph TD;
    +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as e,e as n}from"./app-_Oi5YZFn.js";const r={},o=n(`

    1 示例

    https://paper.pigx.vip/

    2 制图工具

    名称地址特性推荐指数
    visio可视化制图,单独安装,收费★★
    draw.iohttps://www.draw.io/提供在线网页制图工具、单独安装包、各种IDE集成插件,软件本身也有提供额外的插件支持,可安插件来增强drawio功能。制图采用可视化拖拉拽,简单直观,容易上手。★★★★★
    processonhttps://www.processon.com/网页在线制图,通过拖拉拽制图,免费版有很多限制,不推荐★★
    plantUMLhttp://www.plantuml.com/Java开发的,提供网页在线制图,各种IDE插件,vscode和JetBrains家族都有对应插件。采用标记语言进行制图,图片自动根据标记语言生成★★★★
    mermaidhttps://mermaid-js.github.io/mermaid/#/和PlantUML类似,也提供在线制图,支持markdown渲染,和markdown工具有关,大多md工具都会提供mermaid支持★★★

    3 工具选择

    流程图、架构图推荐使用drawio

    时序图、思维导图推荐使用PlantUML标记语言,自动生成的图比较规整好看

    如果使用markdown,推荐使用mermaid,mermaid语法简单,和PlantUML差不多

    graph TD;
         A-->B;
         A-->C;
         B-->D;
    diff --git a/assets/ebooks.html-D9FixpLr.js b/assets/ebooks.html-BgNEqnIF.js
    similarity index 98%
    rename from assets/ebooks.html-D9FixpLr.js
    rename to assets/ebooks.html-BgNEqnIF.js
    index a79338491a..c132aa5879 100644
    --- a/assets/ebooks.html-D9FixpLr.js
    +++ b/assets/ebooks.html-BgNEqnIF.js
    @@ -1 +1 @@
    -import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as i,c as s,a as e,b as o,d as n}from"./app-CVMfKeWw.js";const p={},c=e("h3",{id:"_1、springboot",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、springboot"},[e("span",null,"1、springboot")])],-1),l={href:"https://ddns.chensina.cn:29000/afatpig/ebooks/Springboot.pdf",target:"_blank",rel:"noopener noreferrer"},h=e("h3",{id:"_2、nacos原理",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2、nacos原理"},[e("span",null,"2、Nacos原理")])],-1),d={href:"https://ddns.chensina.cn:29000/afatpig/ebooks/NACOS.pdf",target:"_blank",rel:"noopener noreferrer"};function m(g,_){const t=a("ExternalLinkIcon");return i(),s("div",null,[c,e("p",null,[e("a",l,[o("SpringBoot基本原理-黑马程序员"),n(t)])]),h,e("p",null,[e("a",d,[o("阿里巴巴官方出品"),n(t)])])])}const u=r(p,[["render",m],["__file","ebooks.html.vue"]]),k=JSON.parse('{"path":"/other/books/ebooks.html","title":"电子书资源汇总","lang":"zh-CN","frontmatter":{"title":"电子书资源汇总","date":"2022-07-25T00:00:00.000Z","keys":null,"category":["电子书"],"description":"1、springboot SpringBoot基本原理-黑马程序员 2、Nacos原理 阿里巴巴官方出品","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/books/ebooks.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"电子书资源汇总"}],["meta",{"property":"og:description","content":"1、springboot SpringBoot基本原理-黑马程序员 2、Nacos原理 阿里巴巴官方出品"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-07-25T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"电子书资源汇总\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-25T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":3,"title":"1、springboot","slug":"_1、springboot","link":"#_1、springboot","children":[]},{"level":3,"title":"2、Nacos原理","slug":"_2、nacos原理","link":"#_2、nacos原理","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.16,"words":47},"filePathRelative":"other/books/ebooks.md","localizedDate":"2022年7月25日","excerpt":"

    1、springboot

    \\n

    SpringBoot基本原理-黑马程序员

    \\n

    2、Nacos原理

    \\n

    阿里巴巴官方出品

    ","autoDesc":true}');export{u as comp,k as data}; +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as i,c as s,a as e,b as o,d as n}from"./app-_Oi5YZFn.js";const p={},c=e("h3",{id:"_1、springboot",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1、springboot"},[e("span",null,"1、springboot")])],-1),l={href:"https://ddns.chensina.cn:29000/afatpig/ebooks/Springboot.pdf",target:"_blank",rel:"noopener noreferrer"},h=e("h3",{id:"_2、nacos原理",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_2、nacos原理"},[e("span",null,"2、Nacos原理")])],-1),d={href:"https://ddns.chensina.cn:29000/afatpig/ebooks/NACOS.pdf",target:"_blank",rel:"noopener noreferrer"};function m(g,_){const t=a("ExternalLinkIcon");return i(),s("div",null,[c,e("p",null,[e("a",l,[o("SpringBoot基本原理-黑马程序员"),n(t)])]),h,e("p",null,[e("a",d,[o("阿里巴巴官方出品"),n(t)])])])}const u=r(p,[["render",m],["__file","ebooks.html.vue"]]),k=JSON.parse('{"path":"/other/books/ebooks.html","title":"电子书资源汇总","lang":"zh-CN","frontmatter":{"title":"电子书资源汇总","date":"2022-07-25T00:00:00.000Z","keys":null,"category":["电子书"],"description":"1、springboot SpringBoot基本原理-黑马程序员 2、Nacos原理 阿里巴巴官方出品","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/books/ebooks.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"电子书资源汇总"}],["meta",{"property":"og:description","content":"1、springboot SpringBoot基本原理-黑马程序员 2、Nacos原理 阿里巴巴官方出品"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-07-25T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"电子书资源汇总\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-25T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":3,"title":"1、springboot","slug":"_1、springboot","link":"#_1、springboot","children":[]},{"level":3,"title":"2、Nacos原理","slug":"_2、nacos原理","link":"#_2、nacos原理","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.16,"words":47},"filePathRelative":"other/books/ebooks.md","localizedDate":"2022年7月25日","excerpt":"

    1、springboot

    \\n

    SpringBoot基本原理-黑马程序员

    \\n

    2、Nacos原理

    \\n

    阿里巴巴官方出品

    ","autoDesc":true}');export{u as comp,k as data}; diff --git "a/assets/elasticSearch\346\223\215\344\275\234.html-B7hezSq2.js" "b/assets/elasticSearch\346\223\215\344\275\234.html-DivcwrqA.js" similarity index 99% rename from "assets/elasticSearch\346\223\215\344\275\234.html-B7hezSq2.js" rename to "assets/elasticSearch\346\223\215\344\275\234.html-DivcwrqA.js" index 5045a6887f..b00ea31618 100644 --- "a/assets/elasticSearch\346\223\215\344\275\234.html-B7hezSq2.js" +++ "b/assets/elasticSearch\346\223\215\344\275\234.html-DivcwrqA.js" @@ -1,4 +1,4 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as i,a as n,b as s,d as p,e as a}from"./app-CVMfKeWw.js";const c={},u=a(`

    1.索引库操作

    索引库就类似数据库表,mapping映射就类似表的结构。

    我们要向es中存储数据,必须先创建“库”和“表”

    1.1 Mapping映射属性

    mapping是对索引库中文档的约束,常见的mapping属性包括:

    • type:字段数据类型,常见的简单类型有:

      • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
      • keyword类型只能整体搜索,不支持搜索部分内容
      • 数值:long、integer、short、byte、double、float、
      • 布尔:boolean
      • 日期:date
      • 对象:object
    • index:是否创建索引,默认为true

    • analyzer:使用哪种分词器

    • properties:该字段的子字段

    例如以下json文档:

    {
    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as i,a as n,b as s,d as p,e as a}from"./app-_Oi5YZFn.js";const c={},u=a(`

    1.索引库操作

    索引库就类似数据库表,mapping映射就类似表的结构。

    我们要向es中存储数据,必须先创建“库”和“表”

    1.1 Mapping映射属性

    mapping是对索引库中文档的约束,常见的mapping属性包括:

    • type:字段数据类型,常见的简单类型有:

      • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
      • keyword类型只能整体搜索,不支持搜索部分内容
      • 数值:long、integer、short、byte、double、float、
      • 布尔:boolean
      • 日期:date
      • 对象:object
    • index:是否创建索引,默认为true

    • analyzer:使用哪种分词器

    • properties:该字段的子字段

    例如以下json文档:

    {
         "age": 21,
         "weight": 52.1,
         "isMarried": false,
    diff --git "a/assets/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html-Bl7vnRpA.js" "b/assets/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html-BDHhWiHk.js"
    similarity index 99%
    rename from "assets/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html-Bl7vnRpA.js"
    rename to "assets/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html-BDHhWiHk.js"
    index f24200b846..31fffdf082 100644
    --- "a/assets/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html-Bl7vnRpA.js"
    +++ "b/assets/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html-BDHhWiHk.js"
    @@ -1,4 +1,4 @@
    -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

    常规

    //定义一个函数
    +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

    常规

    //定义一个函数
     
     export const numberValidator = (rule: any, value: any, callback: any) => {
         //数字正则表达式
    diff --git "a/assets/elk\351\203\250\347\275\262.html-DF5G_BDL.js" "b/assets/elk\351\203\250\347\275\262.html-hYHFl7Qx.js"
    similarity index 99%
    rename from "assets/elk\351\203\250\347\275\262.html-DF5G_BDL.js"
    rename to "assets/elk\351\203\250\347\275\262.html-hYHFl7Qx.js"
    index 74697dea73..ad16c0c899 100644
    --- "a/assets/elk\351\203\250\347\275\262.html-DF5G_BDL.js"
    +++ "b/assets/elk\351\203\250\347\275\262.html-hYHFl7Qx.js"
    @@ -1,4 +1,4 @@
    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as i,a as n,b as s,d as t,e as p}from"./app-CVMfKeWw.js";const c={},u=p(`

    1. 目录结构

    elk
    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c as i,a as n,b as s,d as t,e as p}from"./app-_Oi5YZFn.js";const c={},u=p(`

    1. 目录结构

    elk
     ├── config
     │   ├── es
     │   │   ├── config
    diff --git a/assets/encrypt.html-BEDsFZkv.js b/assets/encrypt.html-BVOYksPN.js
    similarity index 95%
    rename from assets/encrypt.html-BEDsFZkv.js
    rename to assets/encrypt.html-BVOYksPN.js
    index 371ef983b9..fc4fceb0cd 100644
    --- a/assets/encrypt.html-BEDsFZkv.js
    +++ b/assets/encrypt.html-BVOYksPN.js
    @@ -1 +1 @@
    -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as a,a as e}from"./app-CVMfKeWw.js";const n={},c=e("h1",{id:"密码加密的文章",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#密码加密的文章"},[e("span",null,"密码加密的文章")])],-1),s=e("p",null,"实际的文章内容。",-1),r=e("p",null,"段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字。",-1),i=e("p",null,"段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字。",-1),l=[c,s,r,i];function d(_,m){return o(),a("div",null,l)}const f=t(n,[["render",d],["__file","encrypt.html.vue"]]),u=JSON.parse('{"path":"/guide/encrypt.html","title":"密码加密的文章","lang":"zh-CN","frontmatter":{"icon":"lock","category":["使用指南"],"tag":["文章加密"],"feed":false,"seo":false,"head":[]},"headers":[],"git":{"createdTime":1659087823000,"updatedTime":1659087823000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.52,"words":156},"filePathRelative":"guide/encrypt.md","localizedDate":"2022年7月29日"}');export{f as comp,u as data};
    +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as a,a as e}from"./app-_Oi5YZFn.js";const n={},c=e("h1",{id:"密码加密的文章",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#密码加密的文章"},[e("span",null,"密码加密的文章")])],-1),s=e("p",null,"实际的文章内容。",-1),r=e("p",null,"段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字。",-1),i=e("p",null,"段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字。",-1),l=[c,s,r,i];function d(_,m){return o(),a("div",null,l)}const f=t(n,[["render",d],["__file","encrypt.html.vue"]]),u=JSON.parse('{"path":"/guide/encrypt.html","title":"密码加密的文章","lang":"zh-CN","frontmatter":{"icon":"lock","category":["使用指南"],"tag":["文章加密"],"feed":false,"seo":false,"head":[]},"headers":[],"git":{"createdTime":1659087823000,"updatedTime":1659087823000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.52,"words":156},"filePathRelative":"guide/encrypt.md","localizedDate":"2022年7月29日"}');export{f as comp,u as data};
    diff --git a/assets/eventBus.html-DxETLIkG.js b/assets/eventBus.html-B0dTFe1w.js
    similarity index 98%
    rename from assets/eventBus.html-DxETLIkG.js
    rename to assets/eventBus.html-B0dTFe1w.js
    index 5eae39a7ef..2c87b605f2 100644
    --- a/assets/eventBus.html-DxETLIkG.js
    +++ b/assets/eventBus.html-B0dTFe1w.js
    @@ -1,4 +1,4 @@
    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as i}from"./app-CVMfKeWw.js";const a={},s=i(`

    1,安装mitt库

    npm install mitt
    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as i}from"./app-_Oi5YZFn.js";const a={},s=i(`

    1,安装mitt库

    npm install mitt
     

    2,在main.ts中引入mitt库

    import mitt from 'mitt'
     app.config.globalProperties.mittBus = mitt()
     

    3,触发事件

     const { proxy } = <any>getCurrentInstance();
    diff --git a/assets/fanType.html-DHpjy03Z.js b/assets/fanType.html-34EbXHhU.js
    similarity index 99%
    rename from assets/fanType.html-DHpjy03Z.js
    rename to assets/fanType.html-34EbXHhU.js
    index 7982f3afc5..13bece1c19 100644
    --- a/assets/fanType.html-DHpjy03Z.js
    +++ b/assets/fanType.html-34EbXHhU.js
    @@ -1,4 +1,4 @@
    -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c,a as s,b as n,d as l,e as a}from"./app-CVMfKeWw.js";const i={},r=a(`

    一,泛型

    泛型就是通过给类型传参,得到一个更加通用的类型,就像给函数传参一样。 如下我们得到一个通用的泛型类型 T1,通过传参,可以得到 T2 类型 string[]、T3 类型 number[]; T 是变量,我们可以用任意其他变量名代替他。

    
    +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c,a as s,b as n,d as l,e as a}from"./app-_Oi5YZFn.js";const i={},r=a(`

    一,泛型

    泛型就是通过给类型传参,得到一个更加通用的类型,就像给函数传参一样。 如下我们得到一个通用的泛型类型 T1,通过传参,可以得到 T2 类型 string[]、T3 类型 number[]; T 是变量,我们可以用任意其他变量名代替他。

    
     type T1<T> = T[]
     
     type T2 = T1<string> // string[]
    diff --git a/assets/fatal.html-CLx7xyLl.js b/assets/fatal.html-CuBowvDc.js
    similarity index 99%
    rename from assets/fatal.html-CLx7xyLl.js
    rename to assets/fatal.html-CuBowvDc.js
    index f1c602b24c..38dc350868 100644
    --- a/assets/fatal.html-CLx7xyLl.js
    +++ b/assets/fatal.html-CuBowvDc.js
    @@ -1,4 +1,4 @@
    -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as t,e as n}from"./app-CVMfKeWw.js";const s={},i=n(`

    如何解决 fatal: unable to access...的问题

    现象

    浏览器可正常访问github,终端无法clone

    确定是否是因为代理问题

    ## 查看git所有配置,检查是否使用了代理
    +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as t,e as n}from"./app-_Oi5YZFn.js";const s={},i=n(`

    如何解决 fatal: unable to access...的问题

    现象

    浏览器可正常访问github,终端无法clone

    确定是否是因为代理问题

    ## 查看git所有配置,检查是否使用了代理
     git config --list
     

    检查浏览器是否使用了代理,例如下图中就是典型的浏览器走了本地代理,否则正常的应该是解析到远程github的ip

    20221104113900

    下图就是未使用代理(当然不能排除他使用了透明代理的可能) 20221104114031

    解决问题

    如果是因为代理的问题导致无法clone,那么根据情况设置或者取消代理即可

    #设置代理,把代理服务器修改成自己的
     git config --global https.proxy http://127.0.0.1:1080
    diff --git a/assets/firewall.html-axsN7QwQ.js b/assets/firewall.html-Bcf4dLh0.js
    similarity index 99%
    rename from assets/firewall.html-axsN7QwQ.js
    rename to assets/firewall.html-Bcf4dLh0.js
    index aaf05d6e99..da7335fca2 100644
    --- a/assets/firewall.html-axsN7QwQ.js
    +++ b/assets/firewall.html-Bcf4dLh0.js
    @@ -1,4 +1,4 @@
    -import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as t,c as d,a as e,b as n,d as l,e as r}from"./app-CVMfKeWw.js";const c={},v=r(`

    集中常用的防火墙配置工具

    1. netfilter
    +import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as t,c as d,a as e,b as n,d as l,e as r}from"./app-_Oi5YZFn.js";const c={},v=r(`

    集中常用的防火墙配置工具

    1. netfilter
         集成在linux内核中
     2. iptables
         比较底层的,基于 Netfilter 
    diff --git a/assets/firewall.html-CDBaOBQ8.js b/assets/firewall.html-CnVk-61G.js
    similarity index 98%
    rename from assets/firewall.html-CDBaOBQ8.js
    rename to assets/firewall.html-CnVk-61G.js
    index 4684f19f68..898619ae90 100644
    --- a/assets/firewall.html-CDBaOBQ8.js
    +++ b/assets/firewall.html-CnVk-61G.js
    @@ -1,4 +1,4 @@
    -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as s,c as i,a as e,b as n,d as l,e as r}from"./app-CVMfKeWw.js";const o={},c={href:"https://www.xh86.me/?p=11324",target:"_blank",rel:"noopener noreferrer"},h=r(`

    一、防火墙简介

    pve有3个层级防火墙:

    1. 数据中心的防火墙
    2. 节点防火墙(主机防火墙)
    3. 虚拟机防火墙

    20240929173211

    1.1 数据中心防火墙

    数据中心,是由节点组成的一个集群。 可以说,数据中心防火墙,是专门处理集群流量的防火墙。

    ==只有数据中心防火墙开启,才能开启集群内的防火墙,否则单独开启vm的防火墙是无效的。== 它是一个总开关

    1.2 主机防火墙

    主机防火墙,只负责处理虚拟化服务器上的流量,比如一台pve服务器ip为192.168.1.2。

    那么这个防火墙,只会关注192.168.1.2的流量。下面的虚拟机流量是独立开的。

    这个防火墙的开关,不会影响vm的防火墙,所以要开启vm的防火墙,不必须开启这个防火墙,不同于数据中心防火墙。

    说的直白一点就是这个防火墙控制的就是pve那个debian系统本身,比如pve的web管理页面(8006端口)、ssh等

    1.3 虚拟机防火墙

    这个就是系统级别的防火墙了,和我们平时用的物理机防火墙就是一样的了

    二: 防火墙配置文件

    pve的防火墙配置文件在下面路径

    #数据中心防火墙
    +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as s,c as i,a as e,b as n,d as l,e as r}from"./app-_Oi5YZFn.js";const o={},c={href:"https://www.xh86.me/?p=11324",target:"_blank",rel:"noopener noreferrer"},h=r(`

    一、防火墙简介

    pve有3个层级防火墙:

    1. 数据中心的防火墙
    2. 节点防火墙(主机防火墙)
    3. 虚拟机防火墙

    20240929173211

    1.1 数据中心防火墙

    数据中心,是由节点组成的一个集群。 可以说,数据中心防火墙,是专门处理集群流量的防火墙。

    ==只有数据中心防火墙开启,才能开启集群内的防火墙,否则单独开启vm的防火墙是无效的。== 它是一个总开关

    1.2 主机防火墙

    主机防火墙,只负责处理虚拟化服务器上的流量,比如一台pve服务器ip为192.168.1.2。

    那么这个防火墙,只会关注192.168.1.2的流量。下面的虚拟机流量是独立开的。

    这个防火墙的开关,不会影响vm的防火墙,所以要开启vm的防火墙,不必须开启这个防火墙,不同于数据中心防火墙。

    说的直白一点就是这个防火墙控制的就是pve那个debian系统本身,比如pve的web管理页面(8006端口)、ssh等

    1.3 虚拟机防火墙

    这个就是系统级别的防火墙了,和我们平时用的物理机防火墙就是一样的了

    二: 防火墙配置文件

    pve的防火墙配置文件在下面路径

    #数据中心防火墙
     
     /etc/pve/firewall/cluster.fw
     
    diff --git a/assets/gitConflict.html-XWxwXqnJ.js b/assets/gitConflict.html-CD4vs1Ct.js
    similarity index 99%
    rename from assets/gitConflict.html-XWxwXqnJ.js
    rename to assets/gitConflict.html-CD4vs1Ct.js
    index d13f696aa4..7f5df91870 100644
    --- a/assets/gitConflict.html-XWxwXqnJ.js
    +++ b/assets/gitConflict.html-CD4vs1Ct.js
    @@ -1 +1 @@
    -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as i,e as n}from"./app-CVMfKeWw.js";const p={},o=n('

    一,git冲突出现的原因及解决方案

    简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。

    1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。

    • 修改component.html本地文件第11行,既没有add,也没有commit
    • 然后修改远程component.html远程文件第14行
    • 本地执行git pull,拉取代码提示,同时修改component.html,提示发生冲突

    image-20220723162517914

    此时,有以下3种解决办法。

    1. 丢弃本地的发生冲突的文件(git checkout -- component.html),然后重新git pull代码

    2. 按照提示,先commit本地修改,然后再次执行pull操作,得到如下丑陋的提交记录。此时本地commit记录便会显示如下的丑陋的commit记录。由于发生了冲突,但是由于是修改的不同行,git直接帮我们把本地修改和远程修改直接合并了,并产生了一个新的提交节点 Merge branch.....

      img

    3. 采用git stash先暂存本地代码->然后git pull 代码->git stash pop 把暂存文件恢复->git add xxx->git commit -m “xxx”-> git push origin master

      本地没有commit的情况下此时采用第3种解决方式会更好,commit记录不会产生一个丑陋的合并节点。

    1.2 本地代码已经commit但是还没有push. 有时候经常会忘记先pull远程代码,直接push代码.如果本地和远程同时修改了同一文件,会产生提示

    image-20220723172630686

    看到这个提示:远程代码有修改,本地代码先要合并远程的代码才可以push.

    不敢随便点按钮,毕竟不知道编辑器不知道隐藏了什么诡异的操作,给代码搞丢了。

    我的操作,先关闭提示框。先执行git pull 同步远程代码,此时编辑器提示我有文件发生了冲突。

    image-20220723173039634

    点击Merge,选择手动合并冲突的代码。此时也会产生一个合并节点。

    image-20220723173207361

    然后git add ->git commit -> git push.

    1.3 就算本地和远程没有同时修改同一个文件。只要出现本地分支落后远程分支,就会出现该提示 push rejected的提示,此时我们就要先pull远程代码,然后再push代码即可。

    例如,我在远程新增一个文件,本地没有pull代码,此时本地分支是落后于远程分支的。如果你此时不知情,commit一些代码并准备push代码到远程会收到如下提示:

    image-20220726104412089

    此时我的操作是,依旧是关闭这个弹框,执行git pull 同步远程代码,这时会产生一个丑陋合并节点,因为远程和本地其实并没有发生冲突,这个merge节点也不会有任何提交内容。

    image-20220726105156403

    image-20220726105424098

    ',22),a=[o];function c(g,m){return e(),i("div",null,a)}const d=t(p,[["render",c],["__file","gitConflict.html.vue"]]),r=JSON.parse('{"path":"/other/git/gitConflict.html","title":"git冲突出现的原因","lang":"zh-CN","frontmatter":{"title":"git冲突出现的原因","date":"2022-03-09T16:57:01.000Z","author":"zxf","category":["git 操作"],"tag":["必会"],"description":"一,git冲突出现的原因及解决方案 简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。 1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。 修改component.html本地文件第11行,既没有add,也没有commit 然后修改远程component.html...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/git/gitConflict.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"git冲突出现的原因"}],["meta",{"property":"og:description","content":"一,git冲突出现的原因及解决方案 简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。 1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。 修改component.html本地文件第11行,既没有add,也没有commit 然后修改远程component.html..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/image-20220723162517914.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"zxf"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:published_time","content":"2022-03-09T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"git冲突出现的原因\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220723162517914.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/clipboard.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220723172630686.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220723173039634.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220723173207361.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220726104412089.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220726105156403.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220726105424098.png\\"],\\"datePublished\\":\\"2022-03-09T16:57:01.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"zxf\\"}]}"]]},"headers":[{"level":2,"title":"一,git冲突出现的原因及解决方案","slug":"一-git冲突出现的原因及解决方案","link":"#一-git冲突出现的原因及解决方案","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":2.72,"words":815},"filePathRelative":"other/git/gitConflict.md","localizedDate":"2022年3月9日","excerpt":"

    一,git冲突出现的原因及解决方案

    \\n

    简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。

    \\n

    1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。

    \\n
      \\n
    • 修改component.html本地文件第11行,既没有add,也没有commit
    • \\n
    • 然后修改远程component.html远程文件第14行
    • \\n
    • 本地执行git pull,拉取代码提示,同时修改component.html,提示发生冲突
    • \\n
    ","autoDesc":true}');export{d as comp,r as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as i,e as n}from"./app-_Oi5YZFn.js";const p={},o=n('

    一,git冲突出现的原因及解决方案

    简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。

    1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。

    • 修改component.html本地文件第11行,既没有add,也没有commit
    • 然后修改远程component.html远程文件第14行
    • 本地执行git pull,拉取代码提示,同时修改component.html,提示发生冲突

    image-20220723162517914

    此时,有以下3种解决办法。

    1. 丢弃本地的发生冲突的文件(git checkout -- component.html),然后重新git pull代码

    2. 按照提示,先commit本地修改,然后再次执行pull操作,得到如下丑陋的提交记录。此时本地commit记录便会显示如下的丑陋的commit记录。由于发生了冲突,但是由于是修改的不同行,git直接帮我们把本地修改和远程修改直接合并了,并产生了一个新的提交节点 Merge branch.....

      img

    3. 采用git stash先暂存本地代码->然后git pull 代码->git stash pop 把暂存文件恢复->git add xxx->git commit -m “xxx”-> git push origin master

      本地没有commit的情况下此时采用第3种解决方式会更好,commit记录不会产生一个丑陋的合并节点。

    1.2 本地代码已经commit但是还没有push. 有时候经常会忘记先pull远程代码,直接push代码.如果本地和远程同时修改了同一文件,会产生提示

    image-20220723172630686

    看到这个提示:远程代码有修改,本地代码先要合并远程的代码才可以push.

    不敢随便点按钮,毕竟不知道编辑器不知道隐藏了什么诡异的操作,给代码搞丢了。

    我的操作,先关闭提示框。先执行git pull 同步远程代码,此时编辑器提示我有文件发生了冲突。

    image-20220723173039634

    点击Merge,选择手动合并冲突的代码。此时也会产生一个合并节点。

    image-20220723173207361

    然后git add ->git commit -> git push.

    1.3 就算本地和远程没有同时修改同一个文件。只要出现本地分支落后远程分支,就会出现该提示 push rejected的提示,此时我们就要先pull远程代码,然后再push代码即可。

    例如,我在远程新增一个文件,本地没有pull代码,此时本地分支是落后于远程分支的。如果你此时不知情,commit一些代码并准备push代码到远程会收到如下提示:

    image-20220726104412089

    此时我的操作是,依旧是关闭这个弹框,执行git pull 同步远程代码,这时会产生一个丑陋合并节点,因为远程和本地其实并没有发生冲突,这个merge节点也不会有任何提交内容。

    image-20220726105156403

    image-20220726105424098

    ',22),a=[o];function c(g,m){return e(),i("div",null,a)}const d=t(p,[["render",c],["__file","gitConflict.html.vue"]]),r=JSON.parse('{"path":"/other/git/gitConflict.html","title":"git冲突出现的原因","lang":"zh-CN","frontmatter":{"title":"git冲突出现的原因","date":"2022-03-09T16:57:01.000Z","author":"zxf","category":["git 操作"],"tag":["必会"],"description":"一,git冲突出现的原因及解决方案 简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。 1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。 修改component.html本地文件第11行,既没有add,也没有commit 然后修改远程component.html...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/git/gitConflict.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"git冲突出现的原因"}],["meta",{"property":"og:description","content":"一,git冲突出现的原因及解决方案 简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。 1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。 修改component.html本地文件第11行,既没有add,也没有commit 然后修改远程component.html..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/image-20220723162517914.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"zxf"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:published_time","content":"2022-03-09T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"git冲突出现的原因\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220723162517914.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/clipboard.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220723172630686.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220723173039634.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220723173207361.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220726104412089.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220726105156403.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220726105424098.png\\"],\\"datePublished\\":\\"2022-03-09T16:57:01.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"zxf\\"}]}"]]},"headers":[{"level":2,"title":"一,git冲突出现的原因及解决方案","slug":"一-git冲突出现的原因及解决方案","link":"#一-git冲突出现的原因及解决方案","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":2.72,"words":815},"filePathRelative":"other/git/gitConflict.md","localizedDate":"2022年3月9日","excerpt":"

    一,git冲突出现的原因及解决方案

    \\n

    简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。

    \\n

    1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。

    \\n
      \\n
    • 修改component.html本地文件第11行,既没有add,也没有commit
    • \\n
    • 然后修改远程component.html远程文件第14行
    • \\n
    • 本地执行git pull,拉取代码提示,同时修改component.html,提示发生冲突
    • \\n
    ","autoDesc":true}');export{d as comp,r as data}; diff --git a/assets/gitRebase.html-Bh0TFroo.js b/assets/gitRebase.html-DF3bIliz.js similarity index 99% rename from assets/gitRebase.html-Bh0TFroo.js rename to assets/gitRebase.html-DF3bIliz.js index 15b3adacf4..4117c427ec 100644 --- a/assets/gitRebase.html-Bh0TFroo.js +++ b/assets/gitRebase.html-DF3bIliz.js @@ -1,4 +1,4 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a,e as n}from"./app-CVMfKeWw.js";const i={},s=n(`

    一,用于合并当前分支的多个commit记录

    应用场景,如下第2-4次提交是对同一功能的代码提交记录,完全可以合并成一次提交记录。这个时候rebase就很有用了。

    image-20220724180044950

    1. 找到想要合并的commit, 使用rebase -i

      git rebase -i bd0d758(第一次提交的commitId)
      +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a,e as n}from"./app-_Oi5YZFn.js";const i={},s=n(`

      一,用于合并当前分支的多个commit记录

      应用场景,如下第2-4次提交是对同一功能的代码提交记录,完全可以合并成一次提交记录。这个时候rebase就很有用了。

      image-20220724180044950

      1. 找到想要合并的commit, 使用rebase -i

        git rebase -i bd0d758(第一次提交的commitId)
         

        注意 git rebase -i [startPonit] [endPoint]

        前开后闭 区间 这里的 [startPonit] 是指需要合并的commit的前一个commit (即当前示例中的 “bd0d758: 第一次提交”)。 因为, 三个commit肯定要基于上一个commit合并成了新的commit。 谨慎使用[endPoint] 省略, 即默认表示从起始commit一直到最后一个,但是一旦你填写了, 则表示 [endPoint]后面的commit全部不要了!

      image-20220724181559992

      image-20220724181803651

      2,进入Interact交互界面

      image-20220724181831357

      说明

      • 最上面三行, 就是刚刚选中的三个commit, 按时间顺序依次往下排序(和git log的展示顺序是反的, 大家查看的时候要注意)
      • 前面的三个Pick 其实就是下面 Commands展示的7种命令中的第一个p, 也就是使用commit。

      3,使用s命令 合并到上一个commit

      image-20220724182138971

      4.修改commit记录

      image-20220724182435924

      再此执行git log 可以发现234此提交合并为一个了

      image-20220724182807371

      image-20220724182912368

      二、使用rebase替代merge合并分支

      2.1 使用merge合并分支代码

      注意:当只有dev分支有提交,master分支没有任何提交,dev分支merge到master分支上,依旧是保持一条直线,也没有多余的提交。

      image-20220724212609039

      给master新增两个提交,dev分支与master分支立即就会不重合,不在一条直线上。

      image-20220724213219850

      再给dev分支新增一个commit "dev新增代码1"可以看出提交节点是按事件排序的,可以看到dev分支的节点显示再master前面。

      image-20220724213304415

      3,切换到master分支,直接merge dev

      image-20220724213729960

      提示产生冲突,因为master前两次提交,与dev最后一次提交都更改过了readme.txt文件,所以合并分支会产生冲突。

      image-20220724214028599

      合并代码,->commit "合并分支dev到master上面"

      image-20220724214057554

      git 分支就显示如下图所示

      !(https://ddns.chensina.cn:29000/afatpig/blog/image-20220724214320960.png)

      image-20220724214614292

      总之,merge dev分支到master上会出现以下结果:

      1. 会保留所有的commit(hashId不变)

      2. 按提交顺序排序

      3. 产生新的commit点(Merge branch ‘XXX’ into develop)(这里的我的显是合并分支dev到master上面)

      2.2 使用rebase合并dev分支到master上

      1,切换当前分支为dev

      git switch  dev
       ~~~\`
       
      diff --git a/assets/gitwork.html-DCYwt7rE.js b/assets/gitwork.html-BenGZ5gF.js
      similarity index 99%
      rename from assets/gitwork.html-DCYwt7rE.js
      rename to assets/gitwork.html-BenGZ5gF.js
      index 28fbe32739..72b149bea6 100644
      --- a/assets/gitwork.html-DCYwt7rE.js
      +++ b/assets/gitwork.html-BenGZ5gF.js
      @@ -1 +1 @@
      -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as i,e as n}from"./app-CVMfKeWw.js";const o={},r=n('

      一,基本概念

      • 工作区:就是你在电脑里能看到的目录。
      • 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
      • 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。

      下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:

      image-20220803144042656

      • 图中左侧为工作区,右侧为版本库。在版本库中标记为 "index" 的区域是暂存区(stage/index),标记为 "master" 的是 master 分支所代表的目录树。
      • 图中我们可以看出此时 "HEAD" 实际是指向 master 分支的一个"游标"。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
      • 图中的 objects 标识的区域为 Git 的对象库,实际位于 ".git/objects" 目录下,里面包含了创建的各种对象及内容。
      • 当对工作区修改(或新增)的文件执行 ==git add 命令时,暂存区的目录树被更新== ,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
      • 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
      • 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
      • 当执行 git rm --cached <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。
      • 当执行 git checkout . 或者 git checkout -- <file> 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。
      • 当执行 git checkout HEAD . 或者 git checkout HEAD <file> 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。
      ',5),a=[r];function g(s,l){return e(),i("div",null,a)}const p=t(o,[["render",g],["__file","gitwork.html.vue"]]),d=JSON.parse('{"path":"/other/git/gitwork.html","title":"git工作区、暂存区、和版本库","lang":"zh-CN","frontmatter":{"title":"git工作区、暂存区、和版本库","date":"2022-03-09T16:57:01.000Z","author":"zxf","category":["git 操作"],"tag":["必会"],"description":"一,基本概念 工作区:就是你在电脑里能看到的目录。 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。 下面这个图展示了工作区、版本库中的暂存区和版本库之间...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/git/gitwork.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"git工作区、暂存区、和版本库"}],["meta",{"property":"og:description","content":"一,基本概念 工作区:就是你在电脑里能看到的目录。 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。 下面这个图展示了工作区、版本库中的暂存区和版本库之间..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/image-20220803144042656.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"zxf"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:published_time","content":"2022-03-09T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"git工作区、暂存区、和版本库\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220803144042656.png\\"],\\"datePublished\\":\\"2022-03-09T16:57:01.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"zxf\\"}]}"]]},"headers":[{"level":3,"title":"一,基本概念","slug":"一-基本概念","link":"#一-基本概念","children":[]}],"git":{"createdTime":1659509231000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":5},{"name":"zhu","email":"819508408@qq.com","commits":2},{"name":"zhuxiaofang1234","email":"819508408@qq.com","commits":1}]},"readingTime":{"minutes":2.15,"words":645},"filePathRelative":"other/git/gitwork.md","localizedDate":"2022年3月9日","excerpt":"

      一,基本概念

      \\n
        \\n
      • 工作区:就是你在电脑里能看到的目录。
      • \\n
      • 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
      • \\n
      • 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。
      • \\n
      \\n

      下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:

      \\n

      \\"image-20220803144042656\\"

      ","autoDesc":true}');export{p as comp,d as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as i,e as n}from"./app-_Oi5YZFn.js";const o={},r=n('

      一,基本概念

      • 工作区:就是你在电脑里能看到的目录。
      • 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
      • 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。

      下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:

      image-20220803144042656

      • 图中左侧为工作区,右侧为版本库。在版本库中标记为 "index" 的区域是暂存区(stage/index),标记为 "master" 的是 master 分支所代表的目录树。
      • 图中我们可以看出此时 "HEAD" 实际是指向 master 分支的一个"游标"。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
      • 图中的 objects 标识的区域为 Git 的对象库,实际位于 ".git/objects" 目录下,里面包含了创建的各种对象及内容。
      • 当对工作区修改(或新增)的文件执行 ==git add 命令时,暂存区的目录树被更新== ,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
      • 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
      • 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
      • 当执行 git rm --cached <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。
      • 当执行 git checkout . 或者 git checkout -- <file> 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。
      • 当执行 git checkout HEAD . 或者 git checkout HEAD <file> 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。
      ',5),a=[r];function g(s,l){return e(),i("div",null,a)}const p=t(o,[["render",g],["__file","gitwork.html.vue"]]),d=JSON.parse('{"path":"/other/git/gitwork.html","title":"git工作区、暂存区、和版本库","lang":"zh-CN","frontmatter":{"title":"git工作区、暂存区、和版本库","date":"2022-03-09T16:57:01.000Z","author":"zxf","category":["git 操作"],"tag":["必会"],"description":"一,基本概念 工作区:就是你在电脑里能看到的目录。 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。 下面这个图展示了工作区、版本库中的暂存区和版本库之间...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/git/gitwork.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"git工作区、暂存区、和版本库"}],["meta",{"property":"og:description","content":"一,基本概念 工作区:就是你在电脑里能看到的目录。 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。 下面这个图展示了工作区、版本库中的暂存区和版本库之间..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/image-20220803144042656.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"zxf"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:published_time","content":"2022-03-09T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"git工作区、暂存区、和版本库\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/image-20220803144042656.png\\"],\\"datePublished\\":\\"2022-03-09T16:57:01.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"zxf\\"}]}"]]},"headers":[{"level":3,"title":"一,基本概念","slug":"一-基本概念","link":"#一-基本概念","children":[]}],"git":{"createdTime":1659509231000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":5},{"name":"zhu","email":"819508408@qq.com","commits":2},{"name":"zhuxiaofang1234","email":"819508408@qq.com","commits":1}]},"readingTime":{"minutes":2.15,"words":645},"filePathRelative":"other/git/gitwork.md","localizedDate":"2022年3月9日","excerpt":"

      一,基本概念

      \\n
        \\n
      • 工作区:就是你在电脑里能看到的目录。
      • \\n
      • 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
      • \\n
      • 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。
      • \\n
      \\n

      下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:

      \\n

      \\"image-20220803144042656\\"

      ","autoDesc":true}');export{p as comp,d as data}; diff --git a/assets/home.html-CdI4hI0D.js b/assets/home.html-Ds_ny-Cd.js similarity index 52% rename from assets/home.html-CdI4hI0D.js rename to assets/home.html-Ds_ny-Cd.js index 2eb27c0e3c..c2ad0ab3db 100644 --- a/assets/home.html-CdI4hI0D.js +++ b/assets/home.html-Ds_ny-Cd.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const i={};function n(r,p){return t(),o("div")}const s=e(i,[["render",n],["__file","home.html.vue"]]),m=JSON.parse('{"path":"/home.html","title":"Home","lang":"zh-CN","frontmatter":{"home":true,"icon":"home","title":"Home","heroImage":"/logo.svg","heroText":"ChenSino","tagline":"小白进阶之路","actions":[{"text":"跟着Sino学Java 💕","link":"/java/","type":"primary"},{"text":"跟着Sino学前端 💗","link":"/frontweb/"}],"features":[{"title":"Markdown 语法","icon":"markdown","details":"MarkDown官方教程","link":"https://markdown.com.cn/"},{"title":"Vue3文档","icon":"vue","details":"Vue3中文文档(预览版)","link":"https://staging-cn.vuejs.org/"},{"title":"VuePress2文档","icon":"vue","details":"Vue 驱动的静态网站生成器","link":"https://v2.vuepress.vuejs.org/zh/"},{"title":"vuepress-theme-hope文档","icon":"vue","details":"一个具有强大功能的 vuepress 主题✨","link":"https://vuepress-theme-hope.github.io/v2/zh/"},{"title":"TypeScript文档","icon":"typescript","details":"typescript民间文档","link":"http://ts.xcatliu.com/introduction/index.html"},{"title":"TypeScript官方文档","icon":"typescript","details":"typescript官方文档","link":"https://typescript.bootcss.com/basic-types.html"},{"title":"优秀博主-victorfengming","icon":"typescript","details":"web开发优质博客","link":"https://victorfengming.gitee.io/index/gitbook/"},{"title":"优秀博主-pdai","icon":"typescript","details":"web开发优质博客","link":"https://pdai.tech"}],"copyright":false,"footer":"MIT Licensed | Copyright © 2019-present Mr.Hope","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/home.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Home"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2023-01-29T08:23:34.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:modified_time","content":"2023-01-29T08:23:34.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Home\\"}"]]},"headers":[],"git":{"createdTime":1659087823000,"updatedTime":1674980614000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":4},{"name":"chenkun","email":"462488588@qq.com","commits":3},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.66,"words":199},"filePathRelative":"home.md","localizedDate":"2022年7月29日","excerpt":""}');export{s as comp,m as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const i={};function n(r,a){return t(),o("div")}const s=e(i,[["render",n],["__file","home.html.vue"]]),m=JSON.parse('{"path":"/home.html","title":"Home","lang":"zh-CN","frontmatter":{"home":true,"icon":"home","title":"Home","heroImage":"/logo.svg","heroText":"ChenSino","tagline":"小白进阶之路","actions":[{"text":"跟着Sino学Java 💕","link":"/java/","type":"primary"},{"text":"跟着Sino学前端 💗","link":"/frontweb/"}],"features":[{"title":"Markdown 语法","icon":"markdown","details":"MarkDown官方教程","link":"https://markdown.com.cn/"},{"title":"Vue3文档","icon":"vue","details":"Vue3中文文档(预览版)","link":"https://staging-cn.vuejs.org/"},{"title":"VuePress2文档","icon":"vue","details":"Vue 驱动的静态网站生成器","link":"https://v2.vuepress.vuejs.org/zh/"},{"title":"vuepress-theme-hope文档","icon":"vue","details":"一个具有强大功能的 vuepress 主题✨","link":"https://vuepress-theme-hope.github.io/v2/zh/"},{"title":"TypeScript文档","icon":"typescript","details":"typescript民间文档","link":"http://ts.xcatliu.com/introduction/index.html"},{"title":"TypeScript官方文档","icon":"typescript","details":"typescript官方文档","link":"https://typescript.bootcss.com/basic-types.html"},{"title":"优秀博主-victorfengming","icon":"typescript","details":"web开发优质博客","link":"https://victorfengming.gitee.io/index/gitbook/"},{"title":"优秀博主-pdai","icon":"typescript","details":"web开发优质博客","link":"https://pdai.tech"}],"copyright":false,"footer":"鄂ICP备2024079959号-1","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/home.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Home"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-02T13:30:46.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:modified_time","content":"2024-11-02T13:30:46.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Home\\"}"]]},"headers":[],"git":{"createdTime":1659087823000,"updatedTime":1730554246000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":4},{"name":"chenkun","email":"462488588@qq.com","commits":4},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.66,"words":199},"filePathRelative":"home.md","localizedDate":"2022年7月29日","excerpt":""}');export{s as comp,m as data}; diff --git a/assets/import.html-DtYCD-Aq.js b/assets/import.html-CNG84pM6.js similarity index 99% rename from assets/import.html-DtYCD-Aq.js rename to assets/import.html-CNG84pM6.js index bc131de49d..8cab2a3619 100644 --- a/assets/import.html-DtYCD-Aq.js +++ b/assets/import.html-CNG84pM6.js @@ -1,4 +1,4 @@ -import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as e,c as p,a as n,b as o,d as c,e as l}from"./app-CVMfKeWw.js";const i={},u=n("h2",{id:"_1-import介绍",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1-import介绍"},[n("span",null,"1. import介绍")])],-1),r={href:"https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Importing_Dependencies",target:"_blank",rel:"noopener noreferrer"},d=l(`

      maven中的import是解决maven只能单个继承的问题,有时候我们的maven项目已经有了一个公司所自定义parent,但是我们想引入另一个项目中的dependencyManagement,此时可以用import,可以理解为把另一个项目中的dependencyManagement内容直接复制到本项目。

      2. 使用方式

      scope=import只能用在dependencyManagement并且type必须为pom,作用就是把另一个项目中dependencyManagement内容直接复制到本项目。以下就是一个最经典的使用方式,把springboot的版本管理直接引入本项目,在idea工具中可以直接点开看看里面管理了很多依赖,对每个依赖设置了version,以后在子项目引入依赖就不要version了。不仅仅springboot家族本身项目可以这样引入,包括其他依赖,只要在spring-boot-dependencies定义了的都可以无需版本引入,我在实际项目开发时有一次导入jedis就遇到过依赖问题,起初我是手动指定jedis版本,一直报错class undefine,后来去掉版本号,直接用spring-boot-dependencies中的就解决了问题。

      <dependencyManagement>
      +import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as t,o as e,c as p,a as n,b as o,d as c,e as l}from"./app-_Oi5YZFn.js";const i={},u=n("h2",{id:"_1-import介绍",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1-import介绍"},[n("span",null,"1. import介绍")])],-1),r={href:"https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Importing_Dependencies",target:"_blank",rel:"noopener noreferrer"},d=l(`

      maven中的import是解决maven只能单个继承的问题,有时候我们的maven项目已经有了一个公司所自定义parent,但是我们想引入另一个项目中的dependencyManagement,此时可以用import,可以理解为把另一个项目中的dependencyManagement内容直接复制到本项目。

      2. 使用方式

      scope=import只能用在dependencyManagement并且type必须为pom,作用就是把另一个项目中dependencyManagement内容直接复制到本项目。以下就是一个最经典的使用方式,把springboot的版本管理直接引入本项目,在idea工具中可以直接点开看看里面管理了很多依赖,对每个依赖设置了version,以后在子项目引入依赖就不要version了。不仅仅springboot家族本身项目可以这样引入,包括其他依赖,只要在spring-boot-dependencies定义了的都可以无需版本引入,我在实际项目开发时有一次导入jedis就遇到过依赖问题,起初我是手动指定jedis版本,一直报错class undefine,后来去掉版本号,直接用spring-boot-dependencies中的就解决了问题。

      <dependencyManagement>
           <dependencies>
               <dependency>
                   <groupId>org.springframework.boot</groupId>
      diff --git "a/assets/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html-qDdBmZbJ.js" "b/assets/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html-Disuf4MT.js"
      similarity index 99%
      rename from "assets/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html-qDdBmZbJ.js"
      rename to "assets/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html-Disuf4MT.js"
      index ecc959a89e..c267945fde 100644
      --- "a/assets/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html-qDdBmZbJ.js"
      +++ "b/assets/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html-Disuf4MT.js"
      @@ -1 +1 @@
      -import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as i,c as e,e as a}from"./app-CVMfKeWw.js";const n={},p=a('

      即时通信需求文档

      1. 概述

      1.1 项目背景

      随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。

      1.2 项目目标

      开发一款满足万人级别同时在线的即时通信软件,专门为医院内的医生、护士、行政人员等设计。该软件旨在提升院内信息传递的效率,确保信息的及时性和安全性, 从而提高医疗服务的质量和工作效率。

      1.3 用户群体

      • 医生:科室主任、主治医师、住院医师等。
      • 护士:护士长、责任护士、实习护士等。
      • 行政人员:医院管理层、科室秘书、后勤人员等。

      2. 功能需求

      2.1 基础功能

      2.1.1 单聊

      1. 消息发送与接收

      • 文本消息:

        • 用户能够发送和接收文字消息。
        • 支持多行文本,自动换行显示。
        • 支持文字消息的格式化(如粗体、斜体、链接)。
      • 语音消息:

        • 用户能够录制、发送和接收语音消息。
        • 支持语音消息的播放、暂停、停止、重听功能。
        • 显示语音消息的时长和发送/接收状态。
      • 语音通话

        • 用户能够发起和接受语音通话、挂断语音。
        • 支持语音通话的开关麦克风、开关静音、免提功能。
      • 视频聊天

        • 用户能够发起和接受视频通话、挂断视频。
        • 支持视频通话的开关摄像头、开关麦克风、开关静音、免提功能。
      • 视频消息:

        • 用户能够录制并发送视频消息。
        • 支持视频消息的播放、暂停、停止。
        • 显示视频消息的时长和缩略图。
      • 图片和文件:

        • 用户能够发送和接收图片、音频文件、文档等。
        • 支持查看、下载、分享图片和文件。
        • 支持图片的预览、放大和缩小功能。
      • 消息状态:

        • 显示消息的发送状态(如已发送、已接收、已读)。
        • 支持消息的发送时间和接收时间显示。

      2. 聊天界面

      • 聊天窗口:

        • 支持显示完整的聊天记录,自动加载历史消息。
        • 支持消息的上下滚动查看。
        • 显示对方和自己的头像、昵称及状态(如在线/离线)。
      • 消息气泡:

        • 消息以气泡形式显示,发送和接收消息使用不同的气泡样式。
        • 支持长按消息气泡进行操作(如复制、转发、删除)。
      • 聊天记录:

        • 支持按日期、联系人等条件搜索聊天记录。
        • 支持聊天记录的备份和恢复。

      3. 消息互动

      • 回复和引用:

        • 支持对消息进行回复和引用功能。
        • 被回复的消息显示为线程或嵌套形式。
      • 转发消息:

        • 支持将消息转发至其他聊天窗口或联系人。
        • 支持批量转发功能。
      • 表情和贴图:

        • 支持发送和接收表情、贴图。
      • 消息撤回和删除:

        • 支持在一定时间内撤回已发送的消息。
        • 支持删除聊天记录(包括单条消息和整个聊天记录)。

      4. 通知和提醒

      • 消息通知:
        • 支持新消息的推送通知(包括声音、震动、横幅)。
        • 支持自定义通知设置(如通知声音、提醒方式)。
      • 聊天提示:
        • 支持未读消息的提示(如红点、消息提醒)。

      5. 离线消息

      • 离线消息接收:

        • 用户在离线状态时能够接收并查看离线期间的消息。
        • 支持离线消息的同步显示。
      • 消息同步:

        • 支持消息在不同设备之间的同步(如手机、电脑)。

      2.1.2 群聊

      用户可以创建群聊,邀请其他用户加入,方便科室或项目组内部的多人沟通。

      1. 群组创建与管理

      • 创建群组:

        • 用户能够创建新的群组,并设置群组名称、头像、简介。
        • 提供选择群组类型(如公开、私密)的选项。
      • 群组邀请:

        • 群主和管理员能够邀请联系人加入群组。
        • 支持通过邀请链接、二维码等方式邀请用户。
      • 群组设置:

        • 群主能够修改群组的基本信息(名称、头像、简介)。
        • 群主和管理员设置群公告。
      • 成员管理:

        • 群主和管理员能够添加、移除群组成员。
      • 权限管理

        • 群主能够设置群成员的权限级别(普通成员、管理员)。
        • 群主可解散群组,转让群主。
        • 除群主外的所有成员可以退群。
        • 非群内成员可以申请加入,需要管理审批通过方可进群
      • 加入群组

        • 用户搜索群组,选择群组,点击申请加入

      2. 群组聊天

      • 消息发送:

        • 支持在群聊中发送文字、语音、图片、视频和文件。
        • 支持群内消息的“@”功能,提醒特定成员。
      • 消息显示:

        • 支持显示群聊中每条消息的发送者头像、昵称、时间等。
        • 消息气泡以不同颜色标识发送者和接收者。
      • 消息通知:

        • 支持群聊消息的推送通知。
        • 用户能够设置接收所有消息、仅接收@消息或静音通知。
      • 消息管理:

        • 支持查看消息的发送状态(已发送、已接收、已读)。
        • 支持消息的回复和引用功能。
      • 群聊搜索:

        • 支持按关键词、发送者、日期等搜索群聊记录。
        • 提供搜索结果高亮显示功能。
      • 消息撤回:

        • 支持在一定时间内撤回已发送的群聊消息。
      • 历史消息

        • 群默认显示最近的历史消息
        • 可根据关键字、时间段搜索群消息

      3. 群组互动功能

      • 群成员列表:

        • 显示群组成员的列表,包括头像和昵称。
        • 支持查看成员的在线状态和活动情况。
      • 群内互动:

        • 支持群成员之间的私聊功能。
        • 支持在群聊中进行投票、问卷调查等互动功能。
      • 群组公告:

        • 群主能够发布群组公告,所有成员都能看到。
        • 支持公告的编辑和删除。
      • 文件共享与管理:

        • 支持在群组内上传、下载和分享文件。
        • 支持群组文件的分类和管理(如按日期、类型)。

      2.1.3 消息通知

      针对重要信息进行通知提醒,用户可以在设置中调整通知方式(如铃声、震动、弹窗等)。

      • 支持紧急消息提醒功能,必要时医生可以发送高优先级消息,并强制提醒对方。
      • 支持离线消息推送功能,即使用户不在线也能接收到消息提醒。
      • 广播通知

      2.1.4 好友管理

      1. 好友添加和删除

      • 添加好友:

        • 通过搜索:用户可以通过搜索功能查找和添加好友。搜索方式包括用户名、手机号、邮箱等。
        • 通过二维码:用户可以通过扫描对方的二维码添加好友。
        • 通过邀请链接:用户可以发送邀请链接给他人,通过链接添加好友。
        • 通过联系人导入:支持从手机或其他社交网络导入联系人并添加为好友。
      • 删除好友:

        • 用户可以从好友列表中删除好友。
        • 删除好友后,双方将不再互相显示在线状态和消息。
      • 好友请求管理:

        • 接收请求:用户能够接收好友请求,查看请求者的基本信息(如头像、昵称)。
        • 接受/拒绝请求:用户可以选择接受或拒绝好友请求。
        • 请求记录:支持查看已发送和已接收的好友请求记录。

      2. 好友列表管理

      • 好友列表显示:

        • 显示好友的基本信息,包括头像、昵称、在线状态。
        • 支持按字母排序、按最近聊天排序或按自定义分组查看好友列表。
      • 好友备注:

        • 用户可以为好友设置备注,以便于识别和管理。
        • 支持编辑和删除好友备注。

      3. 好友互动

      • 查看好友状态:
        • 用户可以查看好友的在线状态(如在线、离线、隐身)。
        • 显示好友的最近活动状态(如最后登录时间、最近聊天记录)。

      4. 隐私和安全设置

      • 隐私设置:

        • 谁可以联系我:用户可以设置谁可以向我发送消息(如所有人、好友、仅群组成员)。
        • 谁可以查看我的状态:用户可以设置谁可以查看自己的在线状态、最后登录时间。
        • 谁可以查看我的资料:设置好友或非好友的资料查看权限。
      • 拉黑与举报: 拉黑好友:用户可以将某些好友拉黑,拉黑后对方将无法向用户发送消息和查看用户状态。 举报好友:用户可以举报好友的异常行为或违规行为,系统将进行审查。

      5. 好友同步与备份

      • 好友同步:
        • 支持好友列表在不同设备间同步(如手机、电脑)。
        • 支持在设备更换时快速恢复好友列表。
        • 消息在不同设备同步

      2.1.5 聊天记录

      1. 支持聊天记录的搜索功能(根据关键字、日期、联系人等)。
      2. 支持聊天记录的备份和恢复。

      2.2 自动回复

      • 自动回复内容管理
      • 支持在特定情况下使用自动回复或机器人处理消息。

      2.3 安全与合规

      • 消息加密:

        • 支持端到端加密,确保消息内容的安全性。
      • 隐私设置:

        • 支持设置隐私选项(如谁可以向我发送消息、消息通知设置)。
      • 账号保护:

        • 支持账号的安全设置(如2FA两步验证、密码保护)。
      • 日志记录与审计:

        • 记录敏感操作日志,供管理人员审计和追踪。
      • 系统设计需符合医疗行业的合规性要求

      • 数据要有备份

      2.4 高级功能

      2.4.1 病例讨论

      为医生提供病例讨论的专属功能,支持在群聊中讨论具体病历,并对讨论内容进行结构化保存。

      • 支持病例资料上传(如CT图像、检验报告等)。
      • 支持标记重点内容,方便后期查阅。

      2.4.2 文件共享与存储

      提供云盘功能,支持在群聊和单聊中直接分享文件,并进行分类存储和权限管理。

      • 支持文件的在线预览及下载。
      • 支持对文件的版本管理,方便查看历史版本。

      2.5 跨平台支持

      软件应支持在多种设备上使用,包括但不限于PC、手机、平板等。

      • 支持iOS和Android系统的手机应用。
      • 支持Windows和macOS的桌面应用。

      2.6 其他需求

      2.6.1 扩展性

      系统设计需具有良好的扩展性,支持未来新增功能的扩展。

      • 支持API接口,便于与其他系统进行数据交互。
      • 支持不同语言客户端

      3. 非功能需求

      3.1 性能要求

      • 系统应能够在十万级用户同时在线时保持流畅运行。
      • 消息的平均延迟应控制在1秒以内。

      3.2 安全要求

      • 系统需通过严格的安全测试,防止数据泄露和外部攻击。
      • 所有数据存储和传输均需符合国家信息安全标准。

      3.3 可维护性

      • 系统架构需清晰,代码需具备良好的可读性和注释。
      • 提供详细的技术文档和用户手册,便于后续维护与升级。

      4. 风险管理

      • 技术风险:可能遇到性能瓶颈,需提前进行压力测试。
      • 安全风险:需重点关注数据安全问题,确保所有安全措施到位。
      • 用户接受度风险:需在上线前进行广泛的用户培训,确保软件顺利推广。
      ',72),t=[p];function u(s,h){return i(),e("div",null,t)}const d=l(n,[["render",u],["__file","im即时通信的需求.html.vue"]]),c=JSON.parse('{"path":"/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html","title":"即时通信软件需求","lang":"zh-CN","frontmatter":{"title":"即时通信软件需求","author":"chenkun","publish":true,"isOriginal":true,"description":"即时通信需求文档 1. 概述 1.1 项目背景 随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。 1.2 项目目标 开发一款满足万人级别同时在线的即时通信软件,专门为医院内的医生、护士、行政人员等设计。该软...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"即时通信软件需求"}],["meta",{"property":"og:description","content":"即时通信需求文档 1. 概述 1.1 项目背景 随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。 1.2 项目目标 开发一款满足万人级别同时在线的即时通信软件,专门为医院内的医生、护士、行政人员等设计。该软..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-09-02T00:39:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:modified_time","content":"2024-09-02T00:39:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"即时通信软件需求\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-09-02T00:39:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1. 概述","slug":"_1-概述","link":"#_1-概述","children":[{"level":3,"title":"1.1 项目背景","slug":"_1-1-项目背景","link":"#_1-1-项目背景","children":[]},{"level":3,"title":"1.2 项目目标","slug":"_1-2-项目目标","link":"#_1-2-项目目标","children":[]},{"level":3,"title":"1.3 用户群体","slug":"_1-3-用户群体","link":"#_1-3-用户群体","children":[]}]},{"level":2,"title":"2. 功能需求","slug":"_2-功能需求","link":"#_2-功能需求","children":[{"level":3,"title":"2.1 基础功能","slug":"_2-1-基础功能","link":"#_2-1-基础功能","children":[]},{"level":3,"title":"2.2 自动回复","slug":"_2-2-自动回复","link":"#_2-2-自动回复","children":[]},{"level":3,"title":"2.3 安全与合规","slug":"_2-3-安全与合规","link":"#_2-3-安全与合规","children":[]},{"level":3,"title":"2.4 高级功能","slug":"_2-4-高级功能","link":"#_2-4-高级功能","children":[]},{"level":3,"title":"2.5 跨平台支持","slug":"_2-5-跨平台支持","link":"#_2-5-跨平台支持","children":[]},{"level":3,"title":"2.6 其他需求","slug":"_2-6-其他需求","link":"#_2-6-其他需求","children":[]}]},{"level":2,"title":"3. 非功能需求","slug":"_3-非功能需求","link":"#_3-非功能需求","children":[{"level":3,"title":"3.1 性能要求","slug":"_3-1-性能要求","link":"#_3-1-性能要求","children":[]},{"level":3,"title":"3.2 安全要求","slug":"_3-2-安全要求","link":"#_3-2-安全要求","children":[]},{"level":3,"title":"3.3 可维护性","slug":"_3-3-可维护性","link":"#_3-3-可维护性","children":[]}]},{"level":2,"title":"4. 风险管理","slug":"_4-风险管理","link":"#_4-风险管理","children":[]}],"git":{"createdTime":1724891306000,"updatedTime":1725237552000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":10.3,"words":3091},"filePathRelative":"other/essay/im即时通信的需求.md","localizedDate":"2024年8月29日","excerpt":"\\n

      1. 概述

      \\n

      1.1 项目背景

      \\n

      随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。

      \\n

      1.2 项目目标

      \\n

      开发一款满足万人级别同时在线的即时通信软件,专门为医院内的医生、护士、行政人员等设计。该软件旨在提升院内信息传递的效率,确保信息的及时性和安全性,\\n从而提高医疗服务的质量和工作效率。

      \\n

      1.3 用户群体

      \\n
        \\n
      • 医生:科室主任、主治医师、住院医师等。
      • \\n
      • 护士:护士长、责任护士、实习护士等。
      • \\n
      • 行政人员:医院管理层、科室秘书、后勤人员等。
      • \\n
      ","autoDesc":true}');export{d as comp,c as data}; +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as i,c as e,e as a}from"./app-_Oi5YZFn.js";const n={},p=a('

      即时通信需求文档

      1. 概述

      1.1 项目背景

      随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。

      1.2 项目目标

      开发一款满足万人级别同时在线的即时通信软件,专门为医院内的医生、护士、行政人员等设计。该软件旨在提升院内信息传递的效率,确保信息的及时性和安全性, 从而提高医疗服务的质量和工作效率。

      1.3 用户群体

      • 医生:科室主任、主治医师、住院医师等。
      • 护士:护士长、责任护士、实习护士等。
      • 行政人员:医院管理层、科室秘书、后勤人员等。

      2. 功能需求

      2.1 基础功能

      2.1.1 单聊

      1. 消息发送与接收

      • 文本消息:

        • 用户能够发送和接收文字消息。
        • 支持多行文本,自动换行显示。
        • 支持文字消息的格式化(如粗体、斜体、链接)。
      • 语音消息:

        • 用户能够录制、发送和接收语音消息。
        • 支持语音消息的播放、暂停、停止、重听功能。
        • 显示语音消息的时长和发送/接收状态。
      • 语音通话

        • 用户能够发起和接受语音通话、挂断语音。
        • 支持语音通话的开关麦克风、开关静音、免提功能。
      • 视频聊天

        • 用户能够发起和接受视频通话、挂断视频。
        • 支持视频通话的开关摄像头、开关麦克风、开关静音、免提功能。
      • 视频消息:

        • 用户能够录制并发送视频消息。
        • 支持视频消息的播放、暂停、停止。
        • 显示视频消息的时长和缩略图。
      • 图片和文件:

        • 用户能够发送和接收图片、音频文件、文档等。
        • 支持查看、下载、分享图片和文件。
        • 支持图片的预览、放大和缩小功能。
      • 消息状态:

        • 显示消息的发送状态(如已发送、已接收、已读)。
        • 支持消息的发送时间和接收时间显示。

      2. 聊天界面

      • 聊天窗口:

        • 支持显示完整的聊天记录,自动加载历史消息。
        • 支持消息的上下滚动查看。
        • 显示对方和自己的头像、昵称及状态(如在线/离线)。
      • 消息气泡:

        • 消息以气泡形式显示,发送和接收消息使用不同的气泡样式。
        • 支持长按消息气泡进行操作(如复制、转发、删除)。
      • 聊天记录:

        • 支持按日期、联系人等条件搜索聊天记录。
        • 支持聊天记录的备份和恢复。

      3. 消息互动

      • 回复和引用:

        • 支持对消息进行回复和引用功能。
        • 被回复的消息显示为线程或嵌套形式。
      • 转发消息:

        • 支持将消息转发至其他聊天窗口或联系人。
        • 支持批量转发功能。
      • 表情和贴图:

        • 支持发送和接收表情、贴图。
      • 消息撤回和删除:

        • 支持在一定时间内撤回已发送的消息。
        • 支持删除聊天记录(包括单条消息和整个聊天记录)。

      4. 通知和提醒

      • 消息通知:
        • 支持新消息的推送通知(包括声音、震动、横幅)。
        • 支持自定义通知设置(如通知声音、提醒方式)。
      • 聊天提示:
        • 支持未读消息的提示(如红点、消息提醒)。

      5. 离线消息

      • 离线消息接收:

        • 用户在离线状态时能够接收并查看离线期间的消息。
        • 支持离线消息的同步显示。
      • 消息同步:

        • 支持消息在不同设备之间的同步(如手机、电脑)。

      2.1.2 群聊

      用户可以创建群聊,邀请其他用户加入,方便科室或项目组内部的多人沟通。

      1. 群组创建与管理

      • 创建群组:

        • 用户能够创建新的群组,并设置群组名称、头像、简介。
        • 提供选择群组类型(如公开、私密)的选项。
      • 群组邀请:

        • 群主和管理员能够邀请联系人加入群组。
        • 支持通过邀请链接、二维码等方式邀请用户。
      • 群组设置:

        • 群主能够修改群组的基本信息(名称、头像、简介)。
        • 群主和管理员设置群公告。
      • 成员管理:

        • 群主和管理员能够添加、移除群组成员。
      • 权限管理

        • 群主能够设置群成员的权限级别(普通成员、管理员)。
        • 群主可解散群组,转让群主。
        • 除群主外的所有成员可以退群。
        • 非群内成员可以申请加入,需要管理审批通过方可进群
      • 加入群组

        • 用户搜索群组,选择群组,点击申请加入

      2. 群组聊天

      • 消息发送:

        • 支持在群聊中发送文字、语音、图片、视频和文件。
        • 支持群内消息的“@”功能,提醒特定成员。
      • 消息显示:

        • 支持显示群聊中每条消息的发送者头像、昵称、时间等。
        • 消息气泡以不同颜色标识发送者和接收者。
      • 消息通知:

        • 支持群聊消息的推送通知。
        • 用户能够设置接收所有消息、仅接收@消息或静音通知。
      • 消息管理:

        • 支持查看消息的发送状态(已发送、已接收、已读)。
        • 支持消息的回复和引用功能。
      • 群聊搜索:

        • 支持按关键词、发送者、日期等搜索群聊记录。
        • 提供搜索结果高亮显示功能。
      • 消息撤回:

        • 支持在一定时间内撤回已发送的群聊消息。
      • 历史消息

        • 群默认显示最近的历史消息
        • 可根据关键字、时间段搜索群消息

      3. 群组互动功能

      • 群成员列表:

        • 显示群组成员的列表,包括头像和昵称。
        • 支持查看成员的在线状态和活动情况。
      • 群内互动:

        • 支持群成员之间的私聊功能。
        • 支持在群聊中进行投票、问卷调查等互动功能。
      • 群组公告:

        • 群主能够发布群组公告,所有成员都能看到。
        • 支持公告的编辑和删除。
      • 文件共享与管理:

        • 支持在群组内上传、下载和分享文件。
        • 支持群组文件的分类和管理(如按日期、类型)。

      2.1.3 消息通知

      针对重要信息进行通知提醒,用户可以在设置中调整通知方式(如铃声、震动、弹窗等)。

      • 支持紧急消息提醒功能,必要时医生可以发送高优先级消息,并强制提醒对方。
      • 支持离线消息推送功能,即使用户不在线也能接收到消息提醒。
      • 广播通知

      2.1.4 好友管理

      1. 好友添加和删除

      • 添加好友:

        • 通过搜索:用户可以通过搜索功能查找和添加好友。搜索方式包括用户名、手机号、邮箱等。
        • 通过二维码:用户可以通过扫描对方的二维码添加好友。
        • 通过邀请链接:用户可以发送邀请链接给他人,通过链接添加好友。
        • 通过联系人导入:支持从手机或其他社交网络导入联系人并添加为好友。
      • 删除好友:

        • 用户可以从好友列表中删除好友。
        • 删除好友后,双方将不再互相显示在线状态和消息。
      • 好友请求管理:

        • 接收请求:用户能够接收好友请求,查看请求者的基本信息(如头像、昵称)。
        • 接受/拒绝请求:用户可以选择接受或拒绝好友请求。
        • 请求记录:支持查看已发送和已接收的好友请求记录。

      2. 好友列表管理

      • 好友列表显示:

        • 显示好友的基本信息,包括头像、昵称、在线状态。
        • 支持按字母排序、按最近聊天排序或按自定义分组查看好友列表。
      • 好友备注:

        • 用户可以为好友设置备注,以便于识别和管理。
        • 支持编辑和删除好友备注。

      3. 好友互动

      • 查看好友状态:
        • 用户可以查看好友的在线状态(如在线、离线、隐身)。
        • 显示好友的最近活动状态(如最后登录时间、最近聊天记录)。

      4. 隐私和安全设置

      • 隐私设置:

        • 谁可以联系我:用户可以设置谁可以向我发送消息(如所有人、好友、仅群组成员)。
        • 谁可以查看我的状态:用户可以设置谁可以查看自己的在线状态、最后登录时间。
        • 谁可以查看我的资料:设置好友或非好友的资料查看权限。
      • 拉黑与举报: 拉黑好友:用户可以将某些好友拉黑,拉黑后对方将无法向用户发送消息和查看用户状态。 举报好友:用户可以举报好友的异常行为或违规行为,系统将进行审查。

      5. 好友同步与备份

      • 好友同步:
        • 支持好友列表在不同设备间同步(如手机、电脑)。
        • 支持在设备更换时快速恢复好友列表。
        • 消息在不同设备同步

      2.1.5 聊天记录

      1. 支持聊天记录的搜索功能(根据关键字、日期、联系人等)。
      2. 支持聊天记录的备份和恢复。

      2.2 自动回复

      • 自动回复内容管理
      • 支持在特定情况下使用自动回复或机器人处理消息。

      2.3 安全与合规

      • 消息加密:

        • 支持端到端加密,确保消息内容的安全性。
      • 隐私设置:

        • 支持设置隐私选项(如谁可以向我发送消息、消息通知设置)。
      • 账号保护:

        • 支持账号的安全设置(如2FA两步验证、密码保护)。
      • 日志记录与审计:

        • 记录敏感操作日志,供管理人员审计和追踪。
      • 系统设计需符合医疗行业的合规性要求

      • 数据要有备份

      2.4 高级功能

      2.4.1 病例讨论

      为医生提供病例讨论的专属功能,支持在群聊中讨论具体病历,并对讨论内容进行结构化保存。

      • 支持病例资料上传(如CT图像、检验报告等)。
      • 支持标记重点内容,方便后期查阅。

      2.4.2 文件共享与存储

      提供云盘功能,支持在群聊和单聊中直接分享文件,并进行分类存储和权限管理。

      • 支持文件的在线预览及下载。
      • 支持对文件的版本管理,方便查看历史版本。

      2.5 跨平台支持

      软件应支持在多种设备上使用,包括但不限于PC、手机、平板等。

      • 支持iOS和Android系统的手机应用。
      • 支持Windows和macOS的桌面应用。

      2.6 其他需求

      2.6.1 扩展性

      系统设计需具有良好的扩展性,支持未来新增功能的扩展。

      • 支持API接口,便于与其他系统进行数据交互。
      • 支持不同语言客户端

      3. 非功能需求

      3.1 性能要求

      • 系统应能够在十万级用户同时在线时保持流畅运行。
      • 消息的平均延迟应控制在1秒以内。

      3.2 安全要求

      • 系统需通过严格的安全测试,防止数据泄露和外部攻击。
      • 所有数据存储和传输均需符合国家信息安全标准。

      3.3 可维护性

      • 系统架构需清晰,代码需具备良好的可读性和注释。
      • 提供详细的技术文档和用户手册,便于后续维护与升级。

      4. 风险管理

      • 技术风险:可能遇到性能瓶颈,需提前进行压力测试。
      • 安全风险:需重点关注数据安全问题,确保所有安全措施到位。
      • 用户接受度风险:需在上线前进行广泛的用户培训,确保软件顺利推广。
      ',72),t=[p];function u(s,h){return i(),e("div",null,t)}const d=l(n,[["render",u],["__file","im即时通信的需求.html.vue"]]),c=JSON.parse('{"path":"/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html","title":"即时通信软件需求","lang":"zh-CN","frontmatter":{"title":"即时通信软件需求","author":"chenkun","publish":true,"isOriginal":true,"description":"即时通信需求文档 1. 概述 1.1 项目背景 随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。 1.2 项目目标 开发一款满足万人级别同时在线的即时通信软件,专门为医院内的医生、护士、行政人员等设计。该软...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"即时通信软件需求"}],["meta",{"property":"og:description","content":"即时通信需求文档 1. 概述 1.1 项目背景 随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。 1.2 项目目标 开发一款满足万人级别同时在线的即时通信软件,专门为医院内的医生、护士、行政人员等设计。该软..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-09-02T00:39:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:modified_time","content":"2024-09-02T00:39:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"即时通信软件需求\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-09-02T00:39:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1. 概述","slug":"_1-概述","link":"#_1-概述","children":[{"level":3,"title":"1.1 项目背景","slug":"_1-1-项目背景","link":"#_1-1-项目背景","children":[]},{"level":3,"title":"1.2 项目目标","slug":"_1-2-项目目标","link":"#_1-2-项目目标","children":[]},{"level":3,"title":"1.3 用户群体","slug":"_1-3-用户群体","link":"#_1-3-用户群体","children":[]}]},{"level":2,"title":"2. 功能需求","slug":"_2-功能需求","link":"#_2-功能需求","children":[{"level":3,"title":"2.1 基础功能","slug":"_2-1-基础功能","link":"#_2-1-基础功能","children":[]},{"level":3,"title":"2.2 自动回复","slug":"_2-2-自动回复","link":"#_2-2-自动回复","children":[]},{"level":3,"title":"2.3 安全与合规","slug":"_2-3-安全与合规","link":"#_2-3-安全与合规","children":[]},{"level":3,"title":"2.4 高级功能","slug":"_2-4-高级功能","link":"#_2-4-高级功能","children":[]},{"level":3,"title":"2.5 跨平台支持","slug":"_2-5-跨平台支持","link":"#_2-5-跨平台支持","children":[]},{"level":3,"title":"2.6 其他需求","slug":"_2-6-其他需求","link":"#_2-6-其他需求","children":[]}]},{"level":2,"title":"3. 非功能需求","slug":"_3-非功能需求","link":"#_3-非功能需求","children":[{"level":3,"title":"3.1 性能要求","slug":"_3-1-性能要求","link":"#_3-1-性能要求","children":[]},{"level":3,"title":"3.2 安全要求","slug":"_3-2-安全要求","link":"#_3-2-安全要求","children":[]},{"level":3,"title":"3.3 可维护性","slug":"_3-3-可维护性","link":"#_3-3-可维护性","children":[]}]},{"level":2,"title":"4. 风险管理","slug":"_4-风险管理","link":"#_4-风险管理","children":[]}],"git":{"createdTime":1724891306000,"updatedTime":1725237552000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":10.3,"words":3091},"filePathRelative":"other/essay/im即时通信的需求.md","localizedDate":"2024年8月29日","excerpt":"\\n

      1. 概述

      \\n

      1.1 项目背景

      \\n

      随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。

      \\n

      1.2 项目目标

      \\n

      开发一款满足万人级别同时在线的即时通信软件,专门为医院内的医生、护士、行政人员等设计。该软件旨在提升院内信息传递的效率,确保信息的及时性和安全性,\\n从而提高医疗服务的质量和工作效率。

      \\n

      1.3 用户群体

      \\n
        \\n
      • 医生:科室主任、主治医师、住院医师等。
      • \\n
      • 护士:护士长、责任护士、实习护士等。
      • \\n
      • 行政人员:医院管理层、科室秘书、后勤人员等。
      • \\n
      ","autoDesc":true}');export{d as comp,c as data}; diff --git a/assets/index.html-DxrpWTG5.js b/assets/index.html--jCoRugf.js similarity index 96% rename from assets/index.html-DxrpWTG5.js rename to assets/index.html--jCoRugf.js index 0d74828cbb..d8d8baf374 100644 --- a/assets/index.html-DxrpWTG5.js +++ b/assets/index.html--jCoRugf.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const n={};function r(a,i){return t(),o("div")}const p=e(n,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/other/tools/","title":"软件工具","lang":"zh-CN","frontmatter":{"title":"软件工具","date":"2022-03-20T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/tools/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"软件工具"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-03-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"软件工具\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-20T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":15},"filePathRelative":"other/tools/README.md","localizedDate":"2022年3月20日","excerpt":"\\n"}');export{p as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function r(a,i){return t(),o("div")}const p=e(n,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/other/tools/","title":"软件工具","lang":"zh-CN","frontmatter":{"title":"软件工具","date":"2022-03-20T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/tools/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"软件工具"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-03-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"软件工具\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-20T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":15},"filePathRelative":"other/tools/README.md","localizedDate":"2022年3月20日","excerpt":"\\n"}');export{p as comp,l as data}; diff --git a/assets/index.html-CrtTLfwo.js b/assets/index.html-0gfJBSgv.js similarity index 99% rename from assets/index.html-CrtTLfwo.js rename to assets/index.html-0gfJBSgv.js index cab0e5d9a7..a7f991e7ea 100644 --- a/assets/index.html-CrtTLfwo.js +++ b/assets/index.html-0gfJBSgv.js @@ -1 +1 @@ -import{_ as h}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as r,c as i,a as t,b as e,d as o,e as p}from"./app-CVMfKeWw.js";const s={},u={class:"hint-container caution"},l=t("p",{class:"hint-container-title"},"申明",-1),c={href:"https://time.geekbang.org/column/article/254565",target:"_blank",rel:"noopener noreferrer"},d=p('

      你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。

      我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,毕竟网关要承载整个开放平台的调用量,同时还要有足够的系统容错能力。

      但随着对开放平台理解的不断深入,我们要想在开放平台支持更多样的业务场景,我才发现网关和授权同样重要,相当于开放平台的 “两条腿”。

      而对于授权 “这条腿”,它不仅要像网关一样要承载访问量,还要同时兼顾业务场景的发展。什么样的业务场景呢?类似的微信登录就是其中之一,越来越多的第三方应用都在向用户提供使用微信登录的解决方案,来减少用户注册的繁琐操作。而这个解决方案的背后原理,也是我们这门课要讲到的OAuth 2.0技术。

      OAuth 2.0是什么?

      那,OAuth 2.0到底是什么呢?我们先从字面上来分析下。OAuth 2.0一词中的 “Auth” 表示 “授权”,字母 “O” 是Open的简称,表示 “开放” ,连在一起就表示 “开放授权”。这也是为什么我们使用OAuth的场景,通常发生在开放平台的环境下。

      看到这里,你可能会说应该还有OAuth 1.0吧。没错,OAuth 2.0之前就是OAuth 1.0。现在,我就来和你说说这两个版本的OAuth有什么区别吧。

      在OAuth 1.0的时候,它有个 “很大的愿望” 就是想用一套授权机制来应对现实中的所有场景,比如Web应用场景、移动App应用场景、官方应用场景等等,但是这些场景并不是完全相同的。比如官方应用场景,你说还需要让用户来授权吗?如果需要,始终使用一套授权机制给用户带来的体验,是好还是坏呢?

      到了OAuth 2.0的时候,就解决了OAuth 1.0 面临的这种“尴尬”。OAuth 2.0 不再局限于一种授权机制,它扩充了授权许可机制类型,有了授权码许可机制、客户端凭据机制、资源拥有者凭据机制和隐式许可机制。这样的OAuth机制就能够很灵活地适应现实中的各种场景,比如移动应用的场景、官方应用的场景,等等。

      此外,OAuth 1.0的弊端还包括安全上的固化攻击等问题,因此OAuth 1.0现在已经是废弃状态了。对于我们来讲,直接使用OAuth 2.0就可以了。

      为什么会有这门课?

      但其实呢,OAuth 2.0并不是一门新的技术,从2007年OAuth 1.0面世,到2011年发布OAuth 2.0草案,互联网上已经有很多关于OAuth的资料了。所以,在我初次接触OAuth 2.0去查阅这些零散的资料时,觉得OAuth 2.0很简单啊,不就是授权吗,看两篇文章就够了啊。

      但是,看似简单的OAuth 2.0却又让我望而却步,在如何使用授权码流程上踌躇不前。比如,在Web应用中到底应该怎么使用授权码流程,移动App中还能使用授权码流程吗?当我带着这些问题尝试到网上搜索资料时,那些不成体系的资料着实也让我走了不少弯路。不知道你是不是也被下面问题困扰着:

      我要开发一个Web应用,当使用OAuth 2.0的时候,担心授权码被拦截,却因为没有较好的解决方法而一筹莫展。 我要开发一款移动App,当使用OAuth 2.0的时候,在确定是否需要Server端上,花费了大把的时间。 后来我看到《OAuth 2 in Action》这本书,如获至宝。它非常系统地讲解了OAuth2.0,让我对这个协议框架有了更全面、深刻的认识。也正是这本书给了我足够的勇气,让我能够把自己这些年在开放平台的工作中,所掌握的OAuth知识体系梳理一遍。也是在这一刻,我才意识到只要有了方向,就有了厚度。

      当我开始试着整理出自己这些年掌握的OAuth 2.0相关技术、实践,并计划输出的时候,我真真切切地发现,OAuth 2.0是讲授权没错,但要用对、用好这个协议,绝不是短短两篇文章就能讲清楚的。这也是我做这门课的初衷。

      ',15),A={href:"https://tools.ietf.org/wg/oauth/",target:"_blank",rel:"noopener noreferrer"},O={href:"https://connect2id.com/assets/oidc-explained.pdf",target:"_blank",rel:"noopener noreferrer"},_=t("h3",{id:"这门课是怎么设计的",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#这门课是怎么设计的"},[t("span",null,"这门课是怎么设计的?")])],-1),m=t("p",null,"在这门课程里,我会分为基础篇和进阶篇两大模块,每个模块都会安排一些实践内容,和你讲清楚OAuth 2.0。接下来,我就和你解释下为什么要这么安排。",-1),g=t("p",null,"==第一部分是基础篇,就是你必须要掌握的OAuth2.0的基础知识。== 在这一模块中,我会和你细致地讲解授权码许可(Authorization Code)类型的流程,包括OAuth 2.0内部组件之间的通信方式,以及授权服务、客户端(第三方软件)、受保护资源服务这三个组件的原理。",-1),b=t("p",null,"在此基础上,我还会为你讲解其他三种常见许可类型,分别是资源拥有者凭据许可(Resource Owner Password Credentials)、隐式许可(Implicit)、客户端凭据许可(Client Credentials)的原理,以及如何选择适合自己实际场景的授权类型。这样一来,你就能掌握整个OAuth 2.0中所有许可类型的运转机制了,并且能够从容地在实际工作环境中使用它们。",-1),f=t("p",null,"为了能够把你带入到OAuth 2.0的场景中,方便你理解这些概念、流程,我在讲述这些基础内容的时候,会用一个小明使用第三方“小兔打单软件”来打印自己在京东店铺的订单数据的例子,来贯穿始终。",-1),k=t("p",null,"我可以告诉你的是,学完基础篇的内容,你就可以把OAuth 2.0用到实际的工作场景了。",-1),x=t("p",null,"==第二部分进阶篇的内容,我会侧重讲一些OAuth 2.0 “更高级” 的用法,== 可以让你知道如何更安全地用、扩展地用OAuth 2.0。",-1),C=t("p",null,"所以,这部分内容会包括如何在移动App中使用OAuth 2.0,因使用不当而导致的OAuth 2.0安全漏洞有哪些,以及如何利用OAuth 2.0实现一个OpenID Connect用户身份认证协议。此外,我还邀请了微服务技术领域的专家杨波老师,给我们分享了一个架构案例,基于OAuth 2.0/JWT的微服务参考架构。",-1),y={href:"https://github.com/xindongbook/oauth2-code",target:"_blank",rel:"noopener noreferrer"},v=t("p",null,"简单的地方在于,代码中除了基本的Servlet技术外,我没有引入任何其它的第三方内容。所以,你只要能够理解Request和Response,就能够理解这份代码。",-1),T=t("p",null,"可落地的地方在于,虽然它是一份简单的代码,但它不仅把整个OAuth 2.0的组件都跑通了,还包含了实践一个OIDC协议的具体实现。当然,我在代码里面还预留了一些TODO的地方,你可以结合上下文来自行实践处理。这是一项开源的工程。",-1),S=t("p",null,"在这里,我总结了OAuth 2.0的知识体系图,你也可以先了解下整个课程的知识结构。",-1),D=t("p",null,[t("img",{src:"https://static001.geekbang.org/resource/image/96/08/969e0e39d327d109ab23e794a4263708.jpg",alt:"体系图"})],-1),N=t("p",null,"这样一来,你学完这门课后,便能在互联网的授权领域练就一双“火眼金睛”,可以发现所有使用过OAuth 2.0的痕迹,诸如微信登录的场景。这样,即使你不用抓包分析,也能够洞悉它背后的原理,为今后快速熟知互联网的类似场景打下基础。",-1),I=t("p",null,"最后,我还想正式认识一下你。你可以在留言区里做个自我介绍,和我聊聊,你目前学习、使用OAuth 2.0的难点、痛点是什么?或者,你也可以聊聊你对OAuth 2.0、对授权还有哪些独特的思考和体验,欢迎在留言区和我交流讨论。",-1),Z=t("p",null,"好了,现在就开启我们的OAuth 2.0之旅吧。",-1);function E(R,j){const n=a("ExternalLinkIcon");return r(),i("div",null,[t("div",u,[l,t("p",null,[e("oauth2相关博客转载于"),t("a",c,[e("极客时间"),o(n)])])]),d,t("p",null,[e("我要结合自己在开放平台上的工作经验以及对OAuth 2.0的理解,一次性地给你说透授权这件事儿。同时,我还查阅了诸多资料,包括"),t("a",A,[e("OAuth 2协议规范"),o(n)]),e("、"),t("a",O,[e("OpenID Connect explained"),o(n)]),e("等,力求给你带来最贴近本质的OAuth 2.0知识的讲解。")]),_,m,g,b,f,k,x,C,t("p",null,[e("最后,为了配合课程的学习,不让理论过于枯燥,也为了学以致用, 我"),t("a",y,[e("GitHub"),o(n)]),e("上为你准备了一份非常简单、可落地的 通过Java语言来实现的代码。")]),v,T,S,D,N,I,Z])}const P=h(s,[["render",E],["__file","index.html.vue"]]),V=JSON.parse('{"path":"/other/oauth2/","title":"开篇词 | 为什么要学OAuth 2.0?","lang":"zh-CN","frontmatter":{"title":"开篇词 | 为什么要学OAuth 2.0?","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","description":"申明 oauth2相关博客转载于极客时间 你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。 我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"开篇词 | 为什么要学OAuth 2.0?"}],["meta",{"property":"og:description","content":"申明 oauth2相关博客转载于极客时间 你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。 我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://static001.geekbang.org/resource/image/96/08/969e0e39d327d109ab23e794a4263708.jpg"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-11-08T08:17:56.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-11-08T08:17:56.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"开篇词 | 为什么要学OAuth 2.0?\\",\\"image\\":[\\"https://static001.geekbang.org/resource/image/96/08/969e0e39d327d109ab23e794a4263708.jpg\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2022-11-08T08:17:56.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"OAuth 2.0是什么?","slug":"oauth-2-0是什么","link":"#oauth-2-0是什么","children":[]},{"level":3,"title":"为什么会有这门课?","slug":"为什么会有这门课","link":"#为什么会有这门课","children":[]},{"level":3,"title":"这门课是怎么设计的?","slug":"这门课是怎么设计的","link":"#这门课是怎么设计的","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1667895476000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":7.87,"words":2360},"filePathRelative":"other/oauth2/README.md","localizedDate":"2022年11月8日","excerpt":"
      \\n

      申明

      \\n

      oauth2相关博客转载于极客时间

      \\n
      \\n

      你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。

      \\n

      我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,毕竟网关要承载整个开放平台的调用量,同时还要有足够的系统容错能力。

      ","autoDesc":true}');export{P as comp,V as data}; +import{_ as h}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as r,c as i,a as t,b as e,d as o,e as p}from"./app-_Oi5YZFn.js";const s={},u={class:"hint-container caution"},l=t("p",{class:"hint-container-title"},"申明",-1),c={href:"https://time.geekbang.org/column/article/254565",target:"_blank",rel:"noopener noreferrer"},d=p('

      你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。

      我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,毕竟网关要承载整个开放平台的调用量,同时还要有足够的系统容错能力。

      但随着对开放平台理解的不断深入,我们要想在开放平台支持更多样的业务场景,我才发现网关和授权同样重要,相当于开放平台的 “两条腿”。

      而对于授权 “这条腿”,它不仅要像网关一样要承载访问量,还要同时兼顾业务场景的发展。什么样的业务场景呢?类似的微信登录就是其中之一,越来越多的第三方应用都在向用户提供使用微信登录的解决方案,来减少用户注册的繁琐操作。而这个解决方案的背后原理,也是我们这门课要讲到的OAuth 2.0技术。

      OAuth 2.0是什么?

      那,OAuth 2.0到底是什么呢?我们先从字面上来分析下。OAuth 2.0一词中的 “Auth” 表示 “授权”,字母 “O” 是Open的简称,表示 “开放” ,连在一起就表示 “开放授权”。这也是为什么我们使用OAuth的场景,通常发生在开放平台的环境下。

      看到这里,你可能会说应该还有OAuth 1.0吧。没错,OAuth 2.0之前就是OAuth 1.0。现在,我就来和你说说这两个版本的OAuth有什么区别吧。

      在OAuth 1.0的时候,它有个 “很大的愿望” 就是想用一套授权机制来应对现实中的所有场景,比如Web应用场景、移动App应用场景、官方应用场景等等,但是这些场景并不是完全相同的。比如官方应用场景,你说还需要让用户来授权吗?如果需要,始终使用一套授权机制给用户带来的体验,是好还是坏呢?

      到了OAuth 2.0的时候,就解决了OAuth 1.0 面临的这种“尴尬”。OAuth 2.0 不再局限于一种授权机制,它扩充了授权许可机制类型,有了授权码许可机制、客户端凭据机制、资源拥有者凭据机制和隐式许可机制。这样的OAuth机制就能够很灵活地适应现实中的各种场景,比如移动应用的场景、官方应用的场景,等等。

      此外,OAuth 1.0的弊端还包括安全上的固化攻击等问题,因此OAuth 1.0现在已经是废弃状态了。对于我们来讲,直接使用OAuth 2.0就可以了。

      为什么会有这门课?

      但其实呢,OAuth 2.0并不是一门新的技术,从2007年OAuth 1.0面世,到2011年发布OAuth 2.0草案,互联网上已经有很多关于OAuth的资料了。所以,在我初次接触OAuth 2.0去查阅这些零散的资料时,觉得OAuth 2.0很简单啊,不就是授权吗,看两篇文章就够了啊。

      但是,看似简单的OAuth 2.0却又让我望而却步,在如何使用授权码流程上踌躇不前。比如,在Web应用中到底应该怎么使用授权码流程,移动App中还能使用授权码流程吗?当我带着这些问题尝试到网上搜索资料时,那些不成体系的资料着实也让我走了不少弯路。不知道你是不是也被下面问题困扰着:

      我要开发一个Web应用,当使用OAuth 2.0的时候,担心授权码被拦截,却因为没有较好的解决方法而一筹莫展。 我要开发一款移动App,当使用OAuth 2.0的时候,在确定是否需要Server端上,花费了大把的时间。 后来我看到《OAuth 2 in Action》这本书,如获至宝。它非常系统地讲解了OAuth2.0,让我对这个协议框架有了更全面、深刻的认识。也正是这本书给了我足够的勇气,让我能够把自己这些年在开放平台的工作中,所掌握的OAuth知识体系梳理一遍。也是在这一刻,我才意识到只要有了方向,就有了厚度。

      当我开始试着整理出自己这些年掌握的OAuth 2.0相关技术、实践,并计划输出的时候,我真真切切地发现,OAuth 2.0是讲授权没错,但要用对、用好这个协议,绝不是短短两篇文章就能讲清楚的。这也是我做这门课的初衷。

      ',15),A={href:"https://tools.ietf.org/wg/oauth/",target:"_blank",rel:"noopener noreferrer"},O={href:"https://connect2id.com/assets/oidc-explained.pdf",target:"_blank",rel:"noopener noreferrer"},_=t("h3",{id:"这门课是怎么设计的",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#这门课是怎么设计的"},[t("span",null,"这门课是怎么设计的?")])],-1),m=t("p",null,"在这门课程里,我会分为基础篇和进阶篇两大模块,每个模块都会安排一些实践内容,和你讲清楚OAuth 2.0。接下来,我就和你解释下为什么要这么安排。",-1),g=t("p",null,"==第一部分是基础篇,就是你必须要掌握的OAuth2.0的基础知识。== 在这一模块中,我会和你细致地讲解授权码许可(Authorization Code)类型的流程,包括OAuth 2.0内部组件之间的通信方式,以及授权服务、客户端(第三方软件)、受保护资源服务这三个组件的原理。",-1),b=t("p",null,"在此基础上,我还会为你讲解其他三种常见许可类型,分别是资源拥有者凭据许可(Resource Owner Password Credentials)、隐式许可(Implicit)、客户端凭据许可(Client Credentials)的原理,以及如何选择适合自己实际场景的授权类型。这样一来,你就能掌握整个OAuth 2.0中所有许可类型的运转机制了,并且能够从容地在实际工作环境中使用它们。",-1),f=t("p",null,"为了能够把你带入到OAuth 2.0的场景中,方便你理解这些概念、流程,我在讲述这些基础内容的时候,会用一个小明使用第三方“小兔打单软件”来打印自己在京东店铺的订单数据的例子,来贯穿始终。",-1),k=t("p",null,"我可以告诉你的是,学完基础篇的内容,你就可以把OAuth 2.0用到实际的工作场景了。",-1),x=t("p",null,"==第二部分进阶篇的内容,我会侧重讲一些OAuth 2.0 “更高级” 的用法,== 可以让你知道如何更安全地用、扩展地用OAuth 2.0。",-1),C=t("p",null,"所以,这部分内容会包括如何在移动App中使用OAuth 2.0,因使用不当而导致的OAuth 2.0安全漏洞有哪些,以及如何利用OAuth 2.0实现一个OpenID Connect用户身份认证协议。此外,我还邀请了微服务技术领域的专家杨波老师,给我们分享了一个架构案例,基于OAuth 2.0/JWT的微服务参考架构。",-1),y={href:"https://github.com/xindongbook/oauth2-code",target:"_blank",rel:"noopener noreferrer"},v=t("p",null,"简单的地方在于,代码中除了基本的Servlet技术外,我没有引入任何其它的第三方内容。所以,你只要能够理解Request和Response,就能够理解这份代码。",-1),T=t("p",null,"可落地的地方在于,虽然它是一份简单的代码,但它不仅把整个OAuth 2.0的组件都跑通了,还包含了实践一个OIDC协议的具体实现。当然,我在代码里面还预留了一些TODO的地方,你可以结合上下文来自行实践处理。这是一项开源的工程。",-1),S=t("p",null,"在这里,我总结了OAuth 2.0的知识体系图,你也可以先了解下整个课程的知识结构。",-1),D=t("p",null,[t("img",{src:"https://static001.geekbang.org/resource/image/96/08/969e0e39d327d109ab23e794a4263708.jpg",alt:"体系图"})],-1),N=t("p",null,"这样一来,你学完这门课后,便能在互联网的授权领域练就一双“火眼金睛”,可以发现所有使用过OAuth 2.0的痕迹,诸如微信登录的场景。这样,即使你不用抓包分析,也能够洞悉它背后的原理,为今后快速熟知互联网的类似场景打下基础。",-1),I=t("p",null,"最后,我还想正式认识一下你。你可以在留言区里做个自我介绍,和我聊聊,你目前学习、使用OAuth 2.0的难点、痛点是什么?或者,你也可以聊聊你对OAuth 2.0、对授权还有哪些独特的思考和体验,欢迎在留言区和我交流讨论。",-1),Z=t("p",null,"好了,现在就开启我们的OAuth 2.0之旅吧。",-1);function E(R,j){const n=a("ExternalLinkIcon");return r(),i("div",null,[t("div",u,[l,t("p",null,[e("oauth2相关博客转载于"),t("a",c,[e("极客时间"),o(n)])])]),d,t("p",null,[e("我要结合自己在开放平台上的工作经验以及对OAuth 2.0的理解,一次性地给你说透授权这件事儿。同时,我还查阅了诸多资料,包括"),t("a",A,[e("OAuth 2协议规范"),o(n)]),e("、"),t("a",O,[e("OpenID Connect explained"),o(n)]),e("等,力求给你带来最贴近本质的OAuth 2.0知识的讲解。")]),_,m,g,b,f,k,x,C,t("p",null,[e("最后,为了配合课程的学习,不让理论过于枯燥,也为了学以致用, 我"),t("a",y,[e("GitHub"),o(n)]),e("上为你准备了一份非常简单、可落地的 通过Java语言来实现的代码。")]),v,T,S,D,N,I,Z])}const P=h(s,[["render",E],["__file","index.html.vue"]]),V=JSON.parse('{"path":"/other/oauth2/","title":"开篇词 | 为什么要学OAuth 2.0?","lang":"zh-CN","frontmatter":{"title":"开篇词 | 为什么要学OAuth 2.0?","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","description":"申明 oauth2相关博客转载于极客时间 你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。 我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/oauth2/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"开篇词 | 为什么要学OAuth 2.0?"}],["meta",{"property":"og:description","content":"申明 oauth2相关博客转载于极客时间 你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。 我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://static001.geekbang.org/resource/image/96/08/969e0e39d327d109ab23e794a4263708.jpg"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-11-08T08:17:56.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-11-08T08:17:56.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"开篇词 | 为什么要学OAuth 2.0?\\",\\"image\\":[\\"https://static001.geekbang.org/resource/image/96/08/969e0e39d327d109ab23e794a4263708.jpg\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2022-11-08T08:17:56.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"OAuth 2.0是什么?","slug":"oauth-2-0是什么","link":"#oauth-2-0是什么","children":[]},{"level":3,"title":"为什么会有这门课?","slug":"为什么会有这门课","link":"#为什么会有这门课","children":[]},{"level":3,"title":"这门课是怎么设计的?","slug":"这门课是怎么设计的","link":"#这门课是怎么设计的","children":[]}],"git":{"createdTime":1667895476000,"updatedTime":1667895476000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":7.87,"words":2360},"filePathRelative":"other/oauth2/README.md","localizedDate":"2022年11月8日","excerpt":"
      \\n

      申明

      \\n

      oauth2相关博客转载于极客时间

      \\n
      \\n

      你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。

      \\n

      我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,毕竟网关要承载整个开放平台的调用量,同时还要有足够的系统容错能力。

      ","autoDesc":true}');export{P as comp,V as data}; diff --git a/assets/index.html-zTuDnnQ9.js b/assets/index.html-1bodzj_2.js similarity index 94% rename from assets/index.html-zTuDnnQ9.js rename to assets/index.html-1bodzj_2.js index bca874b5a9..9d37e6da38 100644 --- a/assets/index.html-zTuDnnQ9.js +++ b/assets/index.html-1bodzj_2.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E5%AF%B9%E8%B1%A1%E9%94%81/","title":"对象锁 分类","lang":"zh-CN","frontmatter":{"title":"对象锁 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"对象锁","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E5%AF%B9%E8%B1%A1%E9%94%81/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"对象锁 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"对象锁 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E5%AF%B9%E8%B1%A1%E9%94%81/","title":"对象锁 分类","lang":"zh-CN","frontmatter":{"title":"对象锁 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"对象锁","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E5%AF%B9%E8%B1%A1%E9%94%81/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"对象锁 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"对象锁 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-BX9tTXXW.js b/assets/index.html-2E2sJd9T.js similarity index 94% rename from assets/index.html-BX9tTXXW.js rename to assets/index.html-2E2sJd9T.js index e93fd41447..eaab5e9410 100644 --- a/assets/index.html-BX9tTXXW.js +++ b/assets/index.html-2E2sJd9T.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E7%94%B5%E5%AD%90%E4%B9%A6/","title":"电子书 分类","lang":"zh-CN","frontmatter":{"title":"电子书 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"电子书","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E7%94%B5%E5%AD%90%E4%B9%A6/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"电子书 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"电子书 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E7%94%B5%E5%AD%90%E4%B9%A6/","title":"电子书 分类","lang":"zh-CN","frontmatter":{"title":"电子书 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"电子书","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E7%94%B5%E5%AD%90%E4%B9%A6/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"电子书 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"电子书 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-D3LGsC8Z.js b/assets/index.html-3y6s76rT.js similarity index 93% rename from assets/index.html-D3LGsC8Z.js rename to assets/index.html-3y6s76rT.js index 7ee98cf742..cc8fc954ca 100644 --- a/assets/index.html-D3LGsC8Z.js +++ b/assets/index.html-3y6s76rT.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const n={};function a(r,i){return t(),o("div")}const l=e(n,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/article/","title":"文章","lang":"zh-CN","frontmatter":{"title":"文章","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"type","key":"article"},"layout":"BlogType","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/article/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"文章"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"文章\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function a(r,i){return t(),o("div")}const l=e(n,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/article/","title":"文章","lang":"zh-CN","frontmatter":{"title":"文章","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"type","key":"article"},"layout":"BlogType","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/article/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"文章"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"文章\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-AC98BttZ.js b/assets/index.html-43Da7xtg.js similarity index 94% rename from assets/index.html-AC98BttZ.js rename to assets/index.html-43Da7xtg.js index c3b09986d5..828219a1e4 100644 --- a/assets/index.html-AC98BttZ.js +++ b/assets/index.html-43Da7xtg.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,s){return t(),o("div")}const c=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/sso/","title":"标签: sso","lang":"zh-CN","frontmatter":{"title":"标签: sso","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"sso","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/sso/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: sso"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: sso\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,s){return t(),o("div")}const c=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/sso/","title":"标签: sso","lang":"zh-CN","frontmatter":{"title":"标签: sso","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"sso","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/sso/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: sso"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: sso\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,l as data}; diff --git a/assets/index.html-SQqXSiBs.js b/assets/index.html-52PFUvm3.js similarity index 96% rename from assets/index.html-SQqXSiBs.js rename to assets/index.html-52PFUvm3.js index e1e6b67000..9982042a1c 100644 --- a/assets/index.html-SQqXSiBs.js +++ b/assets/index.html-52PFUvm3.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const r={};function a(n,i){return t(),o("div")}const p=e(r,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/java/framework/security/","title":"Security学习","lang":"zh-CN","frontmatter":{"title":"Security学习","date":"2022-11-03T00:00:00.000Z","author":"chensino","keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Security学习"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2022-11-03T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Security学习\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-11-03T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[],"git":{"createdTime":1667459365000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.04,"words":11},"filePathRelative":"java/framework/security/README.md","localizedDate":"2022年11月3日","excerpt":""}');export{p as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const r={};function a(n,i){return t(),o("div")}const p=e(r,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/java/framework/security/","title":"Security学习","lang":"zh-CN","frontmatter":{"title":"Security学习","date":"2022-11-03T00:00:00.000Z","author":"chensino","keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/security/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Security学习"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2022-11-03T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Security学习\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-11-03T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[],"git":{"createdTime":1667459365000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.04,"words":11},"filePathRelative":"java/framework/security/README.md","localizedDate":"2022年11月3日","excerpt":""}');export{p as comp,s as data}; diff --git a/assets/index.html-Ah6mlgvk.js b/assets/index.html-Ah6mlgvk.js deleted file mode 100644 index a7f13609b7..0000000000 --- a/assets/index.html-Ah6mlgvk.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e}from"./app-CVMfKeWw.js";const r={},a=e("p",null,[e("img",{src:"https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png",alt:"JetBrains Logo (Main) logo"})],-1),i=[a];function c(s,p){return o(),n("div",null,i)}const g=t(r,[["render",c],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/","title":"ChenSino","lang":"zh-CN","frontmatter":{"home":true,"layout":"BlogHome","icon":"home","title":"ChenSino","heroImage":"/logo.svg","heroText":"ChenSino","heroFullScreen":true,"tagline":"洛星星的学习笔记","projects":[{"icon":"project","name":"ChenSino","desc":"好好学习,天天向上","link":"https://ChenSino.github.io"}],"footer":"Give me a chance,I can surprise the word!","description":"JetBrains Logo (Main) logo","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"ChenSino"}],["meta",{"property":"og:description","content":"JetBrains Logo (Main) logo"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:image","content":"https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"ChenSino\\",\\"description\\":\\"JetBrains Logo (Main) logo\\"}"]]},"headers":[],"git":{"createdTime":1659087823000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":4},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.19,"words":56},"filePathRelative":"README.md","localizedDate":"2022年7月29日","excerpt":"

      \\"JetBrains

      \\n","autoDesc":true}');export{g as comp,h as data}; diff --git a/assets/index.html-CPxFcKAG.js b/assets/index.html-B0HiH-Xn.js similarity index 94% rename from assets/index.html-CPxFcKAG.js rename to assets/index.html-B0HiH-Xn.js index 7dcee5e0cb..081c36b3dd 100644 --- a/assets/index.html-CPxFcKAG.js +++ b/assets/index.html-B0HiH-Xn.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as r,c as n,d as p}from"./app-CVMfKeWw.js";const a={};function i(c,l){const e=o("Catalog");return r(),n("div",null,[p(e)])}const h=t(a,[["render",i],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/other/computerprinciple/","title":"Computerprinciple","lang":"zh-CN","frontmatter":{"title":"Computerprinciple","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/computerprinciple/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Computerprinciple"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Computerprinciple\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,u as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as r,c as n,d as p}from"./app-_Oi5YZFn.js";const a={};function i(c,l){const e=o("Catalog");return r(),n("div",null,[p(e)])}const h=t(a,[["render",i],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/other/computerprinciple/","title":"Computerprinciple","lang":"zh-CN","frontmatter":{"title":"Computerprinciple","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/computerprinciple/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Computerprinciple"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Computerprinciple\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,u as data}; diff --git a/assets/index.html-Dhyapq1H.js b/assets/index.html-BA11w6YR.js similarity index 94% rename from assets/index.html-Dhyapq1H.js rename to assets/index.html-BA11w6YR.js index c8b0b0efe7..b4392a2a1e 100644 --- a/assets/index.html-Dhyapq1H.js +++ b/assets/index.html-BA11w6YR.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/","title":"分类","lang":"zh-CN","frontmatter":{"title":"分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/","title":"分类","lang":"zh-CN","frontmatter":{"title":"分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-BQhu6ulG.js b/assets/index.html-BAA1oPlT.js similarity index 94% rename from assets/index.html-BQhu6ulG.js rename to assets/index.html-BAA1oPlT.js index 1088a51cf6..ced0845ef5 100644 --- a/assets/index.html-BQhu6ulG.js +++ b/assets/index.html-BAA1oPlT.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const r={};function a(n,c){return t(),o("div")}const s=e(r,[["render",a],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/security/","title":"Security 分类","lang":"zh-CN","frontmatter":{"title":"Security 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"Security","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/security/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Security 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Security 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{s as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const r={};function a(n,c){return t(),o("div")}const s=e(r,[["render",a],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/security/","title":"Security 分类","lang":"zh-CN","frontmatter":{"title":"Security 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"Security","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/security/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Security 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Security 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{s as comp,l as data}; diff --git a/assets/index.html-B35-Vn16.js b/assets/index.html-BB0RPG_h.js similarity index 95% rename from assets/index.html-B35-Vn16.js rename to assets/index.html-BB0RPG_h.js index f51c41e84c..4e397b1117 100644 --- a/assets/index.html-B35-Vn16.js +++ b/assets/index.html-BB0RPG_h.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as a}from"./app-CVMfKeWw.js";const o={};function r(n,i){return e(),a("div")}const s=t(o,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/%E4%BD%A0%E6%89%80%E4%B8%8D%E4%BA%86%E8%A7%A3%E7%9A%84javascript/","title":"标签: 你所不了解的JavaScript","lang":"zh-CN","frontmatter":{"title":"标签: 你所不了解的JavaScript","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"你所不了解的JavaScript","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E4%BD%A0%E6%89%80%E4%B8%8D%E4%BA%86%E8%A7%A3%E7%9A%84javascript/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 你所不了解的JavaScript"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 你所不了解的JavaScript\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{s as comp,l as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as a}from"./app-_Oi5YZFn.js";const o={};function r(n,i){return e(),a("div")}const s=t(o,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/%E4%BD%A0%E6%89%80%E4%B8%8D%E4%BA%86%E8%A7%A3%E7%9A%84javascript/","title":"标签: 你所不了解的JavaScript","lang":"zh-CN","frontmatter":{"title":"标签: 你所不了解的JavaScript","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"你所不了解的JavaScript","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E4%BD%A0%E6%89%80%E4%B8%8D%E4%BA%86%E8%A7%A3%E7%9A%84javascript/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 你所不了解的JavaScript"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 你所不了解的JavaScript\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{s as comp,l as data}; diff --git a/assets/index.html-EDXVLWDR.js b/assets/index.html-BBsSpY4m.js similarity index 96% rename from assets/index.html-EDXVLWDR.js rename to assets/index.html-BBsSpY4m.js index 0b8d123c25..a015acfa18 100644 --- a/assets/index.html-EDXVLWDR.js +++ b/assets/index.html-BBsSpY4m.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const n={};function a(r,i){return t(),o("div")}const p=e(n,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/other/essay/","title":"随笔分享","lang":"zh-CN","frontmatter":{"title":"随笔分享","date":"2022-03-17T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/essay/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"随笔分享"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-03-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"随笔分享\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-17T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":14},"filePathRelative":"other/essay/README.md","localizedDate":"2022年3月17日","excerpt":""}');export{p as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function a(r,i){return t(),o("div")}const p=e(n,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/other/essay/","title":"随笔分享","lang":"zh-CN","frontmatter":{"title":"随笔分享","date":"2022-03-17T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/essay/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"随笔分享"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-03-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"随笔分享\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-17T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":14},"filePathRelative":"other/essay/README.md","localizedDate":"2022年3月17日","excerpt":""}');export{p as comp,s as data}; diff --git a/assets/index.html-XxdR190C.js b/assets/index.html-BDDCxsP9.js similarity index 94% rename from assets/index.html-XxdR190C.js rename to assets/index.html-BDDCxsP9.js index afb67be041..10bba84d49 100644 --- a/assets/index.html-XxdR190C.js +++ b/assets/index.html-BDDCxsP9.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E7%BA%BF%E7%A8%8B%E6%B1%A0/","title":"标签: 线程池","lang":"zh-CN","frontmatter":{"title":"标签: 线程池","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"线程池","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E7%BA%BF%E7%A8%8B%E6%B1%A0/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 线程池"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 线程池\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E7%BA%BF%E7%A8%8B%E6%B1%A0/","title":"标签: 线程池","lang":"zh-CN","frontmatter":{"title":"标签: 线程池","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"线程池","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E7%BA%BF%E7%A8%8B%E6%B1%A0/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 线程池"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 线程池\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-Cnhk4PBp.js b/assets/index.html-BGEnglY6.js similarity index 97% rename from assets/index.html-Cnhk4PBp.js rename to assets/index.html-BGEnglY6.js index afd4753f05..5f99f0241a 100644 --- a/assets/index.html-Cnhk4PBp.js +++ b/assets/index.html-BGEnglY6.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as r,a as e}from"./app-CVMfKeWw.js";const o={},n=e("h2",{id:"_1-gradle的放弃之路",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-gradle的放弃之路"},[e("span",null,"1. Gradle的放弃之路")])],-1),i=e("p",null,"世上无难事,只要肯放弃",-1),l=[n,i];function c(d,p){return a(),r("div",null,l)}const h=t(o,[["render",c],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/java/other/gradle/","title":"Gradle","lang":"zh-CN","frontmatter":{"title":"Gradle","date":"2022-10-18T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"1. Gradle的放弃之路 世上无难事,只要肯放弃","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/gradle/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Gradle"}],["meta",{"property":"og:description","content":"1. Gradle的放弃之路 世上无难事,只要肯放弃"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-10-18T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Gradle\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-10-18T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1. Gradle的放弃之路","slug":"_1-gradle的放弃之路","link":"#_1-gradle的放弃之路","children":[]}],"git":{"createdTime":1666600159000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.09,"words":28},"filePathRelative":"java/other/gradle/README.md","localizedDate":"2022年10月18日","excerpt":"

      1. Gradle的放弃之路

      \\n

      世上无难事,只要肯放弃

      \\n","autoDesc":true}');export{h as comp,u as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as r,a as e}from"./app-_Oi5YZFn.js";const o={},n=e("h2",{id:"_1-gradle的放弃之路",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-gradle的放弃之路"},[e("span",null,"1. Gradle的放弃之路")])],-1),i=e("p",null,"世上无难事,只要肯放弃",-1),l=[n,i];function c(d,p){return a(),r("div",null,l)}const h=t(o,[["render",c],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/java/other/gradle/","title":"Gradle","lang":"zh-CN","frontmatter":{"title":"Gradle","date":"2022-10-18T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"1. Gradle的放弃之路 世上无难事,只要肯放弃","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/gradle/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Gradle"}],["meta",{"property":"og:description","content":"1. Gradle的放弃之路 世上无难事,只要肯放弃"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-10-18T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Gradle\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-10-18T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1. Gradle的放弃之路","slug":"_1-gradle的放弃之路","link":"#_1-gradle的放弃之路","children":[]}],"git":{"createdTime":1666600159000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.09,"words":28},"filePathRelative":"java/other/gradle/README.md","localizedDate":"2022年10月18日","excerpt":"

      1. Gradle的放弃之路

      \\n

      世上无难事,只要肯放弃

      \\n","autoDesc":true}');export{h as comp,u as data}; diff --git a/assets/index.html-Ds23VMOY.js b/assets/index.html-BGmSAx78.js similarity index 94% rename from assets/index.html-Ds23VMOY.js rename to assets/index.html-BGmSAx78.js index 7303901c0d..42b7af2d15 100644 --- a/assets/index.html-Ds23VMOY.js +++ b/assets/index.html-BGmSAx78.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/git-%E6%93%8D%E4%BD%9C/","title":"git 操作 分类","lang":"zh-CN","frontmatter":{"title":"git 操作 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"git 操作","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/git-%E6%93%8D%E4%BD%9C/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"git 操作 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"git 操作 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/git-%E6%93%8D%E4%BD%9C/","title":"git 操作 分类","lang":"zh-CN","frontmatter":{"title":"git 操作 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"git 操作","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/git-%E6%93%8D%E4%BD%9C/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"git 操作 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"git 操作 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-B2inZhc8.js b/assets/index.html-BIoY637M.js similarity index 96% rename from assets/index.html-B2inZhc8.js rename to assets/index.html-BIoY637M.js index 687d309c9a..98273540e4 100644 --- a/assets/index.html-B2inZhc8.js +++ b/assets/index.html-BIoY637M.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o,a as n}from"./app-CVMfKeWw.js";const r={},i=n("p",null,"好记性不如烂笔头",-1),a=[i];function c(p,m){return t(),o("div",null,a)}const d=e(r,[["render",c],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/other/linux/","title":"Linux","lang":"zh-CN","frontmatter":{"title":"Linux","date":"2022-03-17T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["运维"],"description":"好记性不如烂笔头","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/linux/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Linux"}],["meta",{"property":"og:description","content":"好记性不如烂笔头"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-03-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Linux\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-17T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.08,"words":23},"filePathRelative":"other/linux/README.md","localizedDate":"2022年3月17日","excerpt":"

      好记性不如烂笔头

      \\n\\n","autoDesc":true}');export{d as comp,u as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o,a as n}from"./app-_Oi5YZFn.js";const r={},i=n("p",null,"好记性不如烂笔头",-1),a=[i];function c(p,m){return t(),o("div",null,a)}const d=e(r,[["render",c],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/other/linux/","title":"Linux","lang":"zh-CN","frontmatter":{"title":"Linux","date":"2022-03-17T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["运维"],"description":"好记性不如烂笔头","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/linux/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Linux"}],["meta",{"property":"og:description","content":"好记性不如烂笔头"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-03-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Linux\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-17T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.08,"words":23},"filePathRelative":"other/linux/README.md","localizedDate":"2022年3月17日","excerpt":"

      好记性不如烂笔头

      \\n\\n","autoDesc":true}');export{d as comp,u as data}; diff --git a/assets/index.html-BKgtb9Ah.js b/assets/index.html-BJ_XZI-3.js similarity index 94% rename from assets/index.html-BKgtb9Ah.js rename to assets/index.html-BJ_XZI-3.js index d1d6d1a91e..471f4b6744 100644 --- a/assets/index.html-BKgtb9Ah.js +++ b/assets/index.html-BJ_XZI-3.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/","title":"使用指南 分类","lang":"zh-CN","frontmatter":{"title":"使用指南 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"使用指南","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"使用指南 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"使用指南 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/","title":"使用指南 分类","lang":"zh-CN","frontmatter":{"title":"使用指南 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"使用指南","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"使用指南 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"使用指南 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-BUg70Vrr.js b/assets/index.html-BJxwK7AA.js similarity index 94% rename from assets/index.html-BUg70Vrr.js rename to assets/index.html-BJxwK7AA.js index 89cb58926b..1664234673 100644 --- a/assets/index.html-BUg70Vrr.js +++ b/assets/index.html-BJxwK7AA.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E4%BA%8C%E5%8F%89%E6%A0%91/","title":"标签: 二叉树","lang":"zh-CN","frontmatter":{"title":"标签: 二叉树","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"二叉树","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E4%BA%8C%E5%8F%89%E6%A0%91/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 二叉树"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 二叉树\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E4%BA%8C%E5%8F%89%E6%A0%91/","title":"标签: 二叉树","lang":"zh-CN","frontmatter":{"title":"标签: 二叉树","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"二叉树","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E4%BA%8C%E5%8F%89%E6%A0%91/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 二叉树"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 二叉树\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-DaPOtDhI.js b/assets/index.html-BLiK3lZA.js similarity index 96% rename from assets/index.html-DaPOtDhI.js rename to assets/index.html-BLiK3lZA.js index 0988cca23d..af08b54bb7 100644 --- a/assets/index.html-DaPOtDhI.js +++ b/assets/index.html-BLiK3lZA.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-CVMfKeWw.js";const o={};function n(r,i){return t(),a("div")}const m=e(o,[["render",n],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/java/advance/","title":"Java进阶","lang":"zh-CN","frontmatter":{"title":"Java进阶","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/advance/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Java进阶"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Java进阶\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.03,"words":8},"filePathRelative":"java/advance/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{m as comp,d as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-_Oi5YZFn.js";const o={};function n(r,i){return t(),a("div")}const m=e(o,[["render",n],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/java/advance/","title":"Java进阶","lang":"zh-CN","frontmatter":{"title":"Java进阶","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/advance/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Java进阶"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Java进阶\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.03,"words":8},"filePathRelative":"java/advance/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{m as comp,d as data}; diff --git a/assets/index.html-pad8GR4o.js b/assets/index.html-BNCitO52.js similarity index 94% rename from assets/index.html-pad8GR4o.js rename to assets/index.html-BNCitO52.js index 371e345b93..a45ecfd411 100644 --- a/assets/index.html-pad8GR4o.js +++ b/assets/index.html-BNCitO52.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E5%B0%8F%E7%BB%84%E5%88%86%E4%BA%AB/","title":"小组分享 分类","lang":"zh-CN","frontmatter":{"title":"小组分享 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"小组分享","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E5%B0%8F%E7%BB%84%E5%88%86%E4%BA%AB/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"小组分享 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"小组分享 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E5%B0%8F%E7%BB%84%E5%88%86%E4%BA%AB/","title":"小组分享 分类","lang":"zh-CN","frontmatter":{"title":"小组分享 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"小组分享","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E5%B0%8F%E7%BB%84%E5%88%86%E4%BA%AB/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"小组分享 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"小组分享 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-Cu9v8LOw.js b/assets/index.html-BOFOt1os.js similarity index 94% rename from assets/index.html-Cu9v8LOw.js rename to assets/index.html-BOFOt1os.js index 2704c4be9f..f1cfc6fd7a 100644 --- a/assets/index.html-Cu9v8LOw.js +++ b/assets/index.html-BOFOt1os.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E5%BF%85%E4%BC%9A/","title":"必会 分类","lang":"zh-CN","frontmatter":{"title":"必会 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"必会","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E5%BF%85%E4%BC%9A/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"必会 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"必会 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E5%BF%85%E4%BC%9A/","title":"必会 分类","lang":"zh-CN","frontmatter":{"title":"必会 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"必会","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E5%BF%85%E4%BC%9A/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"必会 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"必会 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-CF8Xpy4A.js b/assets/index.html-BR_hmahK.js similarity index 94% rename from assets/index.html-CF8Xpy4A.js rename to assets/index.html-BR_hmahK.js index ff9b82d936..c198cfa21f 100644 --- a/assets/index.html-CF8Xpy4A.js +++ b/assets/index.html-BR_hmahK.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/oauth/","title":"OAuth 分类","lang":"zh-CN","frontmatter":{"title":"OAuth 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"OAuth","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/oauth/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"OAuth 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"OAuth 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/oauth/","title":"OAuth 分类","lang":"zh-CN","frontmatter":{"title":"OAuth 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"OAuth","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/oauth/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"OAuth 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"OAuth 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-xL8R2rvI.js b/assets/index.html-Be9YOXiD.js similarity index 94% rename from assets/index.html-xL8R2rvI.js rename to assets/index.html-Be9YOXiD.js index 71cf1c1597..5ffdef7e2e 100644 --- a/assets/index.html-xL8R2rvI.js +++ b/assets/index.html-Be9YOXiD.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E9%83%A8%E7%BD%B2%E6%90%AD%E5%BB%BA/","title":"标签: 部署搭建","lang":"zh-CN","frontmatter":{"title":"标签: 部署搭建","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"部署搭建","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E9%83%A8%E7%BD%B2%E6%90%AD%E5%BB%BA/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 部署搭建"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 部署搭建\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E9%83%A8%E7%BD%B2%E6%90%AD%E5%BB%BA/","title":"标签: 部署搭建","lang":"zh-CN","frontmatter":{"title":"标签: 部署搭建","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"部署搭建","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E9%83%A8%E7%BD%B2%E6%90%AD%E5%BB%BA/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 部署搭建"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 部署搭建\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-DZIt25hg.js b/assets/index.html-BgeMLVVF.js similarity index 97% rename from assets/index.html-DZIt25hg.js rename to assets/index.html-BgeMLVVF.js index 367ed8091f..16b06c9bcb 100644 --- a/assets/index.html-DZIt25hg.js +++ b/assets/index.html-BgeMLVVF.js @@ -1 +1 @@ -import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as i,c,a as e,b as o,d as n}from"./app-CVMfKeWw.js";const p={},h=e("h2",{id:"emoji",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#emoji"},[e("span",null,"Emoji")])],-1),s={href:"https://gist.github.com/rxaviers/7360908",target:"_blank",rel:"noopener noreferrer"},m=e("h2",{id:"vue-press-theme-hope-icon",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#vue-press-theme-hope-icon"},[e("span",null,"vue-press-theme-hope Icon")])],-1),l={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/interface/icon.html",target:"_blank",rel:"noopener noreferrer"};function d(u,g){const t=a("ExternalLinkIcon");return i(),c("div",null,[h,e("p",null,[e("a",s,[o("Emoji"),n(t)])]),m,e("p",null,[e("a",l,[o("Icon"),n(t)])])])}const f=r(p,[["render",d],["__file","index.html.vue"]]),v=JSON.parse('{"path":"/other/markdown/","title":"MarkDown资源","lang":"zh-CN","frontmatter":{"title":"MarkDown资源","date":"2022-07-11T00:00:00.000Z","author":"chenkun","keys":null,"category":["markdown"],"tag":["markdown"],"description":"Emoji Emoji vue-press-theme-hope Icon Icon","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/markdown/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"MarkDown资源"}],["meta",{"property":"og:description","content":"Emoji Emoji vue-press-theme-hope Icon Icon"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-02T06:00:31.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:tag","content":"markdown"}],["meta",{"property":"article:published_time","content":"2022-07-11T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-02T06:00:31.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"MarkDown资源\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-11T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-02T06:00:31.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"Emoji","slug":"emoji","link":"#emoji","children":[]},{"level":2,"title":"vue-press-theme-hope Icon","slug":"vue-press-theme-hope-icon","link":"#vue-press-theme-hope-icon","children":[]}],"git":{"createdTime":1659420031000,"updatedTime":1659420031000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.1,"words":29},"filePathRelative":"other/markdown/README.md","localizedDate":"2022年7月11日","excerpt":"

      Emoji

      \\n

      Emoji

      \\n

      vue-press-theme-hope Icon

      \\n

      Icon

      \\n","autoDesc":true}');export{f as comp,v as data}; +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as i,c,a as e,b as o,d as n}from"./app-_Oi5YZFn.js";const p={},h=e("h2",{id:"emoji",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#emoji"},[e("span",null,"Emoji")])],-1),s={href:"https://gist.github.com/rxaviers/7360908",target:"_blank",rel:"noopener noreferrer"},m=e("h2",{id:"vue-press-theme-hope-icon",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#vue-press-theme-hope-icon"},[e("span",null,"vue-press-theme-hope Icon")])],-1),l={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/interface/icon.html",target:"_blank",rel:"noopener noreferrer"};function d(u,g){const t=a("ExternalLinkIcon");return i(),c("div",null,[h,e("p",null,[e("a",s,[o("Emoji"),n(t)])]),m,e("p",null,[e("a",l,[o("Icon"),n(t)])])])}const f=r(p,[["render",d],["__file","index.html.vue"]]),v=JSON.parse('{"path":"/other/markdown/","title":"MarkDown资源","lang":"zh-CN","frontmatter":{"title":"MarkDown资源","date":"2022-07-11T00:00:00.000Z","author":"chenkun","keys":null,"category":["markdown"],"tag":["markdown"],"description":"Emoji Emoji vue-press-theme-hope Icon Icon","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/markdown/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"MarkDown资源"}],["meta",{"property":"og:description","content":"Emoji Emoji vue-press-theme-hope Icon Icon"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-02T06:00:31.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:tag","content":"markdown"}],["meta",{"property":"article:published_time","content":"2022-07-11T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-02T06:00:31.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"MarkDown资源\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-11T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-02T06:00:31.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"Emoji","slug":"emoji","link":"#emoji","children":[]},{"level":2,"title":"vue-press-theme-hope Icon","slug":"vue-press-theme-hope-icon","link":"#vue-press-theme-hope-icon","children":[]}],"git":{"createdTime":1659420031000,"updatedTime":1659420031000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.1,"words":29},"filePathRelative":"other/markdown/README.md","localizedDate":"2022年7月11日","excerpt":"

      Emoji

      \\n

      Emoji

      \\n

      vue-press-theme-hope Icon

      \\n

      Icon

      \\n","autoDesc":true}');export{f as comp,v as data}; diff --git a/assets/index.html-CpRQKI06.js b/assets/index.html-BgghxhP9.js similarity index 94% rename from assets/index.html-CpRQKI06.js rename to assets/index.html-BgghxhP9.js index aa671da3d7..ab469ba356 100644 --- a/assets/index.html-CpRQKI06.js +++ b/assets/index.html-BgghxhP9.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E5%85%AC%E5%8F%B8%E4%B8%9A%E5%8A%A1/","title":"公司业务 分类","lang":"zh-CN","frontmatter":{"title":"公司业务 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"公司业务","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E5%85%AC%E5%8F%B8%E4%B8%9A%E5%8A%A1/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"公司业务 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"公司业务 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E5%85%AC%E5%8F%B8%E4%B8%9A%E5%8A%A1/","title":"公司业务 分类","lang":"zh-CN","frontmatter":{"title":"公司业务 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"公司业务","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E5%85%AC%E5%8F%B8%E4%B8%9A%E5%8A%A1/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"公司业务 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"公司业务 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-C6jwACfV.js b/assets/index.html-Bk4Xl8z2.js similarity index 94% rename from assets/index.html-C6jwACfV.js rename to assets/index.html-Bk4Xl8z2.js index e72f455ba5..2aac23cdc2 100644 --- a/assets/index.html-C6jwACfV.js +++ b/assets/index.html-Bk4Xl8z2.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,s){return t(),o("div")}const p=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/js%E5%9F%BA%E7%A1%80/","title":"js基础 分类","lang":"zh-CN","frontmatter":{"title":"js基础 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"js基础","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/js%E5%9F%BA%E7%A1%80/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"js基础 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"js基础 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{p as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,s){return t(),o("div")}const p=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/js%E5%9F%BA%E7%A1%80/","title":"js基础 分类","lang":"zh-CN","frontmatter":{"title":"js基础 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"js基础","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/js%E5%9F%BA%E7%A1%80/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"js基础 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"js基础 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{p as comp,l as data}; diff --git a/assets/index.html-CUh5IBbk.js b/assets/index.html-BkurDFO4.js similarity index 94% rename from assets/index.html-CUh5IBbk.js rename to assets/index.html-BkurDFO4.js index 1d49548fcf..80adf5fe8c 100644 --- a/assets/index.html-CUh5IBbk.js +++ b/assets/index.html-BkurDFO4.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/","title":"标签: 工具使用","lang":"zh-CN","frontmatter":{"title":"标签: 工具使用","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"工具使用","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 工具使用"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 工具使用\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/","title":"标签: 工具使用","lang":"zh-CN","frontmatter":{"title":"标签: 工具使用","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"工具使用","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 工具使用"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 工具使用\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-RGleUAuC.js b/assets/index.html-BluLs2ps.js similarity index 94% rename from assets/index.html-RGleUAuC.js rename to assets/index.html-BluLs2ps.js index b693a44d74..540f0da9fc 100644 --- a/assets/index.html-RGleUAuC.js +++ b/assets/index.html-BluLs2ps.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E6%A1%86%E6%9E%B6/","title":"框架 分类","lang":"zh-CN","frontmatter":{"title":"框架 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"框架","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E6%A1%86%E6%9E%B6/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"框架 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"框架 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E6%A1%86%E6%9E%B6/","title":"框架 分类","lang":"zh-CN","frontmatter":{"title":"框架 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"框架","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E6%A1%86%E6%9E%B6/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"框架 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"框架 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-DKR14bqO.js b/assets/index.html-Bm2QW3Yx.js similarity index 96% rename from assets/index.html-DKR14bqO.js rename to assets/index.html-Bm2QW3Yx.js index 57c321379e..61f7348e3e 100644 --- a/assets/index.html-DKR14bqO.js +++ b/assets/index.html-Bm2QW3Yx.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const n={};function r(a,i){return t(),o("div")}const m=e(n,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/other/training/","title":"小组分享","lang":"zh-CN","frontmatter":{"title":"小组分享","date":"2022-07-12T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/training/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"小组分享"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-07-12T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"小组分享\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-12T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":15},"filePathRelative":"other/training/README.md","localizedDate":"2022年7月12日","excerpt":""}');export{m as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function r(a,i){return t(),o("div")}const m=e(n,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/other/training/","title":"小组分享","lang":"zh-CN","frontmatter":{"title":"小组分享","date":"2022-07-12T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/training/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"小组分享"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-07-12T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"小组分享\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-12T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":15},"filePathRelative":"other/training/README.md","localizedDate":"2022年7月12日","excerpt":""}');export{m as comp,l as data}; diff --git a/assets/index.html-COzc4rHJ.js b/assets/index.html-Bn0M7tTB.js similarity index 96% rename from assets/index.html-COzc4rHJ.js rename to assets/index.html-Bn0M7tTB.js index 59804b700f..fbfd0eb475 100644 --- a/assets/index.html-COzc4rHJ.js +++ b/assets/index.html-Bn0M7tTB.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const p=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/java/jvm/","title":"JVM","lang":"zh-CN","frontmatter":{"title":"JVM","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/jvm/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"JVM"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"JVM\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.02,"words":6},"filePathRelative":"java/jvm/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{p as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const p=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/java/jvm/","title":"JVM","lang":"zh-CN","frontmatter":{"title":"JVM","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/jvm/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"JVM"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"JVM\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.02,"words":6},"filePathRelative":"java/jvm/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{p as comp,l as data}; diff --git a/assets/index.html-DqHDZd6R.js b/assets/index.html-BoICU8sT.js similarity index 93% rename from assets/index.html-DqHDZd6R.js rename to assets/index.html-BoICU8sT.js index 91b81c9aff..2d9a2023e0 100644 --- a/assets/index.html-DqHDZd6R.js +++ b/assets/index.html-BoICU8sT.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const n={};function a(r,p){return t(),o("div")}const c=e(n,[["render",a],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/star/","title":"星标","lang":"zh-CN","frontmatter":{"title":"星标","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"type","key":"star"},"layout":"BlogType","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/star/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"星标"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"星标\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function a(r,p){return t(),o("div")}const c=e(n,[["render",a],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/star/","title":"星标","lang":"zh-CN","frontmatter":{"title":"星标","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"type","key":"star"},"layout":"BlogType","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/star/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"星标"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"星标\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,l as data}; diff --git a/assets/index.html-Ci4VSHuy.js b/assets/index.html-Bq73GOIG.js similarity index 94% rename from assets/index.html-Ci4VSHuy.js rename to assets/index.html-Bq73GOIG.js index 0a617af5eb..7d453b9ce6 100644 --- a/assets/index.html-Ci4VSHuy.js +++ b/assets/index.html-Bq73GOIG.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-CVMfKeWw.js";const o={};function n(r,c){return t(),a("div")}const m=e(o,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/maven/","title":"maven 分类","lang":"zh-CN","frontmatter":{"title":"maven 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"maven","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/maven/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"maven 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"maven 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{m as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-_Oi5YZFn.js";const o={};function n(r,c){return t(),a("div")}const m=e(o,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/maven/","title":"maven 分类","lang":"zh-CN","frontmatter":{"title":"maven 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"maven","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/maven/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"maven 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"maven 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{m as comp,l as data}; diff --git a/assets/index.html-uyHOrigb.js b/assets/index.html-Bt1B_fZ7.js similarity index 94% rename from assets/index.html-uyHOrigb.js rename to assets/index.html-Bt1B_fZ7.js index 476bd828b2..948a7705ea 100644 --- a/assets/index.html-uyHOrigb.js +++ b/assets/index.html-Bt1B_fZ7.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const m=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/markdown/","title":"markdown 分类","lang":"zh-CN","frontmatter":{"title":"markdown 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"markdown","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/markdown/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"markdown 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"markdown 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{m as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const m=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/markdown/","title":"markdown 分类","lang":"zh-CN","frontmatter":{"title":"markdown 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"markdown","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/markdown/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"markdown 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"markdown 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{m as comp,l as data}; diff --git a/assets/index.html-BMhOFPmp.js b/assets/index.html-BtN-4LZa.js similarity index 96% rename from assets/index.html-BMhOFPmp.js rename to assets/index.html-BtN-4LZa.js index a4b80a4f45..f70627a3d8 100644 --- a/assets/index.html-BMhOFPmp.js +++ b/assets/index.html-BtN-4LZa.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-CVMfKeWw.js";const i={};function r(n,a){return e(),o("div")}const m=t(i,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/other/git/","title":"Git","lang":"zh-CN","frontmatter":{"title":"Git","date":"2022-03-09T16:57:01.000Z","category":["git 操作"],"tag":["必会"],"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/git/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Git"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:published_time","content":"2022-03-09T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Git\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-09T16:57:01.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":16},"filePathRelative":"other/git/README.md","localizedDate":"2022年3月9日","excerpt":""}');export{m as comp,l as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-_Oi5YZFn.js";const i={};function r(n,a){return e(),o("div")}const m=t(i,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/other/git/","title":"Git","lang":"zh-CN","frontmatter":{"title":"Git","date":"2022-03-09T16:57:01.000Z","category":["git 操作"],"tag":["必会"],"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/git/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Git"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:published_time","content":"2022-03-09T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Git\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-09T16:57:01.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":16},"filePathRelative":"other/git/README.md","localizedDate":"2022年3月9日","excerpt":""}');export{m as comp,l as data}; diff --git a/assets/index.html-CJbe2oEP.js b/assets/index.html-BwnKcSJN.js similarity index 94% rename from assets/index.html-CJbe2oEP.js rename to assets/index.html-BwnKcSJN.js index 6b93595b22..ff317ed118 100644 --- a/assets/index.html-CJbe2oEP.js +++ b/assets/index.html-BwnKcSJN.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E5%BF%85%E4%BC%9A/","title":"标签: 必会","lang":"zh-CN","frontmatter":{"title":"标签: 必会","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"必会","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E5%BF%85%E4%BC%9A/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 必会"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 必会\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E5%BF%85%E4%BC%9A/","title":"标签: 必会","lang":"zh-CN","frontmatter":{"title":"标签: 必会","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"必会","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E5%BF%85%E4%BC%9A/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 必会"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 必会\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-DiiTcF6s.js b/assets/index.html-Bx1FwJMs.js similarity index 97% rename from assets/index.html-DiiTcF6s.js rename to assets/index.html-Bx1FwJMs.js index 064eb02753..c5d570a383 100644 --- a/assets/index.html-DiiTcF6s.js +++ b/assets/index.html-Bx1FwJMs.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as a,a as e}from"./app-CVMfKeWw.js";const n={},r=e("h2",{id:"此分类专门定位线上问题",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#此分类专门定位线上问题"},[e("span",null,"此分类专门定位线上问题")])],-1),i=[r];function c(l,p){return o(),a("div",null,i)}const h=t(n,[["render",c],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/java/other/locateproblem/","title":"线上问题定位","lang":"zh-CN","frontmatter":{"title":"线上问题定位","date":"2022-07-28T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"此分类专门定位线上问题","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/locateproblem/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"线上问题定位"}],["meta",{"property":"og:description","content":"此分类专门定位线上问题"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-07-28T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"线上问题定位\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-28T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"此分类专门定位线上问题","slug":"此分类专门定位线上问题","link":"#此分类专门定位线上问题","children":[]}],"git":{"createdTime":1661423386000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.09,"words":27},"filePathRelative":"java/other/locateproblem/README.md","localizedDate":"2022年7月28日","excerpt":"

      此分类专门定位线上问题

      \\n","autoDesc":true}');export{h as comp,d as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as a,a as e}from"./app-_Oi5YZFn.js";const n={},r=e("h2",{id:"此分类专门定位线上问题",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#此分类专门定位线上问题"},[e("span",null,"此分类专门定位线上问题")])],-1),i=[r];function c(l,p){return o(),a("div",null,i)}const h=t(n,[["render",c],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/java/other/locateproblem/","title":"线上问题定位","lang":"zh-CN","frontmatter":{"title":"线上问题定位","date":"2022-07-28T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"此分类专门定位线上问题","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/locateproblem/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"线上问题定位"}],["meta",{"property":"og:description","content":"此分类专门定位线上问题"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-07-28T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"线上问题定位\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-28T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"此分类专门定位线上问题","slug":"此分类专门定位线上问题","link":"#此分类专门定位线上问题","children":[]}],"git":{"createdTime":1661423386000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.09,"words":27},"filePathRelative":"java/other/locateproblem/README.md","localizedDate":"2022年7月28日","excerpt":"

      此分类专门定位线上问题

      \\n","autoDesc":true}');export{h as comp,d as data}; diff --git a/assets/index.html-EMVH5RI8.js b/assets/index.html-Bxi5AXy-.js similarity index 93% rename from assets/index.html-EMVH5RI8.js rename to assets/index.html-Bxi5AXy-.js index 7aaa62558a..555edf53ce 100644 --- a/assets/index.html-EMVH5RI8.js +++ b/assets/index.html-Bxi5AXy-.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as a,c as r,d as n}from"./app-CVMfKeWw.js";const c={};function p(s,i){const e=o("Catalog");return a(),r("div",null,[n(e)])}const f=t(c,[["render",p],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/other/software/","title":"Software","lang":"zh-CN","frontmatter":{"title":"Software","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/software/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Software"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Software\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{f as comp,h as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as a,c as r,d as n}from"./app-_Oi5YZFn.js";const c={};function p(s,i){const e=o("Catalog");return a(),r("div",null,[n(e)])}const f=t(c,[["render",p],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/other/software/","title":"Software","lang":"zh-CN","frontmatter":{"title":"Software","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/software/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Software"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Software\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{f as comp,h as data}; diff --git a/assets/index.html-dkKrYb2Y.js b/assets/index.html-ByKY6u5T.js similarity index 93% rename from assets/index.html-dkKrYb2Y.js rename to assets/index.html-ByKY6u5T.js index 8ff72cdacf..3fd10126ef 100644 --- a/assets/index.html-dkKrYb2Y.js +++ b/assets/index.html-ByKY6u5T.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as r,c as n,d as a}from"./app-CVMfKeWw.js";const c={};function p(i,l){const e=o("Catalog");return r(),n("div",null,[a(e)])}const h=t(c,[["render",p],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/other/docker/","title":"Docker","lang":"zh-CN","frontmatter":{"title":"Docker","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/docker/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Docker"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Docker\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,d as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as r,c as n,d as a}from"./app-_Oi5YZFn.js";const c={};function p(i,l){const e=o("Catalog");return r(),n("div",null,[a(e)])}const h=t(c,[["render",p],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/other/docker/","title":"Docker","lang":"zh-CN","frontmatter":{"title":"Docker","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/docker/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Docker"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Docker\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,d as data}; diff --git a/assets/index.html-CL8Rn6LN.js b/assets/index.html-C2vp8KMJ.js similarity index 94% rename from assets/index.html-CL8Rn6LN.js rename to assets/index.html-C2vp8KMJ.js index 6258e45c4b..384ada5087 100644 --- a/assets/index.html-CL8Rn6LN.js +++ b/assets/index.html-C2vp8KMJ.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E5%A4%9A%E7%BA%BF%E7%A8%8B/","title":"多线程 分类","lang":"zh-CN","frontmatter":{"title":"多线程 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"多线程","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"多线程 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"多线程 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E5%A4%9A%E7%BA%BF%E7%A8%8B/","title":"多线程 分类","lang":"zh-CN","frontmatter":{"title":"多线程 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"多线程","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"多线程 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"多线程 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-DZIE4r_x.js b/assets/index.html-C4Z9a5GS.js similarity index 94% rename from assets/index.html-DZIE4r_x.js rename to assets/index.html-C4Z9a5GS.js index 885e637d8b..4856fcb0a0 100644 --- a/assets/index.html-DZIE4r_x.js +++ b/assets/index.html-C4Z9a5GS.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n}from"./app-CVMfKeWw.js";const o={};function a(r,i){return t(),n("div")}const c=e(o,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/linux/","title":"标签: linux","lang":"zh-CN","frontmatter":{"title":"标签: linux","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"linux","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/linux/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: linux"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: linux\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n}from"./app-_Oi5YZFn.js";const o={};function a(r,i){return t(),n("div")}const c=e(o,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/linux/","title":"标签: linux","lang":"zh-CN","frontmatter":{"title":"标签: linux","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"linux","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/linux/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: linux"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: linux\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,s as data}; diff --git a/assets/index.html-BOWROSmg.js b/assets/index.html-C6FMNcFL.js similarity index 94% rename from assets/index.html-BOWROSmg.js rename to assets/index.html-C6FMNcFL.js index 6e41a3c21a..d4201b6c72 100644 --- a/assets/index.html-BOWROSmg.js +++ b/assets/index.html-C6FMNcFL.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-CVMfKeWw.js";const n={};function r(a,i){return e(),o("div")}const c=t(n,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/springboot/","title":"标签: springboot","lang":"zh-CN","frontmatter":{"title":"标签: springboot","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"springboot","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/springboot/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: springboot"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: springboot\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,l as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-_Oi5YZFn.js";const n={};function r(a,i){return e(),o("div")}const c=t(n,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/springboot/","title":"标签: springboot","lang":"zh-CN","frontmatter":{"title":"标签: springboot","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"springboot","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/springboot/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: springboot"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: springboot\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,l as data}; diff --git a/assets/index.html-BtjOnGEp.js b/assets/index.html-C6kiZ_HF.js similarity index 96% rename from assets/index.html-BtjOnGEp.js rename to assets/index.html-C6kiZ_HF.js index 96886ede15..97d1b62718 100644 --- a/assets/index.html-BtjOnGEp.js +++ b/assets/index.html-C6kiZ_HF.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const r={};function n(a,i){return t(),o("div")}const m=e(r,[["render",n],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/frontweb/es6/","title":"ECMAScript 6","lang":"zh-CN","frontmatter":{"title":"ECMAScript 6","date":"2021-02-20T00:00:00.000Z","author":"qianxun","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/es6/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"ECMAScript 6"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"qianxun"}],["meta",{"property":"article:published_time","content":"2021-02-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"ECMAScript 6\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2021-02-20T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"qianxun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.03,"words":9},"filePathRelative":"frontweb/es6/README.md","localizedDate":"2021年2月20日","excerpt":""}');export{m as comp,d as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const r={};function n(a,i){return t(),o("div")}const m=e(r,[["render",n],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/frontweb/es6/","title":"ECMAScript 6","lang":"zh-CN","frontmatter":{"title":"ECMAScript 6","date":"2021-02-20T00:00:00.000Z","author":"qianxun","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/es6/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"ECMAScript 6"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"qianxun"}],["meta",{"property":"article:published_time","content":"2021-02-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"ECMAScript 6\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2021-02-20T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"qianxun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.03,"words":9},"filePathRelative":"frontweb/es6/README.md","localizedDate":"2021年2月20日","excerpt":""}');export{m as comp,d as data}; diff --git a/assets/index.html-DKQaObvm.js b/assets/index.html-C8vCtcrV.js similarity index 94% rename from assets/index.html-DKQaObvm.js rename to assets/index.html-C8vCtcrV.js index b7b8203da2..499d20afb2 100644 --- a/assets/index.html-DKQaObvm.js +++ b/assets/index.html-C8vCtcrV.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/---%E5%B9%B6%E5%8F%91/","title":"标签: -- 并发","lang":"zh-CN","frontmatter":{"title":"标签: -- 并发","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"-- 并发","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/---%E5%B9%B6%E5%8F%91/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: -- 并发"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: -- 并发\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/---%E5%B9%B6%E5%8F%91/","title":"标签: -- 并发","lang":"zh-CN","frontmatter":{"title":"标签: -- 并发","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"-- 并发","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/---%E5%B9%B6%E5%8F%91/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: -- 并发"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: -- 并发\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-BnfXAjNS.js b/assets/index.html-CEilrxfK.js similarity index 94% rename from assets/index.html-BnfXAjNS.js rename to assets/index.html-CEilrxfK.js index 9aec8a0db5..335bcd317a 100644 --- a/assets/index.html-BnfXAjNS.js +++ b/assets/index.html-CEilrxfK.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-CVMfKeWw.js";const o={};function n(r,c){return t(),a("div")}const l=e(o,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/java/","title":"java 分类","lang":"zh-CN","frontmatter":{"title":"java 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"java","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/java/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"java 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"java 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-_Oi5YZFn.js";const o={};function n(r,c){return t(),a("div")}const l=e(o,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/java/","title":"java 分类","lang":"zh-CN","frontmatter":{"title":"java 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"java","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/java/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"java 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"java 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-DQDf-iB8.js b/assets/index.html-CJQlCK8P.js similarity index 98% rename from assets/index.html-DQDf-iB8.js rename to assets/index.html-CJQlCK8P.js index fb49a86629..801e1771fb 100644 --- a/assets/index.html-DQDf-iB8.js +++ b/assets/index.html-CJQlCK8P.js @@ -1 +1 @@ -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as a,c as i,a as e,b as r,d as n}from"./app-CVMfKeWw.js";const c={},l=e("h2",{id:"学习typescript",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#学习typescript"},[e("span",null,"学习typeScript")])],-1),s={href:"https://juejin.cn/post/7089809919251054628",target:"_blank",rel:"noopener noreferrer"},h={href:"https://blog.csdn.net/weixin_44242181/article/details/127305342?spm=1001.2014.3001.5502",target:"_blank",rel:"noopener noreferrer"},d={href:"https://juejin.cn/post/7119300462821507103",target:"_blank",rel:"noopener noreferrer"},m={href:"https://juejin.cn/post/7058868160706904078#heading-8",target:"_blank",rel:"noopener noreferrer"},y={href:"https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html",target:"_blank",rel:"noopener noreferrer"};function _(S,u){const t=o("ExternalLinkIcon");return a(),i("div",null,[l,e("p",null,[e("a",s,[r("(1)如何进阶TypeScript功底?一文带你理解TS中各种高级语法"),n(t)])]),e("p",null,[e("a",h,[r("(2) TypeScript学习(十)——缩小类型限制范围"),n(t)])]),e("p",null,[e("a",d,[r("(3)一份够用的TS常用特性总结"),n(t)])]),e("p",null,[e("a",m,[r("(4)如何在项目中用好 TypeScript"),n(t)])]),e("p",null,[e("a",y,[r("(5)TS英文官方文档"),n(t)])])])}const g=p(c,[["render",_],["__file","index.html.vue"]]),b=JSON.parse('{"path":"/frontweb/typeScript/","title":"typeScript学习资料","lang":"zh-CN","frontmatter":{"title":"typeScript学习资料","date":"2022-08-05T16:57:01.000Z","author":"qianxun","category":["vue知识点"],"tag":["必会","vue中的 TypeScript"],"description":"学习typeScript (1)如何进阶TypeScript功底?一文带你理解TS中各种高级语法 (2) TypeScript学习(十)——缩小类型限制范围 (3)一份够用的TS常用特性总结 (4)如何在项目中用好 TypeScript (5)TS英文官方文档","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/typeScript/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"typeScript学习资料"}],["meta",{"property":"og:description","content":"学习typeScript (1)如何进阶TypeScript功底?一文带你理解TS中各种高级语法 (2) TypeScript学习(十)——缩小类型限制范围 (3)一份够用的TS常用特性总结 (4)如何在项目中用好 TypeScript (5)TS英文官方文档"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-11-03T08:05:16.000Z"}],["meta",{"property":"article:author","content":"qianxun"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:tag","content":"vue中的 TypeScript"}],["meta",{"property":"article:published_time","content":"2022-08-05T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2022-11-03T08:05:16.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"typeScript学习资料\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-05T16:57:01.000Z\\",\\"dateModified\\":\\"2022-11-03T08:05:16.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"qianxun\\"}]}"]]},"headers":[{"level":2,"title":"学习typeScript","slug":"学习typescript","link":"#学习typescript","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1667462716000,"contributors":[{"name":"zhu","email":"819508408@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.37,"words":112},"filePathRelative":"frontweb/typeScript/README.md","localizedDate":"2022年8月5日","excerpt":"

      学习typeScript

      \\n

      (1)如何进阶TypeScript功底?一文带你理解TS中各种高级语法

      \\n

      (2) TypeScript学习(十)——缩小类型限制范围

      ","autoDesc":true}');export{g as comp,b as data}; +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as a,c as i,a as e,b as r,d as n}from"./app-_Oi5YZFn.js";const c={},l=e("h2",{id:"学习typescript",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#学习typescript"},[e("span",null,"学习typeScript")])],-1),s={href:"https://juejin.cn/post/7089809919251054628",target:"_blank",rel:"noopener noreferrer"},h={href:"https://blog.csdn.net/weixin_44242181/article/details/127305342?spm=1001.2014.3001.5502",target:"_blank",rel:"noopener noreferrer"},d={href:"https://juejin.cn/post/7119300462821507103",target:"_blank",rel:"noopener noreferrer"},m={href:"https://juejin.cn/post/7058868160706904078#heading-8",target:"_blank",rel:"noopener noreferrer"},y={href:"https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html",target:"_blank",rel:"noopener noreferrer"};function _(S,u){const t=o("ExternalLinkIcon");return a(),i("div",null,[l,e("p",null,[e("a",s,[r("(1)如何进阶TypeScript功底?一文带你理解TS中各种高级语法"),n(t)])]),e("p",null,[e("a",h,[r("(2) TypeScript学习(十)——缩小类型限制范围"),n(t)])]),e("p",null,[e("a",d,[r("(3)一份够用的TS常用特性总结"),n(t)])]),e("p",null,[e("a",m,[r("(4)如何在项目中用好 TypeScript"),n(t)])]),e("p",null,[e("a",y,[r("(5)TS英文官方文档"),n(t)])])])}const g=p(c,[["render",_],["__file","index.html.vue"]]),b=JSON.parse('{"path":"/frontweb/typeScript/","title":"typeScript学习资料","lang":"zh-CN","frontmatter":{"title":"typeScript学习资料","date":"2022-08-05T16:57:01.000Z","author":"qianxun","category":["vue知识点"],"tag":["必会","vue中的 TypeScript"],"description":"学习typeScript (1)如何进阶TypeScript功底?一文带你理解TS中各种高级语法 (2) TypeScript学习(十)——缩小类型限制范围 (3)一份够用的TS常用特性总结 (4)如何在项目中用好 TypeScript (5)TS英文官方文档","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/typeScript/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"typeScript学习资料"}],["meta",{"property":"og:description","content":"学习typeScript (1)如何进阶TypeScript功底?一文带你理解TS中各种高级语法 (2) TypeScript学习(十)——缩小类型限制范围 (3)一份够用的TS常用特性总结 (4)如何在项目中用好 TypeScript (5)TS英文官方文档"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-11-03T08:05:16.000Z"}],["meta",{"property":"article:author","content":"qianxun"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:tag","content":"vue中的 TypeScript"}],["meta",{"property":"article:published_time","content":"2022-08-05T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2022-11-03T08:05:16.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"typeScript学习资料\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-05T16:57:01.000Z\\",\\"dateModified\\":\\"2022-11-03T08:05:16.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"qianxun\\"}]}"]]},"headers":[{"level":2,"title":"学习typeScript","slug":"学习typescript","link":"#学习typescript","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1667462716000,"contributors":[{"name":"zhu","email":"819508408@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.37,"words":112},"filePathRelative":"frontweb/typeScript/README.md","localizedDate":"2022年8月5日","excerpt":"

      学习typeScript

      \\n

      (1)如何进阶TypeScript功底?一文带你理解TS中各种高级语法

      \\n

      (2) TypeScript学习(十)——缩小类型限制范围

      ","autoDesc":true}');export{g as comp,b as data}; diff --git a/assets/index.html-BfisB8tx.js b/assets/index.html-CNk3LVBO.js similarity index 94% rename from assets/index.html-BfisB8tx.js rename to assets/index.html-CNk3LVBO.js index b55f9c6665..4900f22b2d 100644 --- a/assets/index.html-BfisB8tx.js +++ b/assets/index.html-CNk3LVBO.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E9%9B%86%E5%90%88/","title":"标签: 集合","lang":"zh-CN","frontmatter":{"title":"标签: 集合","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"集合","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E9%9B%86%E5%90%88/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 集合"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 集合\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E9%9B%86%E5%90%88/","title":"标签: 集合","lang":"zh-CN","frontmatter":{"title":"标签: 集合","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"集合","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E9%9B%86%E5%90%88/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 集合"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 集合\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-CLj8LoVt.js b/assets/index.html-CONpmD-V.js similarity index 93% rename from assets/index.html-CLj8LoVt.js rename to assets/index.html-CONpmD-V.js index 832fed2584..4cb7afdcdc 100644 --- a/assets/index.html-CLj8LoVt.js +++ b/assets/index.html-CONpmD-V.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/","title":"标签","lang":"zh-CN","frontmatter":{"title":"标签","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/","title":"标签","lang":"zh-CN","frontmatter":{"title":"标签","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-CmHVopsM.js b/assets/index.html-CTGnNX68.js similarity index 93% rename from assets/index.html-CmHVopsM.js rename to assets/index.html-CTGnNX68.js index 561cac71a8..e85a2af36c 100644 --- a/assets/index.html-CmHVopsM.js +++ b/assets/index.html-CTGnNX68.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o,c as r,d as n}from"./app-CVMfKeWw.js";const c={};function p(i,l){const e=a("Catalog");return o(),r("div",null,[n(e)])}const h=t(c,[["render",p],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/other/hardware/","title":"Hardware","lang":"zh-CN","frontmatter":{"title":"Hardware","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/hardware/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Hardware"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Hardware\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,d as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o,c as r,d as n}from"./app-_Oi5YZFn.js";const c={};function p(i,l){const e=a("Catalog");return o(),r("div",null,[n(e)])}const h=t(c,[["render",p],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/other/hardware/","title":"Hardware","lang":"zh-CN","frontmatter":{"title":"Hardware","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/hardware/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Hardware"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Hardware\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,d as data}; diff --git a/assets/index.html-DCau-SSn.js b/assets/index.html-CV_dUHJe.js similarity index 94% rename from assets/index.html-DCau-SSn.js rename to assets/index.html-CV_dUHJe.js index cb190d3f81..ab1ab9cf90 100644 --- a/assets/index.html-DCau-SSn.js +++ b/assets/index.html-CV_dUHJe.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const n={};function a(r,i){return t(),o("div")}const p=e(n,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/linux/","title":"linux 分类","lang":"zh-CN","frontmatter":{"title":"linux 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"linux","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/linux/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"linux 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"linux 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{p as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function a(r,i){return t(),o("div")}const p=e(n,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/linux/","title":"linux 分类","lang":"zh-CN","frontmatter":{"title":"linux 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"linux","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/linux/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"linux 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"linux 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{p as comp,s as data}; diff --git a/assets/index.html-Dv16igET.js b/assets/index.html-CXTrskFo.js similarity index 94% rename from assets/index.html-Dv16igET.js rename to assets/index.html-CXTrskFo.js index 9c7bce3581..85e9a5a02b 100644 --- a/assets/index.html-Dv16igET.js +++ b/assets/index.html-CXTrskFo.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E6%95%B0%E6%8D%AE%E5%BA%93/","title":"标签: 数据库","lang":"zh-CN","frontmatter":{"title":"标签: 数据库","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"数据库","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 数据库"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 数据库\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E6%95%B0%E6%8D%AE%E5%BA%93/","title":"标签: 数据库","lang":"zh-CN","frontmatter":{"title":"标签: 数据库","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"数据库","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 数据库"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 数据库\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-E2fKYy3S.js b/assets/index.html-C_jbP7SN.js similarity index 94% rename from assets/index.html-E2fKYy3S.js rename to assets/index.html-C_jbP7SN.js index e7f70e35e3..131487d840 100644 --- a/assets/index.html-E2fKYy3S.js +++ b/assets/index.html-C_jbP7SN.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const c=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/markdown/","title":"标签: markdown","lang":"zh-CN","frontmatter":{"title":"标签: markdown","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"markdown","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/markdown/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: markdown"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: markdown\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const c=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/markdown/","title":"标签: markdown","lang":"zh-CN","frontmatter":{"title":"标签: markdown","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"markdown","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/markdown/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: markdown"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: markdown\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,l as data}; diff --git a/assets/index.html-CFkGmFia.js b/assets/index.html-CbyN5TWa.js similarity index 93% rename from assets/index.html-CFkGmFia.js rename to assets/index.html-CbyN5TWa.js index e3fded3a8c..e4c278902a 100644 --- a/assets/index.html-CFkGmFia.js +++ b/assets/index.html-CbyN5TWa.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as n,c as a,d as p}from"./app-CVMfKeWw.js";const r={};function c(i,l){const e=o("Catalog");return n(),a("div",null,[p(e)])}const h=t(r,[["render",c],["__file","index.html.vue"]]),_=JSON.parse('{"path":"/cpp/","title":"Cpp","lang":"zh-CN","frontmatter":{"title":"Cpp","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/cpp/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Cpp"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Cpp\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,_ as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as n,c as a,d as p}from"./app-_Oi5YZFn.js";const r={};function c(i,l){const e=o("Catalog");return n(),a("div",null,[p(e)])}const h=t(r,[["render",c],["__file","index.html.vue"]]),_=JSON.parse('{"path":"/cpp/","title":"Cpp","lang":"zh-CN","frontmatter":{"title":"Cpp","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/cpp/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Cpp"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Cpp\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,_ as data}; diff --git a/assets/index.html-76c_E0sL.js b/assets/index.html-ChsojBeT.js similarity index 94% rename from assets/index.html-76c_E0sL.js rename to assets/index.html-ChsojBeT.js index 518136e8c4..512e5bb6ee 100644 --- a/assets/index.html-76c_E0sL.js +++ b/assets/index.html-ChsojBeT.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n}from"./app-CVMfKeWw.js";const o={};function i(a,r){return t(),n("div")}const c=e(o,[["render",i],["__file","index.html.vue"]]),m=JSON.parse('{"path":"/timeline/","title":"时间轴","lang":"zh-CN","frontmatter":{"title":"时间轴","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"type","key":"timeline"},"layout":"Timeline","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/timeline/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"时间轴"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"时间轴\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,m as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n}from"./app-_Oi5YZFn.js";const o={};function i(a,r){return t(),n("div")}const c=e(o,[["render",i],["__file","index.html.vue"]]),m=JSON.parse('{"path":"/timeline/","title":"时间轴","lang":"zh-CN","frontmatter":{"title":"时间轴","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"type","key":"timeline"},"layout":"Timeline","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/timeline/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"时间轴"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"时间轴\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,m as data}; diff --git a/assets/index.html-CzP4p5XK.js b/assets/index.html-CiPcuEin.js similarity index 94% rename from assets/index.html-CzP4p5XK.js rename to assets/index.html-CiPcuEin.js index 5c59d2e7eb..5c0b802864 100644 --- a/assets/index.html-CzP4p5XK.js +++ b/assets/index.html-CiPcuEin.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E9%9B%86%E5%90%88/","title":"集合 分类","lang":"zh-CN","frontmatter":{"title":"集合 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"集合","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E9%9B%86%E5%90%88/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"集合 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"集合 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E9%9B%86%E5%90%88/","title":"集合 分类","lang":"zh-CN","frontmatter":{"title":"集合 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"集合","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E9%9B%86%E5%90%88/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"集合 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"集合 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-DAKYBthR.js b/assets/index.html-Cjv6EKnU.js similarity index 96% rename from assets/index.html-DAKYBthR.js rename to assets/index.html-Cjv6EKnU.js index 24e9f3f539..13b8517fd3 100644 --- a/assets/index.html-DAKYBthR.js +++ b/assets/index.html-Cjv6EKnU.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as a,a as e}from"./app-CVMfKeWw.js";const n={},r=e("h2",{id:"其他分类",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#其他分类"},[e("span",null,"其他分类")])],-1),i=[r];function c(p,l){return o(),a("div",null,i)}const d=t(n,[["render",c],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/java/other/","title":"其他","lang":"zh-CN","frontmatter":{"title":"其他","date":"2022-08-08T00:00:00.000Z","author":"chenkun","description":"其他分类","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"其他"}],["meta",{"property":"og:description","content":"其他分类"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-08-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"其他\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"其他分类","slug":"其他分类","link":"#其他分类","children":[]}],"git":{"createdTime":1659925295000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":3}]},"readingTime":{"minutes":0.04,"words":13},"filePathRelative":"java/other/README.md","localizedDate":"2022年8月8日","excerpt":"

      其他分类

      \\n","autoDesc":true}');export{d as comp,h as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as a,a as e}from"./app-_Oi5YZFn.js";const n={},r=e("h2",{id:"其他分类",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#其他分类"},[e("span",null,"其他分类")])],-1),i=[r];function c(p,l){return o(),a("div",null,i)}const d=t(n,[["render",c],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/java/other/","title":"其他","lang":"zh-CN","frontmatter":{"title":"其他","date":"2022-08-08T00:00:00.000Z","author":"chenkun","description":"其他分类","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"其他"}],["meta",{"property":"og:description","content":"其他分类"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-08-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"其他\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"其他分类","slug":"其他分类","link":"#其他分类","children":[]}],"git":{"createdTime":1659925295000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":3}]},"readingTime":{"minutes":0.04,"words":13},"filePathRelative":"java/other/README.md","localizedDate":"2022年8月8日","excerpt":"

      其他分类

      \\n","autoDesc":true}');export{d as comp,h as data}; diff --git a/assets/index.html-BHP5lSWG.js b/assets/index.html-ClRCgRtJ.js similarity index 94% rename from assets/index.html-BHP5lSWG.js rename to assets/index.html-ClRCgRtJ.js index 1c6425643d..9207a2594a 100644 --- a/assets/index.html-BHP5lSWG.js +++ b/assets/index.html-ClRCgRtJ.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/jdk/","title":"jdk 分类","lang":"zh-CN","frontmatter":{"title":"jdk 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"jdk","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/jdk/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"jdk 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"jdk 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/jdk/","title":"jdk 分类","lang":"zh-CN","frontmatter":{"title":"jdk 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"jdk","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/jdk/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"jdk 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"jdk 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-CeTjFoZy.js b/assets/index.html-CntKYRgb.js similarity index 96% rename from assets/index.html-CeTjFoZy.js rename to assets/index.html-CntKYRgb.js index 5853a3a711..9a8153633c 100644 --- a/assets/index.html-CeTjFoZy.js +++ b/assets/index.html-CntKYRgb.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as a}from"./app-CVMfKeWw.js";const o={};function i(r,n){return e(),a("div")}const p=t(o,[["render",i],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/java/framework/mybatis/","title":"Mybatis","lang":"zh-CN","frontmatter":{"title":"Mybatis","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/mybatis/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Mybatis"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Mybatis\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.02,"words":6},"filePathRelative":"java/framework/mybatis/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{p as comp,s as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as a}from"./app-_Oi5YZFn.js";const o={};function i(r,n){return e(),a("div")}const p=t(o,[["render",i],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/java/framework/mybatis/","title":"Mybatis","lang":"zh-CN","frontmatter":{"title":"Mybatis","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/mybatis/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Mybatis"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Mybatis\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.02,"words":6},"filePathRelative":"java/framework/mybatis/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{p as comp,s as data}; diff --git a/assets/index.html-HvLMGTgp.js b/assets/index.html-CrqLYD8s.js similarity index 94% rename from assets/index.html-HvLMGTgp.js rename to assets/index.html-CrqLYD8s.js index 6c4788d498..922234d2a5 100644 --- a/assets/index.html-HvLMGTgp.js +++ b/assets/index.html-CrqLYD8s.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E5%8F%8D%E6%B1%87%E7%BC%96/","title":"标签: 反汇编","lang":"zh-CN","frontmatter":{"title":"标签: 反汇编","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"反汇编","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E5%8F%8D%E6%B1%87%E7%BC%96/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 反汇编"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 反汇编\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E5%8F%8D%E6%B1%87%E7%BC%96/","title":"标签: 反汇编","lang":"zh-CN","frontmatter":{"title":"标签: 反汇编","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"反汇编","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E5%8F%8D%E6%B1%87%E7%BC%96/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 反汇编"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 反汇编\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-f8Jf88Pj.js b/assets/index.html-CtpU9Ex-.js similarity index 94% rename from assets/index.html-f8Jf88Pj.js rename to assets/index.html-CtpU9Ex-.js index b7ff823bba..936e869a6d 100644 --- a/assets/index.html-f8Jf88Pj.js +++ b/assets/index.html-CtpU9Ex-.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E8%BF%90%E7%BB%B4/","title":"运维 分类","lang":"zh-CN","frontmatter":{"title":"运维 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"运维","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E8%BF%90%E7%BB%B4/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"运维 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"运维 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E8%BF%90%E7%BB%B4/","title":"运维 分类","lang":"zh-CN","frontmatter":{"title":"运维 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"运维","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E8%BF%90%E7%BB%B4/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"运维 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"运维 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-CTOJOPSY.js b/assets/index.html-CuULl194.js similarity index 97% rename from assets/index.html-CTOJOPSY.js rename to assets/index.html-CuULl194.js index a57976eaae..cd34495301 100644 --- a/assets/index.html-CTOJOPSY.js +++ b/assets/index.html-CuULl194.js @@ -1 +1 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as i,c as p,a as e,b as r,d as o}from"./app-CVMfKeWw.js";const l={},c=e("h3",{id:"从入门到放弃",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#从入门到放弃"},[e("span",null,"从入门到放弃")])],-1),m={href:"https://www.rabbitmq.com/getstarted.html",target:"_blank",rel:"noopener noreferrer"},h={href:"https://rabbitmq.mr-ping.com/AMQP/AMQP_0-9-1_Model_Explained.html",target:"_blank",rel:"noopener noreferrer"},s={href:"http://tryrabbitmq.com/",target:"_blank",rel:"noopener noreferrer"};function d(b,_){const t=a("ExternalLinkIcon");return i(),p("div",null,[c,e("p",null,[e("a",m,[r("官网"),o(t)])]),e("p",null,[e("a",h,[r("中文文档"),o(t)])]),e("p",null,[e("a",s,[r("在线 动态演示工具"),o(t)])])])}const u=n(l,[["render",d],["__file","index.html.vue"]]),y=JSON.parse('{"path":"/other/rabbitmq/","title":"rabbitmq","lang":"zh-CN","frontmatter":{"title":"rabbitmq","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","description":"从入门到放弃 官网 中文文档 在线 动态演示工具","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/rabbitmq/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"rabbitmq"}],["meta",{"property":"og:description","content":"从入门到放弃 官网 中文文档 在线 动态演示工具"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-12-07T08:10:55.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-12-07T08:10:55.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"rabbitmq\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2022-12-07T08:10:55.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"从入门到放弃","slug":"从入门到放弃","link":"#从入门到放弃","children":[]}],"git":{"createdTime":1670400655000,"updatedTime":1670400655000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.12,"words":37},"filePathRelative":"other/rabbitmq/README.md","localizedDate":"2022年11月8日","excerpt":"

      从入门到放弃

      \\n

      官网

      \\n

      中文文档

      \\n

      在线 动态演示工具

      ","autoDesc":true}');export{u as comp,y as data}; +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as i,c as p,a as e,b as r,d as o}from"./app-_Oi5YZFn.js";const l={},c=e("h3",{id:"从入门到放弃",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#从入门到放弃"},[e("span",null,"从入门到放弃")])],-1),m={href:"https://www.rabbitmq.com/getstarted.html",target:"_blank",rel:"noopener noreferrer"},h={href:"https://rabbitmq.mr-ping.com/AMQP/AMQP_0-9-1_Model_Explained.html",target:"_blank",rel:"noopener noreferrer"},s={href:"http://tryrabbitmq.com/",target:"_blank",rel:"noopener noreferrer"};function d(b,_){const t=a("ExternalLinkIcon");return i(),p("div",null,[c,e("p",null,[e("a",m,[r("官网"),o(t)])]),e("p",null,[e("a",h,[r("中文文档"),o(t)])]),e("p",null,[e("a",s,[r("在线 动态演示工具"),o(t)])])])}const u=n(l,[["render",d],["__file","index.html.vue"]]),y=JSON.parse('{"path":"/other/rabbitmq/","title":"rabbitmq","lang":"zh-CN","frontmatter":{"title":"rabbitmq","date":"2022-11-08T00:00:00.000Z","author":"ChenSino","description":"从入门到放弃 官网 中文文档 在线 动态演示工具","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/rabbitmq/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"rabbitmq"}],["meta",{"property":"og:description","content":"从入门到放弃 官网 中文文档 在线 动态演示工具"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-12-07T08:10:55.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-11-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-12-07T08:10:55.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"rabbitmq\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-11-08T00:00:00.000Z\\",\\"dateModified\\":\\"2022-12-07T08:10:55.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\"}]}"]]},"headers":[{"level":3,"title":"从入门到放弃","slug":"从入门到放弃","link":"#从入门到放弃","children":[]}],"git":{"createdTime":1670400655000,"updatedTime":1670400655000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.12,"words":37},"filePathRelative":"other/rabbitmq/README.md","localizedDate":"2022年11月8日","excerpt":"

      从入门到放弃

      \\n

      官网

      \\n

      中文文档

      \\n

      在线 动态演示工具

      ","autoDesc":true}');export{u as comp,y as data}; diff --git a/assets/index.html-CJudebxY.js b/assets/index.html-CvVo_Iy8.js similarity index 94% rename from assets/index.html-CJudebxY.js rename to assets/index.html-CvVo_Iy8.js index 1bd667f502..6377f78831 100644 --- a/assets/index.html-CJudebxY.js +++ b/assets/index.html-CvVo_Iy8.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/","title":"问题定位 分类","lang":"zh-CN","frontmatter":{"title":"问题定位 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"问题定位","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"问题定位 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"问题定位 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/","title":"问题定位 分类","lang":"zh-CN","frontmatter":{"title":"问题定位 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"问题定位","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"问题定位 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"问题定位 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-Bo2PC9xb.js b/assets/index.html-Cwy-RPtK.js similarity index 94% rename from assets/index.html-Bo2PC9xb.js rename to assets/index.html-Cwy-RPtK.js index c32179d874..4d30532a78 100644 --- a/assets/index.html-Bo2PC9xb.js +++ b/assets/index.html-Cwy-RPtK.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/web/","title":"标签: web","lang":"zh-CN","frontmatter":{"title":"标签: web","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"web","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/web/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: web"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: web\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/web/","title":"标签: web","lang":"zh-CN","frontmatter":{"title":"标签: web","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"web","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/web/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: web"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: web\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-FIqAJepD.js b/assets/index.html-D0WBxxKk.js similarity index 94% rename from assets/index.html-FIqAJepD.js rename to assets/index.html-D0WBxxKk.js index 65d2d63872..e11ab695f7 100644 --- a/assets/index.html-FIqAJepD.js +++ b/assets/index.html-D0WBxxKk.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","title":"设计模式 分类","lang":"zh-CN","frontmatter":{"title":"设计模式 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"设计模式","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"设计模式 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"设计模式 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/","title":"设计模式 分类","lang":"zh-CN","frontmatter":{"title":"设计模式 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"设计模式","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"设计模式 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"设计模式 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-DJeEzsTa.js b/assets/index.html-D1zTD58M.js similarity index 94% rename from assets/index.html-DJeEzsTa.js rename to assets/index.html-D1zTD58M.js index 3e15769604..93be0e554e 100644 --- a/assets/index.html-DJeEzsTa.js +++ b/assets/index.html-D1zTD58M.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const n={};function r(a,c){return t(),o("div")}const i=e(n,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/open-source/","title":"open-source 分类","lang":"zh-CN","frontmatter":{"title":"open-source 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"open-source","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/open-source/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"open-source 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"open-source 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{i as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function r(a,c){return t(),o("div")}const i=e(n,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/open-source/","title":"open-source 分类","lang":"zh-CN","frontmatter":{"title":"open-source 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"open-source","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/open-source/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"open-source 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"open-source 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{i as comp,l as data}; diff --git a/assets/index.html-B6iZ__OM.js b/assets/index.html-D3l9AP0G.js similarity index 94% rename from assets/index.html-B6iZ__OM.js rename to assets/index.html-D3l9AP0G.js index db0709784d..fb9cb60edb 100644 --- a/assets/index.html-B6iZ__OM.js +++ b/assets/index.html-D3l9AP0G.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/vite/","title":"标签: vite","lang":"zh-CN","frontmatter":{"title":"标签: vite","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"vite","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/vite/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: vite"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: vite\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/vite/","title":"标签: vite","lang":"zh-CN","frontmatter":{"title":"标签: vite","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"vite","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/vite/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: vite"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: vite\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-hGAHmgqz.js b/assets/index.html-D4BNQ3HG.js similarity index 96% rename from assets/index.html-hGAHmgqz.js rename to assets/index.html-D4BNQ3HG.js index dd3fbcf802..115dae8f46 100644 --- a/assets/index.html-hGAHmgqz.js +++ b/assets/index.html-D4BNQ3HG.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-CVMfKeWw.js";const r={};function n(a,i){return e(),o("div")}const m=t(r,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/java/framework/springboot/","title":"SpringBoot","lang":"zh-CN","frontmatter":{"title":"SpringBoot","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/springboot/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"SpringBoot"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"SpringBoot\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.02,"words":6},"filePathRelative":"java/framework/springboot/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{m as comp,l as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-_Oi5YZFn.js";const r={};function n(a,i){return e(),o("div")}const m=t(r,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/java/framework/springboot/","title":"SpringBoot","lang":"zh-CN","frontmatter":{"title":"SpringBoot","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/springboot/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"SpringBoot"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"SpringBoot\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.02,"words":6},"filePathRelative":"java/framework/springboot/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{m as comp,l as data}; diff --git a/assets/index.html-oz23N8Lk.js b/assets/index.html-D5Bas5eh.js similarity index 96% rename from assets/index.html-oz23N8Lk.js rename to assets/index.html-D5Bas5eh.js index 72787a1a30..5c06194915 100644 --- a/assets/index.html-oz23N8Lk.js +++ b/assets/index.html-D5Bas5eh.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o,a as n}from"./app-CVMfKeWw.js";const a={},r=n("p",null,"学习数据库知识",-1),i=[r];function c(p,m){return t(),o("div",null,i)}const l=e(a,[["render",c],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/other/database/","title":"数据库","lang":"zh-CN","frontmatter":{"title":"数据库","date":"2022-04-28T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"学习数据库知识","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/database/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"数据库"}],["meta",{"property":"og:description","content":"学习数据库知识"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-04-28T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"数据库\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-04-28T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.07,"words":21},"filePathRelative":"other/database/README.md","localizedDate":"2022年4月28日","excerpt":"

      学习数据库知识

      \\n\\n","autoDesc":true}');export{l as comp,h as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o,a as n}from"./app-_Oi5YZFn.js";const a={},r=n("p",null,"学习数据库知识",-1),i=[r];function c(p,m){return t(),o("div",null,i)}const l=e(a,[["render",c],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/other/database/","title":"数据库","lang":"zh-CN","frontmatter":{"title":"数据库","date":"2022-04-28T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"学习数据库知识","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/database/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"数据库"}],["meta",{"property":"og:description","content":"学习数据库知识"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-04-28T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"数据库\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-04-28T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.07,"words":21},"filePathRelative":"other/database/README.md","localizedDate":"2022年4月28日","excerpt":"

      学习数据库知识

      \\n\\n","autoDesc":true}');export{l as comp,h as data}; diff --git a/assets/index.html-_iWg3Z3W.js b/assets/index.html-D5egMLMa.js similarity index 96% rename from assets/index.html-_iWg3Z3W.js rename to assets/index.html-D5egMLMa.js index 0e4bc81309..7761dd6f2d 100644 --- a/assets/index.html-_iWg3Z3W.js +++ b/assets/index.html-D5egMLMa.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e}from"./app-CVMfKeWw.js";const a={},r=e("h2",{id:"",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#"},[e("span")])],-1),i=[r];function c(d,p){return o(),n("div",null,i)}const m=t(a,[["render",c],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/frontweb/nodejs/","title":"NodeJS","lang":"zh-CN","frontmatter":{"title":"NodeJS","date":"2024-08-14T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["问题定位"],"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/nodejs/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"NodeJS"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-08-14T06:14:44.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2024-08-14T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-08-14T06:14:44.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"NodeJS\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-08-14T00:00:00.000Z\\",\\"dateModified\\":\\"2024-08-14T06:14:44.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"","slug":"","link":"#","children":[]}],"git":{"createdTime":1723616084000,"updatedTime":1723616084000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":16},"filePathRelative":"frontweb/nodejs/readme.md","localizedDate":"2024年8月14日","excerpt":"

      \\n"}');export{m as comp,h as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e}from"./app-_Oi5YZFn.js";const a={},r=e("h2",{id:"",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#"},[e("span")])],-1),i=[r];function c(d,p){return o(),n("div",null,i)}const m=t(a,[["render",c],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/frontweb/nodejs/","title":"NodeJS","lang":"zh-CN","frontmatter":{"title":"NodeJS","date":"2024-08-14T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["问题定位"],"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/nodejs/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"NodeJS"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-08-14T06:14:44.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2024-08-14T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-08-14T06:14:44.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"NodeJS\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-08-14T00:00:00.000Z\\",\\"dateModified\\":\\"2024-08-14T06:14:44.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"","slug":"","link":"#","children":[]}],"git":{"createdTime":1723616084000,"updatedTime":1723616084000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":16},"filePathRelative":"frontweb/nodejs/readme.md","localizedDate":"2024年8月14日","excerpt":"

      \\n"}');export{m as comp,h as data}; diff --git a/assets/index.html-CHghYPfg.js b/assets/index.html-DAGR5a1Q.js similarity index 96% rename from assets/index.html-CHghYPfg.js rename to assets/index.html-DAGR5a1Q.js index 0243afea8b..b850ae1dcb 100644 --- a/assets/index.html-CHghYPfg.js +++ b/assets/index.html-DAGR5a1Q.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-CVMfKeWw.js";const i={};function n(r,a){return e(),o("div")}const m=t(i,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/frontweb/vite/","title":"Vite","lang":"zh-CN","frontmatter":{"title":"Vite","date":"2021-02-20T00:00:00.000Z","author":"qianxun","publish":true,"category":["vite"],"tag":["vite"],"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/vite/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Vite"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-02T04:20:07.000Z"}],["meta",{"property":"article:author","content":"qianxun"}],["meta",{"property":"article:tag","content":"vite"}],["meta",{"property":"article:published_time","content":"2021-02-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-02T04:20:07.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Vite\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2021-02-20T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-02T04:20:07.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"qianxun\\"}]}"]]},"headers":[],"git":{"createdTime":1659414007000,"updatedTime":1659414007000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":14},"filePathRelative":"frontweb/vite/README.md","localizedDate":"2021年2月20日","excerpt":""}');export{m as comp,l as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-_Oi5YZFn.js";const i={};function n(r,a){return e(),o("div")}const m=t(i,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/frontweb/vite/","title":"Vite","lang":"zh-CN","frontmatter":{"title":"Vite","date":"2021-02-20T00:00:00.000Z","author":"qianxun","publish":true,"category":["vite"],"tag":["vite"],"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/vite/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Vite"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-02T04:20:07.000Z"}],["meta",{"property":"article:author","content":"qianxun"}],["meta",{"property":"article:tag","content":"vite"}],["meta",{"property":"article:published_time","content":"2021-02-20T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-02T04:20:07.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Vite\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2021-02-20T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-02T04:20:07.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"qianxun\\"}]}"]]},"headers":[],"git":{"createdTime":1659414007000,"updatedTime":1659414007000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":14},"filePathRelative":"frontweb/vite/README.md","localizedDate":"2021年2月20日","excerpt":""}');export{m as comp,l as data}; diff --git a/assets/index.html-C3kmdsDq.js b/assets/index.html-DDdnWKF-.js similarity index 94% rename from assets/index.html-C3kmdsDq.js rename to assets/index.html-DDdnWKF-.js index ccfd40deda..f273bcf48a 100644 --- a/assets/index.html-C3kmdsDq.js +++ b/assets/index.html-DDdnWKF-.js @@ -1 +1 @@ -import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as a,c as r,a as e,d as i,w as l,b as c}from"./app-CVMfKeWw.js";const s={},p=e("h2",{id:"目录",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#目录"},[e("span",null,"目录")])],-1);function h(m,d){const t=n("RouteLink");return a(),r("div",null,[p,e("ul",null,[e("li",null,[i(t,{to:"/other/books/ebooks.html"},{default:l(()=>[c("Java开发常用书籍")]),_:1})])])])}const g=o(s,[["render",h],["__file","index.html.vue"]]),b=JSON.parse('{"path":"/other/books/","title":"电子书资源","lang":"zh-CN","frontmatter":{"title":"电子书资源","date":"2022-07-31T00:00:00.000Z","description":"目录","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/books/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"电子书资源"}],["meta",{"property":"og:description","content":"目录"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-07-31T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"电子书资源\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-31T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"目录","slug":"目录","link":"#目录","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.07,"words":20},"filePathRelative":"other/books/README.md","localizedDate":"2022年7月31日","excerpt":"

      目录

      \\n\\n","autoDesc":true}');export{g as comp,b as data}; +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as a,c as r,a as e,d as i,w as l,b as c}from"./app-_Oi5YZFn.js";const s={},p=e("h2",{id:"目录",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#目录"},[e("span",null,"目录")])],-1);function h(m,d){const t=n("RouteLink");return a(),r("div",null,[p,e("ul",null,[e("li",null,[i(t,{to:"/other/books/ebooks.html"},{default:l(()=>[c("Java开发常用书籍")]),_:1})])])])}const g=o(s,[["render",h],["__file","index.html.vue"]]),b=JSON.parse('{"path":"/other/books/","title":"电子书资源","lang":"zh-CN","frontmatter":{"title":"电子书资源","date":"2022-07-31T00:00:00.000Z","description":"目录","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/books/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"电子书资源"}],["meta",{"property":"og:description","content":"目录"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-07-31T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"电子书资源\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-31T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"目录","slug":"目录","link":"#目录","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.07,"words":20},"filePathRelative":"other/books/README.md","localizedDate":"2022年7月31日","excerpt":"

      目录

      \\n\\n","autoDesc":true}');export{g as comp,b as data}; diff --git a/assets/index.html-BvXx7kZb.js b/assets/index.html-DERaj909.js similarity index 94% rename from assets/index.html-BvXx7kZb.js rename to assets/index.html-DERaj909.js index 5fc03185d9..440c046a98 100644 --- a/assets/index.html-BvXx7kZb.js +++ b/assets/index.html-DERaj909.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const n={};function r(a,i){return t(),o("div")}const s=e(n,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/spring/","title":"Spring 分类","lang":"zh-CN","frontmatter":{"title":"Spring 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"Spring","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/spring/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Spring 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Spring 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{s as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function r(a,i){return t(),o("div")}const s=e(n,[["render",r],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/spring/","title":"Spring 分类","lang":"zh-CN","frontmatter":{"title":"Spring 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"Spring","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/spring/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Spring 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Spring 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{s as comp,l as data}; diff --git a/assets/index.html-BJvieq0-.js b/assets/index.html-DESeP-r7.js similarity index 96% rename from assets/index.html-BJvieq0-.js rename to assets/index.html-DESeP-r7.js index 9f910355d0..5a2d34448b 100644 --- a/assets/index.html-BJvieq0-.js +++ b/assets/index.html-DESeP-r7.js @@ -1 +1 @@ -import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as i,c as s,a as t,d as a,w as o,b as n}from"./app-CVMfKeWw.js";const c={},m=t("h2",{id:"目录",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#目录"},[t("span",null,"目录")])],-1);function h(p,d){const e=l("RouteLink");return i(),s("div",null,[m,t("ul",null,[t("li",null,[a(e,{to:"/frontweb/es5/aboutAsync.html"},{default:o(()=>[n("异步async函数")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/aboutEvent.html"},{default:o(()=>[n("JS原生事件")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/aboutThis.html"},{default:o(()=>[n("this指向问题")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/crossDomain.html"},{default:o(()=>[n("前端跨域(一)之proxy配置")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/crossDomain2.html"},{default:o(()=>[n("前端跨域(二)之JSONP跨域")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/lazyLoad.html"},{default:o(()=>[n("图片懒加载")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/throttle.html"},{default:o(()=>[n("节流与防抖")]),_:1})])])])}const b=r(c,[["render",h],["__file","index.html.vue"]]),_=JSON.parse('{"path":"/frontweb/es5/","title":"ECMAScript 5","lang":"zh-CN","frontmatter":{"title":"ECMAScript 5","date":"2022-04-25T00:00:00.000Z","author":"Zxf","tag":["你所不了解的JavaScript"],"description":"目录","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/es5/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"ECMAScript 5"}],["meta",{"property":"og:description","content":"目录"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"Zxf"}],["meta",{"property":"article:tag","content":"你所不了解的JavaScript"}],["meta",{"property":"article:published_time","content":"2022-04-25T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"ECMAScript 5\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-04-25T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zxf\\"}]}"]]},"headers":[{"level":2,"title":"目录","slug":"目录","link":"#目录","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.23,"words":69},"filePathRelative":"frontweb/es5/README.md","localizedDate":"2022年4月25日","excerpt":"

      目录

      \\n","autoDesc":true}');export{b as comp,_ as data}; +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as i,c as s,a as t,d as a,w as o,b as n}from"./app-_Oi5YZFn.js";const c={},m=t("h2",{id:"目录",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#目录"},[t("span",null,"目录")])],-1);function h(p,d){const e=l("RouteLink");return i(),s("div",null,[m,t("ul",null,[t("li",null,[a(e,{to:"/frontweb/es5/aboutAsync.html"},{default:o(()=>[n("异步async函数")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/aboutEvent.html"},{default:o(()=>[n("JS原生事件")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/aboutThis.html"},{default:o(()=>[n("this指向问题")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/crossDomain.html"},{default:o(()=>[n("前端跨域(一)之proxy配置")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/crossDomain2.html"},{default:o(()=>[n("前端跨域(二)之JSONP跨域")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/lazyLoad.html"},{default:o(()=>[n("图片懒加载")]),_:1})]),t("li",null,[a(e,{to:"/frontweb/es5/throttle.html"},{default:o(()=>[n("节流与防抖")]),_:1})])])])}const b=r(c,[["render",h],["__file","index.html.vue"]]),_=JSON.parse('{"path":"/frontweb/es5/","title":"ECMAScript 5","lang":"zh-CN","frontmatter":{"title":"ECMAScript 5","date":"2022-04-25T00:00:00.000Z","author":"Zxf","tag":["你所不了解的JavaScript"],"description":"目录","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/es5/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"ECMAScript 5"}],["meta",{"property":"og:description","content":"目录"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"Zxf"}],["meta",{"property":"article:tag","content":"你所不了解的JavaScript"}],["meta",{"property":"article:published_time","content":"2022-04-25T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"ECMAScript 5\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-04-25T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zxf\\"}]}"]]},"headers":[{"level":2,"title":"目录","slug":"目录","link":"#目录","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.23,"words":69},"filePathRelative":"frontweb/es5/README.md","localizedDate":"2022年4月25日","excerpt":"

      目录

      \\n","autoDesc":true}');export{b as comp,_ as data}; diff --git a/assets/index.html-D5JX-dxI.js b/assets/index.html-DEubCK6b.js similarity index 96% rename from assets/index.html-D5JX-dxI.js rename to assets/index.html-DEubCK6b.js index 808782ec8d..79a8d52be2 100644 --- a/assets/index.html-D5JX-dxI.js +++ b/assets/index.html-DEubCK6b.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e}from"./app-CVMfKeWw.js";const r={},i=e("h3",{id:"pve",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pve"},[e("span",null,"pve")])],-1),a=e("p",null,"这是一个好玩的工具",-1),p=[i,a];function c(s,l){return o(),n("div",null,p)}const m=t(r,[["render",c],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/other/pve/","title":"玩转PVE虚拟机","lang":"zh-CN","frontmatter":{"title":"玩转PVE虚拟机","date":"2024-10-08T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"pve 这是一个好玩的工具","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/pve/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"玩转PVE虚拟机"}],["meta",{"property":"og:description","content":"pve 这是一个好玩的工具"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-10-08T07:30:29.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-10-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-10-08T07:30:29.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"玩转PVE虚拟机\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-10-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-10-08T07:30:29.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":3,"title":"pve","slug":"pve","link":"#pve","children":[]}],"git":{"createdTime":1728372629000,"updatedTime":1728372629000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.09,"words":27},"filePathRelative":"other/pve/README.md","localizedDate":"2024年10月8日","excerpt":"

      pve

      \\n

      这是一个好玩的工具

      \\n","autoDesc":true}');export{m as comp,u as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e}from"./app-_Oi5YZFn.js";const r={},i=e("h3",{id:"pve",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#pve"},[e("span",null,"pve")])],-1),a=e("p",null,"这是一个好玩的工具",-1),p=[i,a];function c(s,l){return o(),n("div",null,p)}const m=t(r,[["render",c],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/other/pve/","title":"玩转PVE虚拟机","lang":"zh-CN","frontmatter":{"title":"玩转PVE虚拟机","date":"2024-10-08T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"pve 这是一个好玩的工具","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/pve/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"玩转PVE虚拟机"}],["meta",{"property":"og:description","content":"pve 这是一个好玩的工具"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-10-08T07:30:29.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-10-08T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-10-08T07:30:29.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"玩转PVE虚拟机\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-10-08T00:00:00.000Z\\",\\"dateModified\\":\\"2024-10-08T07:30:29.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":3,"title":"pve","slug":"pve","link":"#pve","children":[]}],"git":{"createdTime":1728372629000,"updatedTime":1728372629000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.09,"words":27},"filePathRelative":"other/pve/README.md","localizedDate":"2024年10月8日","excerpt":"

      pve

      \\n

      这是一个好玩的工具

      \\n","autoDesc":true}');export{m as comp,u as data}; diff --git a/assets/index.html-dhU_SREp.js b/assets/index.html-DFsL5gjr.js similarity index 93% rename from assets/index.html-dhU_SREp.js rename to assets/index.html-DFsL5gjr.js index 3d50181b86..7ac2185825 100644 --- a/assets/index.html-dhU_SREp.js +++ b/assets/index.html-DFsL5gjr.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as n,c as a,d as r}from"./app-CVMfKeWw.js";const c={};function p(s,i){const e=o("Catalog");return n(),a("div",null,[r(e)])}const h=t(c,[["render",p],["__file","index.html.vue"]]),_=JSON.parse('{"path":"/other/sono/","title":"Sono","lang":"zh-CN","frontmatter":{"title":"Sono","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/sono/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Sono"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Sono\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,_ as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as n,c as a,d as r}from"./app-_Oi5YZFn.js";const c={};function p(s,i){const e=o("Catalog");return n(),a("div",null,[r(e)])}const h=t(c,[["render",p],["__file","index.html.vue"]]),_=JSON.parse('{"path":"/other/sono/","title":"Sono","lang":"zh-CN","frontmatter":{"title":"Sono","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/sono/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Sono"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Sono\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,_ as data}; diff --git a/assets/index.html-DKhBFgRg.js b/assets/index.html-DH61zfDk.js similarity index 94% rename from assets/index.html-DKhBFgRg.js rename to assets/index.html-DH61zfDk.js index 204efa3a9b..a8f911cfb2 100644 --- a/assets/index.html-DKhBFgRg.js +++ b/assets/index.html-DH61zfDk.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E6%97%A5%E5%BF%97/","title":"标签: 日志","lang":"zh-CN","frontmatter":{"title":"标签: 日志","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"日志","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E6%97%A5%E5%BF%97/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 日志"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 日志\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E6%97%A5%E5%BF%97/","title":"标签: 日志","lang":"zh-CN","frontmatter":{"title":"标签: 日志","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"日志","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E6%97%A5%E5%BF%97/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 日志"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 日志\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-CToWg7cr.js b/assets/index.html-DHWvYkOD.js similarity index 96% rename from assets/index.html-CToWg7cr.js rename to assets/index.html-DHWvYkOD.js index 109e4a85b3..638dbc3230 100644 --- a/assets/index.html-CToWg7cr.js +++ b/assets/index.html-DHWvYkOD.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const r={};function a(n,i){return t(),o("div")}const m=e(r,[["render",a],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/frontweb/vue/","title":"Vue","lang":"zh-CN","frontmatter":{"title":"Vue","date":"2022-03-24T00:00:00.000Z","author":"Zxf","tag":["你所不了解的JavaScript"],"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/vue/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Vue"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"Zxf"}],["meta",{"property":"article:tag","content":"你所不了解的JavaScript"}],["meta",{"property":"article:published_time","content":"2022-03-24T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Vue\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-24T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zxf\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":16},"filePathRelative":"frontweb/vue/README.md","localizedDate":"2022年3月24日","excerpt":""}');export{m as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const r={};function a(n,i){return t(),o("div")}const m=e(r,[["render",a],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/frontweb/vue/","title":"Vue","lang":"zh-CN","frontmatter":{"title":"Vue","date":"2022-03-24T00:00:00.000Z","author":"Zxf","tag":["你所不了解的JavaScript"],"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/vue/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Vue"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"Zxf"}],["meta",{"property":"article:tag","content":"你所不了解的JavaScript"}],["meta",{"property":"article:published_time","content":"2022-03-24T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Vue\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-24T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Zxf\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":16},"filePathRelative":"frontweb/vue/README.md","localizedDate":"2022年3月24日","excerpt":""}');export{m as comp,l as data}; diff --git a/assets/index.html-BOnFN1d7.js b/assets/index.html-DI5pE721.js similarity index 94% rename from assets/index.html-BOnFN1d7.js rename to assets/index.html-DI5pE721.js index 431fa8638a..ba1a5d81b5 100644 --- a/assets/index.html-BOnFN1d7.js +++ b/assets/index.html-DI5pE721.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,l){return t(),o("div")}const p=e(a,[["render",n],["__file","index.html.vue"]]),m=JSON.parse('{"path":"/tag/mysql/","title":"标签: mysql","lang":"zh-CN","frontmatter":{"title":"标签: mysql","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"mysql","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/mysql/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: mysql"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: mysql\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{p as comp,m as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,l){return t(),o("div")}const p=e(a,[["render",n],["__file","index.html.vue"]]),m=JSON.parse('{"path":"/tag/mysql/","title":"标签: mysql","lang":"zh-CN","frontmatter":{"title":"标签: mysql","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"mysql","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/mysql/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: mysql"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: mysql\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{p as comp,m as data}; diff --git a/assets/index.html-iq2vqpLb.js b/assets/index.html-DLU057mi.js similarity index 96% rename from assets/index.html-iq2vqpLb.js rename to assets/index.html-DLU057mi.js index c312f30878..f8eb24578c 100644 --- a/assets/index.html-iq2vqpLb.js +++ b/assets/index.html-DLU057mi.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-CVMfKeWw.js";const o={};function n(i,r){return t(),a("div")}const p=e(o,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/java/","title":"Java入门到放弃","lang":"zh-CN","frontmatter":{"title":"Java入门到放弃","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Java入门到放弃"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-11-01T07:45:02.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-11-01T07:45:02.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Java入门到放弃\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-11-01T07:45:02.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1667288702000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.04,"words":11},"filePathRelative":"java/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{p as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-_Oi5YZFn.js";const o={};function n(i,r){return t(),a("div")}const p=e(o,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/java/","title":"Java入门到放弃","lang":"zh-CN","frontmatter":{"title":"Java入门到放弃","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Java入门到放弃"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-11-01T07:45:02.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-11-01T07:45:02.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Java入门到放弃\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-11-01T07:45:02.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1667288702000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.04,"words":11},"filePathRelative":"java/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{p as comp,l as data}; diff --git a/assets/index.html-Dn4DJ2Jv.js b/assets/index.html-DNyKapJd.js similarity index 94% rename from assets/index.html-Dn4DJ2Jv.js rename to assets/index.html-DNyKapJd.js index f5f170b5f8..999caa5d73 100644 --- a/assets/index.html-Dn4DJ2Jv.js +++ b/assets/index.html-DNyKapJd.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const r={};function a(p,n){return t(),o("div")}const s=e(r,[["render",a],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/vue%E4%B8%AD%E7%9A%84-typescript/","title":"标签: vue中的 TypeScript","lang":"zh-CN","frontmatter":{"title":"标签: vue中的 TypeScript","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"vue中的 TypeScript","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/vue%E4%B8%AD%E7%9A%84-typescript/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: vue中的 TypeScript"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: vue中的 TypeScript\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{s as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const r={};function a(p,n){return t(),o("div")}const s=e(r,[["render",a],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/vue%E4%B8%AD%E7%9A%84-typescript/","title":"标签: vue中的 TypeScript","lang":"zh-CN","frontmatter":{"title":"标签: vue中的 TypeScript","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"vue中的 TypeScript","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/vue%E4%B8%AD%E7%9A%84-typescript/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: vue中的 TypeScript"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: vue中的 TypeScript\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{s as comp,l as data}; diff --git a/assets/index.html-D2gpmgi4.js b/assets/index.html-DYNHttZu.js similarity index 94% rename from assets/index.html-D2gpmgi4.js rename to assets/index.html-DYNHttZu.js index af95ef62dc..d26fb9b842 100644 --- a/assets/index.html-D2gpmgi4.js +++ b/assets/index.html-DYNHttZu.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E5%AD%97%E8%8A%82%E7%A0%81/","title":"标签: 字节码","lang":"zh-CN","frontmatter":{"title":"标签: 字节码","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"字节码","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E5%AD%97%E8%8A%82%E7%A0%81/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 字节码"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 字节码\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E5%AD%97%E8%8A%82%E7%A0%81/","title":"标签: 字节码","lang":"zh-CN","frontmatter":{"title":"标签: 字节码","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"字节码","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E5%AD%97%E8%8A%82%E7%A0%81/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 字节码"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 字节码\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-7hihg5cE.js b/assets/index.html-DZ7gwFx0.js similarity index 96% rename from assets/index.html-7hihg5cE.js rename to assets/index.html-DZ7gwFx0.js index 0dba95ff93..ba7b461eda 100644 --- a/assets/index.html-7hihg5cE.js +++ b/assets/index.html-DZ7gwFx0.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e}from"./app-CVMfKeWw.js";const r={},a=e("blockquote",null,[e("p",null,"此分类记录web开发通用知识")],-1),c=[a];function i(p,l){return o(),n("div",null,c)}const d=t(r,[["render",i],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/other/web/","title":"web开发通用知识","lang":"zh-CN","frontmatter":{"title":"web开发通用知识","date":"2022-03-17T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["web"],"tag":["web"],"description":"此分类记录web开发通用知识","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/web/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"web开发通用知识"}],["meta",{"property":"og:description","content":"此分类记录web开发通用知识"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:tag","content":"web"}],["meta",{"property":"article:published_time","content":"2022-03-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"web开发通用知识\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-17T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.11,"words":33},"filePathRelative":"other/web/README.md","localizedDate":"2022年3月17日","excerpt":"
      \\n

      此分类记录web开发通用知识

      \\n
      \\n","autoDesc":true}');export{d as comp,h as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e}from"./app-_Oi5YZFn.js";const r={},a=e("blockquote",null,[e("p",null,"此分类记录web开发通用知识")],-1),c=[a];function i(p,l){return o(),n("div",null,c)}const d=t(r,[["render",i],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/other/web/","title":"web开发通用知识","lang":"zh-CN","frontmatter":{"title":"web开发通用知识","date":"2022-03-17T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"category":["web"],"tag":["web"],"description":"此分类记录web开发通用知识","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/web/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"web开发通用知识"}],["meta",{"property":"og:description","content":"此分类记录web开发通用知识"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:tag","content":"web"}],["meta",{"property":"article:published_time","content":"2022-03-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"web开发通用知识\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-17T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.11,"words":33},"filePathRelative":"other/web/README.md","localizedDate":"2022年3月17日","excerpt":"
      \\n

      此分类记录web开发通用知识

      \\n
      \\n","autoDesc":true}');export{d as comp,h as data}; diff --git a/assets/index.html-B9UffgOy.js b/assets/index.html-D_iYE-Ij.js similarity index 96% rename from assets/index.html-B9UffgOy.js rename to assets/index.html-D_iYE-Ij.js index def978e841..3bae4b6bdd 100644 --- a/assets/index.html-B9UffgOy.js +++ b/assets/index.html-D_iYE-Ij.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const r={};function n(a,i){return t(),o("div")}const m=e(r,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/java/framework/spring/","title":"Spring","lang":"zh-CN","frontmatter":{"title":"Spring","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/spring/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Spring"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Spring\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.02,"words":6},"filePathRelative":"java/framework/spring/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{m as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const r={};function n(a,i){return t(),o("div")}const m=e(r,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/java/framework/spring/","title":"Spring","lang":"zh-CN","frontmatter":{"title":"Spring","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/spring/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Spring"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Spring\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.02,"words":6},"filePathRelative":"java/framework/spring/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{m as comp,l as data}; diff --git a/assets/index.html-Beu4CvPJ.js b/assets/index.html-DaiGMbWY.js similarity index 96% rename from assets/index.html-Beu4CvPJ.js rename to assets/index.html-DaiGMbWY.js index a135a55d24..0326c719fb 100644 --- a/assets/index.html-Beu4CvPJ.js +++ b/assets/index.html-DaiGMbWY.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const r={};function n(i,a){return t(),o("div")}const p=e(r,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/other/distributeservice/","title":"分布式微服务","lang":"zh-CN","frontmatter":{"title":"分布式微服务","date":"2022-03-17T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/distributeservice/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"分布式微服务"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-03-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"分布式微服务\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-17T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.06,"words":17},"filePathRelative":"other/distributeservice/README.md","localizedDate":"2022年3月17日","excerpt":"\\n"}');export{p as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const r={};function n(i,a){return t(),o("div")}const p=e(r,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/other/distributeservice/","title":"分布式微服务","lang":"zh-CN","frontmatter":{"title":"分布式微服务","date":"2022-03-17T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/distributeservice/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"分布式微服务"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-03-17T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"分布式微服务\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-17T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.06,"words":17},"filePathRelative":"other/distributeservice/README.md","localizedDate":"2022年3月17日","excerpt":"\\n"}');export{p as comp,s as data}; diff --git a/assets/index.html-0QvH_nXT.js b/assets/index.html-DcPH7P5y.js similarity index 93% rename from assets/index.html-0QvH_nXT.js rename to assets/index.html-DcPH7P5y.js index 3b7fb5d0b4..bbd83f7284 100644 --- a/assets/index.html-0QvH_nXT.js +++ b/assets/index.html-DcPH7P5y.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as a,c as r,d as n}from"./app-CVMfKeWw.js";const c={};function p(i,m){const e=o("Catalog");return a(),r("div",null,[n(e)])}const h=t(c,[["render",p],["__file","index.html.vue"]]),f=JSON.parse('{"path":"/java/framework/","title":"Framework","lang":"zh-CN","frontmatter":{"title":"Framework","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Framework"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Framework\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,f as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as a,c as r,d as n}from"./app-_Oi5YZFn.js";const c={};function p(i,m){const e=o("Catalog");return a(),r("div",null,[n(e)])}const h=t(c,[["render",p],["__file","index.html.vue"]]),f=JSON.parse('{"path":"/java/framework/","title":"Framework","lang":"zh-CN","frontmatter":{"title":"Framework","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Framework"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Framework\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,f as data}; diff --git a/assets/index.html-DD8LSjG9.js b/assets/index.html-De67gXK7.js similarity index 93% rename from assets/index.html-DD8LSjG9.js rename to assets/index.html-De67gXK7.js index 87b2218b72..be5ce2ac0d 100644 --- a/assets/index.html-DD8LSjG9.js +++ b/assets/index.html-De67gXK7.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as n,c as a,d as r}from"./app-CVMfKeWw.js";const i={};function s(c,p){const e=o("Catalog");return n(),a("div",null,[r(e)])}const d=t(i,[["render",s],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/other/windows/","title":"Windows","lang":"zh-CN","frontmatter":{"title":"Windows","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/windows/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Windows"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Windows\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{d as comp,h as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as n,c as a,d as r}from"./app-_Oi5YZFn.js";const i={};function s(c,p){const e=o("Catalog");return n(),a("div",null,[r(e)])}const d=t(i,[["render",s],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/other/windows/","title":"Windows","lang":"zh-CN","frontmatter":{"title":"Windows","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/windows/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Windows"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Windows\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{d as comp,h as data}; diff --git a/assets/index.html-3CT6OGMP.js b/assets/index.html-DfN87M_p.js similarity index 94% rename from assets/index.html-3CT6OGMP.js rename to assets/index.html-DfN87M_p.js index a92d38efbe..f11e1dca59 100644 --- a/assets/index.html-3CT6OGMP.js +++ b/assets/index.html-DfN87M_p.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E7%A6%81%E7%94%A8/","title":"标签: 禁用","lang":"zh-CN","frontmatter":{"title":"标签: 禁用","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"禁用","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E7%A6%81%E7%94%A8/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 禁用"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 禁用\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E7%A6%81%E7%94%A8/","title":"标签: 禁用","lang":"zh-CN","frontmatter":{"title":"标签: 禁用","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"禁用","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E7%A6%81%E7%94%A8/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 禁用"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 禁用\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-Di3gSe-7.js b/assets/index.html-Di3gSe-7.js new file mode 100644 index 0000000000..2a35517176 --- /dev/null +++ b/assets/index.html-Di3gSe-7.js @@ -0,0 +1 @@ +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function r(i,a){return t(),o("div")}const p=e(n,[["render",r],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/","title":"ChenSino","lang":"zh-CN","frontmatter":{"home":true,"layout":"BlogHome","icon":"home","title":"ChenSino","heroImage":"/logo.svg","heroText":"ChenSino","heroFullScreen":true,"tagline":"洛星星的学习笔记","copyright":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"ChenSino"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-02T13:30:46.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:modified_time","content":"2024-11-02T13:30:46.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"ChenSino\\"}"]]},"headers":[],"git":{"createdTime":1659087823000,"updatedTime":1730554246000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":4},{"name":"chenkun","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.08,"words":25},"filePathRelative":"README.md","localizedDate":"2022年7月29日","excerpt":""}');export{p as comp,h as data}; diff --git a/assets/index.html-BDPTsTEv.js b/assets/index.html-DmeObyHP.js similarity index 94% rename from assets/index.html-BDPTsTEv.js rename to assets/index.html-DmeObyHP.js index d020b251a7..db884cc49a 100644 --- a/assets/index.html-BDPTsTEv.js +++ b/assets/index.html-DmeObyHP.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/vite/","title":"vite 分类","lang":"zh-CN","frontmatter":{"title":"vite 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"vite","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/vite/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"vite 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"vite 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/vite/","title":"vite 分类","lang":"zh-CN","frontmatter":{"title":"vite 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"vite","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/vite/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"vite 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"vite 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-BvSZt_Zx.js b/assets/index.html-DnWLnH1N.js similarity index 94% rename from assets/index.html-BvSZt_Zx.js rename to assets/index.html-DnWLnH1N.js index 9df9f30c25..8d839bf224 100644 --- a/assets/index.html-BvSZt_Zx.js +++ b/assets/index.html-DnWLnH1N.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E9%A1%B5%E9%9D%A2%E9%85%8D%E7%BD%AE/","title":"标签: 页面配置","lang":"zh-CN","frontmatter":{"title":"标签: 页面配置","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"页面配置","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E9%A1%B5%E9%9D%A2%E9%85%8D%E7%BD%AE/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 页面配置"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 页面配置\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E9%A1%B5%E9%9D%A2%E9%85%8D%E7%BD%AE/","title":"标签: 页面配置","lang":"zh-CN","frontmatter":{"title":"标签: 页面配置","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"页面配置","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E9%A1%B5%E9%9D%A2%E9%85%8D%E7%BD%AE/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 页面配置"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 页面配置\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-C7LrpzTI.js b/assets/index.html-DpIMle5T.js similarity index 94% rename from assets/index.html-C7LrpzTI.js rename to assets/index.html-DpIMle5T.js index e3c6410c5c..2513d7a5c1 100644 --- a/assets/index.html-C7LrpzTI.js +++ b/assets/index.html-DpIMle5T.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/vue%E7%9F%A5%E8%AF%86%E7%82%B9/","title":"vue知识点 分类","lang":"zh-CN","frontmatter":{"title":"vue知识点 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"vue知识点","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/vue%E7%9F%A5%E8%AF%86%E7%82%B9/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"vue知识点 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"vue知识点 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/vue%E7%9F%A5%E8%AF%86%E7%82%B9/","title":"vue知识点 分类","lang":"zh-CN","frontmatter":{"title":"vue知识点 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"vue知识点","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/vue%E7%9F%A5%E8%AF%86%E7%82%B9/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"vue知识点 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"vue知识点 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-CCNjkOtl.js b/assets/index.html-DqEIAryj.js similarity index 94% rename from assets/index.html-CCNjkOtl.js rename to assets/index.html-DqEIAryj.js index f29a440a71..5cc355b76b 100644 --- a/assets/index.html-CCNjkOtl.js +++ b/assets/index.html-DqEIAryj.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E8%BF%90%E7%BB%B4/","title":"标签: 运维","lang":"zh-CN","frontmatter":{"title":"标签: 运维","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"运维","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E8%BF%90%E7%BB%B4/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 运维"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 运维\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E8%BF%90%E7%BB%B4/","title":"标签: 运维","lang":"zh-CN","frontmatter":{"title":"标签: 运维","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"运维","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E8%BF%90%E7%BB%B4/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 运维"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 运维\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-LnP3a8gR.js b/assets/index.html-DsIag3k6.js similarity index 94% rename from assets/index.html-LnP3a8gR.js rename to assets/index.html-DsIag3k6.js index 6f55bf077e..1f41de9b28 100644 --- a/assets/index.html-LnP3a8gR.js +++ b/assets/index.html-DsIag3k6.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/","title":"标签: 使用指南","lang":"zh-CN","frontmatter":{"title":"标签: 使用指南","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"使用指南","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 使用指南"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 使用指南\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/","title":"标签: 使用指南","lang":"zh-CN","frontmatter":{"title":"标签: 使用指南","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"使用指南","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 使用指南"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 使用指南\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-DDz84u40.js b/assets/index.html-DtMYUjJK.js similarity index 94% rename from assets/index.html-DDz84u40.js rename to assets/index.html-DtMYUjJK.js index 4d50da071b..f4c3597fce 100644 --- a/assets/index.html-DDz84u40.js +++ b/assets/index.html-DtMYUjJK.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E7%BA%BF%E7%A8%8B%E6%B1%A0/","title":"线程池 分类","lang":"zh-CN","frontmatter":{"title":"线程池 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"线程池","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E7%BA%BF%E7%A8%8B%E6%B1%A0/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"线程池 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"线程池 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E7%BA%BF%E7%A8%8B%E6%B1%A0/","title":"线程池 分类","lang":"zh-CN","frontmatter":{"title":"线程池 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"线程池","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E7%BA%BF%E7%A8%8B%E6%B1%A0/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"线程池 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"线程池 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-B1xDsJG7.js b/assets/index.html-Du2EK4qm.js similarity index 94% rename from assets/index.html-B1xDsJG7.js rename to assets/index.html-Du2EK4qm.js index b0f1c3c841..92538b48a1 100644 --- a/assets/index.html-B1xDsJG7.js +++ b/assets/index.html-Du2EK4qm.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-CVMfKeWw.js";const o={};function n(r,c){return t(),a("div")}const l=e(o,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/java%E5%9F%BA%E7%A1%80/","title":"java基础 分类","lang":"zh-CN","frontmatter":{"title":"java基础 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"java基础","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/java%E5%9F%BA%E7%A1%80/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"java基础 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"java基础 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-_Oi5YZFn.js";const o={};function n(r,c){return t(),a("div")}const l=e(o,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/java%E5%9F%BA%E7%A1%80/","title":"java基础 分类","lang":"zh-CN","frontmatter":{"title":"java基础 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"java基础","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/java%E5%9F%BA%E7%A1%80/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"java基础 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"java基础 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-B-rzW1p0.js b/assets/index.html-DyGScxDV.js similarity index 96% rename from assets/index.html-B-rzW1p0.js rename to assets/index.html-DyGScxDV.js index 1815e0ce33..b356a22a32 100644 --- a/assets/index.html-B-rzW1p0.js +++ b/assets/index.html-DyGScxDV.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o,a as n}from"./app-CVMfKeWw.js";const r={},a=n("p",null,"基础打牢",-1),i=[a];function c(p,m){return e(),o("div",null,i)}const l=t(r,[["render",c],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/frontweb/","title":"JavaScript成神之路","lang":"zh-CN","frontmatter":{"title":"JavaScript成神之路","date":"2018-05-29T00:00:00.000Z","description":"基础打牢","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"JavaScript成神之路"}],["meta",{"property":"og:description","content":"基础打牢"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"JavaScript成神之路\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":14},"filePathRelative":"frontweb/README.md","localizedDate":"2018年5月29日","excerpt":"

      基础打牢

      \\n","autoDesc":true}');export{l as comp,h as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o,a as n}from"./app-_Oi5YZFn.js";const r={},a=n("p",null,"基础打牢",-1),i=[a];function c(p,m){return e(),o("div",null,i)}const l=t(r,[["render",c],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/frontweb/","title":"JavaScript成神之路","lang":"zh-CN","frontmatter":{"title":"JavaScript成神之路","date":"2018-05-29T00:00:00.000Z","description":"基础打牢","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"JavaScript成神之路"}],["meta",{"property":"og:description","content":"基础打牢"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"JavaScript成神之路\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.05,"words":14},"filePathRelative":"frontweb/README.md","localizedDate":"2018年5月29日","excerpt":"

      基础打牢

      \\n","autoDesc":true}');export{l as comp,h as data}; diff --git a/assets/index.html-CuWpjIbb.js b/assets/index.html-Gf4fmU9c.js similarity index 93% rename from assets/index.html-CuWpjIbb.js rename to assets/index.html-Gf4fmU9c.js index 1bf1e34288..dc11e73aa3 100644 --- a/assets/index.html-CuWpjIbb.js +++ b/assets/index.html-Gf4fmU9c.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as n,c as r,d as a}from"./app-CVMfKeWw.js";const p={};function c(i,l){const e=o("Catalog");return n(),r("div",null,[a(e)])}const h=t(p,[["render",c],["__file","index.html.vue"]]),_=JSON.parse('{"path":"/cpp/other/","title":"Other","lang":"zh-CN","frontmatter":{"title":"Other","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/cpp/other/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Other"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Other\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,_ as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as n,c as r,d as a}from"./app-_Oi5YZFn.js";const p={};function c(i,l){const e=o("Catalog");return n(),r("div",null,[a(e)])}const h=t(p,[["render",c],["__file","index.html.vue"]]),_=JSON.parse('{"path":"/cpp/other/","title":"Other","lang":"zh-CN","frontmatter":{"title":"Other","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/cpp/other/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Other"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Other\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{h as comp,_ as data}; diff --git a/assets/index.html-B-zCQLtF.js b/assets/index.html-KBuRJKDT.js similarity index 94% rename from assets/index.html-B-zCQLtF.js rename to assets/index.html-KBuRJKDT.js index 64df4b661a..fb81fc03fe 100644 --- a/assets/index.html-B-zCQLtF.js +++ b/assets/index.html-KBuRJKDT.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E6%95%B0%E6%8D%AE%E5%BA%93/","title":"数据库 分类","lang":"zh-CN","frontmatter":{"title":"数据库 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"数据库","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"数据库 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"数据库 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/%E6%95%B0%E6%8D%AE%E5%BA%93/","title":"数据库 分类","lang":"zh-CN","frontmatter":{"title":"数据库 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"数据库","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/%E6%95%B0%E6%8D%AE%E5%BA%93/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"数据库 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"数据库 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-BQbSFfwU.js b/assets/index.html-QOoLQt7U.js similarity index 94% rename from assets/index.html-BQbSFfwU.js rename to assets/index.html-QOoLQt7U.js index 3f422d055a..e0801e823c 100644 --- a/assets/index.html-BQbSFfwU.js +++ b/assets/index.html-QOoLQt7U.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const m=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/jvm/","title":"jvm 分类","lang":"zh-CN","frontmatter":{"title":"jvm 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"jvm","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/jvm/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"jvm 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"jvm 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{m as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const m=e(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/jvm/","title":"jvm 分类","lang":"zh-CN","frontmatter":{"title":"jvm 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"jvm","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/jvm/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"jvm 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"jvm 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{m as comp,l as data}; diff --git a/assets/index.html-CxAtUmc7.js b/assets/index.html-UDfVv4MI.js similarity index 94% rename from assets/index.html-CxAtUmc7.js rename to assets/index.html-UDfVv4MI.js index 968df3488f..bae2388582 100644 --- a/assets/index.html-CxAtUmc7.js +++ b/assets/index.html-UDfVv4MI.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/","title":"标签: 数据结构","lang":"zh-CN","frontmatter":{"title":"标签: 数据结构","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"数据结构","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 数据结构"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 数据结构\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/","title":"标签: 数据结构","lang":"zh-CN","frontmatter":{"title":"标签: 数据结构","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"数据结构","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 数据结构"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 数据结构\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-09kdO8uk.js b/assets/index.html-UPH_9_-C.js similarity index 97% rename from assets/index.html-09kdO8uk.js rename to assets/index.html-UPH_9_-C.js index 1f2abae744..8c41e10660 100644 --- a/assets/index.html-09kdO8uk.js +++ b/assets/index.html-UPH_9_-C.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e}from"./app-CVMfKeWw.js";const c={},i=e("h2",{id:"c-基础学习",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#c-基础学习"},[e("span",null,"c++基础学习")])],-1),a=e("blockquote",null,[e("p",null,"c++的进阶之路")],-1),r=[i,a];function p(l,s){return o(),n("div",null,r)}const m=t(c,[["render",p],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/cpp/study/","title":"index","lang":"zh-CN","frontmatter":{"title":"index","date":"2023-01-13T00:00:00.000Z","description":"c++基础学习 c++的进阶之路","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/cpp/study/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"index"}],["meta",{"property":"og:description","content":"c++基础学习 c++的进阶之路"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2023-01-13T05:57:56.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-01-13T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2023-01-13T05:57:56.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"index\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-01-13T00:00:00.000Z\\",\\"dateModified\\":\\"2023-01-13T05:57:56.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"c++基础学习","slug":"c-基础学习","link":"#c-基础学习","children":[]}],"git":{"createdTime":1673589476000,"updatedTime":1673589476000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.06,"words":17},"filePathRelative":"cpp/study/README.md","localizedDate":"2023年1月13日","excerpt":"

      c++基础学习

      \\n
      \\n

      c++的进阶之路

      \\n
      \\n","autoDesc":true}');export{m as comp,u as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o,c as n,a as e}from"./app-_Oi5YZFn.js";const c={},i=e("h2",{id:"c-基础学习",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#c-基础学习"},[e("span",null,"c++基础学习")])],-1),a=e("blockquote",null,[e("p",null,"c++的进阶之路")],-1),r=[i,a];function p(l,s){return o(),n("div",null,r)}const m=t(c,[["render",p],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/cpp/study/","title":"index","lang":"zh-CN","frontmatter":{"title":"index","date":"2023-01-13T00:00:00.000Z","description":"c++基础学习 c++的进阶之路","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/cpp/study/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"index"}],["meta",{"property":"og:description","content":"c++基础学习 c++的进阶之路"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2023-01-13T05:57:56.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-01-13T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2023-01-13T05:57:56.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"index\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-01-13T00:00:00.000Z\\",\\"dateModified\\":\\"2023-01-13T05:57:56.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"c++基础学习","slug":"c-基础学习","link":"#c-基础学习","children":[]}],"git":{"createdTime":1673589476000,"updatedTime":1673589476000,"contributors":[{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.06,"words":17},"filePathRelative":"cpp/study/README.md","localizedDate":"2023年1月13日","excerpt":"

      c++基础学习

      \\n
      \\n

      c++的进阶之路

      \\n
      \\n","autoDesc":true}');export{m as comp,u as data}; diff --git a/assets/index.html-vvugCgoe.js b/assets/index.html-YlcxYzGW.js similarity index 94% rename from assets/index.html-vvugCgoe.js rename to assets/index.html-YlcxYzGW.js index 371f1fa74f..e99c82c614 100644 --- a/assets/index.html-vvugCgoe.js +++ b/assets/index.html-YlcxYzGW.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E6%96%87%E7%AB%A0%E5%8A%A0%E5%AF%86/","title":"标签: 文章加密","lang":"zh-CN","frontmatter":{"title":"标签: 文章加密","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"文章加密","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E6%96%87%E7%AB%A0%E5%8A%A0%E5%AF%86/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 文章加密"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 文章加密\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E6%96%87%E7%AB%A0%E5%8A%A0%E5%AF%86/","title":"标签: 文章加密","lang":"zh-CN","frontmatter":{"title":"标签: 文章加密","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"文章加密","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E6%96%87%E7%AB%A0%E5%8A%A0%E5%AF%86/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 文章加密"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 文章加密\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-BqSgUMIT.js b/assets/index.html-ZWa93oKC.js similarity index 95% rename from assets/index.html-BqSgUMIT.js rename to assets/index.html-ZWa93oKC.js index b707c4cc72..4c9c4df773 100644 --- a/assets/index.html-BqSgUMIT.js +++ b/assets/index.html-ZWa93oKC.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const n={};function r(a,i){return t(),o("div")}const m=e(n,[["render",r],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/other/","title":"其他","lang":"zh-CN","frontmatter":{"title":"其他","date":"2022-08-01T00:00:00.000Z","author":"chenkun","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"其他"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-02T06:00:31.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-08-01T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-02T06:00:31.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"其他\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-01T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-02T06:00:31.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659420031000,"updatedTime":1659420031000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.03,"words":9},"filePathRelative":"other/README.md","localizedDate":"2022年8月1日","excerpt":""}');export{m as comp,d as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function r(a,i){return t(),o("div")}const m=e(n,[["render",r],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/other/","title":"其他","lang":"zh-CN","frontmatter":{"title":"其他","date":"2022-08-01T00:00:00.000Z","author":"chenkun","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"其他"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-02T06:00:31.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-08-01T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-02T06:00:31.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"其他\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-08-01T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-02T06:00:31.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1659420031000,"updatedTime":1659420031000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.03,"words":9},"filePathRelative":"other/README.md","localizedDate":"2022年8月1日","excerpt":""}');export{m as comp,d as data}; diff --git a/assets/index.html-aM10vDis.js b/assets/index.html-ZenQwYfE.js similarity index 93% rename from assets/index.html-aM10vDis.js rename to assets/index.html-ZenQwYfE.js index afcfbdec25..0d3c85d77a 100644 --- a/assets/index.html-aM10vDis.js +++ b/assets/index.html-ZenQwYfE.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as a,c as n,d as r}from"./app-CVMfKeWw.js";const c={};function p(l,i){const e=o("Catalog");return a(),n("div",null,[r(e)])}const g=t(c,[["render",p],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/java/other/log/","title":"Log","lang":"zh-CN","frontmatter":{"title":"Log","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/log/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Log"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Log\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{g as comp,h as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as a,c as n,d as r}from"./app-_Oi5YZFn.js";const c={};function p(l,i){const e=o("Catalog");return a(),n("div",null,[r(e)])}const g=t(c,[["render",p],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/java/other/log/","title":"Log","lang":"zh-CN","frontmatter":{"title":"Log","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/log/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Log"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Log\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{g as comp,h as data}; diff --git a/assets/index.html-DYLEdXjZ.js b/assets/index.html-Zxb4dqDx.js similarity index 98% rename from assets/index.html-DYLEdXjZ.js rename to assets/index.html-Zxb4dqDx.js index bcc8f88b9f..38e989fb68 100644 --- a/assets/index.html-DYLEdXjZ.js +++ b/assets/index.html-Zxb4dqDx.js @@ -1 +1 @@ -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o,c as i,a as e,b as t,d as p}from"./app-CVMfKeWw.js";const s={},c=e("h2",{id:"说明",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#说明"},[e("span",null,"说明")])],-1),l={href:"https://github.com/ChenSino/java-design-patterns.git",target:"_blank",rel:"noopener noreferrer"};function d(h,m){const n=r("ExternalLinkIcon");return o(),i("div",null,[c,e("p",null,[t("java设计模式代码请参考开源项目"),e("a",l,[t("java-design-patterns"),p(n)]),t(",本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。")])])}const _=a(s,[["render",d],["__file","index.html.vue"]]),f=JSON.parse('{"path":"/designpattern/","title":"README","lang":"zh-CN","frontmatter":{"title":"README","date":"2023-05-12T00:00:00.000Z","isOriginal":true,"description":"说明 java设计模式代码请参考开源项目java-design-patterns,本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/designpattern/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"README"}],["meta",{"property":"og:description","content":"说明 java设计模式代码请参考开源项目java-design-patterns,本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-05-12T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"README\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-05-12T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"说明","slug":"说明","link":"#说明","children":[]}],"git":{"createdTime":1684307143000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.34,"words":101},"filePathRelative":"designpattern/README.md","localizedDate":"2023年5月12日","excerpt":"

      说明

      \\n

      java设计模式代码请参考开源项目java-design-patterns,本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。

      \\n","autoDesc":true}');export{_ as comp,f as data}; +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o,c as i,a as e,b as t,d as p}from"./app-_Oi5YZFn.js";const s={},c=e("h2",{id:"说明",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#说明"},[e("span",null,"说明")])],-1),l={href:"https://github.com/ChenSino/java-design-patterns.git",target:"_blank",rel:"noopener noreferrer"};function d(h,m){const n=r("ExternalLinkIcon");return o(),i("div",null,[c,e("p",null,[t("java设计模式代码请参考开源项目"),e("a",l,[t("java-design-patterns"),p(n)]),t(",本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。")])])}const _=a(s,[["render",d],["__file","index.html.vue"]]),f=JSON.parse('{"path":"/designpattern/","title":"README","lang":"zh-CN","frontmatter":{"title":"README","date":"2023-05-12T00:00:00.000Z","isOriginal":true,"description":"说明 java设计模式代码请参考开源项目java-design-patterns,本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/designpattern/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"README"}],["meta",{"property":"og:description","content":"说明 java设计模式代码请参考开源项目java-design-patterns,本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-05-12T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"README\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-05-12T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"说明","slug":"说明","link":"#说明","children":[]}],"git":{"createdTime":1684307143000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.34,"words":101},"filePathRelative":"designpattern/README.md","localizedDate":"2023年5月12日","excerpt":"

      说明

      \\n

      java设计模式代码请参考开源项目java-design-patterns,本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。

      \\n","autoDesc":true}');export{_ as comp,f as data}; diff --git a/assets/index.html-BXBwg_XP.js b/assets/index.html-_Hk-UKaP.js similarity index 95% rename from assets/index.html-BXBwg_XP.js rename to assets/index.html-_Hk-UKaP.js index 526a1eac03..dc22e43f32 100644 --- a/assets/index.html-BXBwg_XP.js +++ b/assets/index.html-_Hk-UKaP.js @@ -1 +1 @@ -import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as r,c as p,a as e,d as n,w as a,b as o}from"./app-CVMfKeWw.js";const d={},c=e("h2",{id:"目录",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#目录"},[e("span",null,"目录")])],-1);function m(u,h){const t=l("RouteLink");return r(),p("div",null,[c,e("ul",null,[e("li",null,[e("p",null,[n(t,{to:"/guide/markdown.html"},{default:a(()=>[o("Markdown 展示")]),_:1})])]),e("li",null,[e("p",null,[n(t,{to:"/guide/page.html"},{default:a(()=>[o("页面展示")]),_:1})])]),e("li",null,[e("p",null,[n(t,{to:"/guide/disable.html"},{default:a(()=>[o("禁用展示")]),_:1})])]),e("li",null,[e("p",null,[n(t,{to:"/guide/encrypt.html"},{default:a(()=>[o("加密展示")]),_:1})])])])])}const _=i(d,[["render",m],["__file","index.html.vue"]]),f=JSON.parse('{"path":"/guide/","title":"主要功能与配置演示","lang":"zh-CN","frontmatter":{"title":"主要功能与配置演示","index":false,"icon":"creative","category":["使用指南"],"description":"目录","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/guide/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"主要功能与配置演示"}],["meta",{"property":"og:description","content":"目录"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-07-29T09:43:43.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:modified_time","content":"2022-07-29T09:43:43.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"主要功能与配置演示\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2022-07-29T09:43:43.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"目录","slug":"目录","link":"#目录","children":[]}],"git":{"createdTime":1659087823000,"updatedTime":1659087823000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.13,"words":40},"filePathRelative":"guide/README.md","localizedDate":"2022年7月29日","excerpt":"

      目录

      \\n","autoDesc":true}');export{_ as comp,f as data}; +import{_ as i}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as r,c as p,a as e,d as n,w as a,b as o}from"./app-_Oi5YZFn.js";const d={},c=e("h2",{id:"目录",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#目录"},[e("span",null,"目录")])],-1);function m(u,h){const t=l("RouteLink");return r(),p("div",null,[c,e("ul",null,[e("li",null,[e("p",null,[n(t,{to:"/guide/markdown.html"},{default:a(()=>[o("Markdown 展示")]),_:1})])]),e("li",null,[e("p",null,[n(t,{to:"/guide/page.html"},{default:a(()=>[o("页面展示")]),_:1})])]),e("li",null,[e("p",null,[n(t,{to:"/guide/disable.html"},{default:a(()=>[o("禁用展示")]),_:1})])]),e("li",null,[e("p",null,[n(t,{to:"/guide/encrypt.html"},{default:a(()=>[o("加密展示")]),_:1})])])])])}const _=i(d,[["render",m],["__file","index.html.vue"]]),f=JSON.parse('{"path":"/guide/","title":"主要功能与配置演示","lang":"zh-CN","frontmatter":{"title":"主要功能与配置演示","index":false,"icon":"creative","category":["使用指南"],"description":"目录","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/guide/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"主要功能与配置演示"}],["meta",{"property":"og:description","content":"目录"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-07-29T09:43:43.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:modified_time","content":"2022-07-29T09:43:43.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"主要功能与配置演示\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2022-07-29T09:43:43.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"目录","slug":"目录","link":"#目录","children":[]}],"git":{"createdTime":1659087823000,"updatedTime":1659087823000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.13,"words":40},"filePathRelative":"guide/README.md","localizedDate":"2022年7月29日","excerpt":"

      目录

      \\n","autoDesc":true}');export{_ as comp,f as data}; diff --git a/assets/index.html-CDsKkWs-.js b/assets/index.html-bCycLo4M.js similarity index 94% rename from assets/index.html-CDsKkWs-.js rename to assets/index.html-bCycLo4M.js index cd992b38fb..cff10ebb89 100644 --- a/assets/index.html-CDsKkWs-.js +++ b/assets/index.html-bCycLo4M.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/","title":"标签: 多线程","lang":"zh-CN","frontmatter":{"title":"标签: 多线程","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"多线程","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 多线程"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 多线程\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/","title":"标签: 多线程","lang":"zh-CN","frontmatter":{"title":"标签: 多线程","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"多线程","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 多线程"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 多线程\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-CeUSh-YY.js b/assets/index.html-da2hiqxj.js similarity index 94% rename from assets/index.html-CeUSh-YY.js rename to assets/index.html-da2hiqxj.js index d05492cd7d..94917c9e35 100644 --- a/assets/index.html-CeUSh-YY.js +++ b/assets/index.html-da2hiqxj.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E6%A1%86%E6%9E%B6/","title":"标签: 框架","lang":"zh-CN","frontmatter":{"title":"标签: 框架","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"框架","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E6%A1%86%E6%9E%B6/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 框架"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 框架\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/%E6%A1%86%E6%9E%B6/","title":"标签: 框架","lang":"zh-CN","frontmatter":{"title":"标签: 框架","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"框架","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/%E6%A1%86%E6%9E%B6/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: 框架"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: 框架\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-CY45JW6T.js b/assets/index.html-gaUfJQSN.js similarity index 94% rename from assets/index.html-CY45JW6T.js rename to assets/index.html-gaUfJQSN.js index c67344af4a..febe49576f 100644 --- a/assets/index.html-CY45JW6T.js +++ b/assets/index.html-gaUfJQSN.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const r={};function a(n,c){return t(),o("div")}const l=e(r,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/docker/","title":"docker 分类","lang":"zh-CN","frontmatter":{"title":"docker 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"docker","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/docker/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"docker 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"docker 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const r={};function a(n,c){return t(),o("div")}const l=e(r,[["render",a],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/docker/","title":"docker 分类","lang":"zh-CN","frontmatter":{"title":"docker 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"docker","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/docker/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"docker 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"docker 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-eHCo69H8.js b/assets/index.html-jAf7-iM1.js similarity index 94% rename from assets/index.html-eHCo69H8.js rename to assets/index.html-jAf7-iM1.js index c2845dcf3e..c122fff7f7 100644 --- a/assets/index.html-eHCo69H8.js +++ b/assets/index.html-jAf7-iM1.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as n,c as a,d as r}from"./app-CVMfKeWw.js";const c={};function p(i,l){const e=o("Catalog");return n(),a("div",null,[r(e)])}const d=t(c,[["render",p],["__file","index.html.vue"]]),g=JSON.parse('{"path":"/java/framework/springcloud/","title":"Springcloud","lang":"zh-CN","frontmatter":{"title":"Springcloud","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/springcloud/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Springcloud"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Springcloud\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{d as comp,g as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as n,c as a,d as r}from"./app-_Oi5YZFn.js";const c={};function p(i,l){const e=o("Catalog");return n(),a("div",null,[r(e)])}const d=t(c,[["render",p],["__file","index.html.vue"]]),g=JSON.parse('{"path":"/java/framework/springcloud/","title":"Springcloud","lang":"zh-CN","frontmatter":{"title":"Springcloud","article":false,"feed":false,"sitemap":false,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/springcloud/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Springcloud"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"Springcloud\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":1},"filePathRelative":null,"excerpt":""}');export{d as comp,g as data}; diff --git a/assets/index.html-B3FdepDG.js b/assets/index.html-k__z7vRJ.js similarity index 94% rename from assets/index.html-B3FdepDG.js rename to assets/index.html-k__z7vRJ.js index 411dc3c660..87f4ea9fa7 100644 --- a/assets/index.html-B3FdepDG.js +++ b/assets/index.html-k__z7vRJ.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,s){return e(),o("div")}const c=t(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/ts/","title":"标签: ts","lang":"zh-CN","frontmatter":{"title":"标签: ts","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"ts","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/ts/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: ts"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: ts\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,l as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,s){return e(),o("div")}const c=t(a,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/tag/ts/","title":"标签: ts","lang":"zh-CN","frontmatter":{"title":"标签: ts","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"ts","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/ts/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: ts"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: ts\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{c as comp,l as data}; diff --git a/assets/index.html-Dvn_QmO3.js b/assets/index.html-l0ianYW6.js similarity index 97% rename from assets/index.html-Dvn_QmO3.js rename to assets/index.html-l0ianYW6.js index ead92e43d2..0d8fcfb704 100644 --- a/assets/index.html-Dvn_QmO3.js +++ b/assets/index.html-l0ianYW6.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as a,a as e}from"./app-CVMfKeWw.js";const o={},r=e("h2",{id:"_1-maven的放弃之路",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-maven的放弃之路"},[e("span",null,"1. maven的放弃之路")])],-1),i=e("p",null,"世上无难事,只要肯放弃",-1),c=[r,i];function m(p,l){return n(),a("div",null,c)}const d=t(o,[["render",m],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/java/other/maven/","title":"maven","lang":"zh-CN","frontmatter":{"title":"maven","date":"2022-07-28T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"1. maven的放弃之路 世上无难事,只要肯放弃","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/maven/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"maven"}],["meta",{"property":"og:description","content":"1. maven的放弃之路 世上无难事,只要肯放弃"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-07-28T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"maven\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-28T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1. maven的放弃之路","slug":"_1-maven的放弃之路","link":"#_1-maven的放弃之路","children":[]}],"git":{"createdTime":1659925295000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.09,"words":28},"filePathRelative":"java/other/maven/README.md","localizedDate":"2022年7月28日","excerpt":"

      1. maven的放弃之路

      \\n

      世上无难事,只要肯放弃

      \\n","autoDesc":true}');export{d as comp,u as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as a,a as e}from"./app-_Oi5YZFn.js";const o={},r=e("h2",{id:"_1-maven的放弃之路",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#_1-maven的放弃之路"},[e("span",null,"1. maven的放弃之路")])],-1),i=e("p",null,"世上无难事,只要肯放弃",-1),c=[r,i];function m(p,l){return n(),a("div",null,c)}const d=t(o,[["render",m],["__file","index.html.vue"]]),u=JSON.parse('{"path":"/java/other/maven/","title":"maven","lang":"zh-CN","frontmatter":{"title":"maven","date":"2022-07-28T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"1. maven的放弃之路 世上无难事,只要肯放弃","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/maven/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"maven"}],["meta",{"property":"og:description","content":"1. maven的放弃之路 世上无难事,只要肯放弃"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-07-28T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"maven\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-07-28T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":2,"title":"1. maven的放弃之路","slug":"_1-maven的放弃之路","link":"#_1-maven的放弃之路","children":[]}],"git":{"createdTime":1659925295000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.09,"words":28},"filePathRelative":"java/other/maven/README.md","localizedDate":"2022年7月28日","excerpt":"

      1. maven的放弃之路

      \\n

      世上无难事,只要肯放弃

      \\n","autoDesc":true}');export{d as comp,u as data}; diff --git a/assets/index.html-BXMP0bPq.js b/assets/index.html-l2dMz0nJ.js similarity index 94% rename from assets/index.html-BXMP0bPq.js rename to assets/index.html-l2dMz0nJ.js index acca818c73..985aeff6f9 100644 --- a/assets/index.html-BXMP0bPq.js +++ b/assets/index.html-l2dMz0nJ.js @@ -1 +1 @@ -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return e(),o("div")}const l=t(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/oauth/","title":"标签: oauth","lang":"zh-CN","frontmatter":{"title":"标签: oauth","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"oauth","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/oauth/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: oauth"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: oauth\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return e(),o("div")}const l=t(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/oauth/","title":"标签: oauth","lang":"zh-CN","frontmatter":{"title":"标签: oauth","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"oauth","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/oauth/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: oauth"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: oauth\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-D38QGC2C.js b/assets/index.html-n0MBVKbb.js similarity index 94% rename from assets/index.html-D38QGC2C.js rename to assets/index.html-n0MBVKbb.js index 205831f75b..f733ac7d9b 100644 --- a/assets/index.html-D38QGC2C.js +++ b/assets/index.html-n0MBVKbb.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const n={};function a(r,p){return t(),o("div")}const m=e(n,[["render",a],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/npm%E7%9F%A5%E8%AF%86%E7%82%B9/","title":"npm知识点 分类","lang":"zh-CN","frontmatter":{"title":"npm知识点 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"npm知识点","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/npm%E7%9F%A5%E8%AF%86%E7%82%B9/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"npm知识点 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"npm知识点 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{m as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const n={};function a(r,p){return t(),o("div")}const m=e(n,[["render",a],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/category/npm%E7%9F%A5%E8%AF%86%E7%82%B9/","title":"npm知识点 分类","lang":"zh-CN","frontmatter":{"title":"npm知识点 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"npm知识点","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/npm%E7%9F%A5%E8%AF%86%E7%82%B9/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"npm知识点 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"npm知识点 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{m as comp,l as data}; diff --git a/assets/index.html-COH-CgE0.js b/assets/index.html-raRbuKfh.js similarity index 97% rename from assets/index.html-COH-CgE0.js rename to assets/index.html-raRbuKfh.js index 58709fe858..3e2979687f 100644 --- a/assets/index.html-COH-CgE0.js +++ b/assets/index.html-raRbuKfh.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-CVMfKeWw.js";const r={},o=a('

      个人服务器搭建

      自建NAS

      自己搭建nas

      实现nas外网访问

      使用外部网络访问nas

      搭建家庭影院

      下载,刮削,展示

      ',7),s=[o];function i(p,c){return t(),n("div",null,s)}const l=e(r,[["render",i],["__file","index.html.vue"]]),m=JSON.parse('{"path":"/myserver/","title":"个人服务器","lang":"zh-CN","frontmatter":{"title":"个人服务器","date":"2024-03-22T00:00:00.000Z","author":"chensino","publish":true,"description":"个人服务器搭建 自建NAS 自己搭建nas 实现nas外网访问 使用外部网络访问nas 搭建家庭影院 下载,刮削,展示","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/myserver/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"个人服务器"}],["meta",{"property":"og:description","content":"个人服务器搭建 自建NAS 自己搭建nas 实现nas外网访问 使用外部网络访问nas 搭建家庭影院 下载,刮削,展示"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T06:08:50.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-03-22T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T06:08:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"个人服务器\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-03-22T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T06:08:50.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":3,"title":"个人服务器搭建","slug":"个人服务器搭建","link":"#个人服务器搭建","children":[]},{"level":3,"title":"搭建家庭影院","slug":"搭建家庭影院","link":"#搭建家庭影院","children":[]}],"git":{"createdTime":1711079050000,"updatedTime":1711087730000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.19,"words":57},"filePathRelative":"myserver/README.md","localizedDate":"2024年3月22日","excerpt":"

      个人服务器搭建

      \\n

      自建NAS

      \\n

      自己搭建nas

      \\n

      实现nas外网访问

      \\n

      使用外部网络访问nas

      \\n

      搭建家庭影院

      \\n

      下载,刮削,展示

      \\n","autoDesc":true}');export{l as comp,m as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-_Oi5YZFn.js";const r={},o=a('

      个人服务器搭建

      自建NAS

      自己搭建nas

      实现nas外网访问

      使用外部网络访问nas

      搭建家庭影院

      下载,刮削,展示

      ',7),s=[o];function i(p,c){return t(),n("div",null,s)}const l=e(r,[["render",i],["__file","index.html.vue"]]),m=JSON.parse('{"path":"/myserver/","title":"个人服务器","lang":"zh-CN","frontmatter":{"title":"个人服务器","date":"2024-03-22T00:00:00.000Z","author":"chensino","publish":true,"description":"个人服务器搭建 自建NAS 自己搭建nas 实现nas外网访问 使用外部网络访问nas 搭建家庭影院 下载,刮削,展示","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/myserver/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"个人服务器"}],["meta",{"property":"og:description","content":"个人服务器搭建 自建NAS 自己搭建nas 实现nas外网访问 使用外部网络访问nas 搭建家庭影院 下载,刮削,展示"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T06:08:50.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-03-22T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T06:08:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"个人服务器\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-03-22T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T06:08:50.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":3,"title":"个人服务器搭建","slug":"个人服务器搭建","link":"#个人服务器搭建","children":[]},{"level":3,"title":"搭建家庭影院","slug":"搭建家庭影院","link":"#搭建家庭影院","children":[]}],"git":{"createdTime":1711079050000,"updatedTime":1711087730000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.19,"words":57},"filePathRelative":"myserver/README.md","localizedDate":"2024年3月22日","excerpt":"

      个人服务器搭建

      \\n

      自建NAS

      \\n

      自己搭建nas

      \\n

      实现nas外网访问

      \\n

      使用外部网络访问nas

      \\n

      搭建家庭影院

      \\n

      下载,刮削,展示

      \\n","autoDesc":true}');export{l as comp,m as data}; diff --git a/assets/index.html-CaPxFNNf.js b/assets/index.html-tdfwMWEj.js similarity index 94% rename from assets/index.html-CaPxFNNf.js rename to assets/index.html-tdfwMWEj.js index b373b0459f..e7b665c494 100644 --- a/assets/index.html-CaPxFNNf.js +++ b/assets/index.html-tdfwMWEj.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/vue3/","title":"标签: vue3","lang":"zh-CN","frontmatter":{"title":"标签: vue3","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"vue3","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/vue3/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: vue3"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: vue3\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,i){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/tag/vue3/","title":"标签: vue3","lang":"zh-CN","frontmatter":{"title":"标签: vue3","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"vue3","key":"tag"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/tag/vue3/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"标签: vue3"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"标签: vue3\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-jpPjIq77.js b/assets/index.html-ukDkHwB4.js similarity index 94% rename from assets/index.html-jpPjIq77.js rename to assets/index.html-ukDkHwB4.js index 3ecfd4d767..f9369ba44d 100644 --- a/assets/index.html-jpPjIq77.js +++ b/assets/index.html-ukDkHwB4.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-CVMfKeWw.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/web/","title":"web 分类","lang":"zh-CN","frontmatter":{"title":"web 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"web","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/web/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"web 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"web 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as o}from"./app-_Oi5YZFn.js";const a={};function n(r,c){return t(),o("div")}const l=e(a,[["render",n],["__file","index.html.vue"]]),s=JSON.parse('{"path":"/category/web/","title":"web 分类","lang":"zh-CN","frontmatter":{"title":"web 分类","dir":{"index":false},"index":false,"feed":false,"sitemap":false,"blog":{"type":"category","name":"web","key":"category"},"layout":"BlogCategory","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/category/web/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"web 分类"}],["meta",{"property":"og:type","content":"website"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"article:author","content":"ChenSino"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"WebPage\\",\\"name\\":\\"web 分类\\"}"]]},"headers":[],"git":{},"readingTime":{"minutes":0,"words":0},"filePathRelative":null,"excerpt":""}');export{l as comp,s as data}; diff --git a/assets/index.html-hsfyfYXC.js b/assets/index.html-yLnx4YcF.js similarity index 96% rename from assets/index.html-hsfyfYXC.js rename to assets/index.html-yLnx4YcF.js index 67d027a65e..7cc450661f 100644 --- a/assets/index.html-hsfyfYXC.js +++ b/assets/index.html-yLnx4YcF.js @@ -1 +1 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-CVMfKeWw.js";const o={};function n(r,i){return t(),a("div")}const m=e(o,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/java/base/","title":"Java基础","lang":"zh-CN","frontmatter":{"title":"Java基础","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/base/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Java基础"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Java基础\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.03,"words":8},"filePathRelative":"java/base/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{m as comp,l as data}; +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as a}from"./app-_Oi5YZFn.js";const o={};function n(r,i){return t(),a("div")}const m=e(o,[["render",n],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/java/base/","title":"Java基础","lang":"zh-CN","frontmatter":{"title":"Java基础","date":"2018-05-29T00:00:00.000Z","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/base/"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Java基础"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2018-05-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Java基础\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2018-05-29T00:00:00.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.03,"words":8},"filePathRelative":"java/base/README.md","localizedDate":"2018年5月29日","excerpt":""}');export{m as comp,l as data}; diff --git a/assets/io.html-DYLJUoUT.js b/assets/io.html-C1zkJgLS.js similarity index 99% rename from assets/io.html-DYLJUoUT.js rename to assets/io.html-C1zkJgLS.js index de895b71ac..6b849bc1f1 100644 --- a/assets/io.html-DYLJUoUT.js +++ b/assets/io.html-C1zkJgLS.js @@ -1,4 +1,4 @@ -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c,a as n,b as s,d as e,e as t}from"./app-CVMfKeWw.js";const i={},r=n("h2",{id:"_1、参考",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1、参考"},[n("span",null,"1、参考")])],-1),u={href:"https://zhuanlan.zhihu.com/p/115912936",target:"_blank",rel:"noopener noreferrer"},k={href:"https://segmentfault.com/a/1190000024540660",target:"_blank",rel:"noopener noreferrer"},d={href:"https://www.51cto.com/article/693213.html",target:"_blank",rel:"noopener noreferrer"},m={href:"https://cloud.tencent.com/developer/article/1517734",target:"_blank",rel:"noopener noreferrer"},h={href:"https://blog.csdn.net/yinwenjie/article/details/48274255",target:"_blank",rel:"noopener noreferrer"},v={href:"https://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316399.html",target:"_blank",rel:"noopener noreferrer"},b={href:"https://tech.meituan.com/2016/11/04/nio.html",target:"_blank",rel:"noopener noreferrer"},_={href:"https://www.geeksforgeeks.org/difference-between-java-threads-and-os-threads/",target:"_blank",rel:"noopener noreferrer"},g={href:"https://cloud.tencent.com/developer/article/1739347",target:"_blank",rel:"noopener noreferrer"},f={href:"https://pdai.tech/md/java/io/java-io-nio-select-epoll.html",target:"_blank",rel:"noopener noreferrer"},y={href:"https://blog.csdn.net/yinwenjie/category_5819837.html?spm=1001.2014.3001.5515",target:"_blank",rel:"noopener noreferrer"},w=t('

      2、模型分类

      IO模型分类

      IO模型
      阻塞I/O模型同步阻塞
      非阻塞I/O模型同步非阻塞
      I/O多路复用模型同步非阻塞
      信号驱动I/O模型同步非阻塞
      异步IO(AIO)模型异步非阻塞

      3、形象生活例子

      • 小明去吃同仁四季的椰子鸡,就这样在那里排队,等了一小时,然后才开始吃火锅。(BIO)
      • 小红也去同仁四季的椰子鸡,她一看要等挺久的,于是去逛会商场,每次逛一下,就跑回来看看,是不是轮到她了。于是最后她既购了物,又吃上椰子鸡了。(NIO)
      • 小华一样,去吃椰子鸡,由于他是高级会员,所以店长说,你去商场随便逛会吧,等下有位置,我立马打电话给你。于是小华不用干巴巴坐着等,也不用每过一会儿就跑回来看有没有等到,最后也吃上了美味的椰子鸡(AIO)

      4、同步阻塞I/O(传统BIO)

      本节主要参考了以下博客:

      ',7),O={href:"https://blog.csdn.net/yinwenjie/article/details/48274255",target:"_blank",rel:"noopener noreferrer"},I=n("br",null,null,-1),T={href:"https://cloud.tencent.com/developer/article/1752788",target:"_blank",rel:"noopener noreferrer"},x=n("br",null,null,-1),N={href:"https://zhuanlan.zhihu.com/p/79772089",target:"_blank",rel:"noopener noreferrer"},j=n("br",null,null,-1),S={href:"https://cloud.tencent.com/developer/article/1517734",target:"_blank",rel:"noopener noreferrer"},C=n("h3",{id:"_4-1-介绍",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_4-1-介绍"},[n("span",null,"4.1 介绍")])],-1),q={href:"https://cloud.tencent.com/developer/article/1517734",target:"_blank",rel:"noopener noreferrer"},B=t(`

      4.2 传统BIO存在的问题

      • 同一时间,服务器只能接受来自于客户端A的请求信息;虽然客户端A和客户端B的请求是同时进行的,但客户端B发送的请求信息只能等到服务器接受完A的请求数据后,才能被接受。
      • 由于服务器一次只能处理一个客户端请求,当处理完成并返回后(或者异常时),才能进行第二次请求的处理。很显然,这样的处理方式在高并发的情况下,是不能采用的。

      4.3 使用多线程改进BIO

      1. 虽然在服务器端,请求的处理交给了一个独立线程进行,但是操作系统通知accept()的方式还是单个的。也就是,实际上是服务器接收到数据报文后的“业务处理过程”可以多线程,但是数据报文的接受还是需要一个一个的来(下文的示例代码和debug过程我们可以明确看到这一点)

      2. 在linux系统中,可以创建的线程是有限的。我们可以通过cat /proc/sys/kernel/threads-max 命令查看可以创建的最大线程数。当然这个值是可以更改的,但是线程越多,CPU切换所需的时间也就越长,用来处理真正业务的需求也就越少。

      3. 创建一个线程是有较大的资源消耗的。JVM创建一个线程的时候,即使这个线程不做任何的工作,JVM都会分配一个堆栈空间。这个空间的大小默认为128K,您可以通过-Xss参数进行调整。

      4. 当然您还可以使用ThreadPoolExecutor线程池来缓解线程的创建问题,但是又会造成BlockingQueue积压任务的持续增加,同样消耗了大量资源。

      • 另外,如果您的应用程序大量使用长连接的话,线程是不会关闭的。这样系统资源的消耗更容易失控。 那么,如果你真想单纯使用线程解决阻塞的问题,那么您自己都可以算出来您一个服务器节点可以一次接受多大的并发了。看来,单纯使用线程解决这个问题不是最好的办法

      4.4 验证上下文切换带来的CPU消耗巨大

      在一个线程和多个线程分别操作相同次数的业务逻辑,比较耗时

      
      +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as l,c,a as n,b as s,d as e,e as t}from"./app-_Oi5YZFn.js";const i={},r=n("h2",{id:"_1、参考",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1、参考"},[n("span",null,"1、参考")])],-1),u={href:"https://zhuanlan.zhihu.com/p/115912936",target:"_blank",rel:"noopener noreferrer"},k={href:"https://segmentfault.com/a/1190000024540660",target:"_blank",rel:"noopener noreferrer"},d={href:"https://www.51cto.com/article/693213.html",target:"_blank",rel:"noopener noreferrer"},m={href:"https://cloud.tencent.com/developer/article/1517734",target:"_blank",rel:"noopener noreferrer"},h={href:"https://blog.csdn.net/yinwenjie/article/details/48274255",target:"_blank",rel:"noopener noreferrer"},v={href:"https://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316399.html",target:"_blank",rel:"noopener noreferrer"},b={href:"https://tech.meituan.com/2016/11/04/nio.html",target:"_blank",rel:"noopener noreferrer"},_={href:"https://www.geeksforgeeks.org/difference-between-java-threads-and-os-threads/",target:"_blank",rel:"noopener noreferrer"},g={href:"https://cloud.tencent.com/developer/article/1739347",target:"_blank",rel:"noopener noreferrer"},f={href:"https://pdai.tech/md/java/io/java-io-nio-select-epoll.html",target:"_blank",rel:"noopener noreferrer"},y={href:"https://blog.csdn.net/yinwenjie/category_5819837.html?spm=1001.2014.3001.5515",target:"_blank",rel:"noopener noreferrer"},w=t('

      2、模型分类

      IO模型分类

      IO模型
      阻塞I/O模型同步阻塞
      非阻塞I/O模型同步非阻塞
      I/O多路复用模型同步非阻塞
      信号驱动I/O模型同步非阻塞
      异步IO(AIO)模型异步非阻塞

      3、形象生活例子

      • 小明去吃同仁四季的椰子鸡,就这样在那里排队,等了一小时,然后才开始吃火锅。(BIO)
      • 小红也去同仁四季的椰子鸡,她一看要等挺久的,于是去逛会商场,每次逛一下,就跑回来看看,是不是轮到她了。于是最后她既购了物,又吃上椰子鸡了。(NIO)
      • 小华一样,去吃椰子鸡,由于他是高级会员,所以店长说,你去商场随便逛会吧,等下有位置,我立马打电话给你。于是小华不用干巴巴坐着等,也不用每过一会儿就跑回来看有没有等到,最后也吃上了美味的椰子鸡(AIO)

      4、同步阻塞I/O(传统BIO)

      本节主要参考了以下博客:

      ',7),O={href:"https://blog.csdn.net/yinwenjie/article/details/48274255",target:"_blank",rel:"noopener noreferrer"},I=n("br",null,null,-1),T={href:"https://cloud.tencent.com/developer/article/1752788",target:"_blank",rel:"noopener noreferrer"},x=n("br",null,null,-1),N={href:"https://zhuanlan.zhihu.com/p/79772089",target:"_blank",rel:"noopener noreferrer"},j=n("br",null,null,-1),S={href:"https://cloud.tencent.com/developer/article/1517734",target:"_blank",rel:"noopener noreferrer"},C=n("h3",{id:"_4-1-介绍",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_4-1-介绍"},[n("span",null,"4.1 介绍")])],-1),q={href:"https://cloud.tencent.com/developer/article/1517734",target:"_blank",rel:"noopener noreferrer"},B=t(`

      4.2 传统BIO存在的问题

      • 同一时间,服务器只能接受来自于客户端A的请求信息;虽然客户端A和客户端B的请求是同时进行的,但客户端B发送的请求信息只能等到服务器接受完A的请求数据后,才能被接受。
      • 由于服务器一次只能处理一个客户端请求,当处理完成并返回后(或者异常时),才能进行第二次请求的处理。很显然,这样的处理方式在高并发的情况下,是不能采用的。

      4.3 使用多线程改进BIO

      1. 虽然在服务器端,请求的处理交给了一个独立线程进行,但是操作系统通知accept()的方式还是单个的。也就是,实际上是服务器接收到数据报文后的“业务处理过程”可以多线程,但是数据报文的接受还是需要一个一个的来(下文的示例代码和debug过程我们可以明确看到这一点)

      2. 在linux系统中,可以创建的线程是有限的。我们可以通过cat /proc/sys/kernel/threads-max 命令查看可以创建的最大线程数。当然这个值是可以更改的,但是线程越多,CPU切换所需的时间也就越长,用来处理真正业务的需求也就越少。

      3. 创建一个线程是有较大的资源消耗的。JVM创建一个线程的时候,即使这个线程不做任何的工作,JVM都会分配一个堆栈空间。这个空间的大小默认为128K,您可以通过-Xss参数进行调整。

      4. 当然您还可以使用ThreadPoolExecutor线程池来缓解线程的创建问题,但是又会造成BlockingQueue积压任务的持续增加,同样消耗了大量资源。

      • 另外,如果您的应用程序大量使用长连接的话,线程是不会关闭的。这样系统资源的消耗更容易失控。 那么,如果你真想单纯使用线程解决阻塞的问题,那么您自己都可以算出来您一个服务器节点可以一次接受多大的并发了。看来,单纯使用线程解决这个问题不是最好的办法

      4.4 验证上下文切换带来的CPU消耗巨大

      在一个线程和多个线程分别操作相同次数的业务逻辑,比较耗时

      
       public class DemoApplication {
              public static void main(String[] args) {
                     //运行多线程
      diff --git "a/assets/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html-BzW3aeox.js" "b/assets/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html-BiKvCKWl.js"
      similarity index 99%
      rename from "assets/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html-BzW3aeox.js"
      rename to "assets/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html-BiKvCKWl.js"
      index 04f2d41c16..02af0c1025 100644
      --- "a/assets/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html-BzW3aeox.js"
      +++ "b/assets/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html-BiKvCKWl.js"
      @@ -1,4 +1,4 @@
      -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as s,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

      背景

      今天用Mybatis-plus插入数据,自动产生了一个19位的主键id,后来才发现这是Mybatis-plus3.0后更新的功能,默认使用雪花算法最终会产生一个19位数字。

      然后我在前端项目查询后端接口,后端接口返回的1682195717382606849,而到了前端一直打印的是另一个数字。

      原因,参考gpt回答

      问题1:数据库19位id,在浏览器控制台显示错误?

      如果你的数据库使用的是19位的ID,而浏览器控制台显示错误,可能是因为浏览器默认将超过16位的整数值视为科学计数法表示。在科学计数法表示下,可能会丢失精度或显示不正确。

      为了解决这个问题,可以对ID进行处理,确保在浏览器中正确显示。以下是一种常见的处理方式:

      将ID转换为字符串:将19位的ID转换为字符串形式,在浏览器中显示时可以保留完整的位数。例如,使用Java的String.valueOf方法将ID转换为字符串:

      String idString = String.valueOf(id);
      +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as s,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

      背景

      今天用Mybatis-plus插入数据,自动产生了一个19位的主键id,后来才发现这是Mybatis-plus3.0后更新的功能,默认使用雪花算法最终会产生一个19位数字。

      然后我在前端项目查询后端接口,后端接口返回的1682195717382606849,而到了前端一直打印的是另一个数字。

      原因,参考gpt回答

      问题1:数据库19位id,在浏览器控制台显示错误?

      如果你的数据库使用的是19位的ID,而浏览器控制台显示错误,可能是因为浏览器默认将超过16位的整数值视为科学计数法表示。在科学计数法表示下,可能会丢失精度或显示不正确。

      为了解决这个问题,可以对ID进行处理,确保在浏览器中正确显示。以下是一种常见的处理方式:

      将ID转换为字符串:将19位的ID转换为字符串形式,在浏览器中显示时可以保留完整的位数。例如,使用Java的String.valueOf方法将ID转换为字符串:

      String idString = String.valueOf(id);
       

      将转换后的ID传递给前端:将转换后的ID作为数据传递到前端,确保在浏览器中显示的是字符串形式的完整ID。

      在前端展示时进行格式化:在前端页面中使用适当的方式对19位的ID进行格式化,以便正确显示。例如,可以使用JavaScript的toLocaleString方法将ID格式化为带有千位分隔符的字符串:

      var formattedId = id.toLocaleString();
       

      这样,浏览器就能够正确地显示19位的ID。

      请注意,在处理和展示大型数字时,确保使用适当的数据类型和格式,以避免数据丢失或显示错误。

      问题2: js所支持的number最大值?

      根据 ECMAScript 标准(JavaScript 的规范),浏览器所支持的最大数值可以通过 Number.MAX_SAFE_INTEGER 属性获取。这个属性的值是 9007199254740991,也就是 2^53 - 1。

      超过这个值的数值将无法在 JavaScript 中精确表示,可能会导致精度丢失或不可预期的结果。因此,如果你需要处理超过 Number.MAX_SAFE_INTEGER 的大数字,建议使用专门的大数运算库或其他扩展来处理。

      以下是一个示例,展示了 Number.MAX_SAFE_INTEGER 的值:

      console.log(Number.MAX_SAFE_INTEGER); // 输出 9007199254740991
       

      请注意,不同的浏览器可能会在处理超出 Number.MAX_SAFE_INTEGER 的大数字时表现不同,因此建议在开发过程中仔细考虑数字范围和精度的问题,并结合实际情况选择合适的处理方式。

      解决方法

      1. 不要用这么大的数字主键
      2. 转化为字符串
      3. 序列化处理【推荐】
          @Bean
      diff --git a/assets/lazyLoad.html-Cdfyboon.js b/assets/lazyLoad.html-B0wYE_Yw.js
      similarity index 99%
      rename from assets/lazyLoad.html-Cdfyboon.js
      rename to assets/lazyLoad.html-B0wYE_Yw.js
      index 50fe55f70a..256def648b 100644
      --- a/assets/lazyLoad.html-Cdfyboon.js
      +++ b/assets/lazyLoad.html-B0wYE_Yw.js
      @@ -1,4 +1,4 @@
      -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

      一,为什么需要图片懒加载

      在老版本的Chrome中,图片的加载其实是会阻塞DOM渲染的。在我们现代浏览器中,这一点基本不用担心了,也就是说现在的图片加载不会阻塞DOM渲染,但是每一个图片都会对应一个HTTP请求而浏览器允许同时并发请求的数量是有限的(数量为6),假设你的网站有大量的图片,那么加载的过程是很耗时的,尤其像那些电商类需要大量图片的网站,可想而知,网站的初始加载时间会很长,再加上网络等其它影响,用户体验会很差。为了解决这个问题,提高用户体验,所以就出现了懒加载这种方式来减轻服务器的压力,优先加载可视区域的内容,其他部分等进入了可视区域再加载,从而提高性能。

      二,图片懒加载原理

      原理其实非常简单,主要就是需要判断元素是否进入了可视区,进入了可视区就去请求对应的图片,否则就显示一张兜底的占位图。

      实现图片懒加载基本要点如下所示:

      img

      三,实战

      3.1 常用:利用图片顶部距离小于浏览器可视区域的高度来判断是否加载图片

      //获取图片元素
      +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

      一,为什么需要图片懒加载

      在老版本的Chrome中,图片的加载其实是会阻塞DOM渲染的。在我们现代浏览器中,这一点基本不用担心了,也就是说现在的图片加载不会阻塞DOM渲染,但是每一个图片都会对应一个HTTP请求而浏览器允许同时并发请求的数量是有限的(数量为6),假设你的网站有大量的图片,那么加载的过程是很耗时的,尤其像那些电商类需要大量图片的网站,可想而知,网站的初始加载时间会很长,再加上网络等其它影响,用户体验会很差。为了解决这个问题,提高用户体验,所以就出现了懒加载这种方式来减轻服务器的压力,优先加载可视区域的内容,其他部分等进入了可视区域再加载,从而提高性能。

      二,图片懒加载原理

      原理其实非常简单,主要就是需要判断元素是否进入了可视区,进入了可视区就去请求对应的图片,否则就显示一张兜底的占位图。

      实现图片懒加载基本要点如下所示:

      img

      三,实战

      3.1 常用:利用图片顶部距离小于浏览器可视区域的高度来判断是否加载图片

      //获取图片元素
       const images = document.querySelectorAll('img');
       //scroll滚动事件
       window.addEventListener('scroll',(e)=>{
      diff --git a/assets/logback.html-DOiHN3-e.js b/assets/logback.html-bWzTkpCR.js
      similarity index 99%
      rename from assets/logback.html-DOiHN3-e.js
      rename to assets/logback.html-bWzTkpCR.js
      index 2517ffad19..e591c82a13 100644
      --- a/assets/logback.html-DOiHN3-e.js
      +++ b/assets/logback.html-bWzTkpCR.js
      @@ -1,4 +1,4 @@
      -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

      配置

      <?xml version="1.0" encoding="UTF-8"?>
      +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

      配置

      <?xml version="1.0" encoding="UTF-8"?>
       <!--
           小技巧: 在根pom里面设置统一存放路径,统一管理方便维护
           <properties>
      diff --git a/assets/markdown.html-Ku2vuX_R.js b/assets/markdown.html-DnotFn9F.js
      similarity index 99%
      rename from assets/markdown.html-Ku2vuX_R.js
      rename to assets/markdown.html-DnotFn9F.js
      index f2b2ed3b01..2f6b521efc 100644
      --- a/assets/markdown.html-Ku2vuX_R.js
      +++ b/assets/markdown.html-DnotFn9F.js
      @@ -1,4 +1,4 @@
      -import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as l,c as i,a as n,b as s,d as e,e as t}from"./app-CVMfKeWw.js";const r={},c=n("p",null,"VuePress 主要从 Markdown 文件生成页面。因此,你可以使用它轻松生成文档或博客站点。",-1),u=n("p",null,"你应该创建和编写 Markdown 文件,以便 VuePress 可以根据文件结构将它们转换为不同的页面。",-1),d=n("h2",{id:"markdown-介绍",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#markdown-介绍"},[n("span",null,"Markdown 介绍")])],-1),h={href:"https://vuepress-theme-hope.github.io/v2/zh/cookbook/markdown/",target:"_blank",rel:"noopener noreferrer"},k={href:"https://vuepress-theme-hope.github.io/v2/zh/cookbook/markdown/demo.html",target:"_blank",rel:"noopener noreferrer"},v=n("h2",{id:"markdown-配置",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#markdown-配置"},[n("span",null,"Markdown 配置")])],-1),m=n("p",null,"VuePress 通过 Frontmatter 为每个 Markdown 页面引入配置。",-1),b={class:"hint-container info"},g=n("p",{class:"hint-container-title"},"相关信息",-1),_={href:"https://vuepress-theme-hope.github.io/v2/zh/cookbook/vuepress/page.html#front-matter",target:"_blank",rel:"noopener noreferrer"},f=n("h2",{id:"markdown-扩展",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#markdown-扩展"},[n("span",null,"Markdown 扩展")])],-1),w={href:"https://github.com/markdown-it/markdown-it",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/markdown-it/markdown-it#syntax-extensions",target:"_blank",rel:"noopener noreferrer"},y=n("h3",{id:"vuepress-扩展",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#vuepress-扩展"},[n("span",null,"VuePress 扩展")])],-1),x=n("p",null,"为了丰富文档写作,VuePress 对 Markdown 语法进行了扩展。",-1),M={href:"https://vuepress-theme-hope.github.io/v2/zh/cookbook/vuepress/markdown.html",target:"_blank",rel:"noopener noreferrer"},z=n("h3",{id:"主题扩展",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#主题扩展"},[n("span",null,"主题扩展")])],-1),j={href:"https://vuepress-theme-hope.github.io/v2/md-enhance/zh/",target:"_blank",rel:"noopener noreferrer"},V=n("code",null,"vuepress-plugin-md-enhance",-1),P={class:"hint-container tip"},E=n("p",{class:"hint-container-title"},"一键启用",-1),T=n("code",null,"plugins.mdEnhance.enableAll: true",-1),S={href:"https://vuepress-theme-hope.github.io/v2/md-enhance/zh/",target:"_blank",rel:"noopener noreferrer"},A=t(`

      ::: code-tabs#language

      @tab TS

      import { hopeTheme } from "vuepress-theme-hope";
      +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o as l,c as i,a as n,b as s,d as e,e as t}from"./app-_Oi5YZFn.js";const r={},c=n("p",null,"VuePress 主要从 Markdown 文件生成页面。因此,你可以使用它轻松生成文档或博客站点。",-1),u=n("p",null,"你应该创建和编写 Markdown 文件,以便 VuePress 可以根据文件结构将它们转换为不同的页面。",-1),d=n("h2",{id:"markdown-介绍",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#markdown-介绍"},[n("span",null,"Markdown 介绍")])],-1),h={href:"https://vuepress-theme-hope.github.io/v2/zh/cookbook/markdown/",target:"_blank",rel:"noopener noreferrer"},k={href:"https://vuepress-theme-hope.github.io/v2/zh/cookbook/markdown/demo.html",target:"_blank",rel:"noopener noreferrer"},v=n("h2",{id:"markdown-配置",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#markdown-配置"},[n("span",null,"Markdown 配置")])],-1),m=n("p",null,"VuePress 通过 Frontmatter 为每个 Markdown 页面引入配置。",-1),b={class:"hint-container info"},g=n("p",{class:"hint-container-title"},"相关信息",-1),_={href:"https://vuepress-theme-hope.github.io/v2/zh/cookbook/vuepress/page.html#front-matter",target:"_blank",rel:"noopener noreferrer"},f=n("h2",{id:"markdown-扩展",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#markdown-扩展"},[n("span",null,"Markdown 扩展")])],-1),w={href:"https://github.com/markdown-it/markdown-it",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/markdown-it/markdown-it#syntax-extensions",target:"_blank",rel:"noopener noreferrer"},y=n("h3",{id:"vuepress-扩展",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#vuepress-扩展"},[n("span",null,"VuePress 扩展")])],-1),x=n("p",null,"为了丰富文档写作,VuePress 对 Markdown 语法进行了扩展。",-1),M={href:"https://vuepress-theme-hope.github.io/v2/zh/cookbook/vuepress/markdown.html",target:"_blank",rel:"noopener noreferrer"},z=n("h3",{id:"主题扩展",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#主题扩展"},[n("span",null,"主题扩展")])],-1),j={href:"https://vuepress-theme-hope.github.io/v2/md-enhance/zh/",target:"_blank",rel:"noopener noreferrer"},V=n("code",null,"vuepress-plugin-md-enhance",-1),P={class:"hint-container tip"},E=n("p",{class:"hint-container-title"},"一键启用",-1),T=n("code",null,"plugins.mdEnhance.enableAll: true",-1),S={href:"https://vuepress-theme-hope.github.io/v2/md-enhance/zh/",target:"_blank",rel:"noopener noreferrer"},A=t(`

      ::: code-tabs#language

      @tab TS

      import { hopeTheme } from "vuepress-theme-hope";
       
       export default {
         theme: hopeTheme({
      diff --git a/assets/mergeBranch.html-4sgMZ_ic.js b/assets/mergeBranch.html-CCnBpahJ.js
      similarity index 99%
      rename from assets/mergeBranch.html-4sgMZ_ic.js
      rename to assets/mergeBranch.html-CCnBpahJ.js
      index 886240984e..ed2d3fd25a 100644
      --- a/assets/mergeBranch.html-4sgMZ_ic.js
      +++ b/assets/mergeBranch.html-CCnBpahJ.js
      @@ -1,4 +1,4 @@
      -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-CVMfKeWw.js";const o={},s=a(`

      一,问题

      ​ 在git log中往往会发现在log中出现Merge branch 'master of .....'这种合并节点,造成日志的污染

      image-20220726115529360

      二,产生的原因

      当多人合作开发一个项目时,本地仓库落后于远程仓库是一个非常正常的事情,可参考下图 在这里插入图片描述

      具体情境如下:

      我当前拉取的远端版本为 B,此时修改了代码,并在本地仓库 commit 一次,但并未 push 到远端仓库。 另一位开发者在 B 的基础上,同样 commit 了一次并 push 到远端仓库。那么这个时候,我再 push 自己的代码就会发生错误。

      To github.com:xxx/xxx.git
      +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-_Oi5YZFn.js";const o={},s=a(`

      一,问题

      ​ 在git log中往往会发现在log中出现Merge branch 'master of .....'这种合并节点,造成日志的污染

      image-20220726115529360

      二,产生的原因

      当多人合作开发一个项目时,本地仓库落后于远程仓库是一个非常正常的事情,可参考下图 在这里插入图片描述

      具体情境如下:

      我当前拉取的远端版本为 B,此时修改了代码,并在本地仓库 commit 一次,但并未 push 到远端仓库。 另一位开发者在 B 的基础上,同样 commit 了一次并 push 到远端仓库。那么这个时候,我再 push 自己的代码就会发生错误。

      To github.com:xxx/xxx.git
       ! [rejected]        master -> master (fetch first)
       error: failed to push some refs to 'git@github.com:xxx/xxx.git'
       hint: Updates were rejected because the remote contains work that you do
      diff --git a/assets/multiModule.html-k-P4UAvN.js b/assets/multiModule.html-vU5Z_GHP.js
      similarity index 99%
      rename from assets/multiModule.html-k-P4UAvN.js
      rename to assets/multiModule.html-vU5Z_GHP.js
      index 71be66c0ef..77a2b075b0 100644
      --- a/assets/multiModule.html-k-P4UAvN.js
      +++ b/assets/multiModule.html-vU5Z_GHP.js
      @@ -1,4 +1,4 @@
      -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c,e as a}from"./app-CVMfKeWw.js";const s={},i=a(`

      1、一个真实的多模块maven项目

      $ tree -d -L 2
      +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c,e as a}from"./app-_Oi5YZFn.js";const s={},i=a(`

      1、一个真实的多模块maven项目

      $ tree -d -L 2
       .———————————————————————————————————最顶级pom所在目录
       ├── ccs-auth
       │   ├── src
      diff --git a/assets/mybatis.html-ZRAnLlvk.js b/assets/mybatis.html-CU1fMlIQ.js
      similarity index 99%
      rename from assets/mybatis.html-ZRAnLlvk.js
      rename to assets/mybatis.html-CU1fMlIQ.js
      index 69204513da..d7204a9318 100644
      --- a/assets/mybatis.html-ZRAnLlvk.js
      +++ b/assets/mybatis.html-CU1fMlIQ.js
      @@ -1,4 +1,4 @@
      -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as n,b as a,d as t,e as p}from"./app-CVMfKeWw.js";const i={},u=n("h2",{id:"mybatis缓存",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#mybatis缓存"},[n("span",null,"mybatis缓存")])],-1),k={href:"https://www.cnblogs.com/happyflyingpig/p/7739749.html",target:"_blank",rel:"noopener noreferrer"},r={href:"https://cloud.tencent.com/developer/article/1650481",target:"_blank",rel:"noopener noreferrer"},d=p(`

      1、 一级缓存

      mybatis缓存有一级缓存也叫SelSession缓存,是强制打开的,也就是说Mybatis没有提供关闭一级缓存的方式。一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其查询结果放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。一级缓存的存储位置是在JVM虚拟机内存,它放在一个Map中

      1.1 一级缓存的生命周期有多长?

      a、MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

      b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

      c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

      d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用

      1. 2、怎么判断某两次查询是完全相同的查询?

      mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

      2.1 传入的statementId

      2.2 查询时要求的结果集中的结果范围

      2.3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )

      2.4 传递给java.sql.Statement要设置的参数值

      1.3 一级缓存的问题

      前面说了一级缓存是针对同一个SelSession,如果用SqlSession去执行同一个查询两次,会发现只有一次打印出了sql,说明一级缓存生效了

       @Test
      +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as c,c as l,a as n,b as a,d as t,e as p}from"./app-_Oi5YZFn.js";const i={},u=n("h2",{id:"mybatis缓存",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#mybatis缓存"},[n("span",null,"mybatis缓存")])],-1),k={href:"https://www.cnblogs.com/happyflyingpig/p/7739749.html",target:"_blank",rel:"noopener noreferrer"},r={href:"https://cloud.tencent.com/developer/article/1650481",target:"_blank",rel:"noopener noreferrer"},d=p(`

      1、 一级缓存

      mybatis缓存有一级缓存也叫SelSession缓存,是强制打开的,也就是说Mybatis没有提供关闭一级缓存的方式。一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其查询结果放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。一级缓存的存储位置是在JVM虚拟机内存,它放在一个Map中

      1.1 一级缓存的生命周期有多长?

      a、MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

      b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

      c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

      d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用

      1. 2、怎么判断某两次查询是完全相同的查询?

      mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

      2.1 传入的statementId

      2.2 查询时要求的结果集中的结果范围

      2.3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )

      2.4 传递给java.sql.Statement要设置的参数值

      1.3 一级缓存的问题

      前面说了一级缓存是针对同一个SelSession,如果用SqlSession去执行同一个查询两次,会发现只有一次打印出了sql,说明一级缓存生效了

       @Test
           public void pageQuery() throws IOException {
               String resource = "mybatis-config.xml";
               InputStream inputStream = Resources.getResourceAsStream(resource);
      diff --git a/assets/note.html-CoTL_cZp.js b/assets/note.html-CZMFgVaM.js
      similarity index 99%
      rename from assets/note.html-CoTL_cZp.js
      rename to assets/note.html-CZMFgVaM.js
      index b4ea02fe32..81ed186f42 100644
      --- a/assets/note.html-CoTL_cZp.js
      +++ b/assets/note.html-CZMFgVaM.js
      @@ -1,4 +1,4 @@
      -import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o as i,c as p,a as n,b as s,d as e,e as t}from"./app-CVMfKeWw.js";const l={},r=n("h2",{id:"_1、加密解密流程",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1、加密解密流程"},[n("span",null,"1、加密解密流程")])],-1),d=n("code",null,"PasswordEncoder",-1),u={href:"https://gist.github.com/e33a902140c1ded695c0a782a6875b69",target:"_blank",rel:"noopener noreferrer"},k=n("code",null,"encode",-1),v=n("code",null,"PasswordEncoder",-1),m=n("code",null,"matches",-1),b=t(`

      总结一下: 加密:是新建用户时调用加密方法,把密文写入数据库或者内存; 解密:登陆时,根据用户名从系统查找对应密文A,然后在把登陆时用户传递过来的明文密码使用相同加密方式加密得到密文B,把A和B对比。(这里体现了密文不可反向解密,只能正向加密,再做对比)

      public interface PasswordEncoder {
      +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as c,o as i,c as p,a as n,b as s,d as e,e as t}from"./app-_Oi5YZFn.js";const l={},r=n("h2",{id:"_1、加密解密流程",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#_1、加密解密流程"},[n("span",null,"1、加密解密流程")])],-1),d=n("code",null,"PasswordEncoder",-1),u={href:"https://gist.github.com/e33a902140c1ded695c0a782a6875b69",target:"_blank",rel:"noopener noreferrer"},k=n("code",null,"encode",-1),v=n("code",null,"PasswordEncoder",-1),m=n("code",null,"matches",-1),b=t(`

      总结一下: 加密:是新建用户时调用加密方法,把密文写入数据库或者内存; 解密:登陆时,根据用户名从系统查找对应密文A,然后在把登陆时用户传递过来的明文密码使用相同加密方式加密得到密文B,把A和B对比。(这里体现了密文不可反向解密,只能正向加密,再做对比)

      public interface PasswordEncoder {
       
       	/**
       	 * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
      diff --git a/assets/page.html-CNLLwtGV.js b/assets/page.html-CalA3aZV.js
      similarity index 97%
      rename from assets/page.html-CNLLwtGV.js
      rename to assets/page.html-CalA3aZV.js
      index ab042c812a..ab405687dd 100644
      --- a/assets/page.html-CNLLwtGV.js
      +++ b/assets/page.html-CalA3aZV.js
      @@ -1 +1 @@
      -import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as i,c as p,a as e,b as t,d as o,e as s}from"./app-CVMfKeWw.js";const h={},c=s('

      more 注释之前的内容被视为文章摘要。

      页面信息

      你可以在 Markdown 的 Frontmatter 中设置页面信息。

      • 作者设置为 Ms.Hope。
      • 写作日期为 2020 年 1 月 1 日
      • 分类为 “使用指南”
      • 标签为 “页面配置” 和 “使用指南”

      页面内容

      你可以自由在这里书写你的 Markdown。

      提示

      • 你可以将图片和 Markdown 文件放置在一起,但是你需要使用相对链接./ 进行引用。

      • 对于 .vuepress/public 文件夹的图片,请使用绝对链接 / 进行引用。

      主题包含了一个自定义徽章章可以使用:

      ',8),d=e("h2",{id:"页面结构",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#页面结构"},[e("span",null,"页面结构")])],-1),u=e("p",null,"此页面应当包含:",-1),m={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/layout/breadcrumb.html",target:"_blank",rel:"noopener noreferrer"},g={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/feature/page-info.html",target:"_blank",rel:"noopener noreferrer"},_={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/layout/page.html#%E6%A0%87%E9%A2%98%E5%88%97%E8%A1%A8",target:"_blank",rel:"noopener noreferrer"},f={href:"https://vuepress-theme-hope.github.io/v2/guide/feature/meta.html",target:"_blank",rel:"noopener noreferrer"},b={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/feature/comment.html",target:"_blank",rel:"noopener noreferrer"},v={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/layout/navbar.html",target:"_blank",rel:"noopener noreferrer"},k={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/layout/sidebar.html",target:"_blank",rel:"noopener noreferrer"},y={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/layout/footer.html",target:"_blank",rel:"noopener noreferrer"},M=e("li",null,"返回顶部按钮",-1),x=e("p",null,"你可以通过主题选项和页面 Frontmatter 自定义它们。",-1);function T(w,z){const a=n("Badge"),r=n("ExternalLinkIcon");return i(),p("div",null,[c,e("blockquote",null,[e("p",null,[t("文字结尾应该有深蓝色的 徽章文字 徽章。 "),o(a,{text:"徽章文字",color:"#242378"})])]),d,u,e("ul",null,[e("li",null,[e("a",m,[t("路径导航"),o(r)])]),e("li",null,[e("a",g,[t("标题和页面信息"),o(r)])]),e("li",null,[e("a",_,[t("TOC (文章标题列表)"),o(r)])]),e("li",null,[e("a",f,[t("贡献者、更新时间等页面元信息"),o(r)])]),e("li",null,[e("a",b,[t("评论"),o(r)])]),e("li",null,[e("a",v,[t("导航栏"),o(r)])]),e("li",null,[e("a",k,[t("侧边栏"),o(r)])]),e("li",null,[e("a",y,[t("页脚"),o(r)])]),M]),x])}const N=l(h,[["render",T],["__file","page.html.vue"]]),H=JSON.parse('{"path":"/guide/page.html","title":"页面配置","lang":"zh-CN","frontmatter":{"title":"页面配置","icon":"page","order":1,"author":"Ms.Hope","date":"2020-01-01T00:00:00.000Z","category":["使用指南"],"tag":["页面配置","使用指南"],"sticky":true,"star":true,"footer":"这是测试显示的页脚","copyright":"无版权","description":"more 注释之前的内容被视为文章摘要。 页面信息 你可以在 Markdown 的 Frontmatter 中设置页面信息。 作者设置为 Ms.Hope。 写作日期为 2020 年 1 月 1 日 分类为 “使用指南” 标签为 “页面配置” 和 “使用指南” 页面内容 你可以自由在这里书写你的 Markdown。 提示 你可以将图片和 Markdown...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/guide/page.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"页面配置"}],["meta",{"property":"og:description","content":"more 注释之前的内容被视为文章摘要。 页面信息 你可以在 Markdown 的 Frontmatter 中设置页面信息。 作者设置为 Ms.Hope。 写作日期为 2020 年 1 月 1 日 分类为 “使用指南” 标签为 “页面配置” 和 “使用指南” 页面内容 你可以自由在这里书写你的 Markdown。 提示 你可以将图片和 Markdown..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-07-29T09:43:43.000Z"}],["meta",{"property":"article:author","content":"Ms.Hope"}],["meta",{"property":"article:tag","content":"页面配置"}],["meta",{"property":"article:tag","content":"使用指南"}],["meta",{"property":"article:published_time","content":"2020-01-01T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-07-29T09:43:43.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"页面配置\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2020-01-01T00:00:00.000Z\\",\\"dateModified\\":\\"2022-07-29T09:43:43.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Ms.Hope\\"}]}"]]},"headers":[{"level":2,"title":"页面信息","slug":"页面信息","link":"#页面信息","children":[]},{"level":2,"title":"页面内容","slug":"页面内容","link":"#页面内容","children":[]},{"level":2,"title":"页面结构","slug":"页面结构","link":"#页面结构","children":[]}],"git":{"createdTime":1659087823000,"updatedTime":1659087823000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":1.46,"words":438},"filePathRelative":"guide/page.md","localizedDate":"2020年1月1日","excerpt":"

      more 注释之前的内容被视为文章摘要。

      \\n","autoDesc":true}');export{N as comp,H as data}; +import{_ as l}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as i,c as p,a as e,b as t,d as o,e as s}from"./app-_Oi5YZFn.js";const h={},c=s('

      more 注释之前的内容被视为文章摘要。

      页面信息

      你可以在 Markdown 的 Frontmatter 中设置页面信息。

      • 作者设置为 Ms.Hope。
      • 写作日期为 2020 年 1 月 1 日
      • 分类为 “使用指南”
      • 标签为 “页面配置” 和 “使用指南”

      页面内容

      你可以自由在这里书写你的 Markdown。

      提示

      • 你可以将图片和 Markdown 文件放置在一起,但是你需要使用相对链接./ 进行引用。

      • 对于 .vuepress/public 文件夹的图片,请使用绝对链接 / 进行引用。

      主题包含了一个自定义徽章章可以使用:

      ',8),d=e("h2",{id:"页面结构",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#页面结构"},[e("span",null,"页面结构")])],-1),u=e("p",null,"此页面应当包含:",-1),m={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/layout/breadcrumb.html",target:"_blank",rel:"noopener noreferrer"},g={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/feature/page-info.html",target:"_blank",rel:"noopener noreferrer"},_={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/layout/page.html#%E6%A0%87%E9%A2%98%E5%88%97%E8%A1%A8",target:"_blank",rel:"noopener noreferrer"},f={href:"https://vuepress-theme-hope.github.io/v2/guide/feature/meta.html",target:"_blank",rel:"noopener noreferrer"},b={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/feature/comment.html",target:"_blank",rel:"noopener noreferrer"},v={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/layout/navbar.html",target:"_blank",rel:"noopener noreferrer"},k={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/layout/sidebar.html",target:"_blank",rel:"noopener noreferrer"},y={href:"https://vuepress-theme-hope.github.io/v2/zh/guide/layout/footer.html",target:"_blank",rel:"noopener noreferrer"},M=e("li",null,"返回顶部按钮",-1),x=e("p",null,"你可以通过主题选项和页面 Frontmatter 自定义它们。",-1);function T(w,z){const a=n("Badge"),r=n("ExternalLinkIcon");return i(),p("div",null,[c,e("blockquote",null,[e("p",null,[t("文字结尾应该有深蓝色的 徽章文字 徽章。 "),o(a,{text:"徽章文字",color:"#242378"})])]),d,u,e("ul",null,[e("li",null,[e("a",m,[t("路径导航"),o(r)])]),e("li",null,[e("a",g,[t("标题和页面信息"),o(r)])]),e("li",null,[e("a",_,[t("TOC (文章标题列表)"),o(r)])]),e("li",null,[e("a",f,[t("贡献者、更新时间等页面元信息"),o(r)])]),e("li",null,[e("a",b,[t("评论"),o(r)])]),e("li",null,[e("a",v,[t("导航栏"),o(r)])]),e("li",null,[e("a",k,[t("侧边栏"),o(r)])]),e("li",null,[e("a",y,[t("页脚"),o(r)])]),M]),x])}const N=l(h,[["render",T],["__file","page.html.vue"]]),H=JSON.parse('{"path":"/guide/page.html","title":"页面配置","lang":"zh-CN","frontmatter":{"title":"页面配置","icon":"page","order":1,"author":"Ms.Hope","date":"2020-01-01T00:00:00.000Z","category":["使用指南"],"tag":["页面配置","使用指南"],"sticky":true,"star":true,"footer":"这是测试显示的页脚","copyright":"无版权","description":"more 注释之前的内容被视为文章摘要。 页面信息 你可以在 Markdown 的 Frontmatter 中设置页面信息。 作者设置为 Ms.Hope。 写作日期为 2020 年 1 月 1 日 分类为 “使用指南” 标签为 “页面配置” 和 “使用指南” 页面内容 你可以自由在这里书写你的 Markdown。 提示 你可以将图片和 Markdown...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/guide/page.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"页面配置"}],["meta",{"property":"og:description","content":"more 注释之前的内容被视为文章摘要。 页面信息 你可以在 Markdown 的 Frontmatter 中设置页面信息。 作者设置为 Ms.Hope。 写作日期为 2020 年 1 月 1 日 分类为 “使用指南” 标签为 “页面配置” 和 “使用指南” 页面内容 你可以自由在这里书写你的 Markdown。 提示 你可以将图片和 Markdown..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-07-29T09:43:43.000Z"}],["meta",{"property":"article:author","content":"Ms.Hope"}],["meta",{"property":"article:tag","content":"页面配置"}],["meta",{"property":"article:tag","content":"使用指南"}],["meta",{"property":"article:published_time","content":"2020-01-01T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2022-07-29T09:43:43.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"页面配置\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2020-01-01T00:00:00.000Z\\",\\"dateModified\\":\\"2022-07-29T09:43:43.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"Ms.Hope\\"}]}"]]},"headers":[{"level":2,"title":"页面信息","slug":"页面信息","link":"#页面信息","children":[]},{"level":2,"title":"页面内容","slug":"页面内容","link":"#页面内容","children":[]},{"level":2,"title":"页面结构","slug":"页面结构","link":"#页面结构","children":[]}],"git":{"createdTime":1659087823000,"updatedTime":1659087823000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":1.46,"words":438},"filePathRelative":"guide/page.md","localizedDate":"2020年1月1日","excerpt":"

      more 注释之前的内容被视为文章摘要。

      \\n","autoDesc":true}');export{N as comp,H as data}; diff --git a/assets/problem.html-dW-7sW20.js b/assets/problem.html-220H-omj.js similarity index 99% rename from assets/problem.html-dW-7sW20.js rename to assets/problem.html-220H-omj.js index c91ecd04f4..9368cb2620 100644 --- a/assets/problem.html-dW-7sW20.js +++ b/assets/problem.html-220H-omj.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

      1、继承

      继承使用场景
      把公共的依赖都放在父模块,在子模块通过parent引用,使用了parent后,在父模块中dependencies定义的依赖会自动传递到子模块。

        <parent>
      +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

      1、继承

      继承使用场景
      把公共的依赖都放在父模块,在子模块通过parent引用,使用了parent后,在父模块中dependencies定义的依赖会自动传递到子模块。

        <parent>
               <artifactId>ChenSino</artifactId>
               <groupId>com.chensino</groupId>
               <version>0.0.1-SNAPSHOT</version>
      diff --git a/assets/promise.html-BUt4IEbi.js b/assets/promise.html-DlbJ5Sxz.js
      similarity index 99%
      rename from assets/promise.html-BUt4IEbi.js
      rename to assets/promise.html-DlbJ5Sxz.js
      index c21d36f26d..ec3738453e 100644
      --- a/assets/promise.html-BUt4IEbi.js
      +++ b/assets/promise.html-DlbJ5Sxz.js
      @@ -1,4 +1,4 @@
      -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

      1. Promise介绍

      romise 是 JavaScript 中用于异步编程的一种解决方案。Promise 可以将异步操作进行封装,并提供了更加灵活和强大的处理方式。

      Promise 有三种状态:

      • pending(等待):初始状态,既不是成功也不是失败状态。
      • fulfilled(成功):意味着操作成功完成,Promise 实例的最终值可通过 then 方法获取到。
      • rejected(失败):意味着操作失败,Promise 实例的最终值可通过 catch 方法获取到。

      Promise 实例可以使用 then、catch 和 finally 方法实现异步操作的链式调用:

      promise.then(onResolved, onRejected)
      +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

      1. Promise介绍

      romise 是 JavaScript 中用于异步编程的一种解决方案。Promise 可以将异步操作进行封装,并提供了更加灵活和强大的处理方式。

      Promise 有三种状态:

      • pending(等待):初始状态,既不是成功也不是失败状态。
      • fulfilled(成功):意味着操作成功完成,Promise 实例的最终值可通过 then 方法获取到。
      • rejected(失败):意味着操作失败,Promise 实例的最终值可通过 catch 方法获取到。

      Promise 实例可以使用 then、catch 和 finally 方法实现异步操作的链式调用:

      promise.then(onResolved, onRejected)
              .then(onFulfilled, onRejected)
              .catch(onRejected)
              .finally(onFinally);
      diff --git a/assets/proxy.html-DRPn2ULh.js b/assets/proxy.html-CIpuLV1M.js
      similarity index 97%
      rename from assets/proxy.html-DRPn2ULh.js
      rename to assets/proxy.html-CIpuLV1M.js
      index a8833aa647..d5cbda8f6a 100644
      --- a/assets/proxy.html-DRPn2ULh.js
      +++ b/assets/proxy.html-CIpuLV1M.js
      @@ -1,4 +1,4 @@
      -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as s,c as i,a as e,b as o,d as r,e as l}from"./app-CVMfKeWw.js";const p={},c=l(`

      1、全局代理

      git config --global https.proxy http://127.0.0.1:1080
      +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as s,c as i,a as e,b as o,d as r,e as l}from"./app-_Oi5YZFn.js";const p={},c=l(`

      1、全局代理

      git config --global https.proxy http://127.0.0.1:1080
       git config --global https.proxy https://127.0.0.1:1080
       ##取消代理
       git config --global --unset http.proxy
      diff --git a/assets/rebaseAndMerge.html-LfOBbMMt.js b/assets/rebaseAndMerge.html-DZipbfd7.js
      similarity index 98%
      rename from assets/rebaseAndMerge.html-LfOBbMMt.js
      rename to assets/rebaseAndMerge.html-DZipbfd7.js
      index f352282cd8..71df60a327 100644
      --- a/assets/rebaseAndMerge.html-LfOBbMMt.js
      +++ b/assets/rebaseAndMerge.html-DZipbfd7.js
      @@ -1 +1 @@
      -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as r,c,a as e,b as t,d as s}from"./app-CVMfKeWw.js";const i={},p=e("p",null,"在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的)",-1),g=e("p",null,[e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/4385e8c23d844b6e9c8b3f1171319b00.png",alt:"img"})],-1),d={href:"https://so.csdn.net/so/search?q=rebase&spm=1001.2101.3001.7020",target:"_blank",rel:"noopener noreferrer"},l=e("p",null,[e("strong",null,"merge的方法是合并库时推荐使用的方法。")],-1),m=e("p",null,"如下图,merge操作会将绿色的所有修改合并,解决冲突后在蓝色分支的后面新建一个ID,成为蓝色分支的最新提交,实际上就是基于父节点ID7的提交,入库后将蓝色分支库上代码更新。",-1),h=e("p",null,[e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/bec81683ac884300851f42bd731c4861.png",alt:"img"})],-1),b=e("p",null,"如下图,rebase的方法是将提蓝色分支放到绿色分支后面提交,ID3\\ID4\\ID5\\ID7都会转换为新的ID并进行提交,由于父节点改变,每次提交都需要解决一次冲突,因此会大大增加分支合并的难度。",-1),f=e("p",null,[e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/7ed03a3ace22445b9b5f8646fb8f1cc4.png",alt:"img"})],-1);function _(u,D){const n=o("ExternalLinkIcon");return r(),c("div",null,[p,g,e("p",null,[t("如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetch),需要将本地远程分支代码更新到个人工作分支,这时有两种方法,"),e("a",d,[t("rebase"),s(n)]),t("和merge。")]),l,m,h,b,f])}const T=a(i,[["render",_],["__file","rebaseAndMerge.html.vue"]]),C=JSON.parse('{"path":"/other/git/rebaseAndMerge.html","title":"git rebase与merge的区别","lang":"zh-CN","frontmatter":{"title":"git rebase与merge的区别","date":"2022-07-26T16:57:01.000Z","category":["git 操作"],"tag":["必会"],"description":"在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的) img 如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetc...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/git/rebaseAndMerge.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"git rebase与merge的区别"}],["meta",{"property":"og:description","content":"在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的) img 如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetc..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/4385e8c23d844b6e9c8b3f1171319b00.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:published_time","content":"2022-07-26T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"git rebase与merge的区别\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/4385e8c23d844b6e9c8b3f1171319b00.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/bec81683ac884300851f42bd731c4861.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/7ed03a3ace22445b9b5f8646fb8f1cc4.png\\"],\\"datePublished\\":\\"2022-07-26T16:57:01.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":1.28,"words":383},"filePathRelative":"other/git/rebaseAndMerge.md","localizedDate":"2022年7月26日","excerpt":"

      在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的)

      \\n

      \\"img\\"

      \\n

      如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetch),需要将本地远程分支代码更新到个人工作分支,这时有两种方法,rebase和merge。

      ","autoDesc":true}');export{T as comp,C as data}; +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as o,o as r,c,a as e,b as t,d as s}from"./app-_Oi5YZFn.js";const i={},p=e("p",null,"在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的)",-1),g=e("p",null,[e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/4385e8c23d844b6e9c8b3f1171319b00.png",alt:"img"})],-1),d={href:"https://so.csdn.net/so/search?q=rebase&spm=1001.2101.3001.7020",target:"_blank",rel:"noopener noreferrer"},l=e("p",null,[e("strong",null,"merge的方法是合并库时推荐使用的方法。")],-1),m=e("p",null,"如下图,merge操作会将绿色的所有修改合并,解决冲突后在蓝色分支的后面新建一个ID,成为蓝色分支的最新提交,实际上就是基于父节点ID7的提交,入库后将蓝色分支库上代码更新。",-1),h=e("p",null,[e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/bec81683ac884300851f42bd731c4861.png",alt:"img"})],-1),b=e("p",null,"如下图,rebase的方法是将提蓝色分支放到绿色分支后面提交,ID3\\ID4\\ID5\\ID7都会转换为新的ID并进行提交,由于父节点改变,每次提交都需要解决一次冲突,因此会大大增加分支合并的难度。",-1),f=e("p",null,[e("img",{src:"https://ddns.chensina.cn:29000/afatpig/blog/7ed03a3ace22445b9b5f8646fb8f1cc4.png",alt:"img"})],-1);function _(u,D){const n=o("ExternalLinkIcon");return r(),c("div",null,[p,g,e("p",null,[t("如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetch),需要将本地远程分支代码更新到个人工作分支,这时有两种方法,"),e("a",d,[t("rebase"),s(n)]),t("和merge。")]),l,m,h,b,f])}const T=a(i,[["render",_],["__file","rebaseAndMerge.html.vue"]]),C=JSON.parse('{"path":"/other/git/rebaseAndMerge.html","title":"git rebase与merge的区别","lang":"zh-CN","frontmatter":{"title":"git rebase与merge的区别","date":"2022-07-26T16:57:01.000Z","category":["git 操作"],"tag":["必会"],"description":"在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的) img 如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetc...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/git/rebaseAndMerge.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"git rebase与merge的区别"}],["meta",{"property":"og:description","content":"在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的) img 如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetc..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/4385e8c23d844b6e9c8b3f1171319b00.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:tag","content":"必会"}],["meta",{"property":"article:published_time","content":"2022-07-26T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"git rebase与merge的区别\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/4385e8c23d844b6e9c8b3f1171319b00.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/bec81683ac884300851f42bd731c4861.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/7ed03a3ace22445b9b5f8646fb8f1cc4.png\\"],\\"datePublished\\":\\"2022-07-26T16:57:01.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[],"git":{"createdTime":1659362219000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":1.28,"words":383},"filePathRelative":"other/git/rebaseAndMerge.md","localizedDate":"2022年7月26日","excerpt":"

      在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的)

      \\n

      \\"img\\"

      \\n

      如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetch),需要将本地远程分支代码更新到个人工作分支,这时有两种方法,rebase和merge。

      ","autoDesc":true}');export{T as comp,C as data}; diff --git a/assets/reset.html-CjbSN2yZ.js b/assets/reset.html-JinN7PbC.js similarity index 99% rename from assets/reset.html-CjbSN2yZ.js rename to assets/reset.html-JinN7PbC.js index 80e3d41fc3..58fa020890 100644 --- a/assets/reset.html-CjbSN2yZ.js +++ b/assets/reset.html-JinN7PbC.js @@ -1,4 +1,4 @@ -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as t,e as s}from"./app-CVMfKeWw.js";const n={},i=s(`

      1、几种reset介绍

      git reset 命令用于回退版本,可以指定退回某一次提交的版本。

      git reset 命令语法格式如下:

      git reset [--soft | --mixed | --hard] [HEAD]
      +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as t,e as s}from"./app-_Oi5YZFn.js";const n={},i=s(`

      1、几种reset介绍

      git reset 命令用于回退版本,可以指定退回某一次提交的版本。

      git reset 命令语法格式如下:

      git reset [--soft | --mixed | --hard] [HEAD]
       

      --mixed 为默认,可以不用带该参数,用于重置暂存区的文件,暂存区的文件会回到git add. 之前的状态,并且工作区文件内容保持不变不会丢失

      回到指定版本e2c809aa88

      git reset e2c809aa88
       

      执行改命令的效果 简单粗暴的理解为e2c809aa88之后的commit,add过的文件都回到工作区,代码都不会丢失,之前commit,add 状态都没了。

      实例:

      git reset HEAD^            # 回退所有内容到上一个版本  
       git reset HEAD^ hello.php  # 回退 hello.php 文件的版本到上一个版本  
      diff --git a/assets/slide.html-_uY0duH1.js b/assets/slide.html-gu2cHfI1.js
      similarity index 99%
      rename from assets/slide.html-_uY0duH1.js
      rename to assets/slide.html-gu2cHfI1.js
      index 812a8c0e36..02cb5ffdaf 100644
      --- a/assets/slide.html-_uY0duH1.js
      +++ b/assets/slide.html-gu2cHfI1.js
      @@ -1,4 +1,4 @@
      -import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as t,c as d,a,b as e,d as o,e as n}from"./app-CVMfKeWw.js";const r="/logo.svg",c={},p=n('

      @slidestart

      幻灯片演示

      一个简单的幻灯片演示与各种小贴士。

      作者 Mr.Hope. 请滚动鼠标滚轮进入下一页


      标注幻灯片

      👇

      --

      标注幻灯片

      使用 --- 标注水平幻灯片

      在水平幻灯片中使用 -- 分割垂直幻灯片

      使用 <!-- .slide: ... --> 在幻灯片上添加属性

      使用 <!-- .element: ... --> 在前一个 HTML 元素上添加属性


      Markdown

      你可以在幻灯片中使用 Markdown 语法的各种标记.

      --

      Markdown

      你可以在幻灯片中使用 Markdown 语法的各种标记.

      这是一个 H3

      标题默认会自动转换为大写。

      ',21),h=a("strong",null,"粗体",-1),u=a("em",null,"斜体",-1),k=a("s",null,"删除线",-1),g={href:"https://mrhope.site",target:"_blank",rel:"noopener noreferrer"},m=n(`

      --

      Markdown

      你可以在幻灯片中使用 Markdown 语法的各种标记.

      列表默认为 inline-block

      • 项目
      • 项目
      • 项目
      1. 项目 1
      2. 项目 2
      3. 项目 3

      --

      Markdown

      你可以在幻灯片中使用 Markdown 语法的各种标记.

      在你启用 highlight 插件后,代码块会自动高亮。

      const a = 1;
      +import{_ as s}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as i,o as t,c as d,a,b as e,d as o,e as n}from"./app-_Oi5YZFn.js";const r="/logo.svg",c={},p=n('

      @slidestart

      幻灯片演示

      一个简单的幻灯片演示与各种小贴士。

      作者 Mr.Hope. 请滚动鼠标滚轮进入下一页


      标注幻灯片

      👇

      --

      标注幻灯片

      使用 --- 标注水平幻灯片

      在水平幻灯片中使用 -- 分割垂直幻灯片

      使用 <!-- .slide: ... --> 在幻灯片上添加属性

      使用 <!-- .element: ... --> 在前一个 HTML 元素上添加属性


      Markdown

      你可以在幻灯片中使用 Markdown 语法的各种标记.

      --

      Markdown

      你可以在幻灯片中使用 Markdown 语法的各种标记.

      这是一个 H3

      标题默认会自动转换为大写。

      ',21),h=a("strong",null,"粗体",-1),u=a("em",null,"斜体",-1),k=a("s",null,"删除线",-1),g={href:"https://mrhope.site",target:"_blank",rel:"noopener noreferrer"},m=n(`

      --

      Markdown

      你可以在幻灯片中使用 Markdown 语法的各种标记.

      列表默认为 inline-block

      • 项目
      • 项目
      • 项目
      1. 项目 1
      2. 项目 2
      3. 项目 3

      --

      Markdown

      你可以在幻灯片中使用 Markdown 语法的各种标记.

      在你启用 highlight 插件后,代码块会自动高亮。

      const a = 1;
       

      --

      Markdown

      你可以在幻灯片中使用 Markdown 语法的各种标记.

      在你启用 math 插件后,你也可以使用 TEX 格式使用数学公式。

      $$ J(\\theta_0,\\theta_1) = \\sum_{i=0} $$

      --

      Markdown

      你可以在幻灯片中使用 Markdown 语法的各种标记.

      请注意: 表格和分割线,以及所有不在 Markdown 标准语法中的内容均不受支持。


      布局

      --

      布局

      👆 r-fit-text class 会让文字在不超出幻灯片范围的情况下尽可能大。

      --

      布局

      Logo

      👆 r-stretch class 帮助你控制注入图片或视频的大小,使它们填充满幻灯片垂直方向上的剩余空间。

      --

      布局

      背景

      你可以通过向特定幻灯片添加 data-background 属性自定义幻灯片背景.


      动画片段

      --

      动画片段

      动画片段用于高亮或显隐幻灯片中的元素。

      你需要在元素上添加 fragment 和动画 class。

      --

      动画片段

      动画 class

      • fade-in
      • fade-out
      • fade-up
      • fade-down
      • fade-left
      • fade-right
      • fade-in-then-out
      • fade-in-then-semi-out

      --

      动画片段

      动画 class

      • grow
      • shrink
      • strike
      • highlight-red
      • highlight-green
      • highlight-blue
      • highlight-current-red
      • highlight-current-green
      • highlight-current-blue

      --

      动画片段

      多个动画片段

      你可以按照顺序包裹一个 HTML 元素使其拥有多个动画片段

      渐入 > 变红 > 渐出

      --

      动画片段

      顺序

      你可以使用 data-fragment-index 属性改变元素的动画顺序。

      不同元素可以有相同的动画顺序。

      • 最后显示
      • 第二个显示
      • 第一个显示
      • 第二个显示

      渐变

      --

      渐变

      Transition 可以通过配置中的 transition 选项全局设置,也可以通过在特定幻灯片添加 data-transition 属性局部设置.

      可能的值:

      • none
      • fade
      • slide
      • convex
      • concave
      • zoom

      --

      渐变

      过渡动画

      你可以在相邻的幻灯片上添加 data-auto-animate 使相同的 HTML 元素产生过渡动画效果。


      功能

      --

      功能

      代码

      通过启用 highlight 插件,你可以对代码块进行高亮。

      你可以使用 [a-b|c-d] 语法来分布高亮特定行。

      let a = 1;
       let b = 2;
       let c = (x) => 1 + 2 + x;
      diff --git a/assets/spring-security-oauth2-authorization-server.html-BU0wlxJK.js b/assets/spring-security-oauth2-authorization-server.html-o1Bz8Yim.js
      similarity index 99%
      rename from assets/spring-security-oauth2-authorization-server.html-BU0wlxJK.js
      rename to assets/spring-security-oauth2-authorization-server.html-o1Bz8Yim.js
      index ef2972a157..2afa2237a4 100644
      --- a/assets/spring-security-oauth2-authorization-server.html-BU0wlxJK.js
      +++ b/assets/spring-security-oauth2-authorization-server.html-o1Bz8Yim.js
      @@ -1,4 +1,4 @@
      -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as e,e as a}from"./app-CVMfKeWw.js";const s={},o=a(`

      介绍

      spring-security-oauth2已经被废弃,采用Security5.7 之后就用spring-security-oauth2-authorization-server,

      /oauth2/token

      在之前的老版本中请求token的端点是在一个叫做TokenEndpoint的类中,此类可以处理/oauth/token请求,这个类可看成是一个Controller,而在新版本中已经没有这个类,新版本中请求token的端点是/oauth2/token,我看源码没有找到对应的controller,直到我debug源码,才发现根本没有类似之前的专门处理/oauth/token的controller,其实新版本在Filter(实际是OAuth2TokenEndpointFilter)中就已经直接响应给客户端了, 20230509201806

      下面写个demo测试在filter中直接响应,新建一个springboot的web项目,添加一个filter,这个过滤器仅仅用来测试,所有请求都给他返回一个“hello world",我们请求任意路径发现确实如此,根本没有进入Controller,我甚至都没定义Controller,打破了我的惯性思维,潜意识认为只要有请求,最终都要进入Controller,谬矣。

      @Component
      +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as e,e as a}from"./app-_Oi5YZFn.js";const s={},o=a(`

      介绍

      spring-security-oauth2已经被废弃,采用Security5.7 之后就用spring-security-oauth2-authorization-server,

      /oauth2/token

      在之前的老版本中请求token的端点是在一个叫做TokenEndpoint的类中,此类可以处理/oauth/token请求,这个类可看成是一个Controller,而在新版本中已经没有这个类,新版本中请求token的端点是/oauth2/token,我看源码没有找到对应的controller,直到我debug源码,才发现根本没有类似之前的专门处理/oauth/token的controller,其实新版本在Filter(实际是OAuth2TokenEndpointFilter)中就已经直接响应给客户端了, 20230509201806

      下面写个demo测试在filter中直接响应,新建一个springboot的web项目,添加一个filter,这个过滤器仅仅用来测试,所有请求都给他返回一个“hello world",我们请求任意路径发现确实如此,根本没有进入Controller,我甚至都没定义Controller,打破了我的惯性思维,潜意识认为只要有请求,最终都要进入Controller,谬矣。

      @Component
       public class CustomFilter extends OncePerRequestFilter {
           @Override
           protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
      diff --git a/assets/stash.html-DJPmBi5v.js b/assets/stash.html-D1rEWs9W.js
      similarity index 99%
      rename from assets/stash.html-DJPmBi5v.js
      rename to assets/stash.html-D1rEWs9W.js
      index b633597ff4..231934835e 100644
      --- a/assets/stash.html-DJPmBi5v.js
      +++ b/assets/stash.html-D1rEWs9W.js
      @@ -1 +1 @@
      -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as s,e as i}from"./app-CVMfKeWw.js";const a={},o=i('

      一,使用场景

      在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。

      二,stash的作用

      stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。

      三,使用命令

      1. git status查看哪些文件发生变化

      2. git stash把所有改动暂存起来,git stash等同于git stash push,可以给当前stash增加message方便后续查看

        git stash push -m "message",执行完,会发现git status当前分支变得跟没有改动时一样干净

      3. git stash list 查看当前栈中有多少暂存记录

      4. 恢复栈最新的一个暂存可以git stash pop,取暂存中的某一份 git stash apply stash@{1}

      5. popapply都可以恢复暂存,但是,apply执行后,暂存记录还存在。pop`则会从栈中移除

      6. git stash clear 删除所有缓存的stash

      7. git stash drop stash@{$num} :丢弃stash@{$num}存储,从列表中删除这个存储

      四,本地解决冲突

      1. 把自己开发的代码储藏起来git stash
      2. git pull 拉取最新代码
      3. git stash pop 把暂存文件恢复
      4. 查看冲突,解决冲突 git status -s
      5. 解决完冲突后就可以像正常提交代码一样操作了,
      6. git add xxx
      7. git commit -m “xxx”
      8. git push origin master:my_branch
      ',8),n=[o];function p(h,l){return e(),s("div",null,n)}const r=t(a,[["render",p],["__file","stash.html.vue"]]),g=JSON.parse('{"path":"/other/git/stash.html","title":"git stash 暂存","lang":"zh-CN","frontmatter":{"title":"git stash 暂存","date":"2022-03-09T16:57:01.000Z","category":["git 操作","必会"],"description":"一,使用场景 在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。 二,stash的作用 stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/git/stash.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"git stash 暂存"}],["meta",{"property":"og:description","content":"一,使用场景 在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。 二,stash的作用 stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-03-09T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"git stash 暂存\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-09T16:57:01.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"一,使用场景","slug":"一-使用场景","link":"#一-使用场景","children":[]},{"level":2,"title":"二,stash的作用","slug":"二-stash的作用","link":"#二-stash的作用","children":[]},{"level":2,"title":"三,使用命令","slug":"三-使用命令","link":"#三-使用命令","children":[]},{"level":2,"title":"四,本地解决冲突","slug":"四-本地解决冲突","link":"#四-本地解决冲突","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":1.52,"words":456},"filePathRelative":"other/git/stash.md","localizedDate":"2022年3月9日","excerpt":"

      一,使用场景

      \\n

      在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。

      \\n

      二,stash的作用

      \\n

      stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。

      \\n

      三,使用命令

      \\n
        \\n
      1. \\n

        git status查看哪些文件发生变化

        \\n
      2. \\n
      3. \\n

        git stash把所有改动暂存起来,git stash等同于git stash push,可以给当前stash增加message方便后续查看

        \\n

        git stash push -m \\"message\\",执行完,会发现git status当前分支变得跟没有改动时一样干净

        \\n
      4. \\n
      5. \\n

        git stash list 查看当前栈中有多少暂存记录

        \\n
      6. \\n
      7. \\n

        恢复栈最新的一个暂存可以git stash pop,取暂存中的某一份 git stash apply stash@{1}

        \\n
      8. \\n
      9. \\n

        popapply都可以恢复暂存,但是,apply执行后,暂存记录还存在。pop`则会从栈中移除

        \\n
      10. \\n
      11. \\n

        git stash clear 删除所有缓存的stash

        \\n
      12. \\n
      13. \\n

        git stash drop stash@{$num} :丢弃stash@{$num}存储,从列表中删除这个存储

        \\n
      14. \\n
      ","autoDesc":true}');export{r as comp,g as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as s,e as i}from"./app-_Oi5YZFn.js";const a={},o=i('

      一,使用场景

      在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。

      二,stash的作用

      stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。

      三,使用命令

      1. git status查看哪些文件发生变化

      2. git stash把所有改动暂存起来,git stash等同于git stash push,可以给当前stash增加message方便后续查看

        git stash push -m "message",执行完,会发现git status当前分支变得跟没有改动时一样干净

      3. git stash list 查看当前栈中有多少暂存记录

      4. 恢复栈最新的一个暂存可以git stash pop,取暂存中的某一份 git stash apply stash@{1}

      5. popapply都可以恢复暂存,但是,apply执行后,暂存记录还存在。pop`则会从栈中移除

      6. git stash clear 删除所有缓存的stash

      7. git stash drop stash@{$num} :丢弃stash@{$num}存储,从列表中删除这个存储

      四,本地解决冲突

      1. 把自己开发的代码储藏起来git stash
      2. git pull 拉取最新代码
      3. git stash pop 把暂存文件恢复
      4. 查看冲突,解决冲突 git status -s
      5. 解决完冲突后就可以像正常提交代码一样操作了,
      6. git add xxx
      7. git commit -m “xxx”
      8. git push origin master:my_branch
      ',8),n=[o];function p(h,l){return e(),s("div",null,n)}const r=t(a,[["render",p],["__file","stash.html.vue"]]),g=JSON.parse('{"path":"/other/git/stash.html","title":"git stash 暂存","lang":"zh-CN","frontmatter":{"title":"git stash 暂存","date":"2022-03-09T16:57:01.000Z","category":["git 操作","必会"],"description":"一,使用场景 在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。 二,stash的作用 stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/other/git/stash.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"git stash 暂存"}],["meta",{"property":"og:description","content":"一,使用场景 在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。 二,stash的作用 stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2022-08-01T13:56:59.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2022-03-09T16:57:01.000Z"}],["meta",{"property":"article:modified_time","content":"2022-08-01T13:56:59.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"git stash 暂存\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-03-09T16:57:01.000Z\\",\\"dateModified\\":\\"2022-08-01T13:56:59.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"一,使用场景","slug":"一-使用场景","link":"#一-使用场景","children":[]},{"level":2,"title":"二,stash的作用","slug":"二-stash的作用","link":"#二-stash的作用","children":[]},{"level":2,"title":"三,使用命令","slug":"三-使用命令","link":"#三-使用命令","children":[]},{"level":2,"title":"四,本地解决冲突","slug":"四-本地解决冲突","link":"#四-本地解决冲突","children":[]}],"git":{"createdTime":1659362219000,"updatedTime":1659362219000,"contributors":[{"name":"chenkun","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":1.52,"words":456},"filePathRelative":"other/git/stash.md","localizedDate":"2022年3月9日","excerpt":"

      一,使用场景

      \\n

      在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。

      \\n

      二,stash的作用

      \\n

      stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。

      \\n

      三,使用命令

      \\n
        \\n
      1. \\n

        git status查看哪些文件发生变化

        \\n
      2. \\n
      3. \\n

        git stash把所有改动暂存起来,git stash等同于git stash push,可以给当前stash增加message方便后续查看

        \\n

        git stash push -m \\"message\\",执行完,会发现git status当前分支变得跟没有改动时一样干净

        \\n
      4. \\n
      5. \\n

        git stash list 查看当前栈中有多少暂存记录

        \\n
      6. \\n
      7. \\n

        恢复栈最新的一个暂存可以git stash pop,取暂存中的某一份 git stash apply stash@{1}

        \\n
      8. \\n
      9. \\n

        popapply都可以恢复暂存,但是,apply执行后,暂存记录还存在。pop`则会从栈中移除

        \\n
      10. \\n
      11. \\n

        git stash clear 删除所有缓存的stash

        \\n
      12. \\n
      13. \\n

        git stash drop stash@{$num} :丢弃stash@{$num}存储,从列表中删除这个存储

        \\n
      14. \\n
      ","autoDesc":true}');export{r as comp,g as data}; diff --git a/assets/throttle.html-BVURcdFS.js b/assets/throttle.html-CDssB4Vw.js similarity index 99% rename from assets/throttle.html-BVURcdFS.js rename to assets/throttle.html-CDssB4Vw.js index 12e5190aa7..6047b555b7 100644 --- a/assets/throttle.html-BVURcdFS.js +++ b/assets/throttle.html-CDssB4Vw.js @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

      一,节流概念(Throttle)

      规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

      主要应用场景有:

      1. 鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;
      2. 在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据;
      3. 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断;

      二,节流实现

      思路: 第一次先设定一个变量true,第二次执行这个函数时,会判断变量是否true,是则返回。当第一次的定时器执行完函数最后会设定变量为flase。那么下次判断变量时则为flase,函数会依次运行。

      代码一:

      function throttle(fn,delay=100){
      +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

      一,节流概念(Throttle)

      规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

      主要应用场景有:

      1. 鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;
      2. 在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据;
      3. 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断;

      二,节流实现

      思路: 第一次先设定一个变量true,第二次执行这个函数时,会判断变量是否true,是则返回。当第一次的定时器执行完函数最后会设定变量为flase。那么下次判断变量时则为flase,函数会依次运行。

      代码一:

      function throttle(fn,delay=100){
       //首先设定一个变量,在没有执行我们的定时器时为null
       let timer = null;
       return function(){
      diff --git a/assets/tsAndvue3.html-DuJXkf-s.js b/assets/tsAndvue3.html-8xWV3_Wf.js
      similarity index 99%
      rename from assets/tsAndvue3.html-DuJXkf-s.js
      rename to assets/tsAndvue3.html-8xWV3_Wf.js
      index 64ec294771..627634cdd6 100644
      --- a/assets/tsAndvue3.html-DuJXkf-s.js
      +++ b/assets/tsAndvue3.html-8xWV3_Wf.js
      @@ -1,4 +1,4 @@
      -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

      ts在表单中的应用

            <el-form ref="ruleFormRef" :model="ruleForm" status-icon :rules="rules" label-width="70px">
      +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

      ts在表单中的应用

            <el-form ref="ruleFormRef" :model="ruleForm" status-icon :rules="rules" label-width="70px">
               <h2>后台管理系统</h2>
               <el-form-item label="用户名:" prop="username">
                 <el-input v-model="ruleForm.username" type="text" autocomplete="off" />
      diff --git a/assets/useModule.html-C54V2441.js b/assets/useModule.html-BHtXy4j3.js
      similarity index 99%
      rename from assets/useModule.html-C54V2441.js
      rename to assets/useModule.html-BHtXy4j3.js
      index 7717472c85..473d1fef43 100644
      --- a/assets/useModule.html-C54V2441.js
      +++ b/assets/useModule.html-BHtXy4j3.js
      @@ -1,4 +1,4 @@
      -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},o=e(`

      一,什么是前端模块化

      模块化就是将一个复杂的应用程序,按照规范拆分成几个相互独立的文件,这些文件里面完成共同的或者类似的逻辑,通过对外暴露一些数据或者调用方法,与外部整合。

      这样每个文件彼此独立,我们开发者更容易开发和维护代码,特别是当开发的项目越来越大,代码复杂性也不断增加,这对于模块化的需求也会越来越大。

      模块化主要特点是:可复用性、可组合性、独立性、中心化。

      所以使用模块化可以帮我们解决什么问题呢?

      • 解决了命名冲突:因为每个模块是独立的,所以变量或函数名重名不会发生冲突
      • 提高可维护性:因为每个文件的职责单一,有利于代码维护
      • 性能优化:异步加载模块对页面性能会非常好
      • 模块的版本管理:通过别名等配置,配合构建工具,可以实现模块的版本管理
      • 跨环境共享模块:通过 Sea.jsNodeJS版本,可以实现模块的跨服务器和浏览器共享

      目前前端主流的模块化标准有:

      • CommonJS
      • AMD
      • CMD
      • UMD
      • ES6

      二,CommonJS

      commonJS 是语言层面的规范,当前主要用于Node.js.

      在每个模块内部有一个 module 对象,代表当前模块,通过它来导出当前模块里的 API,module 有几个属性:

      • exports : 是对外的接口,加载某个模块,就是加载该模块的**module.exports**属性
      • loaded: 返回一个布尔值,表示该模块是否已完成加载
      • parent: 返回一个对象,表示调用该模块的模块
      • children:返回一个数组,表示该模块被用到了其他模块的集合
      • filename:模块的文件名,带有绝对路径
      • id:模块的标识符,一般是带有绝对路径的模块文件名

      CommonJS 规范的特点:

      • 每个文件都是独立的模块,有独立的作用域,b变量和方法 不会污染全局空间,并且对其他文件是不可见的。
      • 文件可以被重复引用、加载。第一次加载时会被缓存,之后再引用就直接读取缓存
      • 加载某个模块时,module.exports 输出的是值的拷贝,一旦这个值被输出,模块内再发生变化不会影响已经输出的值

      用法是这样的:

      导出

      
      +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},o=e(`

      一,什么是前端模块化

      模块化就是将一个复杂的应用程序,按照规范拆分成几个相互独立的文件,这些文件里面完成共同的或者类似的逻辑,通过对外暴露一些数据或者调用方法,与外部整合。

      这样每个文件彼此独立,我们开发者更容易开发和维护代码,特别是当开发的项目越来越大,代码复杂性也不断增加,这对于模块化的需求也会越来越大。

      模块化主要特点是:可复用性、可组合性、独立性、中心化。

      所以使用模块化可以帮我们解决什么问题呢?

      • 解决了命名冲突:因为每个模块是独立的,所以变量或函数名重名不会发生冲突
      • 提高可维护性:因为每个文件的职责单一,有利于代码维护
      • 性能优化:异步加载模块对页面性能会非常好
      • 模块的版本管理:通过别名等配置,配合构建工具,可以实现模块的版本管理
      • 跨环境共享模块:通过 Sea.jsNodeJS版本,可以实现模块的跨服务器和浏览器共享

      目前前端主流的模块化标准有:

      • CommonJS
      • AMD
      • CMD
      • UMD
      • ES6

      二,CommonJS

      commonJS 是语言层面的规范,当前主要用于Node.js.

      在每个模块内部有一个 module 对象,代表当前模块,通过它来导出当前模块里的 API,module 有几个属性:

      • exports : 是对外的接口,加载某个模块,就是加载该模块的**module.exports**属性
      • loaded: 返回一个布尔值,表示该模块是否已完成加载
      • parent: 返回一个对象,表示调用该模块的模块
      • children:返回一个数组,表示该模块被用到了其他模块的集合
      • filename:模块的文件名,带有绝对路径
      • id:模块的标识符,一般是带有绝对路径的模块文件名

      CommonJS 规范的特点:

      • 每个文件都是独立的模块,有独立的作用域,b变量和方法 不会污染全局空间,并且对其他文件是不可见的。
      • 文件可以被重复引用、加载。第一次加载时会被缓存,之后再引用就直接读取缓存
      • 加载某个模块时,module.exports 输出的是值的拷贝,一旦这个值被输出,模块内再发生变化不会影响已经输出的值

      用法是这样的:

      导出

      
       module.exports.foo = function(){ ... } // 只能输出一个
       或 可以输出多个
       exports.a = 1
      diff --git a/assets/useNpm.html-DJJdqwXN.js b/assets/useNpm.html-CKLfIIZn.js
      similarity index 99%
      rename from assets/useNpm.html-DJJdqwXN.js
      rename to assets/useNpm.html-CKLfIIZn.js
      index 6e83fc8062..30161a48cf 100644
      --- a/assets/useNpm.html-DJJdqwXN.js
      +++ b/assets/useNpm.html-CKLfIIZn.js
      @@ -1,4 +1,4 @@
      -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o,c as m,a as e,b as n,d as p,e as i}from"./app-CVMfKeWw.js";const s={},c=e("h2",{id:"一-什么是npm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#一-什么是npm"},[e("span",null,"一,什么是npm")])],-1),l={href:"https://developer.aliyun.com/mirror/NPM?from=tnpm",target:"_blank",rel:"noopener noreferrer"},d={href:"https://link.zhihu.com/?target=https%3A//registry.npm.taobao.org/",target:"_blank",rel:"noopener noreferrer"},h=i(`

      npm和cnpm只是下载的地址不同,npm是从国外下载东西,cnpm是从国内下载东西。

      npm默认的仓库地址为:

      http://registry.npmjs.org
      +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o,c as m,a as e,b as n,d as p,e as i}from"./app-_Oi5YZFn.js";const s={},c=e("h2",{id:"一-什么是npm",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#一-什么是npm"},[e("span",null,"一,什么是npm")])],-1),l={href:"https://developer.aliyun.com/mirror/NPM?from=tnpm",target:"_blank",rel:"noopener noreferrer"},d={href:"https://link.zhihu.com/?target=https%3A//registry.npm.taobao.org/",target:"_blank",rel:"noopener noreferrer"},h=i(`

      npm和cnpm只是下载的地址不同,npm是从国外下载东西,cnpm是从国内下载东西。

      npm默认的仓库地址为:

      http://registry.npmjs.org
       

      查看当前npm仓库地址命令:

      npm config get registry
       


      二,什么时候使用cnpm?

      真好笑! 😂 ❤️ 因为npm的远程服务器在国外,所以有时候难免出现访问过慢,甚至无法访问的情况。

      第一种:

      直接安装cnpm 安装淘宝提供的cnpm,并更改服务器地址为淘宝的国内地址,

      npm install -g cnpm --registry=https://registry.npm.taobao.org
       

      这个命令行做了两件事:

      (1)全局安装 cnpm

      (2)并且将安装 (各种包) 的地址切换到 国内的淘宝镜像

      以后安装直接采用cpm替代npm, 例如原生npm命令为:npm install uniq --save

      cnpm命令为:cnpm install uniq --save

      第二种:

      替换npm仓库地址为淘宝镜像地址(推荐) 命令:

      npm config set registry https://registry.npm.taobao.org
      diff --git a/assets/usePnpm.html-mIezI9dx.js b/assets/usePnpm.html-C3QlcelL.js
      similarity index 98%
      rename from assets/usePnpm.html-mIezI9dx.js
      rename to assets/usePnpm.html-C3QlcelL.js
      index 42a8dad4b7..737821b608 100644
      --- a/assets/usePnpm.html-mIezI9dx.js
      +++ b/assets/usePnpm.html-C3QlcelL.js
      @@ -1,4 +1,4 @@
      -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as p,c as i,a as n,b as t,d as l,e as m}from"./app-CVMfKeWw.js";const c={},o=m(`

      一,什么是pnpm

      performant npm ,意味“高性能的 npm”。pnpm由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”

      二,特点

      1. 当使用 npm 或 Yarn 时,如果你有 100 个项目,并且所有项目都有一个相同的依赖包,那么, 你在硬盘上就需要保存 100 份该相同依赖包的副本。然而,如果是使用 pnpm,依赖包将被 存放在一个统一的位置,因此:

        1. 如果你对同一依赖包需要使用不同的版本,则仅有 版本之间不同的文件会被存储起来。例如,如果某个依赖包包含 100 个文件,其发布了一个新 版本,并且新版本中只有一个文件有修改,则 pnpm update 只需要添加一个 新文件到存储中,而不会因为一个文件的修改而保存依赖包的 所有文件。
        2. 所有文件都保存在硬盘上的统一的位置。当安装软件包时, 其包含的所有文件都会硬链接自此位置,而不会占用 额外的硬盘空间。这让你可以在项目之间方便地共享相同版本的 依赖包。

        最终结果就是以项目和依赖包的比例来看,你节省了大量的硬盘空间, 并且安装速度也大大提高了!

      三,安装

      全局安装

      
      +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as s,o as p,c as i,a as n,b as t,d as l,e as m}from"./app-_Oi5YZFn.js";const c={},o=m(`

      一,什么是pnpm

      performant npm ,意味“高性能的 npm”。pnpm由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”

      二,特点

      1. 当使用 npm 或 Yarn 时,如果你有 100 个项目,并且所有项目都有一个相同的依赖包,那么, 你在硬盘上就需要保存 100 份该相同依赖包的副本。然而,如果是使用 pnpm,依赖包将被 存放在一个统一的位置,因此:

        1. 如果你对同一依赖包需要使用不同的版本,则仅有 版本之间不同的文件会被存储起来。例如,如果某个依赖包包含 100 个文件,其发布了一个新 版本,并且新版本中只有一个文件有修改,则 pnpm update 只需要添加一个 新文件到存储中,而不会因为一个文件的修改而保存依赖包的 所有文件。
        2. 所有文件都保存在硬盘上的统一的位置。当安装软件包时, 其包含的所有文件都会硬链接自此位置,而不会占用 额外的硬盘空间。这让你可以在项目之间方便地共享相同版本的 依赖包。

        最终结果就是以项目和依赖包的比例来看,你节省了大量的硬盘空间, 并且安装速度也大大提高了!

      三,安装

      全局安装

      
       npm install pnpm -g
       
       

      安装完成后若出现pnpm : 无法加载文件 C:\\Users\\hp\\AppData\\Roaming\\npm\\pnpm.ps1,因为在此系统上禁止运行脚本

      
      diff --git a/assets/useYarn.html-DUPdL_R5.js b/assets/useYarn.html-BrgPHvAk.js
      similarity index 98%
      rename from assets/useYarn.html-DUPdL_R5.js
      rename to assets/useYarn.html-BrgPHvAk.js
      index 01fd5b28ac..1778885eb9 100644
      --- a/assets/useYarn.html-DUPdL_R5.js
      +++ b/assets/useYarn.html-BrgPHvAk.js
      @@ -1,4 +1,4 @@
      -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o,c as r,a as e,b as i,d as l,e as s}from"./app-CVMfKeWw.js";const p={},c=s(`

      一,yarn简介

      yarn 是由 Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具,yarn 是为了弥补 npm 的一些缺陷而出现的。

      二,npm的缺陷

      • npm install 下载速度慢,重新 install 时速度依旧慢

      • 同一个项目,安装的无法保持一致性。原因是因为 package.json 文件中版本号的特点导致的,下面三个版本号在安装的时候代表不同的含义。

        "5.0.3"     # 表示安装指定的5.0.3版本
        +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o,c as r,a as e,b as i,d as l,e as s}from"./app-_Oi5YZFn.js";const p={},c=s(`

        一,yarn简介

        yarn 是由 Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具,yarn 是为了弥补 npm 的一些缺陷而出现的。

        二,npm的缺陷

        • npm install 下载速度慢,重新 install 时速度依旧慢

        • 同一个项目,安装的无法保持一致性。原因是因为 package.json 文件中版本号的特点导致的,下面三个版本号在安装的时候代表不同的含义。

          "5.0.3"     # 表示安装指定的5.0.3版本
           "~5.0.3"    # 表示安装5.0.X中最新的版本
           "^5.0.3"    # 表示安装5.X.X中最新的版本
           
        • 使用 npm 安装多个 js 包时,包会在同一时间下载和安装。安装过程中,其中一个包抛出了一个异常,但 npm 会继续安装其他包,所以错误信息就会在一大堆提示信息中丢失掉,以至于直到执行前,都不会发现实际发生的错误

        三,yarn优点

        image-20221014114909514

        之前一直有个误区,就是yarn不用设置淘宝镜像,速度慢的话,依旧需要设置

        
        diff --git a/assets/volatile.html-ChArXn5X.js b/assets/volatile.html-B-u5KWIK.js
        similarity index 99%
        rename from assets/volatile.html-ChArXn5X.js
        rename to assets/volatile.html-B-u5KWIK.js
        index 0c680f833f..10f7931303 100644
        --- a/assets/volatile.html-ChArXn5X.js
        +++ b/assets/volatile.html-B-u5KWIK.js
        @@ -1,4 +1,4 @@
        -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as t,c as p,a as n,b as l,d as o,e as i}from"./app-CVMfKeWw.js";const c={},u=i(`

        1、双重检查的单例模式是否真的线程安全?

        public class Single{
        +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as t,c as p,a as n,b as l,d as o,e as i}from"./app-_Oi5YZFn.js";const c={},u=i(`

        1、双重检查的单例模式是否真的线程安全?

        public class Single{
             public static Single single;
         
             private Single(){
        diff --git a/assets/vue-Direactive.html-CrUvqxXL.js b/assets/vue-Direactive.html-CwIH1XaN.js
        similarity index 99%
        rename from assets/vue-Direactive.html-CrUvqxXL.js
        rename to assets/vue-Direactive.html-CwIH1XaN.js
        index 940f999863..778520ce3d 100644
        --- a/assets/vue-Direactive.html-CrUvqxXL.js
        +++ b/assets/vue-Direactive.html-CwIH1XaN.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

        一,自定义指令介绍

        除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。

        vue中重用代码,3种方式:

        1. ​ 组件 --组件是主要的构建模块
        2. ​ 组合式函数--主要是侧重于有状态的逻辑
        3. ​ 自定义指令--主要是为了重用涉及普通元素的底层DOM访问的逻辑

        二,注册自定义指令

        2.1 局部注册

        
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

        一,自定义指令介绍

        除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。

        vue中重用代码,3种方式:

        1. ​ 组件 --组件是主要的构建模块
        2. ​ 组合式函数--主要是侧重于有状态的逻辑
        3. ​ 自定义指令--主要是为了重用涉及普通元素的底层DOM访问的逻辑

        二,注册自定义指令

        2.1 局部注册

        
         const focus = {
           mounted: (el) => el.focus()
         }
        diff --git a/assets/vue-authority.html-hGxiTJj0.js b/assets/vue-authority.html-HH15bglq.js
        similarity index 99%
        rename from assets/vue-authority.html-hGxiTJj0.js
        rename to assets/vue-authority.html-HH15bglq.js
        index 4c9af7d178..5ed30b4fa4 100644
        --- a/assets/vue-authority.html-hGxiTJj0.js
        +++ b/assets/vue-authority.html-HH15bglq.js
        @@ -1,4 +1,4 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c,a as n,b as a,d as t,e as i}from"./app-CVMfKeWw.js";const l={},r=i(`

        前端主要有哪些权限控制?

        一,接口访问权限

        接口权限目前一般采用通用的形式来验证(用户是否登录系统),没有的话一般返回401,跳转到登录页面重新进行登录 ,登录成功后拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token

        
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c,a as n,b as a,d as t,e as i}from"./app-_Oi5YZFn.js";const l={},r=i(`

        前端主要有哪些权限控制?

        一,接口访问权限

        接口权限目前一般采用通用的形式来验证(用户是否登录系统),没有的话一般返回401,跳转到登录页面重新进行登录 ,登录成功后拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token

        
         axios.interceptors.request.use(config => {
             config.headers['token'] = cookie.get('token')
             return config
        diff --git a/assets/vue-in-action.html-6hfWli8S.js b/assets/vue-in-action.html-DaPOjPap.js
        similarity index 99%
        rename from assets/vue-in-action.html-6hfWli8S.js
        rename to assets/vue-in-action.html-DaPOjPap.js
        index e75fc9d3f4..8bc18a78bc 100644
        --- a/assets/vue-in-action.html-6hfWli8S.js
        +++ b/assets/vue-in-action.html-DaPOjPap.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},p=e(`

        一、创建一个新项目

        vue create vue2-admin
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

        一、创建一个新项目

        vue create vue2-admin
         
         

        这里搭建好的脚手架是采用vue2+webpack,并且预装了vueRoutervuexscsseslint.

        image-20220809105240533

        生成的文件夹如下所示:

        image-20220809105924589

        vue-cli 脚手架搭建完成后,项目中是没有 vue.config.js 文件,需要手动创建。

        二、创建vue.config.js

        vue.config.js(相当于之前的webpack.config.js) 是一个可选的配置文件。如果项目的 (和package.json同级的) 根目录中存在这个文件,那么它会被@vue/cli-service自动加载。

        const path = require("path");
         const resolve = (dir) => path.join(__dirname, dir);
        diff --git a/assets/vue-nextTick.html-D03AWrxQ.js b/assets/vue-nextTick.html-BAWrVlUH.js
        similarity index 99%
        rename from assets/vue-nextTick.html-D03AWrxQ.js
        rename to assets/vue-nextTick.html-BAWrVlUH.js
        index 8d048e2dee..a705b75eba 100644
        --- a/assets/vue-nextTick.html-D03AWrxQ.js
        +++ b/assets/vue-nextTick.html-BAWrVlUH.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

        一,什么是nextTick

        定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

        为什么vue中会用到nextTick的回调去获取更新后的DOM?

        这是因为vue 实现响应式并**不是数据发生变化之后 DOM 立即变化**,而是按一定的策略进行 DOM 的更新。
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

        一,什么是nextTick

        定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

        为什么vue中会用到nextTick的回调去获取更新后的DOM?

        这是因为vue 实现响应式并**不是数据发生变化之后 DOM 立即变化**,而是按一定的策略进行 DOM 的更新。
         

        理解:nextTick(),是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:当你通过数据更新了页面后,想获 取更新后的DOM,就需要使用到nextTick.

        <template>
           <div class="hello">
             <div>
        diff --git a/assets/vue-pic.html-CpOJwwqn.js b/assets/vue-pic.html-BGsMqmKX.js
        similarity index 99%
        rename from assets/vue-pic.html-CpOJwwqn.js
        rename to assets/vue-pic.html-BGsMqmKX.js
        index 154ffb2d78..ebe17ea8d7 100644
        --- a/assets/vue-pic.html-CpOJwwqn.js
        +++ b/assets/vue-pic.html-BGsMqmKX.js
        @@ -1,4 +1,4 @@
        -import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o,c,a as n,b as s,d as l,e as a}from"./app-CVMfKeWw.js";const i={},u=a(`

        一,在vue中静态导入相对路径

        <img src="../../assets/1.png" />
        +import{_ as p}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o,c,a as n,b as s,d as l,e as a}from"./app-_Oi5YZFn.js";const i={},u=a(`

        一,在vue中静态导入相对路径

        <img src="../../assets/1.png" />
         <!-- 或者如下 -->
         <img src="@/assets/1.png" />
         
        diff --git a/assets/vue-router1.html-DrI04ly7.js b/assets/vue-router1.html-DZlcHgjO.js
        similarity index 99%
        rename from assets/vue-router1.html-DrI04ly7.js
        rename to assets/vue-router1.html-DZlcHgjO.js
        index 5a79510827..ab7d6963c1 100644
        --- a/assets/vue-router1.html-DrI04ly7.js
        +++ b/assets/vue-router1.html-DZlcHgjO.js
        @@ -1,4 +1,4 @@
        -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as p,c as o,a as s,b as n,d as c,e as l}from"./app-CVMfKeWw.js";const u={},i=l(`

        一,安装

        npm install vue-router@4
        +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as e,o as p,c as o,a as s,b as n,d as c,e as l}from"./app-_Oi5YZFn.js";const u={},i=l(`

        一,安装

        npm install vue-router@4
         

        二,基本用法

        2.1 在项目中新建router/index.js

        import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
         import About from '@/components/About.vue'
         import Home from '@/components/Home.vue'
        diff --git a/assets/vue-router2.html-C4QGqnFR.js b/assets/vue-router2.html-DNXG8jNK.js
        similarity index 99%
        rename from assets/vue-router2.html-C4QGqnFR.js
        rename to assets/vue-router2.html-DNXG8jNK.js
        index 7035b5cf70..7d45b17b00 100644
        --- a/assets/vue-router2.html-C4QGqnFR.js
        +++ b/assets/vue-router2.html-DNXG8jNK.js
        @@ -1,4 +1,4 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c,a as n,b as s,d as t,e as i}from"./app-CVMfKeWw.js";const l={},u=i(`

        一, 路由的本质

        简单来说,浏览器端路由其实并不是真实的网页跳转(和服务器没有任何交互),而是纯粹在浏览器端发生的一系列行为,本质上来说前端路由就是:

        对 url 进行改变和监听,来让某个 dom 节点显示对应的视图

        二, 路由的区别

        一般来说,浏览器端的路由分为两种:

        1. hash 路由,特征是\` url\` 后面会有 # 号,如\` baidu.com/#foo/bar/baz\`。
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c,a as n,b as s,d as t,e as i}from"./app-_Oi5YZFn.js";const l={},u=i(`

        一, 路由的本质

        简单来说,浏览器端路由其实并不是真实的网页跳转(和服务器没有任何交互),而是纯粹在浏览器端发生的一系列行为,本质上来说前端路由就是:

        对 url 进行改变和监听,来让某个 dom 节点显示对应的视图

        二, 路由的区别

        一般来说,浏览器端的路由分为两种:

        1. hash 路由,特征是\` url\` 后面会有 # 号,如\` baidu.com/#foo/bar/baz\`。
         2. history路由,\`url\` 和普通路径没有差异。如 \`baidu.com/foo/bar/baz\`。
         

        我们已经讲过了路由的本质,那么要实现前端路由,需要解决两个核心:

        1. 如何改变 URL 却不引起页面刷新?
         2. 如何检测 URL 变化了?,并且展示对应的组件
        diff --git a/assets/vue3Emit.html-DJy1ivmL.js b/assets/vue3Emit.html-C3GB9oZp.js
        similarity index 99%
        rename from assets/vue3Emit.html-DJy1ivmL.js
        rename to assets/vue3Emit.html-C3GB9oZp.js
        index 8869b3aa81..1e1e1b7fae 100644
        --- a/assets/vue3Emit.html-DJy1ivmL.js
        +++ b/assets/vue3Emit.html-C3GB9oZp.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

        vue3 使用组合式api时如何进行父子组件通信

        // 1.在vue组件中定义事件
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

        vue3 使用组合式api时如何进行父子组件通信

        // 1.在vue组件中定义事件
         const emit = defineEmits(['update:hospitalDept'])
         //2. 调用userXXXControl时,把emit当做参数传递进去,useCreateHospitalDeptControl(emit)实际也就类似一个函数
         const {
        diff --git a/assets/vue3LifeTime.html-CgbHBDaz.js b/assets/vue3LifeTime.html-L4zQOdiA.js
        similarity index 99%
        rename from assets/vue3LifeTime.html-CgbHBDaz.js
        rename to assets/vue3LifeTime.html-L4zQOdiA.js
        index fc09212c2e..8e60cb84d8 100644
        --- a/assets/vue3LifeTime.html-CgbHBDaz.js
        +++ b/assets/vue3LifeTime.html-L4zQOdiA.js
        @@ -1,4 +1,4 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c,a as n,b as a,d as l,e as s}from"./app-CVMfKeWw.js";const i={},u=s(`

        一,vue生命周期钩子函数

        d0c8a786c9177f3e668177cd4bfcf9c19e3d5676

        1.1 beforeCreate

        • 该函数执行在组件创建、数据观测 (data observer) 和 event/watcher 事件配置之前,实例初始化之后被调用。

        • 在该阶段组件未创建不能访问数据,组件中的data,ref均为undefined。

        1.2 created

        • 该函数在组件创建完成后被立即调用,在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。

        • 但是还未渲染成HTML模板,组件中的data对象已经存在,可以对data进行操作了,即可以访问数据,发请求,ref依旧是undefined,挂载阶段还没开始,$el 属性目前尚不可用。

        • 般我们可以将对数据的初始化和初始化页面的请求放到里面,结束loading。

        1.3 beforeMount

        • 该函数在组件挂载之前,在该阶段页面上还没渲染出HTML元素,data初始化完成,ref依旧不可以操作,相关的 render 函数首次被调用。

        • 可以访问数据,编译模板结束,虚拟dom已经存在

        • 该钩子在\`服务器端渲染期间不被调用。

        1.4 mounted

        • 该函数是页面完成挂载之后执行的,这时 el 被新创建的 vm.$el 替换了,就可以操作ref了,一般会用于将组件初始时请求数据的方法放到这里面,filter也是在这里生效。

        • 如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。

        • 可以拿到数据和节点,实例被挂载后调用。

        注意 :mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick:

        mounted: function () {
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as p,o,c,a as n,b as a,d as l,e as s}from"./app-_Oi5YZFn.js";const i={},u=s(`

        一,vue生命周期钩子函数

        d0c8a786c9177f3e668177cd4bfcf9c19e3d5676

        1.1 beforeCreate

        • 该函数执行在组件创建、数据观测 (data observer) 和 event/watcher 事件配置之前,实例初始化之后被调用。

        • 在该阶段组件未创建不能访问数据,组件中的data,ref均为undefined。

        1.2 created

        • 该函数在组件创建完成后被立即调用,在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。

        • 但是还未渲染成HTML模板,组件中的data对象已经存在,可以对data进行操作了,即可以访问数据,发请求,ref依旧是undefined,挂载阶段还没开始,$el 属性目前尚不可用。

        • 般我们可以将对数据的初始化和初始化页面的请求放到里面,结束loading。

        1.3 beforeMount

        • 该函数在组件挂载之前,在该阶段页面上还没渲染出HTML元素,data初始化完成,ref依旧不可以操作,相关的 render 函数首次被调用。

        • 可以访问数据,编译模板结束,虚拟dom已经存在

        • 该钩子在\`服务器端渲染期间不被调用。

        1.4 mounted

        • 该函数是页面完成挂载之后执行的,这时 el 被新创建的 vm.$el 替换了,就可以操作ref了,一般会用于将组件初始时请求数据的方法放到这里面,filter也是在这里生效。

        • 如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。

        • 可以拿到数据和节点,实例被挂载后调用。

        注意 :mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick:

        mounted: function () {
           this.$nextTick(function () {
             // Code that will run only after the
             // entire view has been rendered
        diff --git a/assets/vueExtend.html-Dj4ZUdmc.js b/assets/vueExtend.html-DnRRc5Za.js
        similarity index 99%
        rename from assets/vueExtend.html-Dj4ZUdmc.js
        rename to assets/vueExtend.html-DnRRc5Za.js
        index d1d63f2967..38bb842c38 100644
        --- a/assets/vueExtend.html-Dj4ZUdmc.js
        +++ b/assets/vueExtend.html-DnRRc5Za.js
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

        一,vue.extend基本概念和用法

        使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

        data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数。

        <div id="mount-point"></div>
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

        一,vue.extend基本概念和用法

        使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

        data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数。

        <div id="mount-point"></div>
         
        // 创建构造器
         var Profile = Vue.extend({
           template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
        diff --git "a/assets/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html-DuNb2TND.js" "b/assets/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html-CH2QvxT3.js"
        similarity index 99%
        rename from "assets/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html-DuNb2TND.js"
        rename to "assets/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html-CH2QvxT3.js"
        index 06e4be805c..b9cb6e2112 100644
        --- "a/assets/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html-DuNb2TND.js"
        +++ "b/assets/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html-CH2QvxT3.js"
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const e={},i=t(`

        1.需求

            1. windows下没有像linux的\`nohup xxx &\`的后台启动命令,springboot项目就无法后台运行
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const e={},i=t(`

        1.需求

            1. windows下没有像linux的\`nohup xxx &\`的后台启动命令,springboot项目就无法后台运行
             2. 把任意一个命令注册为服务
         

        2. 实现方式

        2.1 使用nssm

        使用nssm把一个exe命令注册为服务可以直接执行nssm install ServiceName "Path\\to\\your\\executable" "arguments",arguments可以没有,注册 为服务,在服务管理就可以看到,也可以直接在命令行启动ServiceName start

        2.2 使用winsw

        winsw也差不多,不过他要配置一个xml文件

        
         <!-- 这里的根元素必须是service节点 -->
        diff --git a/assets/wrapper.html-Do0j3LhC.js b/assets/wrapper.html-aaFCqiMF.js
        similarity index 96%
        rename from assets/wrapper.html-Do0j3LhC.js
        rename to assets/wrapper.html-aaFCqiMF.js
        index 6cc0753177..095578007a 100644
        --- a/assets/wrapper.html-Do0j3LhC.js
        +++ b/assets/wrapper.html-aaFCqiMF.js
        @@ -1 +1 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as r}from"./app-CVMfKeWw.js";const a={};function o(n,p){return t(),r("div")}const m=e(a,[["render",o],["__file","wrapper.html.vue"]]),l=JSON.parse('{"path":"/java/other/gradle/wrapper.html","title":"Wrapper","lang":"zh-CN","frontmatter":{"title":"Wrapper","date":"2022-10-18T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/gradle/wrapper.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Wrapper"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-10-18T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Wrapper\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-10-18T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1666600159000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.04,"words":11},"filePathRelative":"java/other/gradle/wrapper.md","localizedDate":"2022年10月18日","excerpt":""}');export{m as comp,l as data};
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as r}from"./app-_Oi5YZFn.js";const a={};function o(n,p){return t(),r("div")}const m=e(a,[["render",o],["__file","wrapper.html.vue"]]),l=JSON.parse('{"path":"/java/other/gradle/wrapper.html","title":"Wrapper","lang":"zh-CN","frontmatter":{"title":"Wrapper","date":"2022-10-18T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/gradle/wrapper.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"Wrapper"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2022-10-18T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"Wrapper\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2022-10-18T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[],"git":{"createdTime":1666600159000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1},{"name":"ChenSino","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":0.04,"words":11},"filePathRelative":"java/other/gradle/wrapper.md","localizedDate":"2022年10月18日","excerpt":""}');export{m as comp,l as data};
        diff --git a/assets/wsl.html-DoVf1_uE.js b/assets/wsl.html-DiCvzzYG.js
        similarity index 99%
        rename from assets/wsl.html-DoVf1_uE.js
        rename to assets/wsl.html-DiCvzzYG.js
        index 753055642f..595074c55d 100644
        --- a/assets/wsl.html-DoVf1_uE.js
        +++ b/assets/wsl.html-DiCvzzYG.js
        @@ -1,4 +1,4 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as s,e as a}from"./app-CVMfKeWw.js";const t={},o=a(`

        一、wsl安装

        二、wsl使用

        2.1 更换国内镜像源

        2.2 从windows进入wsl

        在我的电脑地址栏直接输入:\\wsl$ 即可进入wsl文件管理系统。

        C:\\Users\\xiaoPeng\\AppData\\Local\\Packages\\CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc\\LocalState\\rootfs

        2.3 wsl域名解析慢的问题

        域名解析慢会导致apt命令无法在线安装软件,更新等

        查看/etc/resolv.conf发现nameserver是默认的172.x.x.x,即使你修改了,重启终端他也会被还原,此文件的注释也说的很明白了,如下

        # This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as s,e as a}from"./app-_Oi5YZFn.js";const t={},o=a(`

        一、wsl安装

        二、wsl使用

        2.1 更换国内镜像源

        2.2 从windows进入wsl

        在我的电脑地址栏直接输入:\\wsl$ 即可进入wsl文件管理系统。

        C:\\Users\\xiaoPeng\\AppData\\Local\\Packages\\CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc\\LocalState\\rootfs

        2.3 wsl域名解析慢的问题

        域名解析慢会导致apt命令无法在线安装软件,更新等

        查看/etc/resolv.conf发现nameserver是默认的172.x.x.x,即使你修改了,重启终端他也会被还原,此文件的注释也说的很明白了,如下

        # This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
         # [network]
         # generateResolvConf = false
         nameserver 172.27.96.1
        diff --git a/assets/x86_openwrt.html-Rfp5hWIu.js b/assets/x86_openwrt.html-CASth0J6.js
        similarity index 98%
        rename from assets/x86_openwrt.html-Rfp5hWIu.js
        rename to assets/x86_openwrt.html-CASth0J6.js
        index 5167361dc2..a4c907ea3a 100644
        --- a/assets/x86_openwrt.html-Rfp5hWIu.js
        +++ b/assets/x86_openwrt.html-CASth0J6.js
        @@ -1 +1 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o,c as a,a as e,b as i,d as p,e as c}from"./app-CVMfKeWw.js";const s={},m=c('

        vmware中使用openwrt做旁路由网关

        注意!!

        在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下: vmware中其他系统可以正常使用openwrt做旁路由网关,但是局域网内其他终端(包括宿主机)是无法使用这个openwrt作为网关的。 解决方式: 要么不要用windows系统,要么把宿主机切换为有线连接

        参考

        ',3),l={href:"https://blog.csdn.net/m15151850711/article/details/121848463?spm=1001.2014.3001.5506",target:"_blank",rel:"noopener noreferrer"};function w(d,h){const t=r("ExternalLinkIcon");return o(),a("div",null,[m,e("p",null,[e("a",l,[i("关于vmware搭openwrt旁路由 无线网卡设备无法上网"),p(t)])])])}const u=n(s,[["render",w],["__file","x86_openwrt.html.vue"]]),g=JSON.parse('{"path":"/myserver/x86_openwrt.html","title":"旁路由网关","lang":"zh-CN","frontmatter":{"title":"旁路由网关","date":"2024-03-29T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"vmware中使用openwrt做旁路由网关 注意!! 在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下: vmware中其他系统可以正常使用openwrt做旁路由网关,但是局域网内其他终端(包括宿主机)是无法使用这个openwrt作为网关的。 解决方式: 要么不要用w...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/myserver/x86_openwrt.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"旁路由网关"}],["meta",{"property":"og:description","content":"vmware中使用openwrt做旁路由网关 注意!! 在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下: vmware中其他系统可以正常使用openwrt做旁路由网关,但是局域网内其他终端(包括宿主机)是无法使用这个openwrt作为网关的。 解决方式: 要么不要用w..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-29T09:24:34.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-03-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-29T09:24:34.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"旁路由网关\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-03-29T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-29T09:24:34.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":2,"title":"vmware中使用openwrt做旁路由网关","slug":"vmware中使用openwrt做旁路由网关","link":"#vmware中使用openwrt做旁路由网关","children":[]},{"level":2,"title":"参考","slug":"参考","link":"#参考","children":[]}],"git":{"createdTime":1711704274000,"updatedTime":1711704274000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.57,"words":172},"filePathRelative":"myserver/x86_openwrt.md","localizedDate":"2024年3月29日","excerpt":"

        vmware中使用openwrt做旁路由网关

        \\n
        \\n

        注意!!

        \\n

        在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下:\\nvmware中其他系统可以正常使用openwrt做旁路由网关,但是局域网内其他终端(包括宿主机)是无法使用这个openwrt作为网关的。\\n解决方式:\\n要么不要用windows系统,要么把宿主机切换为有线连接

        \\n
        \\n

        参考

        ","autoDesc":true}');export{u as comp,g as data}; +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r,o,c as a,a as e,b as i,d as p,e as c}from"./app-_Oi5YZFn.js";const s={},m=c('

        vmware中使用openwrt做旁路由网关

        注意!!

        在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下: vmware中其他系统可以正常使用openwrt做旁路由网关,但是局域网内其他终端(包括宿主机)是无法使用这个openwrt作为网关的。 解决方式: 要么不要用windows系统,要么把宿主机切换为有线连接

        参考

        ',3),l={href:"https://blog.csdn.net/m15151850711/article/details/121848463?spm=1001.2014.3001.5506",target:"_blank",rel:"noopener noreferrer"};function w(d,h){const t=r("ExternalLinkIcon");return o(),a("div",null,[m,e("p",null,[e("a",l,[i("关于vmware搭openwrt旁路由 无线网卡设备无法上网"),p(t)])])])}const u=n(s,[["render",w],["__file","x86_openwrt.html.vue"]]),g=JSON.parse('{"path":"/myserver/x86_openwrt.html","title":"旁路由网关","lang":"zh-CN","frontmatter":{"title":"旁路由网关","date":"2024-03-29T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"vmware中使用openwrt做旁路由网关 注意!! 在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下: vmware中其他系统可以正常使用openwrt做旁路由网关,但是局域网内其他终端(包括宿主机)是无法使用这个openwrt作为网关的。 解决方式: 要么不要用w...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/myserver/x86_openwrt.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"旁路由网关"}],["meta",{"property":"og:description","content":"vmware中使用openwrt做旁路由网关 注意!! 在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下: vmware中其他系统可以正常使用openwrt做旁路由网关,但是局域网内其他终端(包括宿主机)是无法使用这个openwrt作为网关的。 解决方式: 要么不要用w..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-29T09:24:34.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-03-29T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-29T09:24:34.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"旁路由网关\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-03-29T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-29T09:24:34.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":2,"title":"vmware中使用openwrt做旁路由网关","slug":"vmware中使用openwrt做旁路由网关","link":"#vmware中使用openwrt做旁路由网关","children":[]},{"level":2,"title":"参考","slug":"参考","link":"#参考","children":[]}],"git":{"createdTime":1711704274000,"updatedTime":1711704274000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.57,"words":172},"filePathRelative":"myserver/x86_openwrt.md","localizedDate":"2024年3月29日","excerpt":"

        vmware中使用openwrt做旁路由网关

        \\n
        \\n

        注意!!

        \\n

        在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下:\\nvmware中其他系统可以正常使用openwrt做旁路由网关,但是局域网内其他终端(包括宿主机)是无法使用这个openwrt作为网关的。\\n解决方式:\\n要么不要用windows系统,要么把宿主机切换为有线连接

        \\n
        \\n

        参考

        ","autoDesc":true}');export{u as comp,g as data}; diff --git "a/assets/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html-H7SaEEcF.js" "b/assets/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html-CpfYP_fL.js" similarity index 99% rename from "assets/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html-H7SaEEcF.js" rename to "assets/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html-CpfYP_fL.js" index 7e1633b77c..800e38e13b 100644 --- "a/assets/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html-H7SaEEcF.js" +++ "b/assets/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html-CpfYP_fL.js" @@ -1 +1 @@ -import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as e,e as i}from"./app-CVMfKeWw.js";const o={},n=i('

        1、Spring系列

        1.1 坑1 spring.factories使用方式变更

        2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,同时在此文件中写入类全限定名字,多个类,每行一个,不用符号隔开

        1.2 坑2 Spring Security OAuth2已被新版本security弃用

        Spring Security OAuth2是为构建OAuth2系统提供安全支持的Spring Security子项目。在Spring Security OAuth2中,您可以使用Authorization Server来创建和管理授权服务器,同时也可以使用Resource Server来保护受保护的资源。然而,从Spring Security 5.3开始,Spring Security OAuth2已被弃用,取而代之的是Spring Authorization Server和Spring Security 5的核心Oauth2支持。

        与Spring Security OAuth2相比,Spring Authorization Server是一个独立的模块,它提供了完整的授权服务器,无需其他模块或库的支持,可以轻松地与现有的Spring Security应用程序集成。此外,Spring Authorization Server不依赖于Spring Security的其他功能,它有自己独立的API和文档。

        同时,Spring Authorization Server在实现OAuth2规范方面更加严格,对于OAuth2规范中的各项要求都有很好的支持,并且提供了更加灵活和易于扩展的配置方式。在使用Spring Authorization Server时,您需要将spring-security-oauth2依赖关系修改为spring-security-oauth2-authorization-server。

        总之,如果您想在您的应用程序中实现OAuth2,并且正在使用Spring Security 5.3或更高版本,那么您应该使用Spring Authorization Server而不是Spring Security OAuth2。

        2、Oauth2.0

        2.1 移除password授权类型

        oauth2.0的最新规范已经移除了grant_type类型为password的,并且在spring-authorization-server 明确标记password类型为废弃,强行使用会报grant_type不对。因为新版本Security使用的是spring-authorization-server,所以也意味着在新版本security无法使用password授权类型 z

        ',11),a=[n];function s(p,c){return t(),e("div",null,a)}const g=r(o,[["render",s],["__file","各种框架版本的坑.html.vue"]]),l=JSON.parse('{"path":"/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html","title":"各种版本的坑","lang":"zh-CN","frontmatter":{"title":"各种版本的坑","date":"2023-05-09T00:00:00.000Z","isOriginal":true,"description":"1、Spring系列 1.1 坑1 spring.factories使用方式变更 2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.impor...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"各种版本的坑"}],["meta",{"property":"og:description","content":"1、Spring系列 1.1 坑1 spring.factories使用方式变更 2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.impor..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-05-09T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"各种版本的坑\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-05-09T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、Spring系列","slug":"_1、spring系列","link":"#_1、spring系列","children":[{"level":3,"title":"1.1 坑1 spring.factories使用方式变更","slug":"_1-1-坑1-spring-factories使用方式变更","link":"#_1-1-坑1-spring-factories使用方式变更","children":[]},{"level":3,"title":"1.2 坑2 Spring Security OAuth2已被新版本security弃用","slug":"_1-2-坑2-spring-security-oauth2已被新版本security弃用","link":"#_1-2-坑2-spring-security-oauth2已被新版本security弃用","children":[]}]},{"level":2,"title":"2、Oauth2.0","slug":"_2、oauth2-0","link":"#_2、oauth2-0","children":[{"level":3,"title":"2.1 移除password授权类型","slug":"_2-1-移除password授权类型","link":"#_2-1-移除password授权类型","children":[]}]}],"git":{"createdTime":1683634730000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenxk","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":1.62,"words":487},"filePathRelative":"java/other/locateproblem/各种框架版本的坑.md","localizedDate":"2023年5月9日","excerpt":"

        1、Spring系列

        \\n

        1.1 坑1 spring.factories使用方式变更

        \\n

        2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,同时在此文件中写入类全限定名字,多个类,每行一个,不用符号隔开

        \\n

        1.2 坑2 Spring Security OAuth2已被新版本security弃用

        ","autoDesc":true}');export{g as comp,l as data}; +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as e,e as i}from"./app-_Oi5YZFn.js";const o={},n=i('

        1、Spring系列

        1.1 坑1 spring.factories使用方式变更

        2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,同时在此文件中写入类全限定名字,多个类,每行一个,不用符号隔开

        1.2 坑2 Spring Security OAuth2已被新版本security弃用

        Spring Security OAuth2是为构建OAuth2系统提供安全支持的Spring Security子项目。在Spring Security OAuth2中,您可以使用Authorization Server来创建和管理授权服务器,同时也可以使用Resource Server来保护受保护的资源。然而,从Spring Security 5.3开始,Spring Security OAuth2已被弃用,取而代之的是Spring Authorization Server和Spring Security 5的核心Oauth2支持。

        与Spring Security OAuth2相比,Spring Authorization Server是一个独立的模块,它提供了完整的授权服务器,无需其他模块或库的支持,可以轻松地与现有的Spring Security应用程序集成。此外,Spring Authorization Server不依赖于Spring Security的其他功能,它有自己独立的API和文档。

        同时,Spring Authorization Server在实现OAuth2规范方面更加严格,对于OAuth2规范中的各项要求都有很好的支持,并且提供了更加灵活和易于扩展的配置方式。在使用Spring Authorization Server时,您需要将spring-security-oauth2依赖关系修改为spring-security-oauth2-authorization-server。

        总之,如果您想在您的应用程序中实现OAuth2,并且正在使用Spring Security 5.3或更高版本,那么您应该使用Spring Authorization Server而不是Spring Security OAuth2。

        2、Oauth2.0

        2.1 移除password授权类型

        oauth2.0的最新规范已经移除了grant_type类型为password的,并且在spring-authorization-server 明确标记password类型为废弃,强行使用会报grant_type不对。因为新版本Security使用的是spring-authorization-server,所以也意味着在新版本security无法使用password授权类型 z

        ',11),a=[n];function s(p,c){return t(),e("div",null,a)}const g=r(o,[["render",s],["__file","各种框架版本的坑.html.vue"]]),l=JSON.parse('{"path":"/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html","title":"各种版本的坑","lang":"zh-CN","frontmatter":{"title":"各种版本的坑","date":"2023-05-09T00:00:00.000Z","isOriginal":true,"description":"1、Spring系列 1.1 坑1 spring.factories使用方式变更 2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.impor...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"各种版本的坑"}],["meta",{"property":"og:description","content":"1、Spring系列 1.1 坑1 spring.factories使用方式变更 2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.impor..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-05-09T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"各种版本的坑\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-05-09T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":2,"title":"1、Spring系列","slug":"_1、spring系列","link":"#_1、spring系列","children":[{"level":3,"title":"1.1 坑1 spring.factories使用方式变更","slug":"_1-1-坑1-spring-factories使用方式变更","link":"#_1-1-坑1-spring-factories使用方式变更","children":[]},{"level":3,"title":"1.2 坑2 Spring Security OAuth2已被新版本security弃用","slug":"_1-2-坑2-spring-security-oauth2已被新版本security弃用","link":"#_1-2-坑2-spring-security-oauth2已被新版本security弃用","children":[]}]},{"level":2,"title":"2、Oauth2.0","slug":"_2、oauth2-0","link":"#_2、oauth2-0","children":[{"level":3,"title":"2.1 移除password授权类型","slug":"_2-1-移除password授权类型","link":"#_2-1-移除password授权类型","children":[]}]}],"git":{"createdTime":1683634730000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2},{"name":"chenxk","email":"chenxk@sonoscape.net","commits":1}]},"readingTime":{"minutes":1.62,"words":487},"filePathRelative":"java/other/locateproblem/各种框架版本的坑.md","localizedDate":"2023年5月9日","excerpt":"

        1、Spring系列

        \\n

        1.1 坑1 spring.factories使用方式变更

        \\n

        2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,同时在此文件中写入类全限定名字,多个类,每行一个,不用符号隔开

        \\n

        1.2 坑2 Spring Security OAuth2已被新版本security弃用

        ","autoDesc":true}');export{g as comp,l as data}; diff --git "a/assets/\345\220\204\347\247\215\351\227\256\351\242\230.html-DVSHYPDc.js" "b/assets/\345\220\204\347\247\215\351\227\256\351\242\230.html-BVVorpF6.js" similarity index 99% rename from "assets/\345\220\204\347\247\215\351\227\256\351\242\230.html-DVSHYPDc.js" rename to "assets/\345\220\204\347\247\215\351\227\256\351\242\230.html-BVVorpF6.js" index c4b4727e36..5a7568066d 100644 --- "a/assets/\345\220\204\347\247\215\351\227\256\351\242\230.html-DVSHYPDc.js" +++ "b/assets/\345\220\204\347\247\215\351\227\256\351\242\230.html-BVVorpF6.js" @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-CVMfKeWw.js";const e={},p=t(`

        1. springcloudgateway负载均衡配置不生效

        配置如下,使用lb/serviceid配置负载均衡,一直报错,服务找不到

        spring:
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as a,c as s,e as t}from"./app-_Oi5YZFn.js";const e={},p=t(`

        1. springcloudgateway负载均衡配置不生效

        配置如下,使用lb/serviceid配置负载均衡,一直报错,服务找不到

        spring:
           cloud:
             gateway:
               routes:
        diff --git "a/assets/\345\220\215\350\257\215\350\247\243\351\207\212.html-D6DWrpIk.js" "b/assets/\345\220\215\350\257\215\350\247\243\351\207\212.html-B5GJmRJ1.js"
        similarity index 98%
        rename from "assets/\345\220\215\350\257\215\350\247\243\351\207\212.html-D6DWrpIk.js"
        rename to "assets/\345\220\215\350\257\215\350\247\243\351\207\212.html-B5GJmRJ1.js"
        index 17fd0cdce9..9364366dd8 100644
        --- "a/assets/\345\220\215\350\257\215\350\247\243\351\207\212.html-D6DWrpIk.js"
        +++ "b/assets/\345\220\215\350\257\215\350\247\243\351\207\212.html-B5GJmRJ1.js"
        @@ -1 +1 @@
        -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as a,e as o}from"./app-CVMfKeWw.js";const n={},i=o('

        vm中说的字面量和符号引用是什么

        在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。

        字面量(Literal)

        字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。

        • 整数字面量: 100, -50
        • 浮点数字面量: 3.14, -0.001
        • 字符字面量: 'A', 'z'
        • 字符串字面量: "Hello, World!", "Java"

        在Java编译后的字节码中,字面量通常被存储在常量池(constant pool)中,并且在运行时直接使用这些值。

        符号引用(Symbolic Reference)

        符号引用是在Java编译过程中生成的一种引用,它表示的是某个类、接口、方法或字段的符号名称,而不是直接的内存地址或值。在编译时,Java编译器并不直接解析符号引用,而是将这些符号引用保留在字节码中,直到程序运行时才解析。

        常见的符号引用有:

        • 类或接口的全限定名: java/lang/String

        • 字段名称: System.out

        • 方法名称和描述符: java/io/PrintStream.println:(Ljava/lang/String;)V

        • 这些符号引用在运行时通过类加载器和链接器解析为实际的内存地址或对象引用。

        区别与联系

        • 字面量直接对应实际的数据值,在运行时不需要进一步解析。
        • 符号引用是对类、方法或字段的间接引用,在运行时需要解析以获取实际的内存地址或对象引用。

        总结来说,字面量是编译时直接确定的常量值,而符号引用则是编译时生成的一种间接引用,需在运行时解析以获取具体的内存地址或实例。

        ',13),l=[i];function r(p,c){return e(),a("div",null,l)}const v=t(n,[["render",r],["__file","名词解释.html.vue"]]),h=JSON.parse(`{"path":"/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html","title":"名词解释","lang":"zh-CN","frontmatter":{"title":"名词解释","author":"chenkun","publish":true,"description":"vm中说的字面量和符号引用是什么 在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。 字面量(Literal) 字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。 整数字面量: 100, -50 ...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"名词解释"}],["meta",{"property":"og:description","content":"vm中说的字面量和符号引用是什么 在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。 字面量(Literal) 字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。 整数字面量: 100, -50 ..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-08-29T00:28:26.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:modified_time","content":"2024-08-29T00:28:26.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"名词解释\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-08-29T00:28:26.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":3,"title":"vm中说的字面量和符号引用是什么","slug":"vm中说的字面量和符号引用是什么","link":"#vm中说的字面量和符号引用是什么","children":[]}],"git":{"createdTime":1724891306000,"updatedTime":1724891306000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":1.67,"words":502},"filePathRelative":"java/jvm/名词解释.md","localizedDate":"2024年8月29日","excerpt":"

        vm中说的字面量和符号引用是什么

        \\n

        在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。

        \\n

        字面量(Literal)

        \\n

        字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。

        \\n
          \\n
        • 整数字面量: 100, -50
        • \\n
        • 浮点数字面量: 3.14, -0.001
        • \\n
        • 字符字面量: 'A', 'z'
        • \\n
        • 字符串字面量: \\"Hello, World!\\", \\"Java\\"
        • \\n
        ","autoDesc":true}`);export{v as comp,h as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as e,c as a,e as o}from"./app-_Oi5YZFn.js";const n={},i=o('

        vm中说的字面量和符号引用是什么

        在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。

        字面量(Literal)

        字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。

        • 整数字面量: 100, -50
        • 浮点数字面量: 3.14, -0.001
        • 字符字面量: 'A', 'z'
        • 字符串字面量: "Hello, World!", "Java"

        在Java编译后的字节码中,字面量通常被存储在常量池(constant pool)中,并且在运行时直接使用这些值。

        符号引用(Symbolic Reference)

        符号引用是在Java编译过程中生成的一种引用,它表示的是某个类、接口、方法或字段的符号名称,而不是直接的内存地址或值。在编译时,Java编译器并不直接解析符号引用,而是将这些符号引用保留在字节码中,直到程序运行时才解析。

        常见的符号引用有:

        • 类或接口的全限定名: java/lang/String

        • 字段名称: System.out

        • 方法名称和描述符: java/io/PrintStream.println:(Ljava/lang/String;)V

        • 这些符号引用在运行时通过类加载器和链接器解析为实际的内存地址或对象引用。

        区别与联系

        • 字面量直接对应实际的数据值,在运行时不需要进一步解析。
        • 符号引用是对类、方法或字段的间接引用,在运行时需要解析以获取实际的内存地址或对象引用。

        总结来说,字面量是编译时直接确定的常量值,而符号引用则是编译时生成的一种间接引用,需在运行时解析以获取具体的内存地址或实例。

        ',13),l=[i];function r(p,c){return e(),a("div",null,l)}const v=t(n,[["render",r],["__file","名词解释.html.vue"]]),h=JSON.parse(`{"path":"/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html","title":"名词解释","lang":"zh-CN","frontmatter":{"title":"名词解释","author":"chenkun","publish":true,"description":"vm中说的字面量和符号引用是什么 在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。 字面量(Literal) 字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。 整数字面量: 100, -50 ...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"名词解释"}],["meta",{"property":"og:description","content":"vm中说的字面量和符号引用是什么 在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。 字面量(Literal) 字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。 整数字面量: 100, -50 ..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-08-29T00:28:26.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:modified_time","content":"2024-08-29T00:28:26.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"名词解释\\",\\"image\\":[\\"\\"],\\"dateModified\\":\\"2024-08-29T00:28:26.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":3,"title":"vm中说的字面量和符号引用是什么","slug":"vm中说的字面量和符号引用是什么","link":"#vm中说的字面量和符号引用是什么","children":[]}],"git":{"createdTime":1724891306000,"updatedTime":1724891306000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":1.67,"words":502},"filePathRelative":"java/jvm/名词解释.md","localizedDate":"2024年8月29日","excerpt":"

        vm中说的字面量和符号引用是什么

        \\n

        在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。

        \\n

        字面量(Literal)

        \\n

        字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。

        \\n
          \\n
        • 整数字面量: 100, -50
        • \\n
        • 浮点数字面量: 3.14, -0.001
        • \\n
        • 字符字面量: 'A', 'z'
        • \\n
        • 字符串字面量: \\"Hello, World!\\", \\"Java\\"
        • \\n
        ","autoDesc":true}`);export{v as comp,h as data}; diff --git "a/assets/\345\256\211\350\243\205\351\227\256\351\242\230.html-BUB5ckKH.js" "b/assets/\345\256\211\350\243\205\351\227\256\351\242\230.html-B-h8Zfob.js" similarity index 97% rename from "assets/\345\256\211\350\243\205\351\227\256\351\242\230.html-BUB5ckKH.js" rename to "assets/\345\256\211\350\243\205\351\227\256\351\242\230.html-B-h8Zfob.js" index 22bacef5ea..a8f302767f 100644 --- "a/assets/\345\256\211\350\243\205\351\227\256\351\242\230.html-BUB5ckKH.js" +++ "b/assets/\345\256\211\350\243\205\351\227\256\351\242\230.html-B-h8Zfob.js" @@ -1,3 +1,3 @@ -import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as l,c as s,a as e,b as n,d as i,e as r}from"./app-CVMfKeWw.js";const d={},c=e("h3",{id:"背景",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#背景"},[e("span",null,"背景")])],-1),p=e("blockquote",null,[e("p",null,"从nodejs18开始就不支持Centos7了,这是因为centos7的gilbc版本比较低,因此需要安装非官方构建的版本。"),e("p",null,"Note:如果npm安装的包依赖于glibc,那得改用docker或者换操作系统了。")],-1),u=e("h3",{id:"安装非官方的node",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#安装非官方的node"},[e("span",null,"安装非官方的node")])],-1),h=e("br",null,null,-1),m={href:"https://unofficial-builds.nodejs.org/download/release/v18.19.0/",target:"_blank",rel:"noopener noreferrer"},b=r(`
      • 解压安装
        tar -zxvf node-v18.19.0-linux-x64-glibc-217.tar.gz
        +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as a,o as l,c as s,a as e,b as n,d as i,e as r}from"./app-_Oi5YZFn.js";const d={},c=e("h3",{id:"背景",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#背景"},[e("span",null,"背景")])],-1),p=e("blockquote",null,[e("p",null,"从nodejs18开始就不支持Centos7了,这是因为centos7的gilbc版本比较低,因此需要安装非官方构建的版本。"),e("p",null,"Note:如果npm安装的包依赖于glibc,那得改用docker或者换操作系统了。")],-1),u=e("h3",{id:"安装非官方的node",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#安装非官方的node"},[e("span",null,"安装非官方的node")])],-1),h=e("br",null,null,-1),m={href:"https://unofficial-builds.nodejs.org/download/release/v18.19.0/",target:"_blank",rel:"noopener noreferrer"},b=r(`
      • 解压安装
        tar -zxvf node-v18.19.0-linux-x64-glibc-217.tar.gz
         mv node-v18.19.0-linux-x64-glibc-217  node-v18
         
      • `,1);function g(f,_){const t=a("ExternalLinkIcon");return l(),s("div",null,[c,p,u,e("ol",null,[e("li",null,[n("下载"),h,n(" 下载地址"),e("a",m,[n("https://unofficial-builds.nodejs.org/download/release/v18.19.0/"),i(t)])]),b])])}const x=o(d,[["render",g],["__file","安装问题.html.vue"]]),y=JSON.parse('{"path":"/frontweb/nodejs/%E5%AE%89%E8%A3%85%E9%97%AE%E9%A2%98.html","title":"CentOS7安装node18","lang":"zh-CN","frontmatter":{"title":"CentOS7安装node18","date":"2024-08-14T00:00:00.000Z","author":"chenkun","publish":true,"keys":null,"description":"背景 从nodejs18开始就不支持Centos7了,这是因为centos7的gilbc版本比较低,因此需要安装非官方构建的版本。 Note:如果npm安装的包依赖于glibc,那得改用docker或者换操作系统了。 安装非官方的node 下载 下载地址https://unofficial-builds.nodejs.org/download/rele...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/frontweb/nodejs/%E5%AE%89%E8%A3%85%E9%97%AE%E9%A2%98.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"CentOS7安装node18"}],["meta",{"property":"og:description","content":"背景 从nodejs18开始就不支持Centos7了,这是因为centos7的gilbc版本比较低,因此需要安装非官方构建的版本。 Note:如果npm安装的包依赖于glibc,那得改用docker或者换操作系统了。 安装非官方的node 下载 下载地址https://unofficial-builds.nodejs.org/download/rele..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-08-14T06:14:44.000Z"}],["meta",{"property":"article:author","content":"chenkun"}],["meta",{"property":"article:published_time","content":"2024-08-14T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-08-14T06:14:44.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"CentOS7安装node18\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-08-14T00:00:00.000Z\\",\\"dateModified\\":\\"2024-08-14T06:14:44.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chenkun\\"}]}"]]},"headers":[{"level":3,"title":"背景","slug":"背景","link":"#背景","children":[]},{"level":3,"title":"安装非官方的node","slug":"安装非官方的node","link":"#安装非官方的node","children":[]}],"git":{"createdTime":1723616084000,"updatedTime":1723616084000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.39,"words":117},"filePathRelative":"frontweb/nodejs/安装问题.md","localizedDate":"2024年8月14日","excerpt":"

        背景

        \\n
        \\n

        从nodejs18开始就不支持Centos7了,这是因为centos7的gilbc版本比较低,因此需要安装非官方构建的版本。

        \\n

        Note:如果npm安装的包依赖于glibc,那得改用docker或者换操作系统了。

        \\n
        \\n

        安装非官方的node

        \\n
          \\n
        1. 下载
          \\n下载地址https://unofficial-builds.nodejs.org/download/release/v18.19.0/
        2. \\n
        3. 解压安装
          tar -zxvf node-v18.19.0-linux-x64-glibc-217.tar.gz\\nmv node-v18.19.0-linux-x64-glibc-217  node-v18\\n
        4. \\n
        ","autoDesc":true}');export{x as comp,y as data}; diff --git "a/assets/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html-BpLKsWf9.js" "b/assets/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html-CPvB1dQQ.js" similarity index 99% rename from "assets/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html-BpLKsWf9.js" rename to "assets/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html-CPvB1dQQ.js" index dd0a01a383..7558cc5cd9 100644 --- "a/assets/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html-BpLKsWf9.js" +++ "b/assets/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html-CPvB1dQQ.js" @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

        建造者模式

        这种模式太常见了,开源框架中的各种builder都是

        mybatis中

        org.apache.ibatis.mapping.Environment

        public final class Environment {
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

        建造者模式

        这种模式太常见了,开源框架中的各种builder都是

        mybatis中

        org.apache.ibatis.mapping.Environment

        public final class Environment {
           private final String id;
           private final TransactionFactory transactionFactory;
           private final DataSource dataSource;
        diff --git "a/assets/\346\210\252\345\233\276.html-BRM_4DWV.js" "b/assets/\346\210\252\345\233\276.html-DV7uCl6s.js"
        similarity index 98%
        rename from "assets/\346\210\252\345\233\276.html-BRM_4DWV.js"
        rename to "assets/\346\210\252\345\233\276.html-DV7uCl6s.js"
        index 94e2f01195..53ca589845 100644
        --- "a/assets/\346\210\252\345\233\276.html-BRM_4DWV.js"
        +++ "b/assets/\346\210\252\345\233\276.html-DV7uCl6s.js"
        @@ -1,4 +1,4 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-CVMfKeWw.js";const s={},o=a(`

        manjaro-kde为flameshot设置快捷截图

        1. 打开快捷键设置
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-_Oi5YZFn.js";const s={},o=a(`

        manjaro-kde为flameshot设置快捷截图

        1. 打开快捷键设置
         2. 添加命令
         3. 输入\`/usr/bin/flameshot gui\`
         4. 设置一个快捷键
        diff --git "a/assets/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html-DD7Rd6wj.js" "b/assets/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html-BKBwfgmF.js"
        similarity index 99%
        rename from "assets/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html-DD7Rd6wj.js"
        rename to "assets/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html-BKBwfgmF.js"
        index ca890f416b..620a3ecc6a 100644
        --- "a/assets/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html-DD7Rd6wj.js"
        +++ "b/assets/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html-BKBwfgmF.js"
        @@ -1,4 +1,4 @@
        -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as i,c as e,e as d}from"./app-CVMfKeWw.js";const s={},t=d(`

        对应关系

        备注
        固件UEFI/EFIBIOS
        分区格式GPTMBR
        启动引导程序windows:Windows Boot Managerlinux:GRUB

        老版本的组合是BIOS+MBR启动引导,BIOS是写在主板中的程序,老版本的引导方式有诸多限制,

        # BIOS和uefi区别
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as i,c as e,e as d}from"./app-_Oi5YZFn.js";const s={},t=d(`

        对应关系

        备注
        固件UEFI/EFIBIOS
        分区格式GPTMBR
        启动引导程序windows:Windows Boot Managerlinux:GRUB

        老版本的组合是BIOS+MBR启动引导,BIOS是写在主板中的程序,老版本的引导方式有诸多限制,

        # BIOS和uefi区别
         
         BIOS(基本输入输出系统)和UEFI(统一可扩展固件接口)是两种不同的计算机固件接口,它们在启动计算机时起到了至关重要的作用。以下是BIOS和UEFI之间的主要区别:
         
        diff --git "a/assets/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html-UG8-nnQB.js" "b/assets/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html-rdxyp_Zo.js"
        similarity index 99%
        rename from "assets/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html-UG8-nnQB.js"
        rename to "assets/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html-rdxyp_Zo.js"
        index 1821a300cf..2f9f580fa4 100644
        --- "a/assets/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html-UG8-nnQB.js"
        +++ "b/assets/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html-rdxyp_Zo.js"
        @@ -1,4 +1,4 @@
        -import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as n,e}from"./app-CVMfKeWw.js";const p={},t=e(`

        1.备份脚本

        脚本文件: xxx.sh

        #!/bin/bash
        +import{_ as a}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as n,e}from"./app-_Oi5YZFn.js";const p={},t=e(`

        1.备份脚本

        脚本文件: xxx.sh

        #!/bin/bash
         
         #保存备份个数
         number=5
        diff --git "a/assets/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html-2p0UA79e.js" "b/assets/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html-JyFgPLU6.js"
        similarity index 96%
        rename from "assets/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html-2p0UA79e.js"
        rename to "assets/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html-JyFgPLU6.js"
        index 3eba8a27be..d081401606 100644
        --- "a/assets/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html-2p0UA79e.js"
        +++ "b/assets/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html-JyFgPLU6.js"
        @@ -1,2 +1,2 @@
        -import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o,c as i,a as e,b as t,d as c,e as s}from"./app-CVMfKeWw.js";const l={},d=s(`

        问题1. 内网主机把网关指向旁路由无法通过主路由端口转发

            
        +import{_ as r}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o,c as i,a as e,b as t,d as c,e as s}from"./app-_Oi5YZFn.js";const l={},d=s(`

        问题1. 内网主机把网关指向旁路由无法通过主路由端口转发

            
         
        `,2),p={href:"https://blog.xiaoz.org/archives/19182",target:"_blank",rel:"noopener noreferrer"};function m(h,u){const a=n("ExternalLinkIcon");return o(),i("div",null,[d,e("p",null,[t("参考: "),e("a",p,[t("参考"),c(a)])])])}const v=r(l,[["render",m],["__file","旁路由网关.html.vue"]]),y=JSON.parse('{"path":"/myserver/%E6%97%81%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3.html","title":"旁路由网关","lang":"zh-CN","frontmatter":{"title":"旁路由网关","date":"2024-04-02T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"问题1. 内网主机把网关指向旁路由无法通过主路由端口转发 参考: 参考","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/myserver/%E6%97%81%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"旁路由网关"}],["meta",{"property":"og:description","content":"问题1. 内网主机把网关指向旁路由无法通过主路由端口转发 参考: 参考"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-10-08T07:30:29.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-04-02T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-10-08T07:30:29.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"旁路由网关\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-04-02T00:00:00.000Z\\",\\"dateModified\\":\\"2024-10-08T07:30:29.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":3,"title":"问题1. 内网主机把网关指向旁路由无法通过主路由端口转发","slug":"问题1-内网主机把网关指向旁路由无法通过主路由端口转发","link":"#问题1-内网主机把网关指向旁路由无法通过主路由端口转发","children":[]}],"git":{"createdTime":1712114075000,"updatedTime":1728372629000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.16,"words":49},"filePathRelative":"myserver/旁路由网关.md","localizedDate":"2024年4月2日","excerpt":"

        问题1. 内网主机把网关指向旁路由无法通过主路由端口转发

        \\n
            \\n

        参考:\\n参考

        \\n","autoDesc":true}');export{v as comp,y as data}; diff --git "a/assets/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html-DLubeSKF.js" "b/assets/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html-D86ZbVNZ.js" similarity index 99% rename from "assets/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html-DLubeSKF.js" rename to "assets/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html-D86ZbVNZ.js" index 598e135d31..31e3a64d0c 100644 --- "a/assets/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html-DLubeSKF.js" +++ "b/assets/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html-D86ZbVNZ.js" @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},p=e(`

        定义

        模板方法模式(Template Method Pattern)是一种行为型设计模式,定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。这种设计模式是一种控制反转的实现方式。因为高层代码不再确定(控制)算法的处理流程。模板方法模式多用在某些类别的算法中,实现了相同的方法,造成代码的重复。这个设计模式和策略模式很像,不同的是,模板方法会有一些通用的逻辑,而策略模式是整个方法重写。从类的继承结构也可以看出来,模板方法是提供一个抽象类,有一个通用的方法,不通用的逻辑放到子类去实现,而策略模式是子类直接继承自接口,要重写整个方法。

        例子

        mybatis中org.apache.ibatis.executor.BaseExecutor和他的子类是典型的模板方法模式。

        mybatis中类型处理器也是典型的模板方法,具体在org.apache.ibatis.type.BaseTypeHandler#setParameter,如下,看类名就知道和BaseExecutor 一个套路,它实现了setParameter方法,但是仔细看,它调用了setNonNullParameter(ps, i, parameter, jdbcType);,如果用户自定义类类型处理器 最终调用的其实是setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType),这个方法是个抽象的方法,等待 用户实现。也是一个经典的模板方法模式,相同逻辑放在一个方法,不同的部分,调用一个抽象方法,让用户自行实现。

        
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},p=e(`

        定义

        模板方法模式(Template Method Pattern)是一种行为型设计模式,定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。这种设计模式是一种控制反转的实现方式。因为高层代码不再确定(控制)算法的处理流程。模板方法模式多用在某些类别的算法中,实现了相同的方法,造成代码的重复。这个设计模式和策略模式很像,不同的是,模板方法会有一些通用的逻辑,而策略模式是整个方法重写。从类的继承结构也可以看出来,模板方法是提供一个抽象类,有一个通用的方法,不通用的逻辑放到子类去实现,而策略模式是子类直接继承自接口,要重写整个方法。

        例子

        mybatis中org.apache.ibatis.executor.BaseExecutor和他的子类是典型的模板方法模式。

        mybatis中类型处理器也是典型的模板方法,具体在org.apache.ibatis.type.BaseTypeHandler#setParameter,如下,看类名就知道和BaseExecutor 一个套路,它实现了setParameter方法,但是仔细看,它调用了setNonNullParameter(ps, i, parameter, jdbcType);,如果用户自定义类类型处理器 最终调用的其实是setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType),这个方法是个抽象的方法,等待 用户实现。也是一个经典的模板方法模式,相同逻辑放在一个方法,不同的部分,调用一个抽象方法,让用户自行实现。

        
           @Override
           public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
             if (parameter == null) {
        diff --git "a/assets/\347\224\237\345\221\275\345\221\250\346\234\237.html-DmTGecRc.js" "b/assets/\347\224\237\345\221\275\345\221\250\346\234\237.html-CGxeIKAF.js"
        similarity index 99%
        rename from "assets/\347\224\237\345\221\275\345\221\250\346\234\237.html-DmTGecRc.js"
        rename to "assets/\347\224\237\345\221\275\345\221\250\346\234\237.html-CGxeIKAF.js"
        index 85ef7994b2..2cc191ca21 100644
        --- "a/assets/\347\224\237\345\221\275\345\221\250\346\234\237.html-DmTGecRc.js"
        +++ "b/assets/\347\224\237\345\221\275\345\221\250\346\234\237.html-CGxeIKAF.js"
        @@ -1,4 +1,4 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as c,c as o,a as n,b as a,d as t,e as p}from"./app-CVMfKeWw.js";const i={},u=n("h2",{id:"maven的生命周期",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#maven的生命周期"},[n("span",null,"Maven的生命周期")])],-1),g=n("p",null,"There are three built-in build lifecycles: default, clean and site. The default lifecycle handles your project deployment, the clean lifecycle handles project cleaning, while the site lifecycle handles the creation of your project's web site.",-1),k=n("h2",{id:"各个生命周期包含的phase",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#各个生命周期包含的phase"},[n("span",null,"各个生命周期包含的phase")])],-1),d={href:"https://maven.apache.org/ref/3.9.2/maven-core/lifecycles.html",target:"_blank",rel:"noopener noreferrer"},r=p(`

        default lifecycle包含的phase

        <phases>
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as l,o as c,c as o,a as n,b as a,d as t,e as p}from"./app-_Oi5YZFn.js";const i={},u=n("h2",{id:"maven的生命周期",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#maven的生命周期"},[n("span",null,"Maven的生命周期")])],-1),g=n("p",null,"There are three built-in build lifecycles: default, clean and site. The default lifecycle handles your project deployment, the clean lifecycle handles project cleaning, while the site lifecycle handles the creation of your project's web site.",-1),k=n("h2",{id:"各个生命周期包含的phase",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#各个生命周期包含的phase"},[n("span",null,"各个生命周期包含的phase")])],-1),d={href:"https://maven.apache.org/ref/3.9.2/maven-core/lifecycles.html",target:"_blank",rel:"noopener noreferrer"},r=p(`

        default lifecycle包含的phase

        <phases>
           <phase>validate</phase>
           <phase>initialize</phase>
           <phase>generate-sources</phase>
        diff --git "a/assets/\347\240\264\350\247\243\350\275\257\344\273\266.html-CYd3SkT3.js" "b/assets/\347\240\264\350\247\243\350\275\257\344\273\266.html-DO8LI6dF.js"
        similarity index 98%
        rename from "assets/\347\240\264\350\247\243\350\275\257\344\273\266.html-CYd3SkT3.js"
        rename to "assets/\347\240\264\350\247\243\350\275\257\344\273\266.html-DO8LI6dF.js"
        index 64100d6144..25b0509ea6 100644
        --- "a/assets/\347\240\264\350\247\243\350\275\257\344\273\266.html-CYd3SkT3.js"
        +++ "b/assets/\347\240\264\350\247\243\350\275\257\344\273\266.html-DO8LI6dF.js"
        @@ -1,4 +1,4 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as s}from"./app-CVMfKeWw.js";const i={},a=s(`

        smartsvn

        安装过程略......

        创建smartsvn.license,把以下内容复制进去,激活时选择smartsvn.license就可以了

        Name=csdn  
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as s}from"./app-_Oi5YZFn.js";const i={},a=s(`

        smartsvn

        安装过程略......

        创建smartsvn.license,把以下内容复制进去,激活时选择smartsvn.license就可以了

        Name=csdn  
         Address=1337 iNViSiBLE Str.  
         Email=admin@csdn.net  
         FreeUpdatesUntil=2099-09-26  
        diff --git "a/assets/\347\255\226\347\225\245\346\250\241\345\274\217.html-BzaYdrRx.js" "b/assets/\347\255\226\347\225\245\346\250\241\345\274\217.html-1pJjZLuA.js"
        similarity index 99%
        rename from "assets/\347\255\226\347\225\245\346\250\241\345\274\217.html-BzaYdrRx.js"
        rename to "assets/\347\255\226\347\225\245\346\250\241\345\274\217.html-1pJjZLuA.js"
        index b15a738023..c4fc7e6fd9 100644
        --- "a/assets/\347\255\226\347\225\245\346\250\241\345\274\217.html-BzaYdrRx.js"
        +++ "b/assets/\347\255\226\347\225\245\346\250\241\345\274\217.html-1pJjZLuA.js"
        @@ -1,4 +1,4 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-CVMfKeWw.js";const i={},r=a(`

        策略模式【CHATGPT回答】

        策略模式(Strategy Pattern)是一种行为型设计模式,它将一组可相互替换的算法封装成独立的对象,并对外暴露相同的接口,从而使得它们可以根据需要动态地替换,以实现不同的行为。
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as t,c as n,e as a}from"./app-_Oi5YZFn.js";const i={},r=a(`

        策略模式【CHATGPT回答】

        策略模式(Strategy Pattern)是一种行为型设计模式,它将一组可相互替换的算法封装成独立的对象,并对外暴露相同的接口,从而使得它们可以根据需要动态地替换,以实现不同的行为。
         
         策略模式由三个角色组成,分别是:
         
        diff --git "a/assets/\347\275\221\347\273\234\350\256\276\347\275\256.html-DbRAJ3uC.js" "b/assets/\347\275\221\347\273\234\350\256\276\347\275\256.html-CT2typrG.js"
        similarity index 99%
        rename from "assets/\347\275\221\347\273\234\350\256\276\347\275\256.html-DbRAJ3uC.js"
        rename to "assets/\347\275\221\347\273\234\350\256\276\347\275\256.html-CT2typrG.js"
        index f0247d549e..fb83913539 100644
        --- "a/assets/\347\275\221\347\273\234\350\256\276\347\275\256.html-DbRAJ3uC.js"
        +++ "b/assets/\347\275\221\347\273\234\350\256\276\347\275\256.html-CT2typrG.js"
        @@ -1,4 +1,4 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as t,e as i}from"./app-CVMfKeWw.js";const p={},a=i(`

        本篇介绍我的网络配置过程,如何获得公网ip,以及升级我的家庭路由,mesh组网,我的网络是湖北电信500m,光猫一开始是天翼网关3.0, 后来咸鱼买了一个二手的4.0的花了50,曾打电话给电信客服升级4.0的是不是免费,他说199元。 家庭组网章节适合所有用户阅读,公网部分适合程序员朋友参考。

        家庭组网

        为什么要组网

        我家套内93平,一个无线路由器无法覆盖整个房间,起初是在每个房间放一个路由器,每次从一个房间到另一房间都要切换网络,即使手机有
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as t,e as i}from"./app-_Oi5YZFn.js";const p={},a=i(`

        本篇介绍我的网络配置过程,如何获得公网ip,以及升级我的家庭路由,mesh组网,我的网络是湖北电信500m,光猫一开始是天翼网关3.0, 后来咸鱼买了一个二手的4.0的花了50,曾打电话给电信客服升级4.0的是不是免费,他说199元。 家庭组网章节适合所有用户阅读,公网部分适合程序员朋友参考。

        家庭组网

        为什么要组网

        我家套内93平,一个无线路由器无法覆盖整个房间,起初是在每个房间放一个路由器,每次从一个房间到另一房间都要切换网络,即使手机有
         自动切换网络功能,但是总是不丝滑,我想做到wifi切换无感知,所以就生出组网的想法。
         

        家庭网线图

        下图我虽然在每个房间留了网口,但是电信的猫只有3个网口,还有一个是iptv的口,所以我有个房间是用不了(确实有需要的可以加交换机)

        20240322142629

        网上的组网教程比较多,会让你看的眼花缭乱,并且有的组网是要求你预埋网线,并且走线还有要求,比如如果你想通过有线组网,那么你的各个网孔节点要能实现串联,就是“有线回程”,这个是 组网效果最好的,另外还有各种无线组网等的等,但是我的网线布局是从弱电箱分别分一个网口到各个房间,并没有串联,我想90%的家庭装修应该都是这样留的。如果你这种走线有个简单的方法实现组网, 可以使用电信的elink协议实现组网,天翼网关3.0以及4.0都有这个功能,你要做的就是买几个电信路由器直接插在光猫的网口,注意别插到iptv的口了,至于需要几个,根据自己的情况,一般情况下买 两个就够了,我是在咸鱼买的二手路由器,tplink和电信合作的(型号wta301 ax3000 支持wifi6+),60块钱买的。

        所以组网后我家实际拓扑图如下,买了两个二手的wta301总共花了120,实现全屋wifi漫游

        20240322144108

        wta301设置也比较简单,买回来后先重置一下,再用网线到光猫,然后手机连接到它发射的wifi根据提示设置elink组网就行了,设置好以后手机 会掉线,因为天翼网关会把主路由的wifi名字同步过来,两个wta301设置方式一样,组网完成以后整个家里形成一主两副的拓扑结构,一共三个无线 路由器,120平的房间够用了。

        桥接拨号还是光猫拨号

        一开始我不知道光猫下直连几个路由器也可以组网,所以我一直在研究自己的路由器桥接到光猫,然后拨号,但是一直没搞定,一直无法进入天翼网关的超级管理界面, 只能进入普通路由器设置界面。后来无意间从咸鱼买了一个4.0版本的网关,神奇的是它居然可以直接进入管理界面,也不像网上说的超级管理员密码是动态变化的, 就很神奇。后来发现电信本身elink也可以组网,我就没有折腾桥接拨号了

        开通公网

        公网适合有主机的朋友折腾,如果你仅仅是用一个nas,家庭影院简单看看电影,感觉没必要折腾,耗时耗力。
         

        我的网络是湖北电信,打电话给客服要求开通公网,客户问你干嘛,你就说家庭nas,我打完电话就给我开了,包括ipv4和ipv6公网。 开通公网后可以在外面访问到家里的服务,对我个人而言,我有一个小mini主机,在上面部署了各种服务,家庭影院、nas、博客、 chatgpt等等,我开外网就可以随时访问,比较方便,有时候晚上没看完的电影,在公司午休的时候还可以接着看,以下是我的几个 服务:

        20240322145826

        ipv4玩法

        申请一个ddns

        众所周知,ipv4地址是很稀缺的,所以运营商即使给你开通了公网ip,也不会给你一个固定ip,它是一直i动态变化的,隔几天就要变,这对我们外网访问 很不友好,所以你需要一个ddns,它的作用就是把你的动态的ip和一个域名绑定,其实原理也很简单,就是一直轮询,定时发送你的ip到ddns运营商,然后 进行绑定。最简单的ddns就是买个带ddns的路由器,tplink一般都有,或者你自己找一个免费ddns运营商,我用的是dynv6他家的,免费的,自己写了一个 脚本定时获取我本地的ip发送到服务器进行绑定。

        ipv6玩法

        我的ipv6地址是固定的,天翼网关4.0里面可以设置ipv6前缀,然后在客户端(你的服务器)绑定一个ipv6地址,这个我依然用的是ddns,毕竟免费,如果要 绑定到一个标准域名还得备案,麻烦

        要想使用ipv6从外部访问到家里的服务器,需要天翼网关4.0关闭ipv6防火墙,这里切忌要把服务器防火墙打开,不必要的端口一律关闭访问权限,一旦在天翼 网关关闭了防火墙,那么你的电脑如果通过ipv6联网,那么它就暴露在公网之上了,所有的人都可以直接访问你的电脑了,所以如果关闭天翼网关这道防火墙,务必 把电脑本身防火墙开启,不然就是裸奔。

        天翼网关4.0关闭ipv6防火墙的方法(前提是你已经能进入超级管理页面):

        访问你的路由管理页面192.168.1.1,点击快速装维入口,输入超级管理员的账号密码登录:

        20240322151706

        登录后,点击安全---防火墙----ipv6防火墙:

        20240322151834

        正常情况下你的 “使能IPv6防火墙控制转发报文”是勾选上的,我们要给他去掉勾选,但是它是灰色的,无法点击,所以需要懂点前端知识, 也比较简单,按F12(或者ctrl+shift+i)打开浏览器调试界面,使用元素选择器定位到复选框以及“应用”按钮,把disabled去掉,然后就可以点击应用了。

        20240322152203

        然后让天翼网关给客户端分配ipv6地址,设置如下,主要是前缀来源选择static,启用dhcpv6就行了,其他你的界面是什么不重要,不要动它。

        20240322152428

        `,32),s=[a];function c(d,h){return n(),t("div",null,s)}const o=e(p,[["render",c],["__file","网络设置.html.vue"]]),g=JSON.parse('{"path":"/myserver/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE.html","title":"网络设置","lang":"zh-CN","frontmatter":{"title":"网络设置","date":"2024-03-22T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"本篇介绍我的网络配置过程,如何获得公网ip,以及升级我的家庭路由,mesh组网,我的网络是湖北电信500m,光猫一开始是天翼网关3.0, 后来咸鱼买了一个二手的4.0的花了50,曾打电话给电信客服升级4.0的是不是免费,他说199元。 家庭组网章节适合所有用户阅读,公网部分适合程序员朋友参考。 家庭组网 为什么要组网 家庭网线图 下图我虽然在每个房间留...","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/myserver/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"网络设置"}],["meta",{"property":"og:description","content":"本篇介绍我的网络配置过程,如何获得公网ip,以及升级我的家庭路由,mesh组网,我的网络是湖北电信500m,光猫一开始是天翼网关3.0, 后来咸鱼买了一个二手的4.0的花了50,曾打电话给电信客服升级4.0的是不是免费,他说199元。 家庭组网章节适合所有用户阅读,公网部分适合程序员朋友参考。 家庭组网 为什么要组网 家庭网线图 下图我虽然在每个房间留..."}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:image","content":"https://ddns.chensina.cn:29000/afatpig/blog/20240322142629.png"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-11-01T09:56:25.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-03-22T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-11-01T09:56:25.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"网络设置\\",\\"image\\":[\\"https://ddns.chensina.cn:29000/afatpig/blog/20240322142629.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240322144108.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240322145826.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240322151706.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240322151834.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240322152203.png\\",\\"https://ddns.chensina.cn:29000/afatpig/blog/20240322152428.png\\"],\\"datePublished\\":\\"2024-03-22T00:00:00.000Z\\",\\"dateModified\\":\\"2024-11-01T09:56:25.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":2,"title":"家庭组网","slug":"家庭组网","link":"#家庭组网","children":[{"level":3,"title":"为什么要组网","slug":"为什么要组网","link":"#为什么要组网","children":[]},{"level":3,"title":"家庭网线图","slug":"家庭网线图","link":"#家庭网线图","children":[]}]},{"level":2,"title":"桥接拨号还是光猫拨号","slug":"桥接拨号还是光猫拨号","link":"#桥接拨号还是光猫拨号","children":[]},{"level":2,"title":"开通公网","slug":"开通公网","link":"#开通公网","children":[{"level":3,"title":"ipv4玩法","slug":"ipv4玩法","link":"#ipv4玩法","children":[]},{"level":3,"title":"ipv6玩法","slug":"ipv6玩法","link":"#ipv6玩法","children":[]}]}],"git":{"createdTime":1711087730000,"updatedTime":1730454985000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":4}]},"readingTime":{"minutes":5.99,"words":1797},"filePathRelative":"myserver/网络设置.md","localizedDate":"2024年3月22日","excerpt":"

        本篇介绍我的网络配置过程,如何获得公网ip,以及升级我的家庭路由,mesh组网,我的网络是湖北电信500m,光猫一开始是天翼网关3.0,\\n后来咸鱼买了一个二手的4.0的花了50,曾打电话给电信客服升级4.0的是不是免费,他说199元。\\n家庭组网章节适合所有用户阅读,公网部分适合程序员朋友参考。

        \\n

        家庭组网

        \\n

        为什么要组网

        \\n
        我家套内93平,一个无线路由器无法覆盖整个房间,起初是在每个房间放一个路由器,每次从一个房间到另一房间都要切换网络,即使手机有\\n自动切换网络功能,但是总是不丝滑,我想做到wifi切换无感知,所以就生出组网的想法。\\n
        ","autoDesc":true}');export{o as comp,g as data}; diff --git "a/assets/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html-C1dSRMpn.js" "b/assets/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html-dDt_1gUV.js" similarity index 99% rename from "assets/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html-C1dSRMpn.js" rename to "assets/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html-dDt_1gUV.js" index dd950a2bae..9ef1422a51 100644 --- "a/assets/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html-C1dSRMpn.js" +++ "b/assets/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html-dDt_1gUV.js" @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-CVMfKeWw.js";const p={},e=t(`

        需求

        需求是: 在前端页面有很多文本,要求提取文本,根据规则判断文本是否和规则互斥,比如“女”和“前列腺”就是互斥的。当互斥时,需要高亮,同时当鼠标 指在文本上,弹出提示框,提示“女和前列腺规则互斥” 在页面中,通过自定义指令,实现对文本的局部高亮,其中高亮内容是通过接口动态获取。

        实现方案

        利用vue的自定义指令,实现局部高亮。组件调用自定义指令,同时传入高亮内容,指令内部根据传入的高亮内容,对文本进行局部高亮。

        代码

        export default {
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e as t}from"./app-_Oi5YZFn.js";const p={},e=t(`

        需求

        需求是: 在前端页面有很多文本,要求提取文本,根据规则判断文本是否和规则互斥,比如“女”和“前列腺”就是互斥的。当互斥时,需要高亮,同时当鼠标 指在文本上,弹出提示框,提示“女和前列腺规则互斥” 在页面中,通过自定义指令,实现对文本的局部高亮,其中高亮内容是通过接口动态获取。

        实现方案

        利用vue的自定义指令,实现局部高亮。组件调用自定义指令,同时传入高亮内容,指令内部根据传入的高亮内容,对文本进行局部高亮。

        代码

        export default {
           mounted(el, binding) {
             const applyHighlight = () => {
               const wordsToHighlight = binding.value.words
        diff --git "a/assets/\350\207\252\345\273\272nas.html-BhQRnqm0.js" "b/assets/\350\207\252\345\273\272nas.html-D-Ky5W6L.js"
        similarity index 97%
        rename from "assets/\350\207\252\345\273\272nas.html-BhQRnqm0.js"
        rename to "assets/\350\207\252\345\273\272nas.html-D-Ky5W6L.js"
        index 850463a0f1..7a025a9570 100644
        --- "a/assets/\350\207\252\345\273\272nas.html-BhQRnqm0.js"
        +++ "b/assets/\350\207\252\345\273\272nas.html-D-Ky5W6L.js"
        @@ -1 +1 @@
        -import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as a,a as e}from"./app-CVMfKeWw.js";const o={},r=e("h2",{id:"nas介绍",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#nas介绍"},[e("span",null,"nas介绍")])],-1),s=e("p",null,"nas是什么",-1),i=[r,s];function c(p,l){return n(),a("div",null,i)}const d=t(o,[["render",c],["__file","自建nas.html.vue"]]),_=JSON.parse('{"path":"/myserver/%E8%87%AA%E5%BB%BAnas.html","title":"自建nas","lang":"zh-CN","frontmatter":{"title":"自建nas","date":"2024-03-22T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"nas介绍 nas是什么","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/myserver/%E8%87%AA%E5%BB%BAnas.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"自建nas"}],["meta",{"property":"og:description","content":"nas介绍 nas是什么"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T06:08:50.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-03-22T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T06:08:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"自建nas\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-03-22T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T06:08:50.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":2,"title":"nas介绍","slug":"nas介绍","link":"#nas介绍","children":[]}],"git":{"createdTime":1711087730000,"updatedTime":1711087730000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.07,"words":21},"filePathRelative":"myserver/自建nas.md","localizedDate":"2024年3月22日","excerpt":"

        nas介绍

        \\n

        nas是什么

        \\n","autoDesc":true}');export{d as comp,_ as data}; +import{_ as t}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as a,a as e}from"./app-_Oi5YZFn.js";const o={},r=e("h2",{id:"nas介绍",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#nas介绍"},[e("span",null,"nas介绍")])],-1),s=e("p",null,"nas是什么",-1),i=[r,s];function c(p,l){return n(),a("div",null,i)}const d=t(o,[["render",c],["__file","自建nas.html.vue"]]),_=JSON.parse('{"path":"/myserver/%E8%87%AA%E5%BB%BAnas.html","title":"自建nas","lang":"zh-CN","frontmatter":{"title":"自建nas","date":"2024-03-22T00:00:00.000Z","author":"chensino","publish":true,"isOriginal":true,"description":"nas介绍 nas是什么","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/myserver/%E8%87%AA%E5%BB%BAnas.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"自建nas"}],["meta",{"property":"og:description","content":"nas介绍 nas是什么"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T06:08:50.000Z"}],["meta",{"property":"article:author","content":"chensino"}],["meta",{"property":"article:published_time","content":"2024-03-22T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T06:08:50.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"自建nas\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2024-03-22T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T06:08:50.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"chensino\\"}]}"]]},"headers":[{"level":2,"title":"nas介绍","slug":"nas介绍","link":"#nas介绍","children":[]}],"git":{"createdTime":1711087730000,"updatedTime":1711087730000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":1}]},"readingTime":{"minutes":0.07,"words":21},"filePathRelative":"myserver/自建nas.md","localizedDate":"2024年3月22日","excerpt":"

        nas介绍

        \\n

        nas是什么

        \\n","autoDesc":true}');export{d as comp,_ as data}; diff --git "a/assets/\350\243\205\351\245\260\346\250\241\345\274\217.html-NVA1JlXh.js" "b/assets/\350\243\205\351\245\260\346\250\241\345\274\217.html-Bq_Ohlm8.js" similarity index 99% rename from "assets/\350\243\205\351\245\260\346\250\241\345\274\217.html-NVA1JlXh.js" rename to "assets/\350\243\205\351\245\260\346\250\241\345\274\217.html-Bq_Ohlm8.js" index 82c31f7f54..03d08b1513 100644 --- "a/assets/\350\243\205\351\245\260\346\250\241\345\274\217.html-NVA1JlXh.js" +++ "b/assets/\350\243\205\351\245\260\346\250\241\345\274\217.html-Bq_Ohlm8.js" @@ -1,4 +1,4 @@ -import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-CVMfKeWw.js";const t={},o=e(`

        定义

        装饰者模式是一种常用的设计模式,它动态地给一个对象添加一些额外的职责1。装饰者模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为2。

        框架中使用

        mybatis中:

        打开sqlSession时,会创建Executor,最终会进入org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

          public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        +import{_ as n}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as s,c as a,e}from"./app-_Oi5YZFn.js";const t={},o=e(`

        定义

        装饰者模式是一种常用的设计模式,它动态地给一个对象添加一些额外的职责1。装饰者模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为2。

        框架中使用

        mybatis中:

        打开sqlSession时,会创建Executor,最终会进入org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

          public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
             executorType = executorType == null ? defaultExecutorType : executorType;
             Executor executor;
             if (ExecutorType.BATCH == executorType) {
        diff --git "a/assets/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html-WcnUM2LR.js" "b/assets/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html-C6zRijOG.js"
        similarity index 99%
        rename from "assets/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html-WcnUM2LR.js"
        rename to "assets/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html-C6zRijOG.js"
        index 5a3747a53a..d8cc9af606 100644
        --- "a/assets/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html-WcnUM2LR.js"
        +++ "b/assets/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html-C6zRijOG.js"
        @@ -1,4 +1,4 @@
        -import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as t,e as i}from"./app-CVMfKeWw.js";const a={},r=i(`

        定义

        责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它可以将多个对象组合成一条链,并按照事先规定的顺序依次处理请求。每个对象都可以选择处理请求,或者将请求传递给链中的下一个对象。这种模式将请求的发送者和接收者解耦,使得多个对象都有机会处理请求,从而提高了系统的灵活性和可扩展性。
        +import{_ as e}from"./plugin-vue_export-helper-DlAUqK2U.js";import{o as n,c as t,e as i}from"./app-_Oi5YZFn.js";const a={},r=i(`

        定义

        责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它可以将多个对象组合成一条链,并按照事先规定的顺序依次处理请求。每个对象都可以选择处理请求,或者将请求传递给链中的下一个对象。这种模式将请求的发送者和接收者解耦,使得多个对象都有机会处理请求,从而提高了系统的灵活性和可扩展性。
         
         具体来说,责任链模式包含以下几个角色:
         
        diff --git "a/assets/\351\203\250\347\275\262.html-FCiywWGM.js" "b/assets/\351\203\250\347\275\262.html-Cup61qP7.js"
        similarity index 97%
        rename from "assets/\351\203\250\347\275\262.html-FCiywWGM.js"
        rename to "assets/\351\203\250\347\275\262.html-Cup61qP7.js"
        index 7b48503a3b..9a99933ba6 100644
        --- "a/assets/\351\203\250\347\275\262.html-FCiywWGM.js"
        +++ "b/assets/\351\203\250\347\275\262.html-Cup61qP7.js"
        @@ -1 +1 @@
        -import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as r,c as a,a as e,b as i,d as p}from"./app-CVMfKeWw.js";const c={},l=e("h3",{id:"参考",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#参考"},[e("span",null,"参考")])],-1),s={href:"https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment.installing",target:"_blank",rel:"noopener noreferrer"};function m(d,h){const t=n("ExternalLinkIcon");return r(),a("div",null,[l,e("p",null,[e("a",s,[i("官方部署说明"),p(t)])])])}const u=o(c,[["render",m],["__file","部署.html.vue"]]),f=JSON.parse('{"path":"/java/framework/springboot/%E9%83%A8%E7%BD%B2.html","title":"springboot部署","lang":"zh-CN","frontmatter":{"title":"springboot部署","date":"2023-09-26T00:00:00.000Z","isOriginal":true,"description":"参考 官方部署说明","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/springboot/%E9%83%A8%E7%BD%B2.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"springboot部署"}],["meta",{"property":"og:description","content":"参考 官方部署说明"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-09-26T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"springboot部署\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-09-26T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":3,"title":"参考","slug":"参考","link":"#参考","children":[]}],"git":{"createdTime":1697163880000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.07,"words":22},"filePathRelative":"java/framework/springboot/部署.md","localizedDate":"2023年9月26日","excerpt":"

        参考

        \\n

        官方部署说明

        \\n","autoDesc":true}');export{u as comp,f as data}; +import{_ as o}from"./plugin-vue_export-helper-DlAUqK2U.js";import{r as n,o as r,c as a,a as e,b as i,d as p}from"./app-_Oi5YZFn.js";const c={},l=e("h3",{id:"参考",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#参考"},[e("span",null,"参考")])],-1),s={href:"https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment.installing",target:"_blank",rel:"noopener noreferrer"};function m(d,h){const t=n("ExternalLinkIcon");return r(),a("div",null,[l,e("p",null,[e("a",s,[i("官方部署说明"),p(t)])])])}const u=o(c,[["render",m],["__file","部署.html.vue"]]),f=JSON.parse('{"path":"/java/framework/springboot/%E9%83%A8%E7%BD%B2.html","title":"springboot部署","lang":"zh-CN","frontmatter":{"title":"springboot部署","date":"2023-09-26T00:00:00.000Z","isOriginal":true,"description":"参考 官方部署说明","head":[["meta",{"property":"og:url","content":"https://ChenSino.github.io/java/framework/springboot/%E9%83%A8%E7%BD%B2.html"}],["meta",{"property":"og:site_name","content":"ChenSino"}],["meta",{"property":"og:title","content":"springboot部署"}],["meta",{"property":"og:description","content":"参考 官方部署说明"}],["meta",{"property":"og:type","content":"article"}],["meta",{"property":"og:locale","content":"zh-CN"}],["meta",{"property":"og:updated_time","content":"2024-03-22T03:45:12.000Z"}],["meta",{"property":"article:author","content":"ChenSino"}],["meta",{"property":"article:published_time","content":"2023-09-26T00:00:00.000Z"}],["meta",{"property":"article:modified_time","content":"2024-03-22T03:45:12.000Z"}],["script",{"type":"application/ld+json"},"{\\"@context\\":\\"https://schema.org\\",\\"@type\\":\\"Article\\",\\"headline\\":\\"springboot部署\\",\\"image\\":[\\"\\"],\\"datePublished\\":\\"2023-09-26T00:00:00.000Z\\",\\"dateModified\\":\\"2024-03-22T03:45:12.000Z\\",\\"author\\":[{\\"@type\\":\\"Person\\",\\"name\\":\\"ChenSino\\",\\"url\\":\\"https://ChenSino.github.io\\"}]}"]]},"headers":[{"level":3,"title":"参考","slug":"参考","link":"#参考","children":[]}],"git":{"createdTime":1697163880000,"updatedTime":1711079112000,"contributors":[{"name":"ChenSino","email":"462488588@qq.com","commits":2}]},"readingTime":{"minutes":0.07,"words":22},"filePathRelative":"java/framework/springboot/部署.md","localizedDate":"2023年9月26日","excerpt":"

        参考

        \\n

        官方部署说明

        \\n","autoDesc":true}');export{u as comp,f as data}; diff --git a/category/docker/index.html b/category/docker/index.html index 3024789006..bd21cd7747 100644 --- a/category/docker/index.html +++ b/category/docker/index.html @@ -30,8 +30,8 @@ docker 分类 | ChenSino - - + +
        - +

        chenkun小于 1 分钟docker
        + diff --git "a/category/git-\346\223\215\344\275\234/index.html" "b/category/git-\346\223\215\344\275\234/index.html" index aa62d89b90..228642efac 100644 --- "a/category/git-\346\223\215\344\275\234/index.html" +++ "b/category/git-\346\223\215\344\275\234/index.html" @@ -30,8 +30,8 @@ git 操作 分类 | ChenSino - - + + - +在这里插入图片描述


        ChenSino大约 3 分钟git 操作必会
        + diff --git a/category/index.html b/category/index.html index 4a0db862d2..bcb391958a 100644 --- a/category/index.html +++ b/category/index.html @@ -30,11 +30,11 @@ 分类 | ChenSino - - + + - - + + diff --git a/category/java/index.html b/category/java/index.html index dab282f12c..9e5ce9c5c4 100644 --- a/category/java/index.html +++ b/category/java/index.html @@ -30,8 +30,8 @@ java 分类 | ChenSino - - + +

    ChenSino大约 7 分钟java部署搭建
    - +

    ChenSino大约 7 分钟java部署搭建
    + diff --git "a/category/java\345\237\272\347\241\200/index.html" "b/category/java\345\237\272\347\241\200/index.html" index b5f8e0f50c..bc800ffd4d 100644 --- "a/category/java\345\237\272\347\241\200/index.html" +++ "b/category/java\345\237\272\347\241\200/index.html" @@ -30,8 +30,8 @@ java基础 分类 | ChenSino - - + +
    -

    ChenSino大约 3 分钟java基础
    - +

    ChenSino大约 3 分钟java基础
    + diff --git a/category/jdk/index.html b/category/jdk/index.html index dd538d6504..06bdb53c7b 100644 --- a/category/jdk/index.html +++ b/category/jdk/index.html @@ -30,14 +30,14 @@ jdk 分类 | ChenSino - - + +
    跳至主要內容
    在Manjaro中编译JDK11

    1 下载源码

    网上根据关键词查找jdk源码,查找出来很多可以下载源码的链接,这里我们使用github去官方仓库,openjdk是托管在github的OpenJDK组织下,该组织下有各个版本的openjdk源码,不要直接使用jdk仓库,这个仓库存放的是当前正在开发的最新版本代码,我们要用的是jdk11,因此我们搜索jdk11仓库,我这里选择的是jdk11u这个库。

    -

    20230113095229


    chenkun大约 5 分钟jdk
    - +

    20230113095229


    chenkun大约 5 分钟jdk
    + diff --git "a/category/js\345\237\272\347\241\200/index.html" "b/category/js\345\237\272\347\241\200/index.html" index 9220717dc2..ebfe6bfe35 100644 --- "a/category/js\345\237\272\347\241\200/index.html" +++ "b/category/js\345\237\272\347\241\200/index.html" @@ -30,8 +30,8 @@ js基础 分类 | ChenSino - - + +
    跳至主要內容
    JS原生事件

    一,事件注册的三种方式

    @@ -89,7 +89,7 @@
  • 鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;
  • 在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据;
  • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断;
  • -

    qianxun大约 3 分钟js基础必会
    - +

    qianxun大约 3 分钟js基础必会
    + diff --git a/category/jvm/index.html b/category/jvm/index.html index f4609d1a9d..175f23501c 100644 --- a/category/jvm/index.html +++ b/category/jvm/index.html @@ -30,8 +30,8 @@ jvm 分类 | ChenSino - - + + - +
    ChenSino大约 3 分钟javajvm字节码反汇编
    + diff --git a/category/linux/index.html b/category/linux/index.html index 02ae849ccf..ac9a55048b 100644 --- a/category/linux/index.html +++ b/category/linux/index.html @@ -30,15 +30,15 @@ linux 分类 | ChenSino - - + + - +
    chenkun大约 5 分钟linuxlinux
    + diff --git a/category/markdown/index.html b/category/markdown/index.html index 94bf2de546..0d78176fbc 100644 --- a/category/markdown/index.html +++ b/category/markdown/index.html @@ -30,15 +30,15 @@ markdown 分类 | ChenSino - - + + - +
    chenkun小于 1 分钟markdownmarkdown
    + diff --git a/category/maven/index.html b/category/maven/index.html index 68db48689c..a22bd93a9c 100644 --- a/category/maven/index.html +++ b/category/maven/index.html @@ -30,8 +30,8 @@ maven 分类 | ChenSino - - + +
    跳至主要內容
    - +

    官方说明


    ChenSino原创大约 5 分钟maven
    + diff --git "a/category/npm\347\237\245\350\257\206\347\202\271/index.html" "b/category/npm\347\237\245\350\257\206\347\202\271/index.html" index ad86e3fe58..c8875a88a1 100644 --- "a/category/npm\347\237\245\350\257\206\347\202\271/index.html" +++ "b/category/npm\347\237\245\350\257\206\347\202\271/index.html" @@ -30,8 +30,8 @@ npm知识点 分类 | ChenSino - - + + - +
    qianxun大约 2 分钟npm知识点必会
    + diff --git a/category/oauth/index.html b/category/oauth/index.html index fbe45b5ba8..bda4c66cfc 100644 --- a/category/oauth/index.html +++ b/category/oauth/index.html @@ -30,8 +30,8 @@ OAuth 分类 | ChenSino - - + +

    2.1 第1步

    用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页
    -20230104105625


    chenkun大约 2 分钟SecurityOAuth
    - +20230104105625


    chenkun大约 2 分钟SecurityOAuth
    + diff --git a/category/open-source/index.html b/category/open-source/index.html index fb154acc5d..8e6b2046e8 100644 --- a/category/open-source/index.html +++ b/category/open-source/index.html @@ -30,12 +30,12 @@ open-source 分类 | ChenSino - - + +
    跳至主要內容
    Spring IOC 容器源码分析

    Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器。既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文并不能让你成为 Spring 专家,不过一定有助于大家理解 Spring 的很多概念,帮助大家排查应用中和 Spring 相关的一些问题。

    -

    本文采用的源码版本是 4.3.11.RELEASE,算是 5.0.x 前比较新的版本了。为了降低难度,本文所说的所有的内容都是基于 xml 的配置的方式,实际使用已经很少人这么做了,至少不是纯 xml 配置,不过从理解源码的角度来看用这种方式来说无疑是最合适的。


    ChenSino大约 62 分钟open-source
    - +

    本文采用的源码版本是 4.3.11.RELEASE,算是 5.0.x 前比较新的版本了。为了降低难度,本文所说的所有的内容都是基于 xml 的配置的方式,实际使用已经很少人这么做了,至少不是纯 xml 配置,不过从理解源码的角度来看用这种方式来说无疑是最合适的。


    ChenSino大约 62 分钟open-source
    + diff --git a/category/security/index.html b/category/security/index.html index 9656766074..9c5f7cfab8 100644 --- a/category/security/index.html +++ b/category/security/index.html @@ -30,8 +30,8 @@ Security 分类 | ChenSino - - + +
    跳至主要內容
    Ruoyi使用oauth对接pig

    1、时序图

    @@ -65,7 +65,7 @@

    2.1 第1步

    一个请求在一个过滤器只执行一次doFilter,因在在不同版本的Servlet容器中是存在多次执 行doFilter的可能的,比如一个request forward到另一个request,在servlet2.0和3.0 表现可能都不一样,在Tomcat和weblogic容器中可能表现也不一样,为了统一此行为,所以 -Spring官方提供了此类。


    chenkun大约 3 分钟Security
    - +Spring官方提供了此类。


    chenkun大约 3 分钟Security
    + diff --git a/category/spring/index.html b/category/spring/index.html index fd897a8b9f..74a456ae4e 100644 --- a/category/spring/index.html +++ b/category/spring/index.html @@ -30,8 +30,8 @@ Spring 分类 | ChenSino - - + +
    - +
    ChenSino大约 1 分钟Spring
    + diff --git a/category/vite/index.html b/category/vite/index.html index 89982b705b..05d8c62acf 100644 --- a/category/vite/index.html +++ b/category/vite/index.html @@ -30,11 +30,11 @@ vite 分类 | ChenSino - - + + - - + + diff --git "a/category/vue\347\237\245\350\257\206\347\202\271/index.html" "b/category/vue\347\237\245\350\257\206\347\202\271/index.html" index 1dac2c1c87..3dbfd41b4f 100644 --- "a/category/vue\347\237\245\350\257\206\347\202\271/index.html" +++ "b/category/vue\347\237\245\350\257\206\347\202\271/index.html" @@ -30,8 +30,8 @@ vue知识点 分类 | ChenSino - - + +
    - +
    qianxun大约 2 分钟vue知识点必会vue中的 TypeScript
    + diff --git a/category/web/index.html b/category/web/index.html index 528ef3a54a..ba449c9f87 100644 --- a/category/web/index.html +++ b/category/web/index.html @@ -30,8 +30,8 @@ web 分类 | ChenSino - - + + - +
    chenkun小于 1 分钟webweb
    + diff --git "a/category/\344\275\277\347\224\250\346\214\207\345\215\227/index.html" "b/category/\344\275\277\347\224\250\346\214\207\345\215\227/index.html" index dae9a62284..293797b623 100644 --- "a/category/\344\275\277\347\224\250\346\214\207\345\215\227/index.html" +++ "b/category/\344\275\277\347\224\250\346\214\207\345\215\227/index.html" @@ -30,8 +30,8 @@ 使用指南 分类 | ChenSino - - + +
    跳至主要內容
    页面配置

    more 注释之前的内容被视为文章摘要。

    @@ -52,7 +52,7 @@

    ChenSino小于 1 分钟使用指南
    Markdown 展示

    VuePress 主要从 Markdown 文件生成页面。因此,你可以使用它轻松生成文档或博客站点。

    你应该创建和编写 Markdown 文件,以便 VuePress 可以根据文件结构将它们转换为不同的页面。

    -

    ChenSino大约 4 分钟使用指南Markdown
    - +
    ChenSino大约 4 分钟使用指南Markdown
    + diff --git "a/category/\345\205\254\345\217\270\344\270\232\345\212\241/index.html" "b/category/\345\205\254\345\217\270\344\270\232\345\212\241/index.html" index b581ab68f4..d393fc8932 100644 --- "a/category/\345\205\254\345\217\270\344\270\232\345\212\241/index.html" +++ "b/category/\345\205\254\345\217\270\344\270\232\345\212\241/index.html" @@ -30,8 +30,8 @@ 公司业务 分类 | ChenSino - - + + - +
    ChenSino大约 1 分钟公司业务
    + diff --git "a/category/\345\244\232\347\272\277\347\250\213/index.html" "b/category/\345\244\232\347\272\277\347\250\213/index.html" index c101760634..993b2c7444 100644 --- "a/category/\345\244\232\347\272\277\347\250\213/index.html" +++ "b/category/\345\244\232\347\272\277\347\250\213/index.html" @@ -30,8 +30,8 @@ 多线程 分类 | ChenSino - - + + - +
    chenkun大约 12 分钟线程池多线程多线程线程池
    + diff --git "a/category/\345\257\271\350\261\241\351\224\201/index.html" "b/category/\345\257\271\350\261\241\351\224\201/index.html" index fa6651e629..40e70258d4 100644 --- "a/category/\345\257\271\350\261\241\351\224\201/index.html" +++ "b/category/\345\257\271\350\261\241\351\224\201/index.html" @@ -30,12 +30,12 @@ 对象锁 分类 | ChenSino - - + + - +
    ChenSino小于 1 分钟对象锁
    + diff --git "a/category/\345\260\217\347\273\204\345\210\206\344\272\253/index.html" "b/category/\345\260\217\347\273\204\345\210\206\344\272\253/index.html" index 78a2be2b83..12b1d830c2 100644 --- "a/category/\345\260\217\347\273\204\345\210\206\344\272\253/index.html" +++ "b/category/\345\260\217\347\273\204\345\210\206\344\272\253/index.html" @@ -30,8 +30,8 @@ 小组分享 分类 | ChenSino - - + +
    跳至主要內容
    画图工具

    1 示例

    @@ -96,7 +96,7 @@

    一、DNS

    本章节参考DNS 原理入门——阮一峰

    1.1 什么是DNS

    DNS (Domain Name System 的缩写)的作用非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。 -举例来说,如果你要访问域名math.stackexchange.com,首先要通过DNS查出它的IP地址是151.101.129.69。


    chenkun大约 10 分钟小组分享
    - +举例来说,如果你要访问域名math.stackexchange.com,首先要通过DNS查出它的IP地址是151.101.129.69。


    chenkun大约 10 分钟小组分享
    + diff --git "a/category/\345\277\205\344\274\232/index.html" "b/category/\345\277\205\344\274\232/index.html" index 74c994fd94..16fc44b73f 100644 --- "a/category/\345\277\205\344\274\232/index.html" +++ "b/category/\345\277\205\344\274\232/index.html" @@ -30,8 +30,8 @@ 必会 分类 | ChenSino - - + + - +
    ChenSino大约 2 分钟git 操作必会
    + diff --git "a/category/\346\225\260\346\215\256\345\272\223/index.html" "b/category/\346\225\260\346\215\256\345\272\223/index.html" index 4c9d53a431..c894dd3f8a 100644 --- "a/category/\346\225\260\346\215\256\345\272\223/index.html" +++ "b/category/\346\225\260\346\215\256\345\272\223/index.html" @@ -30,8 +30,8 @@ 数据库 分类 | ChenSino - - + + - +
    chenkun大约 1 分钟数据库数据库
    + diff --git "a/category/\346\241\206\346\236\266/index.html" "b/category/\346\241\206\346\236\266/index.html" index 08ab689da5..9518ce2259 100644 --- "a/category/\346\241\206\346\236\266/index.html" +++ "b/category/\346\241\206\346\236\266/index.html" @@ -30,15 +30,15 @@ 框架 分类 | ChenSino - - + +
    跳至主要內容
    Mybatis使用

    mybatis缓存

    缓存介绍

    一级缓存存在的问题

    1、 一级缓存

    -

    mybatis缓存有一级缓存也叫SelSession缓存,是强制打开的,也就是说Mybatis没有提供关闭一级缓存的方式。一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其查询结果放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。一级缓存的存储位置是在JVM虚拟机内存,它放在一个Map中


    chenkun大约 12 分钟框架框架
    - +

    mybatis缓存有一级缓存也叫SelSession缓存,是强制打开的,也就是说Mybatis没有提供关闭一级缓存的方式。一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其查询结果放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。一级缓存的存储位置是在JVM虚拟机内存,它放在一个Map中


    chenkun大约 12 分钟框架框架
    + diff --git "a/category/\347\224\265\345\255\220\344\271\246/index.html" "b/category/\347\224\265\345\255\220\344\271\246/index.html" index ace5a08c2d..80424c1ff8 100644 --- "a/category/\347\224\265\345\255\220\344\271\246/index.html" +++ "b/category/\347\224\265\345\255\220\344\271\246/index.html" @@ -30,14 +30,14 @@ 电子书 分类 | ChenSino - - + + - +

    阿里巴巴官方出品


    ChenSino小于 1 分钟电子书
    + diff --git "a/category/\347\272\277\347\250\213\346\261\240/index.html" "b/category/\347\272\277\347\250\213\346\261\240/index.html" index f08e77df72..a5dfcb0642 100644 --- "a/category/\347\272\277\347\250\213\346\261\240/index.html" +++ "b/category/\347\272\277\347\250\213\346\261\240/index.html" @@ -30,8 +30,8 @@ 线程池 分类 | ChenSino - - + + - +
    chenkun大约 12 分钟线程池多线程多线程线程池
    + diff --git "a/category/\350\256\276\350\256\241\346\250\241\345\274\217/index.html" "b/category/\350\256\276\350\256\241\346\250\241\345\274\217/index.html" index d74a6728a3..d487ab34f8 100644 --- "a/category/\350\256\276\350\256\241\346\250\241\345\274\217/index.html" +++ "b/category/\350\256\276\350\256\241\346\250\241\345\274\217/index.html" @@ -30,8 +30,8 @@ 设计模式 分类 | ChenSino - - + +
    跳至主要內容
    Decorator Design Pattern

    定义

    @@ -133,7 +133,7 @@

    mybatis中

    3. Client:客户端,它向链中的第一个ConcreteHandler对象发起请求,然后等待链条响应请求。 责任链模式的优点在于它可以简化对象之间的耦合关系,增加系统的灵活性。它对系统进行解耦,使得请求发送者不必关心请求的具体处理者,发起请求后,请求会在链条中依次被多个对象处理。同时,责任链模式还可以方便地进行动态的链式调整,即在运行时往链中添加或删除具体处理者,以便满足实时的业务需求。 -

    ChenSino原创大约 2 分钟设计模式
    - +
    ChenSino原创大约 2 分钟设计模式
    + diff --git "a/category/\350\277\220\347\273\264/index.html" "b/category/\350\277\220\347\273\264/index.html" index 33690d480f..5ce19f206b 100644 --- "a/category/\350\277\220\347\273\264/index.html" +++ "b/category/\350\277\220\347\273\264/index.html" @@ -30,8 +30,8 @@ 运维 分类 | ChenSino - - + +
    跳至主要內容
    jenkins部署及使用
    @@ -41,7 +41,7 @@

    1.1 报错如下,ssl证书问题

    Jenkins(2020年及以后版本,2.260以上)安装后,插件下载时失败,网上找了各种解决方法,修改jenkins插件的下载源地址:

    找到菜单Manage Jenkins → Manage Plugins → Advanced → Update Site,


    chenkun大约 5 分钟运维
    Linux

    好记性不如烂笔头

    -

    chenkun小于 1 分钟运维
    - +
    chenkun小于 1 分钟运维
    + diff --git "a/category/\351\227\256\351\242\230\345\256\232\344\275\215/index.html" "b/category/\351\227\256\351\242\230\345\256\232\344\275\215/index.html" index 0a84eeb83e..f2f6bfd768 100644 --- "a/category/\351\227\256\351\242\230\345\256\232\344\275\215/index.html" +++ "b/category/\351\227\256\351\242\230\345\256\232\344\275\215/index.html" @@ -30,8 +30,8 @@ 问题定位 分类 | ChenSino - - + + - +
    chenkun大约 1 分钟问题定位
    + diff --git "a/category/\351\233\206\345\220\210/index.html" "b/category/\351\233\206\345\220\210/index.html" index 412f27b348..b69a21956b 100644 --- "a/category/\351\233\206\345\220\210/index.html" +++ "b/category/\351\233\206\345\220\210/index.html" @@ -30,14 +30,14 @@ 集合 分类 | ChenSino - - + + - +
    ChenSino小于 1 分钟集合集合
    + diff --git a/cpp/index.html b/cpp/index.html index b37f7d1cf1..060da37267 100644 --- a/cpp/index.html +++ b/cpp/index.html @@ -30,11 +30,11 @@ Cpp | ChenSino - - + + - - + + diff --git a/cpp/other/1.html b/cpp/other/1.html index 0293c6638a..9796c76e4e 100644 --- a/cpp/other/1.html +++ b/cpp/other/1.html @@ -30,11 +30,11 @@ c++中使用的编译工具介绍 | ChenSino - - + + -
    跳至主要內容

    c++中使用的编译工具介绍

    ChenSino大约 3 分钟

    文章来源:https://zhuanlan.zhihu.com/p/111110992

    gcc

    它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。

    我们的程序只有一个源文件时,直接就可以用gcc命令编译它。

    可是,如果我们的程序包含很多个源文件时,该咋整?用gcc命令逐个去编译时,就发现很容易混乱而且工作量大,所以出现了下面make工具。

    gcc命令在链接时默认使用C的库,只有添加了-lstdc++选项才会使用 C++ 的库。 不过 GCC 中还有一个g++命令,它专门用来编译 C++ 程序,广大 C++ 开发人员也都使用这个命令。g++命令和gcc命令的用法是一样的。

    make

    make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式—通过调用makefile文件中用户指定的命令来进行编译和链接的。

    makefile

    这个是啥东西?

    简单的说就像一首歌的乐谱,make工具就像指挥家,指挥家根据乐谱指挥整个乐团怎么样演奏,make工具就根据makefile中的命令进行编译和链接的。makefile命令中就包含了调用gcc(也可以是别的编译器)去编译某个源文件的命令。

    makefile在一些简单的工程完全可以人工拿下,但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改,这时候就出现了下面的Cmake这个工具。

    cmake

    cmake就可以更加简单的生成makefile文件给上面那个make用。当然cmake还有其他更牛X功能,就是可以跨平台生成对应平台能用的makefile,我们就不用再自己去修改了。

    可是cmake根据什么生成makefile呢?它又要根据一个叫CMakeLists.txt文件(学名:组态档)去生成makefile。

    CMakeList.txt

    到最后CMakeLists.txt文件谁写啊?亲,是你自己手写的。

    nmake[1]

    nmake又是啥东西?

    nmake是Microsoft Visual Studio中的附带命令,需要安装VS,实际上可以说相当于linux的make,明白了么?

    20230113135511

    总结一下大体流程[2] 1.用编辑器编写源代码,如.c文件。

    2.用编译器编译代码生成目标文件,如.o。

    3.用链接器连接目标代码生成可执行文件,如.exe。

    但如果源文件太多,一个一个编译那得多麻烦啊?于是人们想到,为啥不设计一种类似批处理的程序,来批处理编译源文件呢?

    于是就有了make工具,它是一个自动化编译工具,你可以使用一条命令实现完全编译。但是你需要编写一个规则文件,make依据它来批处理编译,这个文件就是makefile,所以编写makefile文件也是一个程序员所必备的技能。

    对于一个大工程,编写makefile实在是件复杂的事,于是人们又想,为什么不设计一个工具,读入所有源文件之后,自动生成makefile呢,于是就出现了cmake工具,它能够输出各种各样的makefile或者project文件,从而帮助程序员减轻负担。但是随之而来也就是编写cmakelist文件,它是cmake所依据的规则。(cmake中有很多设置库的,此时还不是可执行文件,而make生成后才是二进制可执行文件。)

    - +
    跳至主要內容

    c++中使用的编译工具介绍

    ChenSino大约 3 分钟

    文章来源:https://zhuanlan.zhihu.com/p/111110992

    gcc

    它是GNU Compiler Collection(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。

    我们的程序只有一个源文件时,直接就可以用gcc命令编译它。

    可是,如果我们的程序包含很多个源文件时,该咋整?用gcc命令逐个去编译时,就发现很容易混乱而且工作量大,所以出现了下面make工具。

    gcc命令在链接时默认使用C的库,只有添加了-lstdc++选项才会使用 C++ 的库。 不过 GCC 中还有一个g++命令,它专门用来编译 C++ 程序,广大 C++ 开发人员也都使用这个命令。g++命令和gcc命令的用法是一样的。

    make

    make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式—通过调用makefile文件中用户指定的命令来进行编译和链接的。

    makefile

    这个是啥东西?

    简单的说就像一首歌的乐谱,make工具就像指挥家,指挥家根据乐谱指挥整个乐团怎么样演奏,make工具就根据makefile中的命令进行编译和链接的。makefile命令中就包含了调用gcc(也可以是别的编译器)去编译某个源文件的命令。

    makefile在一些简单的工程完全可以人工拿下,但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改,这时候就出现了下面的Cmake这个工具。

    cmake

    cmake就可以更加简单的生成makefile文件给上面那个make用。当然cmake还有其他更牛X功能,就是可以跨平台生成对应平台能用的makefile,我们就不用再自己去修改了。

    可是cmake根据什么生成makefile呢?它又要根据一个叫CMakeLists.txt文件(学名:组态档)去生成makefile。

    CMakeList.txt

    到最后CMakeLists.txt文件谁写啊?亲,是你自己手写的。

    nmake[1]

    nmake又是啥东西?

    nmake是Microsoft Visual Studio中的附带命令,需要安装VS,实际上可以说相当于linux的make,明白了么?

    20230113135511

    总结一下大体流程[2] 1.用编辑器编写源代码,如.c文件。

    2.用编译器编译代码生成目标文件,如.o。

    3.用链接器连接目标代码生成可执行文件,如.exe。

    但如果源文件太多,一个一个编译那得多麻烦啊?于是人们想到,为啥不设计一种类似批处理的程序,来批处理编译源文件呢?

    于是就有了make工具,它是一个自动化编译工具,你可以使用一条命令实现完全编译。但是你需要编写一个规则文件,make依据它来批处理编译,这个文件就是makefile,所以编写makefile文件也是一个程序员所必备的技能。

    对于一个大工程,编写makefile实在是件复杂的事,于是人们又想,为什么不设计一个工具,读入所有源文件之后,自动生成makefile呢,于是就出现了cmake工具,它能够输出各种各样的makefile或者project文件,从而帮助程序员减轻负担。但是随之而来也就是编写cmakelist文件,它是cmake所依据的规则。(cmake中有很多设置库的,此时还不是可执行文件,而make生成后才是二进制可执行文件。)

    + diff --git a/cpp/other/2.html b/cpp/other/2.html index 6dad0051cf..1ac15b5ee8 100644 --- a/cpp/other/2.html +++ b/cpp/other/2.html @@ -30,11 +30,11 @@ 使用Clion搭建jdk源码调试环境 | ChenSino - - + + - - + + diff --git a/cpp/other/index.html b/cpp/other/index.html index a8bc5b8526..e85bf35802 100644 --- a/cpp/other/index.html +++ b/cpp/other/index.html @@ -30,11 +30,11 @@ Other | ChenSino - - + + - - + + diff --git a/cpp/study/index.html b/cpp/study/index.html index 5cffbd81a4..5d08071f3d 100644 --- a/cpp/study/index.html +++ b/cpp/study/index.html @@ -30,11 +30,11 @@ index | ChenSino - - + + - - + + diff --git a/designpattern/index.html b/designpattern/index.html index 31f8f3b178..f1a2257609 100644 --- a/designpattern/index.html +++ b/designpattern/index.html @@ -30,11 +30,11 @@ README | ChenSino - - + + -
    跳至主要內容

    README

    ChenSino原创小于 1 分钟

    说明

    java设计模式代码请参考开源项目java-design-patternsopen in new window,本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。

    - +
    跳至主要內容

    README

    ChenSino原创小于 1 分钟

    说明

    java设计模式代码请参考开源项目java-design-patternsopen in new window,本博客只是基于自己的理解做简单介绍,让设计模式不再那么难理解,另外看到开源框架中的设计模式我也会慢慢摘取出来,主要是学习spring家族是如何使用设计模式的。

    + diff --git "a/designpattern/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html" "b/designpattern/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html" index 72fcdfa490..8807bfe73a 100644 --- "a/designpattern/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html" +++ "b/designpattern/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.html" @@ -30,8 +30,8 @@ Builder Pattern | ChenSino - - + + - +
    + diff --git "a/designpattern/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html" "b/designpattern/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html" index dce28c9ead..2b10c1e866 100644 --- "a/designpattern/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html" +++ "b/designpattern/\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.html" @@ -30,8 +30,8 @@ Template Method | ChenSino - - + +
    跳至主要內容

    Template Method

    ChenSino原创设计模式大约 2 分钟

    定义

    模板方法模式(Template Method Pattern)是一种行为型设计模式,定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。这种设计模式是一种控制反转的实现方式。因为高层代码不再确定(控制)算法的处理流程。模板方法模式多用在某些类别的算法中,实现了相同的方法,造成代码的重复。这个设计模式和策略模式很像,不同的是,模板方法会有一些通用的逻辑,而策略模式是整个方法重写。从类的继承结构也可以看出来,模板方法是提供一个抽象类,有一个通用的方法,不通用的逻辑放到子类去实现,而策略模式是子类直接继承自接口,要重写整个方法。

    例子

    mybatis中org.apache.ibatis.executor.BaseExecutor和他的子类是典型的模板方法模式。

    mybatis中类型处理器也是典型的模板方法,具体在org.apache.ibatis.type.BaseTypeHandler#setParameter,如下,看类名就知道和BaseExecutor 一个套路,它实现了setParameter方法,但是仔细看,它调用了setNonNullParameter(ps, i, parameter, jdbcType);,如果用户自定义类类型处理器 最终调用的其实是setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType),这个方法是个抽象的方法,等待 用户实现。也是一个经典的模板方法模式,相同逻辑放在一个方法,不同的部分,调用一个抽象方法,让用户自行实现。

    
    @@ -63,7 +63,7 @@
     
         public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
           throws SQLException;
    -
    - +
    + diff --git "a/designpattern/\347\255\226\347\225\245\346\250\241\345\274\217.html" "b/designpattern/\347\255\226\347\225\245\346\250\241\345\274\217.html" index 7a5285ccdd..3393a69618 100644 --- "a/designpattern/\347\255\226\347\225\245\346\250\241\345\274\217.html" +++ "b/designpattern/\347\255\226\347\225\245\346\250\241\345\274\217.html" @@ -30,8 +30,8 @@ Strategy Pattern | ChenSino - - + +
    跳至主要內容

    Strategy Pattern

    ChenSino原创设计模式大约 2 分钟

    策略模式【CHATGPT回答】

    策略模式(Strategy Pattern)是一种行为型设计模式,它将一组可相互替换的算法封装成独立的对象,并对外暴露相同的接口,从而使得它们可以根据需要动态地替换,以实现不同的行为。
    @@ -51,7 +51,7 @@
     java策略模式是否就是给定一个接口或者抽象类,提供多个实现类?
     
     是的,Java策略模式就是定义一个接口或抽象类,然后提供多个实现类来实现接口或抽象类中的方法,从而实现不同的算法策略。通过使用策略模式,我们能够在程序运行时动态地切换不同的算法实现,从而实现更加灵活和可扩展的设计。同时,策略模式也符合开闭原则,因为我们可以在不修改已有代码的情况下,通过添加新的策略类来扩展系统的功能。
    -

    Spring中策略模式的使用

    太多了,一个接口多个实现,

    我的工作中用到的策略模式

    登录功能,一个系统可以有很多中登录方式,比如帐号密码登录、手机号验证码登录、oauth2登录,我们可以定义一个接口,接口中有个login方法,返回一个LoginVO对象,不同的登录方式用不同的类实现就可以了。通常策略模式要配合工厂模式实现,工厂根据前端传递来的参数获取不同策略类,然后进行相应的逻辑处理

    - +

    Spring中策略模式的使用

    太多了,一个接口多个实现,

    我的工作中用到的策略模式

    登录功能,一个系统可以有很多中登录方式,比如帐号密码登录、手机号验证码登录、oauth2登录,我们可以定义一个接口,接口中有个login方法,返回一个LoginVO对象,不同的登录方式用不同的类实现就可以了。通常策略模式要配合工厂模式实现,工厂根据前端传递来的参数获取不同策略类,然后进行相应的逻辑处理

    + diff --git "a/designpattern/\350\243\205\351\245\260\346\250\241\345\274\217.html" "b/designpattern/\350\243\205\351\245\260\346\250\241\345\274\217.html" index a30920aef7..5a8e824875 100644 --- "a/designpattern/\350\243\205\351\245\260\346\250\241\345\274\217.html" +++ "b/designpattern/\350\243\205\351\245\260\346\250\241\345\274\217.html" @@ -30,8 +30,8 @@ Decorator Design Pattern | ChenSino - - + +
    跳至主要內容

    Decorator Design Pattern

    ChenSino原创设计模式小于 1 分钟

    定义

    装饰者模式是一种常用的设计模式,它动态地给一个对象添加一些额外的职责1。装饰者模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为2。

    框架中使用

    mybatis中:

    打开sqlSession时,会创建Executor,最终会进入org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    @@ -70,7 +70,7 @@
       //略……
     }
     
    -
    - +
    + diff --git "a/designpattern/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html" "b/designpattern/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html" index 8ebce43824..8c9f993f61 100644 --- "a/designpattern/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html" +++ "b/designpattern/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.html" @@ -30,8 +30,8 @@ Chain of Responsibility Pattern | ChenSino - - + +
    跳至主要內容

    Chain of Responsibility Pattern

    ChenSino原创设计模式大约 2 分钟

    定义

    责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它可以将多个对象组合成一条链,并按照事先规定的顺序依次处理请求。每个对象都可以选择处理请求,或者将请求传递给链中的下一个对象。这种模式将请求的发送者和接收者解耦,使得多个对象都有机会处理请求,从而提高了系统的灵活性和可扩展性。
    @@ -45,7 +45,7 @@
     3. Client:客户端,它向链中的第一个ConcreteHandler对象发起请求,然后等待链条响应请求。
     
     责任链模式的优点在于它可以简化对象之间的耦合关系,增加系统的灵活性。它对系统进行解耦,使得请求发送者不必关心请求的具体处理者,发起请求后,请求会在链条中依次被多个对象处理。同时,责任链模式还可以方便地进行动态的链式调整,即在运行时往链中添加或删除具体处理者,以便满足实时的业务需求。
    -

    Spring中的责任链

    ProviderManager和AuthenticationProvider在Spring Security中,它们通常被归类为责任链模式(Chain of Responsibility)。

    责任链模式可将多个对象组合在一起,每个对象都尝试处理某个请求。如果处理器不能处理请求,则传递给链中的下一个处理器,直到找到能够处理请求的处理器。在Spring Security中,ProviderManager就扮演了责任链的角色,它持有一个AuthenticationProvider的列表,每个AuthenticationProvider尝试对请求进行身份验证,如果认证失败,ProviderManager会将请求传递给下一个AuthenticationProvider对象。

    因此,ProviderManager和AuthenticationProvider在Spring Security中通常被视为责任链模式的实现。这种设计模式使得多个对象可以自由组合在一起,只要它们都遵循同样的接口,便可以形成一个强大而灵活的处理链条。

    - +

    Spring中的责任链

    ProviderManager和AuthenticationProvider在Spring Security中,它们通常被归类为责任链模式(Chain of Responsibility)。

    责任链模式可将多个对象组合在一起,每个对象都尝试处理某个请求。如果处理器不能处理请求,则传递给链中的下一个处理器,直到找到能够处理请求的处理器。在Spring Security中,ProviderManager就扮演了责任链的角色,它持有一个AuthenticationProvider的列表,每个AuthenticationProvider尝试对请求进行身份验证,如果认证失败,ProviderManager会将请求传递给下一个AuthenticationProvider对象。

    因此,ProviderManager和AuthenticationProvider在Spring Security中通常被视为责任链模式的实现。这种设计模式使得多个对象可以自由组合在一起,只要它们都遵循同样的接口,便可以形成一个强大而灵活的处理链条。

    + diff --git a/frontweb/es5/aboutAsync.html b/frontweb/es5/aboutAsync.html index b07f95cf9f..56d0a6c9e4 100644 --- a/frontweb/es5/aboutAsync.html +++ b/frontweb/es5/aboutAsync.html @@ -30,8 +30,8 @@ 异步async函数 | ChenSino - - + +
    跳至主要內容

    异步async函数

    qianxunvue知识点必会大约 5 分钟

    一,async函数的定义

    async函数是使用async关键字声明的函数。 并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promiseopen in new window异步行为,而无需刻意地链式调用promise。

    备注:async/await的目的为了简化使用基于 promise 的 API 时所需的语法。async/await的行为就好像搭配使用了生成器和 promise。

    async函数的书写方式如下:

    // 函数声明
    @@ -222,7 +222,7 @@
       }
     }
     
    -

    六,总结

    image-20220712101429059

    - +

    六,总结

    image-20220712101429059

    + diff --git a/frontweb/es5/aboutEvent.html b/frontweb/es5/aboutEvent.html index e8bd4ee363..f8afd577c3 100644 --- a/frontweb/es5/aboutEvent.html +++ b/frontweb/es5/aboutEvent.html @@ -30,8 +30,8 @@ JS原生事件 | ChenSino - - + + - +
    + diff --git a/frontweb/es5/aboutThis.html b/frontweb/es5/aboutThis.html index 5d1c155de8..801cb4b4ba 100644 --- a/frontweb/es5/aboutThis.html +++ b/frontweb/es5/aboutThis.html @@ -30,8 +30,8 @@ this指向问题 | ChenSino - - + +
    跳至主要內容

    this指向问题

    qianxunjs基础必会大约 2 分钟

    一,函数内部的this指向

    函数内this指向,是当我们调用函数的时候才能确定。调用方式的不同决定this的指向不同。
    @@ -86,7 +86,7 @@
                 }) 
             }
         }
    -

    总结:

    Promise对象的回调函数是匿名函数时,this指向window,需要对回调函数bind(this)来改变其this指向。

    Promise对象的回调函数是箭头函数时,this指向外层第一个普通函数的this。本例中指向Vue实例。

    img

    - +

    总结:

    Promise对象的回调函数是匿名函数时,this指向window,需要对回调函数bind(this)来改变其this指向。

    Promise对象的回调函数是箭头函数时,this指向外层第一个普通函数的this。本例中指向Vue实例。

    img

    + diff --git a/frontweb/es5/asyncError.html b/frontweb/es5/asyncError.html index ec9cb59d56..57889a8c8f 100644 --- a/frontweb/es5/asyncError.html +++ b/frontweb/es5/asyncError.html @@ -30,8 +30,8 @@ 关于async/await的异常捕获 | ChenSino - - + + - +
    + diff --git a/frontweb/es5/crossDomain.html b/frontweb/es5/crossDomain.html index bddf50ee1a..12dcf38952 100644 --- a/frontweb/es5/crossDomain.html +++ b/frontweb/es5/crossDomain.html @@ -30,8 +30,8 @@ 前端跨域(一)之proxy配置 | ChenSino - - + +
    跳至主要內容

    前端跨域(一)之proxy配置

    qianxunvue知识点必会大约 6 分钟

    一,同源策略

    所谓同源是指:当浏览器向后端发送请求时其请求的协议、域名、端口要和当前服务完全一致。比如前端项目的服务位于http://localhost:8080,则其发送的所有请求必须是http://localhost:8080/xxx/xxx这种格式,否则就会被同源策略拦截。

    http://www.test.com:8000/  协议(http)、主域名(test)、子域名(www)、端口号(8000)
    @@ -96,7 +96,7 @@
              }
          }
      }
    -

    这里/allin相当于http://www.test.com/allin/policy

    这里必须要写pathRewrite: { ‘^/allin’: ‘/’},而且里边必须要写成’^/allin’: ‘/’,这里的斜杠代表的意思就是使用target中的/allin/policy,否则就要使用上面的方式把斜杠写成/allin/policy,并把target中只写域名,如果不写pathRewrite则请求不会成功。

    pathRewrite:如果不写则只能修改代理的域名,如果写则可以修改代理的域名和后边的路径

    3.2,后端解决方案CROS

    跨域资源共享(CORS):通过修改Http协议header的方式,实现跨域。说的简单点就是,通过设置HTTP的响应头信息,在浏览器收到响应时告知浏览器哪些情况在不符合同源策略的条件下也可以跨域访问,浏览器通过解析Http协议中的Header执行具体判断。具体的Header如下:

    CROS跨域常用header

    • Access-Control-Allow-Origin: 允许哪些ip或域名可以跨域访问

    • Access-Control-Max-Age: 表示在多少秒之内不需要重复校验该请求的跨域访问权限

    • Access-Control-Allow-Methods: 表示允许跨域请求的HTTP方法,如:GET,POST,PUT,DELETE

    • Access-Control-Allow-Headers: 表示访问请求中允许携带

    3.3, 反向代理(Nginx、nodejs)

    CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案

    JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

    不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。

    日常工作中,生产环境用得比较多的跨域方案是cors和nginx反向代理。

    本地项目中调试用的最多的就是 node 代理,当然像 nginx、charles(抓包工具)做代理也可以,只要你会配置。

    其实实现代理跨域的逻辑非常简单:就是在不同的资源服务:js资源、html资源、css资源、接口数据资源服务的前端搭建一个中间层,所有的浏览器及客户端访问都通过代理转发。所以在浏览器、客户端看来,它们访问的都是同一个ip、同一个端口的资源,从而符合同源策略实现跨域访问。

    image-20220704100300804

    - +

    这里/allin相当于http://www.test.com/allin/policy

    这里必须要写pathRewrite: { ‘^/allin’: ‘/’},而且里边必须要写成’^/allin’: ‘/’,这里的斜杠代表的意思就是使用target中的/allin/policy,否则就要使用上面的方式把斜杠写成/allin/policy,并把target中只写域名,如果不写pathRewrite则请求不会成功。

    pathRewrite:如果不写则只能修改代理的域名,如果写则可以修改代理的域名和后边的路径

    3.2,后端解决方案CROS

    跨域资源共享(CORS):通过修改Http协议header的方式,实现跨域。说的简单点就是,通过设置HTTP的响应头信息,在浏览器收到响应时告知浏览器哪些情况在不符合同源策略的条件下也可以跨域访问,浏览器通过解析Http协议中的Header执行具体判断。具体的Header如下:

    CROS跨域常用header

    • Access-Control-Allow-Origin: 允许哪些ip或域名可以跨域访问

    • Access-Control-Max-Age: 表示在多少秒之内不需要重复校验该请求的跨域访问权限

    • Access-Control-Allow-Methods: 表示允许跨域请求的HTTP方法,如:GET,POST,PUT,DELETE

    • Access-Control-Allow-Headers: 表示访问请求中允许携带

    3.3, 反向代理(Nginx、nodejs)

    CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案

    JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

    不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。

    日常工作中,生产环境用得比较多的跨域方案是cors和nginx反向代理。

    本地项目中调试用的最多的就是 node 代理,当然像 nginx、charles(抓包工具)做代理也可以,只要你会配置。

    其实实现代理跨域的逻辑非常简单:就是在不同的资源服务:js资源、html资源、css资源、接口数据资源服务的前端搭建一个中间层,所有的浏览器及客户端访问都通过代理转发。所以在浏览器、客户端看来,它们访问的都是同一个ip、同一个端口的资源,从而符合同源策略实现跨域访问。

    image-20220704100300804

    + diff --git a/frontweb/es5/crossDomain2.html b/frontweb/es5/crossDomain2.html index be5e4fd783..2d935d6d30 100644 --- a/frontweb/es5/crossDomain2.html +++ b/frontweb/es5/crossDomain2.html @@ -30,8 +30,8 @@ 前端跨域(二)之JSONP跨域 | ChenSino - - + +
    跳至主要內容

    前端跨域(二)之JSONP跨域

    qianxunvue知识点必会大约 3 分钟

    一,什么是JSONP

    Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

    为什么我们从不同的域(网站)访问数据需要一个特殊的技术( JSONP )呢?这是因为同源策略。 同源策略,它是由 Netscape 提出的一个著名的安全策略,现在所有支持 JavaScript 的浏览器都会使用这个策略。

    二,JSONP的原理

    我们知道,在页面上有三种资源是可以与页面本身不同源的。它们是:js脚本,css样式文件,图片。像taobao等大型网站,很定会将这些静态资源放入cdn中,然后在页面上链接。而jsonp就是利用了script标签可以链接到不同源的js脚本,来到达跨域目的。 于是可以判断,当前阶段如果想通过纯web端(ActiveX控件、服务端代理、Web socket等方式不算)跨域访问数据就只有一种可能,那就是在远程服务器上设法把数据装进js格式的文件里,供客户端调用和进一步处理;

    三,JSONP的具体实现

    JSONP实现跨域请求的具体实现就是动态创建script标签,利用“src”不受同源策略约束的性质来实现跨域获取数据。

    1,服务端JSONP格式数据

    如客户想访问 : https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction。
    @@ -94,7 +94,7 @@
                 console.log(data); //["customername1","customername2"]
             }
         })
    -

    四,总结

    1,我们知道 script,link, img 等等标签引入外部资源,都是 get 请求的,那么就决定了 jsonp 一定是 get请求。

    2,jsonp是一种方式或者说非强制性协议,如同ajax一样,它也不一定非要用json格式来传递数据,如果你愿意,字符串都行,只不过这样不利于用jsonp提供公开服务。

    jsonp图片

    - +

    四,总结

    1,我们知道 script,link, img 等等标签引入外部资源,都是 get 请求的,那么就决定了 jsonp 一定是 get请求。

    2,jsonp是一种方式或者说非强制性协议,如同ajax一样,它也不一定非要用json格式来传递数据,如果你愿意,字符串都行,只不过这样不利于用jsonp提供公开服务。

    jsonp图片

    + diff --git a/frontweb/es5/index.html b/frontweb/es5/index.html index d27ebe0a8a..08bcbf1538 100644 --- a/frontweb/es5/index.html +++ b/frontweb/es5/index.html @@ -30,11 +30,11 @@ ECMAScript 5 | ChenSino - - + + - - + + diff --git a/frontweb/es5/lazyLoad.html b/frontweb/es5/lazyLoad.html index f4fb88455a..e58c0ce073 100644 --- a/frontweb/es5/lazyLoad.html +++ b/frontweb/es5/lazyLoad.html @@ -30,8 +30,8 @@ 图片懒加载 | ChenSino - - + +
    跳至主要內容

    图片懒加载

    qianxunjs基础必会大约 3 分钟

    一,为什么需要图片懒加载

    在老版本的Chrome中,图片的加载其实是会阻塞DOM渲染的。在我们现代浏览器中,这一点基本不用担心了,也就是说现在的图片加载不会阻塞DOM渲染,但是每一个图片都会对应一个HTTP请求而浏览器允许同时并发请求的数量是有限的(数量为6),假设你的网站有大量的图片,那么加载的过程是很耗时的,尤其像那些电商类需要大量图片的网站,可想而知,网站的初始加载时间会很长,再加上网络等其它影响,用户体验会很差。为了解决这个问题,提高用户体验,所以就出现了懒加载这种方式来减轻服务器的压力,优先加载可视区域的内容,其他部分等进入了可视区域再加载,从而提高性能。

    二,图片懒加载原理

    原理其实非常简单,主要就是需要判断元素是否进入了可视区,进入了可视区就去请求对应的图片,否则就显示一张兜底的占位图。

    实现图片懒加载基本要点如下所示:

    img

    三,实战

    3.1 常用:利用图片顶部距离小于浏览器可视区域的高度来判断是否加载图片

    //获取图片元素
    @@ -78,7 +78,7 @@
         obser.observe(image);
     })
     
    -
    - +
    + diff --git a/frontweb/es5/throttle.html b/frontweb/es5/throttle.html index 90f2659427..99592b3d63 100644 --- a/frontweb/es5/throttle.html +++ b/frontweb/es5/throttle.html @@ -30,8 +30,8 @@ 节流与防抖 | ChenSino - - + +
    跳至主要內容

    节流与防抖

    qianxunjs基础必会大约 3 分钟

    一,节流概念(Throttle)

    规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

    主要应用场景有:

    1. 鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;
    2. 在页面的无限加载场景下,需要用户在滚动页面时,每隔一段时间发一次 ajax 请求,而不是在用户停下滚动页面操作时才去请求数据;
    3. 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断;

    二,节流实现

    思路: 第一次先设定一个变量true,第二次执行这个函数时,会判断变量是否true,是则返回。当第一次的定时器执行完函数最后会设定变量为flase。那么下次判断变量时则为flase,函数会依次运行。

    代码一:

    function throttle(fn,delay=100){
    @@ -66,7 +66,7 @@
     		},delay);
     	}
     }
    -
    - +
    + diff --git a/frontweb/es6/index.html b/frontweb/es6/index.html index 0aa2967afc..b6f11e99c1 100644 --- a/frontweb/es6/index.html +++ b/frontweb/es6/index.html @@ -30,11 +30,11 @@ ECMAScript 6 | ChenSino - - + + - - + + diff --git "a/frontweb/es6/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html" "b/frontweb/es6/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html" index ab51915e97..755fbe1280 100644 --- "a/frontweb/es6/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html" +++ "b/frontweb/es6/js\344\270\255\346\225\264\346\225\260\347\232\204\346\234\200\345\244\247\345\200\274.html" @@ -30,8 +30,8 @@ js能识别最大的正整数 | ChenSino - - + +
    跳至主要內容

    js能识别最大的正整数

    ChenSino原创大约 2 分钟

    背景

    今天用Mybatis-plus插入数据,自动产生了一个19位的主键id,后来才发现这是Mybatis-plus3.0后更新的功能,默认使用雪花算法最终会产生一个19位数字。

    然后我在前端项目查询后端接口,后端接口返回的1682195717382606849,而到了前端一直打印的是另一个数字。

    原因,参考gpt回答

    问题1:数据库19位id,在浏览器控制台显示错误?

    如果你的数据库使用的是19位的ID,而浏览器控制台显示错误,可能是因为浏览器默认将超过16位的整数值视为科学计数法表示。在科学计数法表示下,可能会丢失精度或显示不正确。

    为了解决这个问题,可以对ID进行处理,确保在浏览器中正确显示。以下是一种常见的处理方式:

    将ID转换为字符串:将19位的ID转换为字符串形式,在浏览器中显示时可以保留完整的位数。例如,使用Java的String.valueOf方法将ID转换为字符串:

    String idString = String.valueOf(id);
    @@ -43,7 +43,7 @@
                 builder.serializerByType(Long.class, ToStringSerializer.instance);
             };
         }
    -
    - +
    + diff --git a/frontweb/es6/promise.html b/frontweb/es6/promise.html index 4a423bb277..59efd267ec 100644 --- a/frontweb/es6/promise.html +++ b/frontweb/es6/promise.html @@ -30,8 +30,8 @@ Promise介绍 | ChenSino - - + +
    跳至主要內容

    Promise介绍

    ChenSino原创大约 5 分钟

    1. Promise介绍

    romise 是 JavaScript 中用于异步编程的一种解决方案。Promise 可以将异步操作进行封装,并提供了更加灵活和强大的处理方式。

    Promise 有三种状态:

    • pending(等待):初始状态,既不是成功也不是失败状态。
    • fulfilled(成功):意味着操作成功完成,Promise 实例的最终值可通过 then 方法获取到。
    • rejected(失败):意味着操作失败,Promise 实例的最终值可通过 catch 方法获取到。

    Promise 实例可以使用 then、catch 和 finally 方法实现异步操作的链式调用:

    promise.then(onResolved, onRejected)
    @@ -80,7 +80,7 @@
     
     fetchData();
     
    -

    在上述示例中,使用 then 方法时,需要通过回调函数来处理异步操作的结果。而使用 await 时,可以将异步操作的结果直接赋给一个变量,并在 try/catch 语句中处理成功或失败的情况。

    需要注意的是,await 只能在异步函数中使用,所以在使用 await 时,需要将它放在一个异步函数内部(如上述示例中的 async function fetchData())

    4. 如果我有多个异步请求,要求每个异步请求顺序执行,该如何做

    有2个方法

    1. 使用await

      在每个强求前添加await同步执行

    2. 使用then,在then的成功回掉函数中,添加下一个请求

    - +

    在上述示例中,使用 then 方法时,需要通过回调函数来处理异步操作的结果。而使用 await 时,可以将异步操作的结果直接赋给一个变量,并在 try/catch 语句中处理成功或失败的情况。

    需要注意的是,await 只能在异步函数中使用,所以在使用 await 时,需要将它放在一个异步函数内部(如上述示例中的 async function fetchData())

    4. 如果我有多个异步请求,要求每个异步请求顺序执行,该如何做

    有2个方法

    1. 使用await

      在每个强求前添加await同步执行

    2. 使用then,在then的成功回掉函数中,添加下一个请求

    + diff --git a/frontweb/es6/useModule.html b/frontweb/es6/useModule.html index fbe9754bf2..cf79df858a 100644 --- a/frontweb/es6/useModule.html +++ b/frontweb/es6/useModule.html @@ -30,8 +30,8 @@ 前端模块化 | ChenSino - - + +
    跳至主要內容

    前端模块化

    qianxunvue知识点必会大约 9 分钟

    一,什么是前端模块化

    模块化就是将一个复杂的应用程序,按照规范拆分成几个相互独立的文件,这些文件里面完成共同的或者类似的逻辑,通过对外暴露一些数据或者调用方法,与外部整合。

    这样每个文件彼此独立,我们开发者更容易开发和维护代码,特别是当开发的项目越来越大,代码复杂性也不断增加,这对于模块化的需求也会越来越大。

    模块化主要特点是:可复用性、可组合性、独立性、中心化。

    所以使用模块化可以帮我们解决什么问题呢?

    • 解决了命名冲突:因为每个模块是独立的,所以变量或函数名重名不会发生冲突
    • 提高可维护性:因为每个文件的职责单一,有利于代码维护
    • 性能优化:异步加载模块对页面性能会非常好
    • 模块的版本管理:通过别名等配置,配合构建工具,可以实现模块的版本管理
    • 跨环境共享模块:通过 Sea.jsNodeJS版本,可以实现模块的跨服务器和浏览器共享

    目前前端主流的模块化标准有:

    • CommonJS
    • AMD
    • CMD
    • UMD
    • ES6

    二,CommonJS

    commonJS 是语言层面的规范,当前主要用于Node.js.

    在每个模块内部有一个 module 对象,代表当前模块,通过它来导出当前模块里的 API,module 有几个属性:

    • exports : 是对外的接口,加载某个模块,就是加载该模块的**module.exports**属性
    • loaded: 返回一个布尔值,表示该模块是否已完成加载
    • parent: 返回一个对象,表示调用该模块的模块
    • children:返回一个数组,表示该模块被用到了其他模块的集合
    • filename:模块的文件名,带有绝对路径
    • id:模块的标识符,一般是带有绝对路径的模块文件名

    CommonJS 规范的特点:

    • 每个文件都是独立的模块,有独立的作用域,b变量和方法 不会污染全局空间,并且对其他文件是不可见的。
    • 文件可以被重复引用、加载。第一次加载时会被缓存,之后再引用就直接读取缓存
    • 加载某个模块时,module.exports 输出的是值的拷贝,一旦这个值被输出,模块内再发生变化不会影响已经输出的值

    用法是这样的:

    导出

    
    @@ -139,7 +139,7 @@
     

    
     npx babel-node
     
    -

    其他

    Webpack 本身维护了一套模块系统,兼容了几乎所有前端历史下的模块规范,上面说的模块化全都有。

    - +

    其他

    Webpack 本身维护了一套模块系统,兼容了几乎所有前端历史下的模块规范,上面说的模块化全都有。

    + diff --git a/frontweb/es6/useNpm.html b/frontweb/es6/useNpm.html index 04d6e39787..b5b24823fc 100644 --- a/frontweb/es6/useNpm.html +++ b/frontweb/es6/useNpm.html @@ -30,8 +30,8 @@ 搞懂npm与cnpm | ChenSino - - + +
    跳至主要內容

    搞懂npm与cnpm

    qianxunvue知识点必会大约 2 分钟

    一,什么是npm

    npm是node官方的包管理工具 cnpm 是淘宝NPM镜像官网,来自淘宝NPM镜像官网open in new window的说明:

    淘宝为我们搭建了一个国内的npm服务器,它目前是每隔10分钟将国外npm仓库的所有内容“搬运”回国内的服务器上,这样我们直接访问淘宝的国内服务器就可以了,它的地址是:https://registry.npm.taobao.orgopen in new window

    npm和cnpm只是下载的地址不同,npm是从国外下载东西,cnpm是从国内下载东西。

    npm默认的仓库地址为:

    http://registry.npmjs.org
    @@ -39,7 +39,7 @@
     


    二,什么时候使用cnpm?

    真好笑! 😂 ❤️ 因为npm的远程服务器在国外,所以有时候难免出现访问过慢,甚至无法访问的情况。

    第一种:

    直接安装cnpm 安装淘宝提供的cnpm,并更改服务器地址为淘宝的国内地址,

    npm install -g cnpm --registry=https://registry.npm.taobao.org
     

    这个命令行做了两件事:

    (1)全局安装 cnpm

    (2)并且将安装 (各种包) 的地址切换到 国内的淘宝镜像

    以后安装直接采用cpm替代npm, 例如原生npm命令为:npm install uniq --save

    cnpm命令为:cnpm install uniq --save

    第二种:

    替换npm仓库地址为淘宝镜像地址(推荐) 命令:

    npm config set registry https://registry.npm.taobao.org
     

    查看是否更改成功:

    npm config get registry
    -

    以后安装时,依然用npm命令,但是实际是从淘宝国内服务器下载的

    【混淆系列】三问:npx、npm、cnpm、pnpm区别你搞清楚了吗?open in new window

    - +

    以后安装时,依然用npm命令,但是实际是从淘宝国内服务器下载的

    【混淆系列】三问:npx、npm、cnpm、pnpm区别你搞清楚了吗?open in new window

    + diff --git a/frontweb/es6/usePnpm.html b/frontweb/es6/usePnpm.html index a337c5cfbe..9041808897 100644 --- a/frontweb/es6/usePnpm.html +++ b/frontweb/es6/usePnpm.html @@ -30,8 +30,8 @@ 搞懂npm与pnpm | ChenSino - - + +
    跳至主要內容

    搞懂npm与pnpm

    qianxunnpm知识点必会大约 2 分钟

    一,什么是pnpm

    performant npm ,意味“高性能的 npm”。pnpm由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”

    二,特点

    1. 当使用 npm 或 Yarn 时,如果你有 100 个项目,并且所有项目都有一个相同的依赖包,那么, 你在硬盘上就需要保存 100 份该相同依赖包的副本。然而,如果是使用 pnpm,依赖包将被 存放在一个统一的位置,因此:

      1. 如果你对同一依赖包需要使用不同的版本,则仅有 版本之间不同的文件会被存储起来。例如,如果某个依赖包包含 100 个文件,其发布了一个新 版本,并且新版本中只有一个文件有修改,则 pnpm update 只需要添加一个 新文件到存储中,而不会因为一个文件的修改而保存依赖包的 所有文件。
      2. 所有文件都保存在硬盘上的统一的位置。当安装软件包时, 其包含的所有文件都会硬链接自此位置,而不会占用 额外的硬盘空间。这让你可以在项目之间方便地共享相同版本的 依赖包。

      最终结果就是以项目和依赖包的比例来看,你节省了大量的硬盘空间, 并且安装速度也大大提高了!

    三,安装

    全局安装

    
    @@ -81,7 +81,7 @@
     # 设置存储路径
     pnpm config set store-dir /path/to/.pnpm-store
     
    -

    pnpm官方网站open in new window

    - +

    pnpm官方网站open in new window

    + diff --git a/frontweb/es6/useYarn.html b/frontweb/es6/useYarn.html index 974b2415a5..62cb467ef2 100644 --- a/frontweb/es6/useYarn.html +++ b/frontweb/es6/useYarn.html @@ -30,8 +30,8 @@ npm与yarn的区别 | ChenSino - - + +
    跳至主要內容

    npm与yarn的区别

    qianxunvue知识点必会大约 1 分钟

    一,yarn简介

    yarn 是由 Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具,yarn 是为了弥补 npm 的一些缺陷而出现的。

    二,npm的缺陷

    • npm install 下载速度慢,重新 install 时速度依旧慢

    • 同一个项目,安装的无法保持一致性。原因是因为 package.json 文件中版本号的特点导致的,下面三个版本号在安装的时候代表不同的含义。

      "5.0.3"     # 表示安装指定的5.0.3版本
      @@ -40,7 +40,7 @@
       
    • 使用 npm 安装多个 js 包时,包会在同一时间下载和安装。安装过程中,其中一个包抛出了一个异常,但 npm 会继续安装其他包,所以错误信息就会在一大堆提示信息中丢失掉,以至于直到执行前,都不会发现实际发生的错误

    三,yarn优点

    image-20221014114909514

    之前一直有个误区,就是yarn不用设置淘宝镜像,速度慢的话,依旧需要设置

    
     yarn config set registry 'https://registry.npm.taobao.org' 
     
    -

    yarn 中文文档open in new window

    - +

    yarn 中文文档open in new window

    + diff --git a/frontweb/index.html b/frontweb/index.html index 214ccf2ee7..ad04c83327 100644 --- a/frontweb/index.html +++ b/frontweb/index.html @@ -30,11 +30,11 @@ JavaScript成神之路 | ChenSino - - + + - - + + diff --git a/frontweb/nodejs/index.html b/frontweb/nodejs/index.html index b61b92791b..0e55a30881 100644 --- a/frontweb/nodejs/index.html +++ b/frontweb/nodejs/index.html @@ -30,11 +30,11 @@ NodeJS | ChenSino - - + + - - + + diff --git "a/frontweb/nodejs/\345\256\211\350\243\205\351\227\256\351\242\230.html" "b/frontweb/nodejs/\345\256\211\350\243\205\351\227\256\351\242\230.html" index e0dde6f7c2..e629d74abb 100644 --- "a/frontweb/nodejs/\345\256\211\350\243\205\351\227\256\351\242\230.html" +++ "b/frontweb/nodejs/\345\256\211\350\243\205\351\227\256\351\242\230.html" @@ -30,13 +30,13 @@ CentOS7安装node18 | ChenSino - - + +
    跳至主要內容

    CentOS7安装node18

    chenkun小于 1 分钟

    背景

    从nodejs18开始就不支持Centos7了,这是因为centos7的gilbc版本比较低,因此需要安装非官方构建的版本。

    Note:如果npm安装的包依赖于glibc,那得改用docker或者换操作系统了。

    安装非官方的node

    1. 下载
      下载地址https://unofficial-builds.nodejs.org/download/release/v18.19.0/open in new window
    2. 解压安装
      tar -zxvf node-v18.19.0-linux-x64-glibc-217.tar.gz
       mv node-v18.19.0-linux-x64-glibc-217  node-v18
      -
    - +
    + diff --git a/frontweb/typeScript/action-usage.html b/frontweb/typeScript/action-usage.html index c470c7f84f..27817d20d5 100644 --- a/frontweb/typeScript/action-usage.html +++ b/frontweb/typeScript/action-usage.html @@ -30,8 +30,8 @@ typeScript项目实战 | ChenSino - - + +
    跳至主要內容

    typeScript项目实战

    qianxunvue知识点必会vue中的 TypeScript大约 1 分钟

    一,利用typeScript实现新增,删除一行数据

    这里基本涵盖了typeScript在项目中的实战用法

    1,html部分

    <!DOCTYPE html>
    @@ -145,7 +145,7 @@
     tableBody?.addEventListener<'click'>('click', (ev: MouseEvent) => {
         WebDisplay.deleteData(<HTMLAnchorElement>ev.target);
     });
    -

    项目来源:b站技术蛋老师open in new window

    - +

    项目来源:b站技术蛋老师open in new window

    + diff --git a/frontweb/typeScript/axios.html b/frontweb/typeScript/axios.html index 714d6da869..3cccabe107 100644 --- a/frontweb/typeScript/axios.html +++ b/frontweb/typeScript/axios.html @@ -30,8 +30,8 @@ typeScript中使用axios | ChenSino - - + + - +
    + diff --git a/frontweb/typeScript/basic-usage.html b/frontweb/typeScript/basic-usage.html index 7d32b13278..ced04ca348 100644 --- a/frontweb/typeScript/basic-usage.html +++ b/frontweb/typeScript/basic-usage.html @@ -30,8 +30,8 @@ typeScript在vue项目中常见用法 | ChenSino +

    这个类型参数应该是一个带调用签名open in new window的类型字面量。这个类型字面量的类型就是返回的 emit 函数的类型。我们可以看到,基于类型的声明使我们可以对所触发事件的类型进行更细粒度的控制。

    + diff --git a/frontweb/typeScript/fanType.html b/frontweb/typeScript/fanType.html index 19f02865f3..3dcb9ae223 100644 --- a/frontweb/typeScript/fanType.html +++ b/frontweb/typeScript/fanType.html @@ -30,8 +30,8 @@ typeScript中的泛型 | ChenSino - - + +
    跳至主要內容

    typeScript中的泛型

    qianxunvue知识点必会vue中的 TypeScript大约 2 分钟

    一,泛型

    泛型就是通过给类型传参,得到一个更加通用的类型,就像给函数传参一样。 如下我们得到一个通用的泛型类型 T1,通过传参,可以得到 T2 类型 string[]、T3 类型 number[]; T 是变量,我们可以用任意其他变量名代替他。

    
    @@ -107,7 +107,7 @@
     const S1: I4 = { name: '123' } // 默认 name: string类型
     const S2: I4<number> = { name: 123 }
     
    -
    - +
    + diff --git a/frontweb/typeScript/index.html b/frontweb/typeScript/index.html index 8b923e8f15..ecfd91a7f6 100644 --- a/frontweb/typeScript/index.html +++ b/frontweb/typeScript/index.html @@ -30,11 +30,11 @@ typeScript学习资料 | ChenSino - - + + - - + + diff --git a/frontweb/typeScript/tsAndvue3.html b/frontweb/typeScript/tsAndvue3.html index 4460fa4168..5ba489cdc3 100644 --- a/frontweb/typeScript/tsAndvue3.html +++ b/frontweb/typeScript/tsAndvue3.html @@ -30,8 +30,8 @@ typeScript在vue3中的实战 | ChenSino - - + + - +
    + diff --git a/frontweb/vite/index.html b/frontweb/vite/index.html index 255cf3e8b6..352f369d96 100644 --- a/frontweb/vite/index.html +++ b/frontweb/vite/index.html @@ -30,11 +30,11 @@ Vite | ChenSino - - + + - - + + diff --git "a/frontweb/vue/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html" "b/frontweb/vue/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html" index e80f25f371..f7fcec43ca 100644 --- "a/frontweb/vue/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html" +++ "b/frontweb/vue/elementui\350\241\250\345\215\225\350\207\252\345\256\232\344\271\211\346\240\241\351\252\214.html" @@ -30,8 +30,8 @@ ElementUI表单自定义校验 | ChenSino - - + + - +

    说明

    这种方式可以通过回掉参数中rule获取到规则中传递的required、min、max等参数,使用时,只需要正常传递即可

    + diff --git a/frontweb/vue/eventBus.html b/frontweb/vue/eventBus.html index ddfd2e2300..7dd1fe2e2f 100644 --- a/frontweb/vue/eventBus.html +++ b/frontweb/vue/eventBus.html @@ -30,8 +30,8 @@ 事件总线Mitt | ChenSino - - + + - +
    + diff --git a/frontweb/vue/index.html b/frontweb/vue/index.html index 3f07f46e7e..3cc37cb6ab 100644 --- a/frontweb/vue/index.html +++ b/frontweb/vue/index.html @@ -30,11 +30,11 @@ Vue | ChenSino - - + + - - + + diff --git a/frontweb/vue/vue-Direactive.html b/frontweb/vue/vue-Direactive.html index 08c8bb198e..d67636c470 100644 --- a/frontweb/vue/vue-Direactive.html +++ b/frontweb/vue/vue-Direactive.html @@ -30,8 +30,8 @@ vue自定义指令控制按钮级别权限 | ChenSino - - + +
    跳至主要內容

    vue自定义指令控制按钮级别权限

    qianxunvue知识点必会大约 3 分钟

    一,自定义指令介绍

    除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。

    vue中重用代码,3种方式:

    1. ​ 组件 --组件是主要的构建模块
    2. ​ 组合式函数--主要是侧重于有状态的逻辑
    3. ​ 自定义指令--主要是为了重用涉及普通元素的底层DOM访问的逻辑

    二,注册自定义指令

    2.1 局部注册

    
    @@ -118,7 +118,7 @@
                 }     
         });  
     }
    -
    - +
    + diff --git a/frontweb/vue/vue-authority.html b/frontweb/vue/vue-authority.html index 90121d5c76..6d04d0d988 100644 --- a/frontweb/vue/vue-authority.html +++ b/frontweb/vue/vue-authority.html @@ -30,8 +30,8 @@ vue中权限相关的问题 | ChenSino - - + +
    跳至主要內容

    vue中权限相关的问题

    qianxunvue知识点必会大约 3 分钟

    前端主要有哪些权限控制?

    一,接口访问权限

    接口权限目前一般采用通用的形式来验证(用户是否登录系统),没有的话一般返回401,跳转到登录页面重新进行登录 ,登录成功后拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token

    
    @@ -96,7 +96,7 @@
     }
     mapComponent(serviceMap)
     
    -

    三,按钮权限

    使用自定义指令来实现按钮级别的权限控制open in new window

    参考文章

    vue之权限控制和动态路由open in new window

    手摸手,带你用vue撸后台 系列二(登录权限篇)open in new window

    - +

    三,按钮权限

    使用自定义指令来实现按钮级别的权限控制open in new window

    参考文章

    vue之权限控制和动态路由open in new window

    手摸手,带你用vue撸后台 系列二(登录权限篇)open in new window

    + diff --git a/frontweb/vue/vue-in-action.html b/frontweb/vue/vue-in-action.html index 2e60148229..e726b3ddf3 100644 --- a/frontweb/vue/vue-in-action.html +++ b/frontweb/vue/vue-in-action.html @@ -30,8 +30,8 @@ 利用vue-cli搭建项目 | ChenSino - - + + - +
    + diff --git a/frontweb/vue/vue-nextTick.html b/frontweb/vue/vue-nextTick.html index 15a33d4f7f..101fa0d11d 100644 --- a/frontweb/vue/vue-nextTick.html +++ b/frontweb/vue/vue-nextTick.html @@ -30,8 +30,8 @@ 关于vue-nextTick | ChenSino - - + +
    跳至主要內容

    关于vue-nextTick

    qianxunvue知识点必会大约 3 分钟

    一,什么是nextTick

    定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

    为什么vue中会用到nextTick的回调去获取更新后的DOM?

    这是因为vue 实现响应式并**不是数据发生变化之后 DOM 立即变化**,而是按一定的策略进行 DOM 的更新。
    @@ -76,7 +76,7 @@
         // entire view has been rendered
       })
     }
    -

    2、当项目中你想在改变DOM元素的数据后基于新的dom做点什么,对新DOM一系列的js操作都需要放进Vue.nextTick()的回调函数中;通俗的理解是:更改数据后当你想立即使用js操作新的视图的时候需要使用它.

    三,Vue.nextTick(callback) 使用原理

    原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。

    当你设置 vm.someData = 'new value',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。

    - +

    2、当项目中你想在改变DOM元素的数据后基于新的dom做点什么,对新DOM一系列的js操作都需要放进Vue.nextTick()的回调函数中;通俗的理解是:更改数据后当你想立即使用js操作新的视图的时候需要使用它.

    三,Vue.nextTick(callback) 使用原理

    原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。

    当你设置 vm.someData = 'new value',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。

    + diff --git a/frontweb/vue/vue-pic.html b/frontweb/vue/vue-pic.html index bb9e593614..30dee837bb 100644 --- a/frontweb/vue/vue-pic.html +++ b/frontweb/vue/vue-pic.html @@ -30,8 +30,8 @@ vue图片路径问题 | ChenSino - - + + - +
    + diff --git a/frontweb/vue/vue-router1.html b/frontweb/vue/vue-router1.html index 8b2d913ac5..77505d81d7 100644 --- a/frontweb/vue/vue-router1.html +++ b/frontweb/vue/vue-router1.html @@ -30,8 +30,8 @@ vue-router4.0的基本使用 | ChenSino - - + + - +

    更多关于在组合式 API中使用vue-router请参考官网open in new window

    + diff --git a/frontweb/vue/vue-router2.html b/frontweb/vue/vue-router2.html index 8b66ada6fc..01ef23c0c4 100644 --- a/frontweb/vue/vue-router2.html +++ b/frontweb/vue/vue-router2.html @@ -30,8 +30,8 @@ vue-router源码浅析 | ChenSino - - + +
    跳至主要內容

    vue-router源码浅析

    qianxunvue知识点必会大约 14 分钟

    一, 路由的本质

    简单来说,浏览器端路由其实并不是真实的网页跳转(和服务器没有任何交互),而是纯粹在浏览器端发生的一系列行为,本质上来说前端路由就是:

    对 url 进行改变和监听,来让某个 dom 节点显示对应的视图

    二, 路由的区别

    一般来说,浏览器端的路由分为两种:

    1. hash 路由,特征是` url` 后面会有 # 号,如` baidu.com/#foo/bar/baz`。
    @@ -559,7 +559,7 @@
     };
     
     export default VueRouter
    -

    参考:手写Vue-router核心原理open in new window

    跟着来,你也可以手写vue-routeropen in new window

    - +

    参考:手写Vue-router核心原理open in new window

    跟着来,你也可以手写vue-routeropen in new window

    + diff --git a/frontweb/vue/vue3Emit.html b/frontweb/vue/vue3Emit.html index da5bc51eb6..12ac741212 100644 --- a/frontweb/vue/vue3Emit.html +++ b/frontweb/vue/vue3Emit.html @@ -30,8 +30,8 @@ vue3使用emit进行父子组件传值 | ChenSino - - + + - +
    + diff --git a/frontweb/vue/vue3LifeTime.html b/frontweb/vue/vue3LifeTime.html index e198a15b7e..038547b1bb 100644 --- a/frontweb/vue/vue3LifeTime.html +++ b/frontweb/vue/vue3LifeTime.html @@ -30,8 +30,8 @@ vue中组件的生命周期 | ChenSino - - + +
    跳至主要內容

    vue中组件的生命周期

    qianxunvue知识点必会大约 5 分钟

    一,vue生命周期钩子函数

    d0c8a786c9177f3e668177cd4bfcf9c19e3d5676

    1.1 beforeCreate

    • 该函数执行在组件创建、数据观测 (data observer) 和 event/watcher 事件配置之前,实例初始化之后被调用。

    • 在该阶段组件未创建不能访问数据,组件中的data,ref均为undefined。

    1.2 created

    • 该函数在组件创建完成后被立即调用,在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。

    • 但是还未渲染成HTML模板,组件中的data对象已经存在,可以对data进行操作了,即可以访问数据,发请求,ref依旧是undefined,挂载阶段还没开始,$el 属性目前尚不可用。

    • 般我们可以将对数据的初始化和初始化页面的请求放到里面,结束loading。

    1.3 beforeMount

    • 该函数在组件挂载之前,在该阶段页面上还没渲染出HTML元素,data初始化完成,ref依旧不可以操作,相关的 render 函数首次被调用。

    • 可以访问数据,编译模板结束,虚拟dom已经存在

    • 该钩子在`服务器端渲染期间不被调用。

    1.4 mounted

    • 该函数是页面完成挂载之后执行的,这时 el 被新创建的 vm.$el 替换了,就可以操作ref了,一般会用于将组件初始时请求数据的方法放到这里面,filter也是在这里生效。

    • 如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。

    • 可以拿到数据和节点,实例被挂载后调用。

    注意 :mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick:

    mounted: function () {
    @@ -132,7 +132,7 @@
         })
     </script>
     
    -

    1.11 errorCaptured

    • 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

    二、 父子组件生命周期执行顺序

    在正常开发,挂载周期的执行顺序为:

    父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父mounted

    在数据更新阶段执行顺序为:

    父beforeUpdate => 子beforeUpdate => 子updated => 父updated

    在组件销毁阶段执行顺序为:

    父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

    由此可见,其实所有周期规律就是:只要子组件被引入触发,所处不管任何周期都是父组件先开始执行,然后等到子组件执行完,父组件收尾。

    实际开发中遇到一个问题,父组件中挂载子组件,子组件里也含有子组件,祖父组件中挂载父组件,

    。父,子,孙,没有if渲染条件的阻止,mounted的执行顺序为

    孙mounted->父mounted->祖父mounted

    - +

    1.11 errorCaptured

    • 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

    二、 父子组件生命周期执行顺序

    在正常开发,挂载周期的执行顺序为:

    父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount => 子mounted => 父mounted

    在数据更新阶段执行顺序为:

    父beforeUpdate => 子beforeUpdate => 子updated => 父updated

    在组件销毁阶段执行顺序为:

    父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

    由此可见,其实所有周期规律就是:只要子组件被引入触发,所处不管任何周期都是父组件先开始执行,然后等到子组件执行完,父组件收尾。

    实际开发中遇到一个问题,父组件中挂载子组件,子组件里也含有子组件,祖父组件中挂载父组件,

    。父,子,孙,没有if渲染条件的阻止,mounted的执行顺序为

    孙mounted->父mounted->祖父mounted

    + diff --git a/frontweb/vue/vueExtend.html b/frontweb/vue/vueExtend.html index a97adbe28d..02d2015ece 100644 --- a/frontweb/vue/vueExtend.html +++ b/frontweb/vue/vueExtend.html @@ -30,8 +30,8 @@ 利用Vue.extend定义全局组件 | ChenSino - - + + - +
    + diff --git "a/frontweb/vue/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html" "b/frontweb/vue/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html" index ead3d8d7c8..ccad75f5e9 100644 --- "a/frontweb/vue/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html" +++ "b/frontweb/vue/\350\207\252\345\256\232\344\271\211\346\214\207\344\273\244\347\232\204\345\256\236\350\267\265.html" @@ -30,8 +30,8 @@ 使用自定义指令实现高亮 | ChenSino - - + +
    跳至主要內容

    使用自定义指令实现高亮

    chensino原创大约 2 分钟

    需求

    需求是: 在前端页面有很多文本,要求提取文本,根据规则判断文本是否和规则互斥,比如“女”和“前列腺”就是互斥的。当互斥时,需要高亮,同时当鼠标 指在文本上,弹出提示框,提示“女和前列腺规则互斥” 在页面中,通过自定义指令,实现对文本的局部高亮,其中高亮内容是通过接口动态获取。

    实现方案

    利用vue的自定义指令,实现局部高亮。组件调用自定义指令,同时传入高亮内容,指令内部根据传入的高亮内容,对文本进行局部高亮。

    代码

    export default {
    @@ -147,7 +147,7 @@
         wordsTooltip.visible = false
       }
     }
    -

    参考elementui的tooltip组件,虚拟触发~

    - +

    参考elementui的tooltip组件,虚拟触发~

    + diff --git a/guide/disable.html b/guide/disable.html index c0e8af819c..172f232d28 100644 --- a/guide/disable.html +++ b/guide/disable.html @@ -30,11 +30,11 @@ 布局与功能禁用 | ChenSino - - + +
    跳至主要內容

    布局与功能禁用


    你可以通过设置页面的 Frontmatter,在页面禁用功能与布局。

    本页面就是一个示例,禁用了如下功能:

    • 导航栏
    • 侧边栏
    • 路径导航
    • 页面信息
    • 贡献者
    • 编辑此页链接
    • 更新时间
    • 上一篇/下一篇 链接
    • 评论
    • 页脚
    • 返回顶部按钮
    - + diff --git a/guide/encrypt.html b/guide/encrypt.html index eed2c7ab53..022c0b16db 100644 --- a/guide/encrypt.html +++ b/guide/encrypt.html @@ -30,11 +30,11 @@ 密码加密的文章 | ChenSino - - + + - - + + diff --git a/guide/index.html b/guide/index.html index b7fee0ddcd..5f1c202194 100644 --- a/guide/index.html +++ b/guide/index.html @@ -30,11 +30,11 @@ 主要功能与配置演示 | ChenSino - - + + - - + + diff --git a/guide/markdown.html b/guide/markdown.html index 34299c68dd..9c32ea6e30 100644 --- a/guide/markdown.html +++ b/guide/markdown.html @@ -30,8 +30,8 @@ Markdown 展示 | ChenSino - - + +
    跳至主要內容

    Markdown 展示

    ChenSino使用指南Markdown大约 4 分钟

    VuePress 主要从 Markdown 文件生成页面。因此,你可以使用它轻松生成文档或博客站点。

    你应该创建和编写 Markdown 文件,以便 VuePress 可以根据文件结构将它们转换为不同的页面。

    Markdown 介绍

    如果你是一个新手,还不会编写 Markdown,请先阅读 Markdown 介绍open in new windowMarkdown 演示open in new window

    Markdown 配置

    VuePress 通过 Frontmatter 为每个 Markdown 页面引入配置。

    相关信息

    Frontmatter 是 VuePress 中很重要的一个概念,如果你不了解它,你需要阅读 Frontmatter 介绍open in new window

    Markdown 扩展

    VuePress 会使用 markdown-itopen in new window 来解析 Markdown 内容,因此可以借助于 markdown-it 插件来实现 语法扩展open in new window

    VuePress 扩展

    为了丰富文档写作,VuePress 对 Markdown 语法进行了扩展。

    关于这些扩展,请阅读 VuePress 中的 Markdown 扩展open in new window

    主题扩展

    通过 vuepress-plugin-md-enhanceopen in new window,主题扩展了更多 Markdown 语法,提供更加丰富的写作功能。

    一键启用

    你可以设置在主题选项中设置 plugins.mdEnhance.enableAll: true 启用 md-enhanceopen in new window 插件的所有功能。

    ::: code-tabs#language

    @tab TS

    import { hopeTheme } from "vuepress-theme-hope";
    @@ -164,7 +164,7 @@
       color: red;
     }
     

    :::

    样式化

    设置它没有任何效果,请不要这样使用

    幻灯片

    @slidestart

    幻灯片 1

    一个有文字和 链接open in new window 的段落


    幻灯片 2

    • 项目 1
    • 项目 2

    幻灯片 3.1

    const a = 1;
    -

    --

    幻灯片 3.2

    $$ J(\theta_0,\theta_1) = \sum_{i=0} $$

    @slideend

    - +

    --

    幻灯片 3.2

    $$ J(\theta_0,\theta_1) = \sum_{i=0} $$

    @slideend

    + diff --git a/guide/page.html b/guide/page.html index 80d4869db5..077d66d82d 100644 --- a/guide/page.html +++ b/guide/page.html @@ -30,11 +30,11 @@ 页面配置 | ChenSino - - + +
    跳至主要內容

    页面配置

    Ms.Hope使用指南页面配置使用指南大约 1 分钟

    more 注释之前的内容被视为文章摘要。

    页面信息

    你可以在 Markdown 的 Frontmatter 中设置页面信息。

    • 作者设置为 Ms.Hope。
    • 写作日期为 2020 年 1 月 1 日
    • 分类为 “使用指南”
    • 标签为 “页面配置” 和 “使用指南”

    页面内容

    你可以自由在这里书写你的 Markdown。

    提示

    • 你可以将图片和 Markdown 文件放置在一起,但是你需要使用相对链接./ 进行引用。

    • 对于 .vuepress/public 文件夹的图片,请使用绝对链接 / 进行引用。

    主题包含了一个自定义徽章章可以使用:

    文字结尾应该有深蓝色的 徽章文字 徽章。 徽章文字

    页面结构

    此页面应当包含:

    你可以通过主题选项和页面 Frontmatter 自定义它们。

    - + diff --git a/home.html b/home.html index d982b06188..9545b879c4 100644 --- a/home.html +++ b/home.html @@ -28,13 +28,13 @@ document.documentElement.setAttribute("data-theme", "dark"); } - Home | ChenSino + Home | ChenSino - - + + - - + + diff --git a/index.html b/index.html index fdbd63a964..f4c9af5ead 100644 --- a/index.html +++ b/index.html @@ -28,13 +28,13 @@ document.documentElement.setAttribute("data-theme", "dark"); } - ChenSino | ChenSino + ChenSino | ChenSino - - + + -
    跳至主要內容
    ChenSino

    ChenSino

    洛星星的学习笔记

    ChenSino
    好好学习,天天向上
    Spring缓存

    Spring缓存大揭秘

    +
    跳至主要內容
    ChenSino

    ChenSino

    洛星星的学习笔记

    SpringAOP

    1、SpringAOP

    > SpringAOP的本质就是动态代理,底层使用JDK动态代理或者CGlib动态代理,通过代理框架生成代理类,实现对目标类的增强,Spring代理是方法级别的代理,是对方法增强,
     > 
    @@ -142,7 +142,7 @@ 

    1、什么是双亲委派?

    ​双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现却异常简单,用以实现双亲委 派的代码只有短短十余行,全部集中在java.lang.ClassLoader的loadClass()方法之中。


    chenkun大约 13 分钟

    JetBrains Logo (Main) logo

    - +
    ChenSino大约 8 分钟java
    + diff --git a/java/advance/Arthas.html b/java/advance/Arthas.html index 00783f1c05..07d3bb67a1 100644 --- a/java/advance/Arthas.html +++ b/java/advance/Arthas.html @@ -30,8 +30,8 @@ 使用Arthas定位线上问题 | ChenSino - - + +
    跳至主要內容

    使用Arthas定位线上问题

    chenkun问题定位大约 1 分钟

    背景:有一个导出PDF的功能,在本地运行正常,在线上异常,抛出的异常无法直接定位到问题。

    1、使用arthas来跟踪

    1. 进入arths,attach进入项目
    $ as.sh
    @@ -45,7 +45,7 @@
     2
     
     
    1. 根据线上log定位到异常大致的位置

    image-20220428111351818

    1. 到本地IDE打开源码

    image-20220428111556978

    createPDF方法空指针,那首先肯定想到的是参数htmlStr是null

    1. 进入freemarkerRender方法查看,为何返回null

    image-20220428112248916

    1. 直接查看关键处理函数template.process(dataMap, out);

    2. 使用arthas的watch命令,追踪参数、返回值、异常信息

    watch freemarker.template.Template process '{params,returnObj,throwExp}'  -n 5  -x 3 '1==1'
    -

    追踪发现

    image-20220428113034007

    1. 源码中因为异常未指定捕获TemplateModelException所以template.process(dataMap, out);方法报错后直接返回了null,所以直接把异常捕获改成ExcelpTion即可

      image-20220428113408828

    - +

    追踪发现

    image-20220428113034007

    1. 源码中因为异常未指定捕获TemplateModelException所以template.process(dataMap, out);方法报错后直接返回了null,所以直接把异常捕获改成ExcelpTion即可

      image-20220428113408828

    + diff --git a/java/advance/Collection.html b/java/advance/Collection.html index 4400d17a93..cc75700609 100644 --- a/java/advance/Collection.html +++ b/java/advance/Collection.html @@ -30,11 +30,11 @@ java集合 | ChenSino - - + + - - + + diff --git a/java/advance/CompileJdk11.html b/java/advance/CompileJdk11.html index 10f0311807..0c70e8765d 100644 --- a/java/advance/CompileJdk11.html +++ b/java/advance/CompileJdk11.html @@ -30,8 +30,8 @@ 在Manjaro中编译JDK11 | ChenSino - - + +
    跳至主要內容

    在Manjaro中编译JDK11

    chenkunjdk大约 5 分钟

    1 下载源码

    网上根据关键词查找jdk源码,查找出来很多可以下载源码的链接,这里我们使用github去官方仓库,openjdk是托管在github的OpenJDK组织open in new window下,该组织下有各个版本的openjdk源码,不要直接使用jdk仓库,这个仓库存放的是当前正在开发的最新版本代码,我们要用的是jdk11,因此我们搜索jdk11仓库,我这里选择的是jdk11u这个库。

    20230113095229

    git clone https://github.com/openjdk/jdk11u.git
    @@ -127,7 +127,7 @@
     ## 3. 把gcc7设为默认
     sudo cp gcc-7 gcc
     sudo cp g++-7 g++
    -

    设置默认gcc后,重新执行bash configure ,然后make clean,然后再make images就成功了,编译好的jdk默认在./build/linux-x86_64-normal-server-release/images/jdk

    3、jdk源码目录结构

    jdk源码目录结构open in new window

    - +

    设置默认gcc后,重新执行bash configure ,然后make clean,然后再make images就成功了,编译好的jdk默认在./build/linux-x86_64-normal-server-release/images/jdk

    3、jdk源码目录结构

    jdk源码目录结构open in new window

    + diff --git a/java/advance/Concurrent.html b/java/advance/Concurrent.html index f4edb36c30..35565f64bc 100644 --- a/java/advance/Concurrent.html +++ b/java/advance/Concurrent.html @@ -30,8 +30,8 @@ 并发问题 | ChenSino - - + +
    跳至主要內容

    并发问题

    ChenSino原创-- 并发大约 2 分钟

    1、背景

    因没做过大项目,并发经验匮乏,所以很多时候考虑不到并发问题,今天做接口的压力测试,无意间发现一个并发的问题,就是判断数据库是否存在某个记录时,如果没做并发处理,就会出现并发的问题,比如以下代码,给某个医院添加科室,当医院已经有这个科室则不允许重复添加重名的科室

    public void addHospitalDept(SysHospitalDept sysHospitalDept) {
    @@ -60,7 +60,7 @@
     		sysHospitalDept.setCreateTime(LocalDateTime.now());
     		this.save(sysHospitalDept);
     	}
    -

    总结

    这种并发问题是因为使用了第三方数据库中的东西来作为判断条件,这有点分布式的感觉,因为数据是放在第三方,不是在本地程序,第三方数据是可能随时被别人修改的。这种情况一定要注意并发问题。

    - +

    总结

    这种并发问题是因为使用了第三方数据库中的东西来作为判断条件,这有点分布式的感觉,因为数据是放在第三方,不是在本地程序,第三方数据是可能随时被别人修改的。这种情况一定要注意并发问题。

    + diff --git a/java/advance/Future.html b/java/advance/Future.html index a22db19a57..0a36cbda53 100644 --- a/java/advance/Future.html +++ b/java/advance/Future.html @@ -30,11 +30,11 @@ 多线程中的Future | ChenSino - - + + - - + + diff --git a/java/advance/IO-Model.html b/java/advance/IO-Model.html index 20e4bf7f40..e78b1c0ed7 100644 --- a/java/advance/IO-Model.html +++ b/java/advance/IO-Model.html @@ -30,8 +30,8 @@ 100%搞懂5中I/O模型 | ChenSino - - + +
    跳至主要內容

    100%搞懂5中I/O模型

    勤劳的小手大约 23 分钟

    本文是转载于知乎100%弄明白5种IO模型open in new window,原作者勤劳的小手open in new window,在原博客基础上整理了其他 与IO模型相关的优质内容。

    1、从TCP发送数据的流程说起

    所有的系统I/O都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。
    @@ -115,7 +115,7 @@
             }
         }
     }
    -

    2.2 什么是非阻塞IO

    我敢保证如果你已经理解了阻塞IO,那么必定已经知道了什么是非阻塞IO。按照上面的思路,所谓非阻塞IO就是当应用B发起读取数据申请时,如果内核数据没有准备好会即刻告诉应用B,不会让B在这里等待。

    术语:非阻塞IO是在应用调用recvfrom读取数据时,如果该缓冲区没有数据的话,就会直接返回一个EWOULDBLOCK错误,不会让应用一直等待中。在没有数据的时候会即刻返回错误标识,那也意味着如果应用要读取数据就需要不断的调用recvfrom请求,直到读取到它数据要的数据为止。

    流程:

    1、应用进程向内核发起recvfrom读取数据。

    2、没有数据报准备好,即刻返回EWOULDBLOCK错误码。

    3、应用进程向内核发起recvfrom读取数据。

    4、已有数据包准备好就进行一下 步骤,否则还是返回错误码。

    5、将数据从内核拷贝到用户空间。

    6、完成后,返回成功提示。

    3、IO复用模型

    如果你已经明白了非阻塞IO的工作模式,那么接下来我们继续了解IO复用模型的产生原因和思路。

    思考一个问题:

    我们还是把视角放到应用B从TCP缓冲区中读取数据这个环节来。如果在并发的环境下,可能会N个人向应用B发送消息,这种情况下我们的应用就必须创建多个线程去读取数据,每个线程都会自己调用recvfrom 去读取数据。那么此时情况可能如下图:

    20221227100808

    如上图一样,并发情况下服务器很可能一瞬间会收到几十上百万的请求,这种情况下应用B就需要创建几十上百万的线程去读取数据,同时又因为应用线程是不知道什么时候会有数据读取,为了保证消息能及时读取到,那么这些线程自己必须不断的向内核发送recvfrom 请求来读取数据;

    那么问题来了,这么多的线程不断调用recvfrom 请求数据,先不说服务器能不能扛得住这么多线程,就算扛得住那么很明显这种方式是不是太浪费资源了,线程是我们操作系统的宝贵资源,大量的线程用来去读取数据了,那么就意味着能做其它事情的线程就会少。

    所以,有人就提出了一个思路,能不能提供一种方式,可以由一个线程监控多个网络请求(我们后面将称为fd文件描述符,linux系统把所有网络请求以一个fd来标识),这样就可以只需要一个或几个线程就可以完成数据状态询问的操作,当有数据准备就绪之后再分配对应的线程去读取数据,这么做就可以节省出大量的线程资源出来,这个就是IO复用模型的思路。

    20221227100820

    正如上图,IO复用模型的思路就是系统提供了一种函数可以同时监控多个fd的操作,这个函数就是我们常说到的select、poll、epoll函数,有了这个函数后,应用线程通过调用select函数就可以同时监控多个fd,select函数监控的fd中只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时询问线程再去通知处理数据的线程,对应线程此时再发起recvfrom请求去读取数据。

    术语描述:进程通过将一个或多个fd传递给select,阻塞在select操作上,select帮我们侦测多个fd是否准备就绪,当有fd准备就绪时,select返回数据可读状态,应用程序再调用recvfrom读取数据。

    20221227100834

    总结:复用IO的基本思路就是通过slect或poll、epoll 来监控多fd ,来达到不必为每个fd创建一个对应的监控线程,从而减少线程资源创建的目的。

    3.1 IO多路复用模型扩展

    IO多路复用是利用了系统内核提供的select、poll、epoll等函数来实现的。

    3.1.1 select

    select会将TCP中的全连接队列中的Socket对应生成的文件描述符放入到一个集合中,然后复制到内核中,让内核不断去轮询是否有读写事件的产生,一旦有,就把对应的Socket标记为可读/可写,再将全部的文件描述符集合拷贝到用户空间,select函数返回,应用程序需要再一次对文件描述符集合进行遍历,检查是否为可读/可写,对其进行处理。

    具体过程

    其实这里说细一点,就涉及到了操作系统调度和中断知识了~ 当应用进程调用select函数时会陷入内核态,内核程序会去轮询有无产生读写事件的socket,如果没有的话,会将当前应用进程停靠在需要检查的socket的等待队列中(补充:socket的结构有三块:写缓存,读缓存,等待队列),也就是挂起该进程了,CPU切换其他进程运行。 一旦任意一个socket有事件产生,也就是网络数据包到达时,会触发网络数据传输完毕对应的中断,CPU转而执行中断处理程序,分析出该数据包是属于哪个socket,将数据包(根据TCP首部的端口号)放入对应的socket的读缓存中,然后去检查socket的等待队列是否有等待进程,有的话把等待进程移回工作队列中,中断结束。CPU的使用权交还给用户态。刚刚挂起的进程又回到工作队列中,又有机会获得CPU的运行时间片了,然后再次执行select函数,检查是否有读写事件发生的socket,有的话标记为可读,就接下去上面说的步骤啦~

    几个缺点:

    使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。 将文件描述符集合从用户态到内核态,有拷贝的开销 当有数据时select就会返回,但是select函数并不知道哪个文件描述符有数据,后面还需要再次对文件描述符进行遍历,效率比较低。

    3.1.2 poll

    poll是对select的增强。它采用链表的形式来存储文件描述符,突破了select对文件描述符的限制,只受内核内存大小的限制。

    但还是需要经历内核、应用进程对文件描述符集合的遍历检查,内核到应用进程的拷贝开销。

    3.1.3 epoll

    它使用了两种红黑树和就绪链表两种数据结构解决了select/poll的缺点。在Linux2.5.44版本中就使用了这种I/O复用机制。 主要有三个系统调用API: // 内核创建epoll实例,包括红黑树和就绪链表 int epoll_create(int size);

    // 对红黑树进行修改、删除、增加一个socket节点 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    // 内核利用红黑树,快速查找活跃的socket,放入就绪链表 // 再将就绪链表中一定数量的内容拷贝到events int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    复制代码 首先应用进程调用epoll_create创建epoll实例,同时在内核中建立红黑树和就绪链表; 调用epoll_ctl将会对红黑树增删改一个socket节点:

    ADD 会检查红黑树有无这个socket,有的话加入就绪链表中,没有就会插入该红黑树中维护。 DEL 从epoll实例的各个资源删除。 MOD 会修改对应socket的状态,并再次检查红黑树,有活跃的socket会加入就绪链表中,没有就注册事件回调函数,每当有事件发生时就通过回调函数把这些socket放入就绪链表中。

    epoll_wait会去检查就绪链表有无已经就绪的socket,没有就等待唤醒,有的话就拷贝回用户空间。 由于epoll从内核态仅需要拷贝活跃的socket到用户态,就解决了select/poll的大量socket拷贝开销和无效遍历的缺点。

    适用场景

    并不是说epoll就一定比select/poll好,每种技术都有适合的场景。如果是并发量比较低且socket都比较活跃的情况下,无需创建红黑树和就绪链表的开销,两次遍历的时间开销不会很大并且充分利用了每个遍历节点,所以select/poll会更适合。而如果是高并发且任一时间只有少数socket是活跃的,那epoll会更适合,因为它每次只拷贝活跃的socket到用户态。

    4、信号驱动IO模型

    复用IO模型解决了一个线程可以监控多个fd的问题,但是select是采用轮询的方式来监控多个fd的,通过不断的轮询fd的可读状态来知道是否有可读的数据,而无脑的轮询就显得有点暴力,因为大部分情况下的轮询都是无效的,所以有人就想,能不能不要我总是去问你是否数据准备就绪,能不能我发出请求后等你数据准备好了就通知我,所以就衍生了信号驱动IO模型。

    于是信号驱动IO不是用循环请求询问的方式去监控数据就绪状态,而是在调用sigaction时候建立一个SIGIO的信号联系,当内核数据准备好之后再通过SIGIO信号通知线程数据准备好后的可读状态,当线程收到可读状态的信号后,此时再向内核发起recvfrom读取数据的请求,因为信号驱动IO的模型下应用线程在发出信号监控后即可返回,不会阻塞,所以这样的方式下,一个应用线程也可以同时监控多个fd。

    类似于下图描述: 20221227100856

    术语描述:首先开启套接口信号驱动IO功能,并通过系统调用sigaction执行一个信号处理函数,此时请求即刻返回,当数据准备就绪时,就生成对应进程的SIGIO信号,通过信号回调通知应用线程调用recvfrom来读取数据。

    20221227100909

    总结: IO复用模型里面的select虽然可以监控多个fd了,但select其实现的本质上还是通过不断的轮询fd来监控数据状态, 因为大部分轮询请求其实都是无效的,所以信号驱动IO意在通过这种建立信号关联的方式,实现了发出请求后只需要等待数据就绪的通知即可,这样就可以避免大量无效的数据状态轮询操作。

    5、异步IO

    其实经过了上面两个模型的优化,我们的效率有了很大的提升,但是我们当然不会就这样满足了,有没有更好的办法,通过观察我们发现,不管是IO复用还是信号驱动,我们要读取一个数据总是要发起两阶段的请求,第一次发送select请求,询问数据状态是否准备好,第二次发送recevform请求读取数据。

    思考一个问题:

    也许你一开始就有一个疑问,为什么我们明明是想读取数据,而却非得要先发起一个select询问数据状态的请求,然后再发起真正的读取数据请求,能不能有一种一劳永逸的方式,我只要发送一个请求我告诉内核我要读取数据,然后我就什么都不管了,然后内核去帮我去完成剩下的所有事情?

    当然既然你想得出来,那么就会有人做得到,有人设计了一种方案,应用只需要向内核发送一个read 请求,告诉内核它要读取数据后即刻返回;内核收到请求后会建立一个信号联系,当数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用,我们称这种一劳永逸的模式为异步IO模型。

    20221227100942

    术语描述: 应用告知内核启动某个操作,并让内核在整个操作完成之后,通知应用,这种模型与信号驱动模型的主要区别在于,信号驱动IO只是由内核通知我们合适可以开始下一个IO操作,而异步IO模型是由内核通知我们操作什么时候完成。

    20221227100952

    总结:异步IO的优化思路是解决了应用程序需要先后发送询问请求、发送接收数据请求两个阶段的模式,在异步IO的模式下,只需要向内核发送一次请求就可以完成状态询问和数拷贝的所有操作。

    6、再谈IO模型里面的同步异步

    我们通常会说到同步阻塞IO、同步非阻塞IO,异步IO几种术语,通过上面的内容,那么我想你现在肯定已经理解了什么是阻塞什么是非阻塞了,所谓阻塞就是发起读取数据请求的时,当数据还没准备就绪的时候,这时请求是即刻返回,还是在这里等待数据的就绪,如果需要等待的话就是阻塞,反之如果即刻返回就是非阻塞。

    我们区分了阻塞和非阻塞后再来分别下同步和异步,在IO模型里面如果请求方从发起请求到数据最后完成的这一段过程中都需要自己参与,那么这种我们就称为同步请求;反之,如果应用发送完指令后就不再参与过程了,只需要等待最终完成结果的通知,那么这就属于异步。

    我们再看同步阻塞、同步非阻塞,他们不同的只是发起读取请求的时候一个请求阻塞,一个请求不阻塞,但是相同的是,他们都需要应用自己监控整个数据完成的过程。而为什么只有异步非阻塞 而没有异步阻塞呢,因为异步模型下请求指定发送完后就即刻返回了,没有任何后续流程了,所以它注定不会阻塞,所以也就只会有异步非阻塞模型了。

    7、tomcat中的IO模型

    模型描述
    BIO阻塞式IO,即Tomcat使用传统的java.io进行操作。该模式下每个请求都会创建一个线程,对性能开销大,不适合高并发场景。优点是稳定,适合连接数目小且固定架构。
    NIO非阻塞式IO,jdk1.4 之后实现的新IO。该模式基于多路复用选择器监测连接状态在通知线程处理,从而达到非阻塞的目的。比传统BIO能更好的支持并发性能。Tomcat 8.0之后默认采用该模式
    APR全称是 Apache Portable Runtime/Apache可移植运行库),是Apache HTTP服务器的支持库。可以简单地理解为,Tomcat将以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作。使用需要编译安装APR 库
    AIO异步非阻塞式IO,jdk1.7后之支持 。与nio不同在于不需要多路复用选择器,而是请求处理线程执行完程进行回调调知,已继续执行后续操作。Tomcat 8之后支持。

    8、Redis中的IO模型

    IO多路复用-epoll

    9、Nginx中的IO模型

    select:IO多路复用、标准并发模型。在编译 nginx 时,如果所使用的系统平台没有更高效的并发模型,select 模块将被自动编译。configure 脚本的选项:–with-select_module 和 --without-select_module 可被用来强制性地开启或禁止 select 模块的编译

    poll:IO多路复用、标准并发模型。与 select 类似,在编译 nginx 时,如果所使用的系统平台没有更高效的并发模型,poll 模块将被自动编译。configure 脚本的选项:–with-poll_module 和 --without-poll_module 可用于强制性地开启或禁止 poll 模块的编译

    epoll:IO多路复用、高效并发模型,可在 Linux 2.6+ 及以上内核可以使用

    kqueue:IO多路复用、高效并发模型,可在 FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0, and Mac OS X 平台中使用

    /dev/poll:高效并发模型,可在 Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+, and Tru64 UNIX 5.1A+ 平台使用

    eventport:高效并发模型,可用于 Solaris 10 平台,PS:由于一些已知的问题,建议 使用/dev/poll替代。

    为什么epoll快? 比较一下Apache常用的select和Nginx常用的epoll

    select: 1、最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧。 2、效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了。 3、内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法,在FD非常多的时候,非常的耗费时间。 总结为:1、连接数受限 2、查找配对速度慢 3、数据由内核拷贝到用户态消耗时间

    epoll: 1、Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 查看。 2、效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll的效率就会远远高于 select 和 poll 。 3、内存共享, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。

    10、java中的IO模型

    提起IO模型大家都知道在java中有io和nio(java1.4+),包括我在内的很多人起初都认为io就是阻塞io模型,nio就是非阻塞的io模型,其实这是完全错误的观点。事实上无论在java中无论使用bio还是nio都可以实现非阻塞io模型。

    在java中,使用io可以实现阻塞io模型,也可以实现非阻塞io模型,非阻塞io模型-残缺版open in new window非阻塞io模型——新增非阻塞open in new window非阻塞io模型-新增多线程,解决并发请求open in new window

    nio在java.nio这个包中指的是 new io,而在linux系统内核中nio指的是non-blockingio,所以很多人笼统的说nio既可以翻译为new io 也可以翻译为non-blockingio是非常扯淡的,说明他对io根本不了解。nio实现io多路复用模型open in new window

    nio实现io多路复用模型open in new window的demo中,仅仅使用一个main线程就实现了并发请求处理

    10、netty中的io模型

    - +

    2.2 什么是非阻塞IO

    我敢保证如果你已经理解了阻塞IO,那么必定已经知道了什么是非阻塞IO。按照上面的思路,所谓非阻塞IO就是当应用B发起读取数据申请时,如果内核数据没有准备好会即刻告诉应用B,不会让B在这里等待。

    术语:非阻塞IO是在应用调用recvfrom读取数据时,如果该缓冲区没有数据的话,就会直接返回一个EWOULDBLOCK错误,不会让应用一直等待中。在没有数据的时候会即刻返回错误标识,那也意味着如果应用要读取数据就需要不断的调用recvfrom请求,直到读取到它数据要的数据为止。

    流程:

    1、应用进程向内核发起recvfrom读取数据。

    2、没有数据报准备好,即刻返回EWOULDBLOCK错误码。

    3、应用进程向内核发起recvfrom读取数据。

    4、已有数据包准备好就进行一下 步骤,否则还是返回错误码。

    5、将数据从内核拷贝到用户空间。

    6、完成后,返回成功提示。

    3、IO复用模型

    如果你已经明白了非阻塞IO的工作模式,那么接下来我们继续了解IO复用模型的产生原因和思路。

    思考一个问题:

    我们还是把视角放到应用B从TCP缓冲区中读取数据这个环节来。如果在并发的环境下,可能会N个人向应用B发送消息,这种情况下我们的应用就必须创建多个线程去读取数据,每个线程都会自己调用recvfrom 去读取数据。那么此时情况可能如下图:

    20221227100808

    如上图一样,并发情况下服务器很可能一瞬间会收到几十上百万的请求,这种情况下应用B就需要创建几十上百万的线程去读取数据,同时又因为应用线程是不知道什么时候会有数据读取,为了保证消息能及时读取到,那么这些线程自己必须不断的向内核发送recvfrom 请求来读取数据;

    那么问题来了,这么多的线程不断调用recvfrom 请求数据,先不说服务器能不能扛得住这么多线程,就算扛得住那么很明显这种方式是不是太浪费资源了,线程是我们操作系统的宝贵资源,大量的线程用来去读取数据了,那么就意味着能做其它事情的线程就会少。

    所以,有人就提出了一个思路,能不能提供一种方式,可以由一个线程监控多个网络请求(我们后面将称为fd文件描述符,linux系统把所有网络请求以一个fd来标识),这样就可以只需要一个或几个线程就可以完成数据状态询问的操作,当有数据准备就绪之后再分配对应的线程去读取数据,这么做就可以节省出大量的线程资源出来,这个就是IO复用模型的思路。

    20221227100820

    正如上图,IO复用模型的思路就是系统提供了一种函数可以同时监控多个fd的操作,这个函数就是我们常说到的select、poll、epoll函数,有了这个函数后,应用线程通过调用select函数就可以同时监控多个fd,select函数监控的fd中只要有任何一个数据状态准备就绪了,select函数就会返回可读状态,这时询问线程再去通知处理数据的线程,对应线程此时再发起recvfrom请求去读取数据。

    术语描述:进程通过将一个或多个fd传递给select,阻塞在select操作上,select帮我们侦测多个fd是否准备就绪,当有fd准备就绪时,select返回数据可读状态,应用程序再调用recvfrom读取数据。

    20221227100834

    总结:复用IO的基本思路就是通过slect或poll、epoll 来监控多fd ,来达到不必为每个fd创建一个对应的监控线程,从而减少线程资源创建的目的。

    3.1 IO多路复用模型扩展

    IO多路复用是利用了系统内核提供的select、poll、epoll等函数来实现的。

    3.1.1 select

    select会将TCP中的全连接队列中的Socket对应生成的文件描述符放入到一个集合中,然后复制到内核中,让内核不断去轮询是否有读写事件的产生,一旦有,就把对应的Socket标记为可读/可写,再将全部的文件描述符集合拷贝到用户空间,select函数返回,应用程序需要再一次对文件描述符集合进行遍历,检查是否为可读/可写,对其进行处理。

    具体过程

    其实这里说细一点,就涉及到了操作系统调度和中断知识了~ 当应用进程调用select函数时会陷入内核态,内核程序会去轮询有无产生读写事件的socket,如果没有的话,会将当前应用进程停靠在需要检查的socket的等待队列中(补充:socket的结构有三块:写缓存,读缓存,等待队列),也就是挂起该进程了,CPU切换其他进程运行。 一旦任意一个socket有事件产生,也就是网络数据包到达时,会触发网络数据传输完毕对应的中断,CPU转而执行中断处理程序,分析出该数据包是属于哪个socket,将数据包(根据TCP首部的端口号)放入对应的socket的读缓存中,然后去检查socket的等待队列是否有等待进程,有的话把等待进程移回工作队列中,中断结束。CPU的使用权交还给用户态。刚刚挂起的进程又回到工作队列中,又有机会获得CPU的运行时间片了,然后再次执行select函数,检查是否有读写事件发生的socket,有的话标记为可读,就接下去上面说的步骤啦~

    几个缺点:

    使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。 将文件描述符集合从用户态到内核态,有拷贝的开销 当有数据时select就会返回,但是select函数并不知道哪个文件描述符有数据,后面还需要再次对文件描述符进行遍历,效率比较低。

    3.1.2 poll

    poll是对select的增强。它采用链表的形式来存储文件描述符,突破了select对文件描述符的限制,只受内核内存大小的限制。

    但还是需要经历内核、应用进程对文件描述符集合的遍历检查,内核到应用进程的拷贝开销。

    3.1.3 epoll

    它使用了两种红黑树和就绪链表两种数据结构解决了select/poll的缺点。在Linux2.5.44版本中就使用了这种I/O复用机制。 主要有三个系统调用API: // 内核创建epoll实例,包括红黑树和就绪链表 int epoll_create(int size);

    // 对红黑树进行修改、删除、增加一个socket节点 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    // 内核利用红黑树,快速查找活跃的socket,放入就绪链表 // 再将就绪链表中一定数量的内容拷贝到events int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    复制代码 首先应用进程调用epoll_create创建epoll实例,同时在内核中建立红黑树和就绪链表; 调用epoll_ctl将会对红黑树增删改一个socket节点:

    ADD 会检查红黑树有无这个socket,有的话加入就绪链表中,没有就会插入该红黑树中维护。 DEL 从epoll实例的各个资源删除。 MOD 会修改对应socket的状态,并再次检查红黑树,有活跃的socket会加入就绪链表中,没有就注册事件回调函数,每当有事件发生时就通过回调函数把这些socket放入就绪链表中。

    epoll_wait会去检查就绪链表有无已经就绪的socket,没有就等待唤醒,有的话就拷贝回用户空间。 由于epoll从内核态仅需要拷贝活跃的socket到用户态,就解决了select/poll的大量socket拷贝开销和无效遍历的缺点。

    适用场景

    并不是说epoll就一定比select/poll好,每种技术都有适合的场景。如果是并发量比较低且socket都比较活跃的情况下,无需创建红黑树和就绪链表的开销,两次遍历的时间开销不会很大并且充分利用了每个遍历节点,所以select/poll会更适合。而如果是高并发且任一时间只有少数socket是活跃的,那epoll会更适合,因为它每次只拷贝活跃的socket到用户态。

    4、信号驱动IO模型

    复用IO模型解决了一个线程可以监控多个fd的问题,但是select是采用轮询的方式来监控多个fd的,通过不断的轮询fd的可读状态来知道是否有可读的数据,而无脑的轮询就显得有点暴力,因为大部分情况下的轮询都是无效的,所以有人就想,能不能不要我总是去问你是否数据准备就绪,能不能我发出请求后等你数据准备好了就通知我,所以就衍生了信号驱动IO模型。

    于是信号驱动IO不是用循环请求询问的方式去监控数据就绪状态,而是在调用sigaction时候建立一个SIGIO的信号联系,当内核数据准备好之后再通过SIGIO信号通知线程数据准备好后的可读状态,当线程收到可读状态的信号后,此时再向内核发起recvfrom读取数据的请求,因为信号驱动IO的模型下应用线程在发出信号监控后即可返回,不会阻塞,所以这样的方式下,一个应用线程也可以同时监控多个fd。

    类似于下图描述: 20221227100856

    术语描述:首先开启套接口信号驱动IO功能,并通过系统调用sigaction执行一个信号处理函数,此时请求即刻返回,当数据准备就绪时,就生成对应进程的SIGIO信号,通过信号回调通知应用线程调用recvfrom来读取数据。

    20221227100909

    总结: IO复用模型里面的select虽然可以监控多个fd了,但select其实现的本质上还是通过不断的轮询fd来监控数据状态, 因为大部分轮询请求其实都是无效的,所以信号驱动IO意在通过这种建立信号关联的方式,实现了发出请求后只需要等待数据就绪的通知即可,这样就可以避免大量无效的数据状态轮询操作。

    5、异步IO

    其实经过了上面两个模型的优化,我们的效率有了很大的提升,但是我们当然不会就这样满足了,有没有更好的办法,通过观察我们发现,不管是IO复用还是信号驱动,我们要读取一个数据总是要发起两阶段的请求,第一次发送select请求,询问数据状态是否准备好,第二次发送recevform请求读取数据。

    思考一个问题:

    也许你一开始就有一个疑问,为什么我们明明是想读取数据,而却非得要先发起一个select询问数据状态的请求,然后再发起真正的读取数据请求,能不能有一种一劳永逸的方式,我只要发送一个请求我告诉内核我要读取数据,然后我就什么都不管了,然后内核去帮我去完成剩下的所有事情?

    当然既然你想得出来,那么就会有人做得到,有人设计了一种方案,应用只需要向内核发送一个read 请求,告诉内核它要读取数据后即刻返回;内核收到请求后会建立一个信号联系,当数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用,我们称这种一劳永逸的模式为异步IO模型。

    20221227100942

    术语描述: 应用告知内核启动某个操作,并让内核在整个操作完成之后,通知应用,这种模型与信号驱动模型的主要区别在于,信号驱动IO只是由内核通知我们合适可以开始下一个IO操作,而异步IO模型是由内核通知我们操作什么时候完成。

    20221227100952

    总结:异步IO的优化思路是解决了应用程序需要先后发送询问请求、发送接收数据请求两个阶段的模式,在异步IO的模式下,只需要向内核发送一次请求就可以完成状态询问和数拷贝的所有操作。

    6、再谈IO模型里面的同步异步

    我们通常会说到同步阻塞IO、同步非阻塞IO,异步IO几种术语,通过上面的内容,那么我想你现在肯定已经理解了什么是阻塞什么是非阻塞了,所谓阻塞就是发起读取数据请求的时,当数据还没准备就绪的时候,这时请求是即刻返回,还是在这里等待数据的就绪,如果需要等待的话就是阻塞,反之如果即刻返回就是非阻塞。

    我们区分了阻塞和非阻塞后再来分别下同步和异步,在IO模型里面如果请求方从发起请求到数据最后完成的这一段过程中都需要自己参与,那么这种我们就称为同步请求;反之,如果应用发送完指令后就不再参与过程了,只需要等待最终完成结果的通知,那么这就属于异步。

    我们再看同步阻塞、同步非阻塞,他们不同的只是发起读取请求的时候一个请求阻塞,一个请求不阻塞,但是相同的是,他们都需要应用自己监控整个数据完成的过程。而为什么只有异步非阻塞 而没有异步阻塞呢,因为异步模型下请求指定发送完后就即刻返回了,没有任何后续流程了,所以它注定不会阻塞,所以也就只会有异步非阻塞模型了。

    7、tomcat中的IO模型

    模型描述
    BIO阻塞式IO,即Tomcat使用传统的java.io进行操作。该模式下每个请求都会创建一个线程,对性能开销大,不适合高并发场景。优点是稳定,适合连接数目小且固定架构。
    NIO非阻塞式IO,jdk1.4 之后实现的新IO。该模式基于多路复用选择器监测连接状态在通知线程处理,从而达到非阻塞的目的。比传统BIO能更好的支持并发性能。Tomcat 8.0之后默认采用该模式
    APR全称是 Apache Portable Runtime/Apache可移植运行库),是Apache HTTP服务器的支持库。可以简单地理解为,Tomcat将以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作。使用需要编译安装APR 库
    AIO异步非阻塞式IO,jdk1.7后之支持 。与nio不同在于不需要多路复用选择器,而是请求处理线程执行完程进行回调调知,已继续执行后续操作。Tomcat 8之后支持。

    8、Redis中的IO模型

    IO多路复用-epoll

    9、Nginx中的IO模型

    select:IO多路复用、标准并发模型。在编译 nginx 时,如果所使用的系统平台没有更高效的并发模型,select 模块将被自动编译。configure 脚本的选项:–with-select_module 和 --without-select_module 可被用来强制性地开启或禁止 select 模块的编译

    poll:IO多路复用、标准并发模型。与 select 类似,在编译 nginx 时,如果所使用的系统平台没有更高效的并发模型,poll 模块将被自动编译。configure 脚本的选项:–with-poll_module 和 --without-poll_module 可用于强制性地开启或禁止 poll 模块的编译

    epoll:IO多路复用、高效并发模型,可在 Linux 2.6+ 及以上内核可以使用

    kqueue:IO多路复用、高效并发模型,可在 FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0, and Mac OS X 平台中使用

    /dev/poll:高效并发模型,可在 Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+, and Tru64 UNIX 5.1A+ 平台使用

    eventport:高效并发模型,可用于 Solaris 10 平台,PS:由于一些已知的问题,建议 使用/dev/poll替代。

    为什么epoll快? 比较一下Apache常用的select和Nginx常用的epoll

    select: 1、最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧。 2、效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了。 3、内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法,在FD非常多的时候,非常的耗费时间。 总结为:1、连接数受限 2、查找配对速度慢 3、数据由内核拷贝到用户态消耗时间

    epoll: 1、Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 查看。 2、效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll的效率就会远远高于 select 和 poll 。 3、内存共享, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。

    10、java中的IO模型

    提起IO模型大家都知道在java中有io和nio(java1.4+),包括我在内的很多人起初都认为io就是阻塞io模型,nio就是非阻塞的io模型,其实这是完全错误的观点。事实上无论在java中无论使用bio还是nio都可以实现非阻塞io模型。

    在java中,使用io可以实现阻塞io模型,也可以实现非阻塞io模型,非阻塞io模型-残缺版open in new window非阻塞io模型——新增非阻塞open in new window非阻塞io模型-新增多线程,解决并发请求open in new window

    nio在java.nio这个包中指的是 new io,而在linux系统内核中nio指的是non-blockingio,所以很多人笼统的说nio既可以翻译为new io 也可以翻译为non-blockingio是非常扯淡的,说明他对io根本不了解。nio实现io多路复用模型open in new window

    nio实现io多路复用模型open in new window的demo中,仅仅使用一个main线程就实现了并发请求处理

    10、netty中的io模型

    + diff --git a/java/advance/IO-model1.html b/java/advance/IO-model1.html index 70abcf2944..439b6fc46d 100644 --- a/java/advance/IO-model1.html +++ b/java/advance/IO-model1.html @@ -30,8 +30,8 @@ I/O模型总结 | ChenSino - - + +
    跳至主要內容

    I/O模型总结

    ChenSino大约 3 分钟

    1 BIO

    当给BIO的accept和read方法加上超时机制后,可以在代码层面解决阻塞问题,但这不是真正的非阻塞,通常我们说的非阻塞是指的操作系统层面的非阻塞,就是当accept通过jni调用native方法后,最终系统不会一直被阻塞。真正的非阻塞是操作系统增加非阻塞功能后,java同步添加java.nio出现以后才有的,因此通过java实现非阻塞,需要调用nio中的类。

    2 NIO

    3 IO多路复用

    4 异步IO

    5 事件驱动的io

    6 reactor线程模型

    reactor线程模型可参考Scalable IO in javaopen in new window,该书作者也是java.nio的作者

    注意

    注意reactor线程模型并不是5种io模型之一,它是一种经典的事件驱动的线程模型,它是基于IO多路复用模型衍生出来的:

    Reactor线程模式 = Reactor(I/O多路复用)+ 线程池

    Reactor负责监听和分配事件,线程池负责处理事件

    根据Reactor的数量和线程池的数量,又可以将Reactor分为三种模型

    • 单Reactor单线程模型 (固定大小为1的线程池)
    • 单Reactor多线程模型
    • 多Reactor多线程模型 (一般是主从2个Reactor)

    reactor模型中有三种角色,分别是:

    Acceptor:处理客户端新连接,并分派请求到处理器链中
    Reactor:负责监听和分配事件,将I/O事件分派给对应的Handler
    Handler: 事件处理,如编码、解码等

    6.1 单reactor单线程

    应用:redis4.0

    20230209155247

    源码示例:

    //------------------------reactor------------------
    @@ -178,7 +178,7 @@
         }
     }
     
    -

    6.2 单reactor多线程

    20230209155305

    6.3 主从(多)reactor多线程

    应用:netty

    20230209155722

    - +

    6.2 单reactor多线程

    20230209155305

    6.3 主从(多)reactor多线程

    应用:netty

    20230209155722

    + diff --git a/java/advance/ImplementSameInterface.html b/java/advance/ImplementSameInterface.html index c45bc175ef..bb6efee757 100644 --- a/java/advance/ImplementSameInterface.html +++ b/java/advance/ImplementSameInterface.html @@ -30,8 +30,8 @@ 子类和父类(或者父接口)实现同一个接口 | ChenSino - - + +
    跳至主要內容

    子类和父类(或者父接口)实现同一个接口

    ChenSinooauthsso大约 2 分钟

    1、背景

    今天看Securfity的源码,其中org.springframework.security.config.annotation.web.builders.HttpSecurity类的UML看着很奇怪,如下图所示,命名其父类和父接口都实现过SecurityBuilder,为什么自己要再次实现呢?

    20230105160622

    2、探索

    我一开始注意力被泛型吸引了,想着是不是因为用了不同的泛型类的原因,为此我还专门去复习了一下泛型的东西。后来确定和泛型没关系,然后百度了一下,找到了以下网友的博客,为此我还专门写demo验证了他的博客内容,发现确实如此。 20230105161007

    2.1 demo验证

    //接口
    @@ -94,7 +94,7 @@
     I am male
     org.example.Animal
     ----------------后置分割线----------------
    -

    20230105161911

    2.2 回到security源码验证

    我下载了Security的源码,然后直接在源码中删除了HttpSecurity中对SecurityBuilder的实现,重新编译,编译没报错,

    3、结论

    子类和父类实现同一个接口,这种写法是为了保证反射获取到接口类型时能正常获取到其实现的接口类型。

    - +

    20230105161911

    2.2 回到security源码验证

    我下载了Security的源码,然后直接在源码中删除了HttpSecurity中对SecurityBuilder的实现,重新编译,编译没报错,

    3、结论

    子类和父类实现同一个接口,这种写法是为了保证反射获取到接口类型时能正常获取到其实现的接口类型。

    + diff --git a/java/advance/MysqlMasterSlave.html b/java/advance/MysqlMasterSlave.html index 2a3fa962a2..5a9a73aefd 100644 --- a/java/advance/MysqlMasterSlave.html +++ b/java/advance/MysqlMasterSlave.html @@ -30,8 +30,8 @@ mysql8搭建主从复制 | ChenSino - - + +
    跳至主要內容

    mysql8搭建主从复制

    ChenSinojava部署搭建大约 7 分钟

    1、mysql主从复制

    1.1 搭建主从复制目的?

    为了实现读写分离,解决数据库性能问题,读写分离中,“读”的数据是从哪里来呢?其实他是从“写”库copy过来的

    1.2 使用docker搭建基于mysql8的主从复制

    1. 创建容器

      docker run --name mysql_master -p 3001:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:latest
      @@ -228,7 +228,7 @@
           }
       ]
       
      -
      2.5 、测试代码地址

      githubopen in new window

    - +
    2.5 、测试代码地址

    githubopen in new window

    + diff --git a/java/advance/NativeMethod.html b/java/advance/NativeMethod.html index 49d47feebe..da96681f9d 100644 --- a/java/advance/NativeMethod.html +++ b/java/advance/NativeMethod.html @@ -30,8 +30,8 @@ 自定义native方法 | ChenSino - - + +
    跳至主要內容

    自定义native方法

    ChenSino大约 2 分钟

    使用c++实现一个native方法供java调用

    环境

    实验环境linux、jdk11、gcc
    @@ -77,7 +77,7 @@
     编译中断。
     

    其实jni_md.h,这个文件位于和jni.h同级目录的linux目录下,在jni.h中直接使用#include "jni_md.h"肯定不行,这里我尝试了在同级目录建立软连接,但是莫名其妙的报错,所以我索性直接吧linux路径下的两个文件全部拷贝到和jni.h同级目录,执行编译就ok了,编译好了以后在当前目录就生成了一个Hello.so,这就是我们需要的动态链接库。

    2.5 执行java程序验证

    $ java TestMain           
     Hello,wolrd!
    -

    3、总结

    有些方法对性能要求很高,或者需要直接调用系统内核等,可以使用native方法,调用c++的库来实现。 实现方法:

    1. 编写java源文件并编译
    2. 使用jdk自带工具生成头文件
    3. 编写c++的具体实现
    4. 修改头文件中路径问题
    5. 编译动态链接库
    6. 执行java代码
    - +

    3、总结

    有些方法对性能要求很高,或者需要直接调用系统内核等,可以使用native方法,调用c++的库来实现。 实现方法:

    1. 编写java源文件并编译
    2. 使用jdk自带工具生成头文件
    3. 编写c++的具体实现
    4. 修改头文件中路径问题
    5. 编译动态链接库
    6. 执行java代码
    + diff --git a/java/advance/ParentDelegationClassLoader.html b/java/advance/ParentDelegationClassLoader.html index 268b80f389..5b25ecbe09 100644 --- a/java/advance/ParentDelegationClassLoader.html +++ b/java/advance/ParentDelegationClassLoader.html @@ -30,8 +30,8 @@ 证明SPI打破双亲委派模式 | ChenSino - - + +
    跳至主要內容

    证明SPI打破双亲委派模式

    chenkun大约 13 分钟

    为什么说spi打破了双亲委派机制?

    1、什么是双亲委派?

    image-20220330170731913

    注:此处直接摘抄周志明老师的《深入理解java虚拟机》

    站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现[1] ,是虚拟机自身的一部分;另外一种就是其他所有 的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。 ​站在Java开发人员的角度来看,类加载器就应当划分得更细致一些。自JDK 1.2以来,Java一直保 持着三层类加载器、双亲委派的类加载架构,尽管这套架构在Java模块化系统出现后有了一些调整变 动,但依然未改变其主体结构,我们将在7.5节中专门讨论模块化系统下的类加载器。 本节内容将针对JDK 8及之前版本的Java来介绍什么是三层类加载器,以及什么是双亲委派模型。 对于这个时期的Java应用,绝大多数Java程序都会使用到以下3个系统提供的类加载器来进行加载。 ·启动类加载器(Bootstrap Class Loader):前面已经介绍过,这个类加载器负责加载存放在 JAVA_HOM/lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够 识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类 库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时, 如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可,代码清单7-9展示的就是 java.lang.ClassLoader.getClassLoader()方法的代码片段,其中的注释和代码实现都明确地说明了以null值 来代表引导类加载器的约定规则。

    ·扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.Launcher$ExtClassLoader 中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所 指定的路径中所有的类库。根据“扩展类加载器”这个名称,就可以推断出这是一种Java系统类库的扩 展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能,在JDK 9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。由于扩展类加载器是由Java代码实现 的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。 ·应用程序类加载器(Application Class Loader):这个类加载器由 sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem- ClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径 (ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有 自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

    ​JDK 9之前的Java应用都是由这三种类加载器互相配合来完成加载的,如果用户认为有必要,还可 以加入自定义的类加载器来进行拓展,典型的如增加除了磁盘位置之外的Class文件来源,或者通过类 加载器实现类的隔离、重载等功能。这些类加载器之间的协作关系“通常”会如图7-2所示。 图7-2中展示的各种类加载器之间的层次关系被称为类加载器的“双亲委派模型(Parents Delegation M odel)”。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载 器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用 组合(Composition)关系来复用父加载器的代码。 读者可能注意到前面描述这种类加载器协作关系时,笔者专门用双引号强调这是“通常”的协作关 系。类加载器的双亲委派模型在JDK 1.2时期被引入,并被广泛应用于此后几乎所有的Java程序中,但 它并不是一个具有强制性约束力的模型,而是Java设计者们推荐给开发者的一种类加载器实现的最佳 实践。 ​双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加 载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的 加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请 求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。 使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java中的类随着它的类 加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一 个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类 在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个 类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的 ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应 用程序将会变得一片混乱。如果读者有兴趣的话,可以尝试去写一个与rt.jar类库中已有类重名的Java 类,将会发现它可以正常编译,但永远无法被加载运行[2]。 ​双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现却异常简单,用以实现双亲委 派的代码只有短短十余行,全部集中在java.lang.ClassLoader的loadClass()方法之中。

    2、什么是SPI

    2.1 定义

    SPI(Service provide interface),直译过来是服务提供接口,在这里指的是厂商负责定义一个接口但不负责提供实现类,定义完接口后厂商直接使用这个接口的方法,但是如果不给此接口提供实现肯定运行要报错的,所以谁要想用厂商这个接口,谁负责实现。最典型的是jdbc,java可以连接各种数据库,比如mysql、oracle、h2……若是让各个数据库厂商都去实现自己的数据库连接方式,那么非常不利于统一管理,所以sun公司为了避免这种各自为战的乱象,他们就规定了一个规范,这就是jdbc了,在java.sql包下,sun指定一个接口叫做Driver,各大厂商负责实现这个Driver就可以了,只要你实现按要求这个接口的方法,那么你就可以直接连接到你的数据库。此处不得不说一句“一流的公司卖标准,二流公司卖实物,三流公司卖服务”

    2.2 使用场景

    1. jdbc4(jdbc4是随着jdk1.6发布的,此版本才开始支持SPI)
    2. springboot的自动话配置也是同样的原理
    3. 阿里的dubbo
    4. 其他

    2.3 自己写一个SPI模拟jdbc的spi

    2.3.1 定义规范(sun公司定义的jdbc规范在java.sql包)

    <?xml version="1.0" encoding="UTF-8"?>
    @@ -125,7 +125,7 @@
             //测试代码就这一行,jvm参数:-verbose:class 
             Connection connection = DriverManager.getConnection("jdbc:mysql:///abc123", "root", "123");
         }
    -
    1. 记得打上断点开启debug之路,第一次进入断点输出的类加载信息如下,可以看到我们的Client类被加载了,看完了日志后清理,防止太多看起来烟花缭乱

    image-20220401112702397

    1. 接下来肯用到DriverManager,肯定要触发加载,在日志中可以看到

    image-20220401112907223

    1. 提前到java.sql.DriverManager#loadInitialDrivers打好断点,586行看到了熟悉的ServiceLoader,这就是SPI的核心,它要触发Driver.class的加载了。

      ==注意:此时我们还在DriverManager中这个类在jdk的核心包中lib下,也就是rt.jar中,在第一节就说了,此包中的类是由启动类加载器BootStrapClassLoader负责的,这是由C++写的,java中看不到,这个类加载器就要委托其子孙加载器来加载Driver==,先到java.util.ServiceLoader#load(java.lang.Class<S>)提前打好断点继续调试,来证明

    image-20220401113115859

    image-20220401113617886

    image-20220401113707945

    1. 先加载了java.sql.Driver,这个肯定也是启动类加载器加载的,然后注意看ClassLoader cl = Thread.currentThread().getContextClassLoader();这里获取线程上下文加载器,默认就是AppClassLoader,然后用获取到的类加载器来加载Driver==>ServiceLoader.load(service, cl);

      image-20220401114332820

    2. 跟踪进入另一个load方法

    image-20220401114631784

    1. 继续跟踪,java.util.ServiceLoader#load(java.lang.Class<S>)执行完了回到此处

      image-20220401115310972

    2. 这里就是真正要加载com.mysql.jdbc.Driver

    image-20220401115430364

    1. 跟踪进去,最后会进入java.util.ServiceLoader.LazyIterator#nextService,可以到这里用AppClassLoader加载了com.mysql.jdbc.Driver,这个类就是之前在java.sql.DriverManager的静态代码快中受到BootStrapClassLoader的委托而加载的。这就证明了父加载器委托子加载器加载,从而证明了spi打破了双亲委派机制

      image-20220401115555240

    注:

    ==打破双亲委派不是看类最终由哪个类加载器加载,而是看有没有父加载器委托子加载器进行加载,这个逆向的动作。==

    - +
    1. 记得打上断点开启debug之路,第一次进入断点输出的类加载信息如下,可以看到我们的Client类被加载了,看完了日志后清理,防止太多看起来烟花缭乱

    image-20220401112702397

    1. 接下来肯用到DriverManager,肯定要触发加载,在日志中可以看到

    image-20220401112907223

    1. 提前到java.sql.DriverManager#loadInitialDrivers打好断点,586行看到了熟悉的ServiceLoader,这就是SPI的核心,它要触发Driver.class的加载了。

      ==注意:此时我们还在DriverManager中这个类在jdk的核心包中lib下,也就是rt.jar中,在第一节就说了,此包中的类是由启动类加载器BootStrapClassLoader负责的,这是由C++写的,java中看不到,这个类加载器就要委托其子孙加载器来加载Driver==,先到java.util.ServiceLoader#load(java.lang.Class<S>)提前打好断点继续调试,来证明

    image-20220401113115859

    image-20220401113617886

    image-20220401113707945

    1. 先加载了java.sql.Driver,这个肯定也是启动类加载器加载的,然后注意看ClassLoader cl = Thread.currentThread().getContextClassLoader();这里获取线程上下文加载器,默认就是AppClassLoader,然后用获取到的类加载器来加载Driver==>ServiceLoader.load(service, cl);

      image-20220401114332820

    2. 跟踪进入另一个load方法

    image-20220401114631784

    1. 继续跟踪,java.util.ServiceLoader#load(java.lang.Class<S>)执行完了回到此处

      image-20220401115310972

    2. 这里就是真正要加载com.mysql.jdbc.Driver

    image-20220401115430364

    1. 跟踪进去,最后会进入java.util.ServiceLoader.LazyIterator#nextService,可以到这里用AppClassLoader加载了com.mysql.jdbc.Driver,这个类就是之前在java.sql.DriverManager的静态代码快中受到BootStrapClassLoader的委托而加载的。这就证明了父加载器委托子加载器加载,从而证明了spi打破了双亲委派机制

      image-20220401115555240

    注:

    ==打破双亲委派不是看类最终由哪个类加载器加载,而是看有没有父加载器委托子加载器进行加载,这个逆向的动作。==

    + diff --git a/java/advance/ProxyInJava.html b/java/advance/ProxyInJava.html index 493344e40a..b52d7ecc94 100644 --- a/java/advance/ProxyInJava.html +++ b/java/advance/ProxyInJava.html @@ -30,8 +30,8 @@ 彻底理清Java中的几种代理 | ChenSino - - + + - +

    5.4 AspectJ

    //TODO 未完待续

    6、参考

    JDK Proxy和Cglib代理的区别open in new window

    + diff --git a/java/advance/Synchronized.html b/java/advance/Synchronized.html index e17ff04c38..eb1af878dc 100644 --- a/java/advance/Synchronized.html +++ b/java/advance/Synchronized.html @@ -30,11 +30,11 @@ synchronized实现 原理 | ChenSino - - + + - - + + diff --git a/java/advance/ThreadLocal.html b/java/advance/ThreadLocal.html index 36425a177d..51e795dfb6 100644 --- a/java/advance/ThreadLocal.html +++ b/java/advance/ThreadLocal.html @@ -30,8 +30,8 @@ ThreadLocal | ChenSino - - + + - +
    + diff --git a/java/advance/ThreadPool.html b/java/advance/ThreadPool.html index beb19301e2..52adf885cf 100644 --- a/java/advance/ThreadPool.html +++ b/java/advance/ThreadPool.html @@ -30,8 +30,8 @@ 线程池总结 | ChenSino - - + +
    跳至主要內容

    线程池总结

    chenkun线程池多线程多线程线程池大约 12 分钟

    线程池

    前言:

    线程池使用submit提交任务若遇到异常,线程不会直接抛出异常,在开发中要注意处理异常情况

    1、先上测试代码

        public static void main(String[] args) throws InterruptedException {
    @@ -346,7 +346,7 @@
                     System.out.println(e);
                 }
             }
    -

    测试结果,发现刚开始有4个线程,

    image-20220329212937714

    过了10s后,发现只有一个了,符合我们的预期

    image-20220329213033143

    java.util.concurrent.Future#get()有说明,get是会一直等到此线程结束

    image-20220329213244994

    - +

    测试结果,发现刚开始有4个线程,

    image-20220329212937714

    过了10s后,发现只有一个了,符合我们的预期

    image-20220329213033143

    java.util.concurrent.Future#get()有说明,get是会一直等到此线程结束

    image-20220329213244994

    + diff --git a/java/advance/index.html b/java/advance/index.html index aebfe06070..eb1f9cc0d8 100644 --- a/java/advance/index.html +++ b/java/advance/index.html @@ -30,11 +30,11 @@ Java进阶 | ChenSino - - + + - - + + diff --git a/java/advance/io.html b/java/advance/io.html index 0923f49c68..9a802793d3 100644 --- a/java/advance/io.html +++ b/java/advance/io.html @@ -30,8 +30,8 @@ I/O模型 | ChenSino - - + +
    跳至主要內容

    I/O模型

    ChenSino大约 6 分钟

    1、参考

    2、模型分类

    IO模型分类

    IO模型
    阻塞I/O模型同步阻塞
    非阻塞I/O模型同步非阻塞
    I/O多路复用模型同步非阻塞
    信号驱动I/O模型同步非阻塞
    异步IO(AIO)模型异步非阻塞

    3、形象生活例子

    • 小明去吃同仁四季的椰子鸡,就这样在那里排队,等了一小时,然后才开始吃火锅。(BIO)
    • 小红也去同仁四季的椰子鸡,她一看要等挺久的,于是去逛会商场,每次逛一下,就跑回来看看,是不是轮到她了。于是最后她既购了物,又吃上椰子鸡了。(NIO)
    • 小华一样,去吃椰子鸡,由于他是高级会员,所以店长说,你去商场随便逛会吧,等下有位置,我立马打电话给你。于是小华不用干巴巴坐着等,也不用每过一会儿就跑回来看有没有等到,最后也吃上了美味的椰子鸡(AIO)

    4、同步阻塞I/O(传统BIO)

    本节主要参考了以下博客:

    架构设计:系统间通信(1)——概述从“聊天”开始上篇open in new window
    多线程中的上下文切换open in new window
    多线程中的上下文切换性能开销open in new window
    系统线程状态和jvm线程状态open in new window

    4.1 介绍

    假设一种场景,有一个CPU只有一个核心,有个简单的java程序要上传文件,当有个线程执行时遇到了I/O操作,一直等待文件上传(此时系统线程会休眠,让出cpu使用权,而java线程依然时RUNNABLE状态,参考系统线程状态和jvm线程状态open in new window),这就是典型的同步阻塞。这里同步指的是java代码卡在这无法执行后面的代码,阻塞指的是应用程序一直等待内核提供数据。

    4.2 传统BIO存在的问题

    • 同一时间,服务器只能接受来自于客户端A的请求信息;虽然客户端A和客户端B的请求是同时进行的,但客户端B发送的请求信息只能等到服务器接受完A的请求数据后,才能被接受。
    • 由于服务器一次只能处理一个客户端请求,当处理完成并返回后(或者异常时),才能进行第二次请求的处理。很显然,这样的处理方式在高并发的情况下,是不能采用的。

    4.3 使用多线程改进BIO

    1. 虽然在服务器端,请求的处理交给了一个独立线程进行,但是操作系统通知accept()的方式还是单个的。也就是,实际上是服务器接收到数据报文后的“业务处理过程”可以多线程,但是数据报文的接受还是需要一个一个的来(下文的示例代码和debug过程我们可以明确看到这一点)

    2. 在linux系统中,可以创建的线程是有限的。我们可以通过cat /proc/sys/kernel/threads-max 命令查看可以创建的最大线程数。当然这个值是可以更改的,但是线程越多,CPU切换所需的时间也就越长,用来处理真正业务的需求也就越少。

    3. 创建一个线程是有较大的资源消耗的。JVM创建一个线程的时候,即使这个线程不做任何的工作,JVM都会分配一个堆栈空间。这个空间的大小默认为128K,您可以通过-Xss参数进行调整。

    4. 当然您还可以使用ThreadPoolExecutor线程池来缓解线程的创建问题,但是又会造成BlockingQueue积压任务的持续增加,同样消耗了大量资源。

    • 另外,如果您的应用程序大量使用长连接的话,线程是不会关闭的。这样系统资源的消耗更容易失控。 那么,如果你真想单纯使用线程解决阻塞的问题,那么您自己都可以算出来您一个服务器节点可以一次接受多大的并发了。看来,单纯使用线程解决这个问题不是最好的办法

    4.4 验证上下文切换带来的CPU消耗巨大

    在一个线程和多个线程分别操作相同次数的业务逻辑,比较耗时

    
    @@ -116,7 +116,7 @@
     counter: 100000000
     serial exec time: 956s
     counter: 100000000
    -

    4.5 NIO

    NIO浅析open in new window

    4.5.1 NIO解决了什么问题

    NIO是用较少的线程,可以实现以前较多线程的并发量,并且对资源的占用少了很多。 以前是一个连接一个线程,并发上来以后,线程会特别多。NIO是少量线程处理大量连接。 以前一个连接进来后,只要数据没有传输完成,这个线程就会一直停在这,不能释放。NIO是用一个线程去处理所有连接,有数据进来才会通知业务线程去处理。收到多少数据就读多少,读完就释放了,不会一直停在这,对资源的利用率更高。所以在面对高并发的时候,NIO 更省资源,换句话说,同样的资源下,NIO 可以处理更多的并发, 这也是为什么一提到NIO总是说他的并发高。

    - +

    4.5 NIO

    NIO浅析open in new window

    4.5.1 NIO解决了什么问题

    NIO是用较少的线程,可以实现以前较多线程的并发量,并且对资源的占用少了很多。 以前是一个连接一个线程,并发上来以后,线程会特别多。NIO是少量线程处理大量连接。 以前一个连接进来后,只要数据没有传输完成,这个线程就会一直停在这,不能释放。NIO是用一个线程去处理所有连接,有数据进来才会通知业务线程去处理。收到多少数据就读多少,读完就释放了,不会一直停在这,对资源的利用率更高。所以在面对高并发的时候,NIO 更省资源,换句话说,同样的资源下,NIO 可以处理更多的并发, 这也是为什么一提到NIO总是说他的并发高。

    + diff --git a/java/base/ConstantPool.html b/java/base/ConstantPool.html index 46fc719ebf..08ff82f6a4 100644 --- a/java/base/ConstantPool.html +++ b/java/base/ConstantPool.html @@ -30,8 +30,8 @@ ConstantPool | ChenSino - - + +
    跳至主要內容

    ConstantPool

    ChenSinojava基础必会小于 1 分钟

    1. Integer常量池默认的范围

    范围:[-128,127],Integer内部有个缓存池,最小值-128是固定的,最大的值127是可以调整的,看源码知道, 最大值是和integerCacheHighPropValue有关,这个值是可以通过~java.lang.Integer.IntegerCache.high~ 属性指定,实际测试~~~System.setProperty("java.lang.Integer.IntegerCache.high","300")~~~不生效, 因为他是jvm参数应该在启动时设置vm参数,-Djava.lang.Integer.IntegerCache.high=300 使用-XX:AutoBoxCacheMax=300也可以。

    private static class IntegerCache {
    @@ -87,7 +87,7 @@
             System.out.println(c == d);
         }
     //输出: null false
    -
    - +
    + diff --git a/java/base/CustomLRU.html b/java/base/CustomLRU.html index 8241c869f6..a735d03a44 100644 --- a/java/base/CustomLRU.html +++ b/java/base/CustomLRU.html @@ -30,8 +30,8 @@ 自定义LRU实现 | ChenSino - - + +
    跳至主要內容

    自定义LRU实现

    ChenSinojava基础必会大约 1 分钟

    LRU介绍

    lru(latest recently used)最近最少使用,在缓存中可以使用LRU算法移除最近最少使用的

    自定义lru算法

    在java中LinkedHashMap已经实现了LRU算法,在使用时只需要继承此类,然后重写removeEldestEntry方法即可

    public class MyLRU<K, V> extends LinkedHashMap<K, V> {
    @@ -86,7 +86,7 @@
     aa:11
     dd:44
     
    -

    发现当使用一次aa后,这次被删除的是bb,说明当刚加入aa,bb,cc时的排序是aa-bb-cc,当我们使用一次aa,则aa被放到头部,变成了bb-cc-aa,当在加入dd时,需要移除尾部的bb,就变成了cc-aa-dd

    - +

    发现当使用一次aa后,这次被删除的是bb,说明当刚加入aa,bb,cc时的排序是aa-bb-cc,当我们使用一次aa,则aa被放到头部,变成了bb-cc-aa,当在加入dd时,需要移除尾部的bb,就变成了cc-aa-dd

    + diff --git a/java/base/IntegerConstantPool.html b/java/base/IntegerConstantPool.html index 01d1c7f9e2..930b14609a 100644 --- a/java/base/IntegerConstantPool.html +++ b/java/base/IntegerConstantPool.html @@ -30,8 +30,8 @@ Integer常量池 | ChenSino - - + +
    跳至主要內容

    Integer常量池

    ChenSinojava基础小于 1 分钟

    1. Integer常量池默认的范围

    范围:[-128,127],Integer内部有个缓存池,最小值-128是固定的,最大的值127是可以调整的,看源码知道,最大值是和integerCacheHighPropValue有关,这个值是可以通过~java.lang.Integer.IntegerCache.high~属性指定,实际测试~~~System.setProperty("java.lang.Integer.IntegerCache.high","300")~~~不生效,使用-XX:AutoBoxCacheMax=300可以。

    private static class IntegerCache {
    @@ -87,7 +87,7 @@
             System.out.println(c == d);
         }
     //输出: null false
    -
    - +
    + diff --git a/java/base/Serialization.html b/java/base/Serialization.html index 11f76e087f..835302ca6b 100644 --- a/java/base/Serialization.html +++ b/java/base/Serialization.html @@ -30,8 +30,8 @@ 序列化 | ChenSino - - + +
    跳至主要內容

    序列化

    ChenSinojava基础大约 2 分钟

    1、序列化、反序列化是什么?

    • 序列化:把对象转化成字节码
    • 反序列化:把字节码(从IO流获取或者从硬盘文件读取)转化为对象

    2、 举例说明作用?

    只有序列化成字节码文件后,对象才能在网络中通过IO流(传输的是字节码)传输或者存到硬盘上

    2.1 实现分布式对象

    例如在RMI(Remote Method Invoke)中利用对象序列化来运行远程主机上的服务,就像执行本地的对象方法一样;

    2.2 Java对象序列化不仅保留一个对象的数据,而且会递归保留引用的对象

    序列化时,把虚拟机内存中的对象保存到文件,反序列化时再把文件中的对象还原到内存中去

    注意如果序列化Student时,Teacher类没有被序列化则会抛出异常~~~ java.io.NotSerializableException: com.chen.Teacher~~~

    如果想不抛出异常有两种方法:

    1. 老老实实把Teacher类也实现Serializable接口
    2. 在Student类中把Teacher字段用transient修饰,则反序列化后看到的teacher是null这是和第一种方法不一样的地方
    //学生类实现序列化就接口,并且学生有一个老师属性字段
    @@ -158,7 +158,7 @@
         }
     }
     
    -
    2.3 序列化把对象保存到数据库,反序列化把数据库中的数据还原到内存
    2.4 对象、文件、数据,有许多不同的格式,很难统一传输和保存

    序列化以后就是统一的字节码文件,是一个通用的存储格式,反序列化的时候,只需要按照约定好的格式进行“解码”就可以了

    - +
    2.3 序列化把对象保存到数据库,反序列化把数据库中的数据还原到内存
    2.4 对象、文件、数据,有许多不同的格式,很难统一传输和保存

    序列化以后就是统一的字节码文件,是一个通用的存储格式,反序列化的时候,只需要按照约定好的格式进行“解码”就可以了

    + diff --git a/java/base/String.html b/java/base/String.html index 676d3bdf45..05821929aa 100644 --- a/java/base/String.html +++ b/java/base/String.html @@ -30,8 +30,8 @@ 字符串设计 | ChenSino - - + +
    跳至主要內容

    字符串设计

    ChenSinojava基础大约 3 分钟

    1、String类
    1. 为什么String类要被设计为不可变?

      string为什么不可变open in new window

      Thanks to the immutability of Strings in Java, the JVM can optimize the amount of memory allocated for them by storing only one copy of each literal String in the pool. This process is called interning.
      @@ -139,7 +139,7 @@
       }
       SourceFile: "JavaBase.java"
       
      -
    - +
    + diff --git a/java/base/index.html b/java/base/index.html index 8dd00d7a8e..369026afda 100644 --- a/java/base/index.html +++ b/java/base/index.html @@ -30,11 +30,11 @@ Java基础 | ChenSino - - + + - - + + diff --git a/java/framework/index.html b/java/framework/index.html index b9826dba2f..cb8c2f4802 100644 --- a/java/framework/index.html +++ b/java/framework/index.html @@ -30,11 +30,11 @@ Framework | ChenSino - - + + - - + + diff --git a/java/framework/mybatis/MybatisPlusDataSource.html b/java/framework/mybatis/MybatisPlusDataSource.html index d3b54392f6..a492f10697 100644 --- a/java/framework/mybatis/MybatisPlusDataSource.html +++ b/java/framework/mybatis/MybatisPlusDataSource.html @@ -30,8 +30,8 @@ MybatisPlus多线程数据源切换问题 | ChenSino - - + +
    跳至主要內容

    MybatisPlus多线程数据源切换问题

    chenkun大约 2 分钟

    1、问题的背景

    有两个库,ccsx_data、ccsx_weibao,默认库是ccsx_data,我在代码中使用了>mybatis-pulus的@DS()注解,想切换到ccsx_weibao这个库,但是切换一直失败,代码如下:

    	@DS("ccsx_weibao")
    @@ -133,7 +133,7 @@
     		DynamicDataSourceContextHolder.clear();
             //略
         }
    -

    使用 ThreadLocalUtil.getThreadLocalMap()open in new window获取所有的线程的ThreadLocalMap,debug模式查看每一行执行过后数据源的变化

    - +

    使用 ThreadLocalUtil.getThreadLocalMap()open in new window获取所有的线程的ThreadLocalMap,debug模式查看每一行执行过后数据源的变化

    + diff --git a/java/framework/mybatis/index.html b/java/framework/mybatis/index.html index 58ef9bbfb3..d27da4bde6 100644 --- a/java/framework/mybatis/index.html +++ b/java/framework/mybatis/index.html @@ -30,11 +30,11 @@ Mybatis | ChenSino - - + + - - + + diff --git a/java/framework/mybatis/mybatis.html b/java/framework/mybatis/mybatis.html index c4a23b2ac8..bcf26e5b11 100644 --- a/java/framework/mybatis/mybatis.html +++ b/java/framework/mybatis/mybatis.html @@ -30,8 +30,8 @@ Mybatis使用 | ChenSino - - + +
    跳至主要內容

    Mybatis使用

    chenkun框架框架大约 12 分钟

    mybatis缓存

    缓存介绍open in new window

    一级缓存存在的问题open in new window

    1、 一级缓存

    mybatis缓存有一级缓存也叫SelSession缓存,是强制打开的,也就是说Mybatis没有提供关闭一级缓存的方式。一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其查询结果放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。一级缓存的存储位置是在JVM虚拟机内存,它放在一个Map中

    1.1 一级缓存的生命周期有多长?

    a、MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

    b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

    c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

    d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用

    1. 2、怎么判断某两次查询是完全相同的查询?

    mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

    2.1 传入的statementId

    2.2 查询时要求的结果集中的结果范围

    2.3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )

    2.4 传递给java.sql.Statement要设置的参数值

    1.3 一级缓存的问题

    前面说了一级缓存是针对同一个SelSession,如果用SqlSession去执行同一个查询两次,会发现只有一次打印出了sql,说明一级缓存生效了

     @Test
    @@ -381,7 +381,7 @@
     
     		return false;
     	}
    -
    2.3

    3. mybatis使用中遇到问题

    3.1
    - +
    2.3

    3. mybatis使用中遇到问题

    3.1
    + diff --git a/java/framework/security/Authorization.html b/java/framework/security/Authorization.html index 81130ee9f3..fe18a6e282 100644 --- a/java/framework/security/Authorization.html +++ b/java/framework/security/Authorization.html @@ -30,11 +30,11 @@ 权限校验原理 | ChenSino - - + + -
    跳至主要內容

    权限校验原理

    ChenSinosecurity大约 1 分钟

    1、权限说明

    认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401
    授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403

    2、Security中负责权限校验的类结构图

    Security中权限类

    如上图,可以看到最底层有两个类,分别是FilterSecurityInterceptorMethodSecurityInterceptor,这两个类都是AbstractSecurityInterceptor的子类。
    其中FilterSecurityInterceptor还实现了Filter接口,它是一个SecurityFilter,是众多SecurityFilterChain过滤器中的一个,它处理认证问题,当用户访问未认证接口 会被此类拦截,抛出异常,返回401。

    MethodSecurityInterceptor是当程序即将调用Controller中方法之前调用,对应的它处理Controller层被使用了@PreAuthorize注解的方法,它用来校验当前用户是否有注解中 包含的权限,当前用户不包含对应权限时,会抛出异常返回403。

    - +
    跳至主要內容

    权限校验原理

    ChenSinosecurity大约 1 分钟

    1、权限说明

    认证(Authentication):登录操作就是最常见的认证方式,就是提供用户名和密码来证明自己是某个系统的合法用户,当用户没有经过认证去访问一个受保护资源时,应当响应401
    授权(Authorization):授权是检验用户是否有权限访问某个资源,比如普通用户是无法看到管理员界面的,当用户无权访问某个资源,应当响应403

    2、Security中负责权限校验的类结构图

    Security中权限类

    如上图,可以看到最底层有两个类,分别是FilterSecurityInterceptorMethodSecurityInterceptor,这两个类都是AbstractSecurityInterceptor的子类。
    其中FilterSecurityInterceptor还实现了Filter接口,它是一个SecurityFilter,是众多SecurityFilterChain过滤器中的一个,它处理认证问题,当用户访问未认证接口 会被此类拦截,抛出异常,返回401。

    MethodSecurityInterceptor是当程序即将调用Controller中方法之前调用,对应的它处理Controller层被使用了@PreAuthorize注解的方法,它用来校验当前用户是否有注解中 包含的权限,当前用户不包含对应权限时,会抛出异常返回403。

    + diff --git a/java/framework/security/CustomAuthenticationProvider.html b/java/framework/security/CustomAuthenticationProvider.html index 0fe73e79c0..bbd1d6ec65 100644 --- a/java/framework/security/CustomAuthenticationProvider.html +++ b/java/framework/security/CustomAuthenticationProvider.html @@ -30,8 +30,8 @@ Security扩展自定义登录方式 | ChenSino - - + + - +

    20221223102912

    + diff --git a/java/framework/security/CustomLoginPage.html b/java/framework/security/CustomLoginPage.html index d6649a8a8c..46bccfc975 100644 --- a/java/framework/security/CustomLoginPage.html +++ b/java/framework/security/CustomLoginPage.html @@ -30,8 +30,8 @@ 自定义登陆页面 | ChenSino - - + +
    跳至主要內容

    自定义登陆页面

    chenkun大约 2 分钟

    注意

    本篇博客是用来配置前后不分离的项目,正常情况下现在的项目都是前后分离了,因此本篇内容 并没有太多学习价值,但是网上大多数教程都特别喜欢讲这一部分内容,就我目前了解到的内容, 在搭建oauth2授权服务器可能会用到,因为授权服务器需要一个登录页面,这个页面可以单独放到后端,仅仅做个登录没有必要开一个前端项目.

    1、修改自定义的登陆页面以及登陆请求校验

    官方文档

    https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html

    1.1 Secutity配置

    在Security配置文件配置loginPage指定登陆的页面,loginProcessingUrl指定用户名密码认证处理地址,同时一定要放行这两个页面,否则会一直被拦截导致重定向到登陆页面。 另外,还要准备自定义页面,以及自定义处理接口。

    注意

    一般情况,无需自定义登陆处理逻辑,只需要修改登陆页面,在登陆页面把action保留原来的login即可

    protected void configure(HttpSecurity http) {
    @@ -83,7 +83,7 @@
             </#if>
         </form>
     </div>
    -

    2、开启表单认证

    1. 如下图,开启表单登录可以Security开启了14个过滤器(忽略TokenAuthenticationFilter这个是我自定义的)

    2022122310030920221223100226

    1. 开启表单登录后,当访问一个未认证的接口时,会被重定向到登录页,因为开启表单认证后,默认的AuthenticationEntryPoint实现是LoginUrlAuthenticationEntryPoint, 20221223100757

    3、关闭表单认证

    除了我自定义的TokenAuthenticationFilter过滤器外还有11个,与上面相比刚好少了3个和表单相关的过滤器(UsernamePassworkAuthenticationFilter,DefaultLoginPageGeneratingFilter,DefaultLogoutPageGeneratingFilter) 20221223100930

    并且关闭表单验证后,默认的AuthenticationEntryPoint变成了Http403ForbiddenEntryPoint,当用户未认证时,去访问一个接口就会被此类处理,返回403异常,不会跳转到登录表单。

    - +

    2、开启表单认证

    1. 如下图,开启表单登录可以Security开启了14个过滤器(忽略TokenAuthenticationFilter这个是我自定义的)

    2022122310030920221223100226

    1. 开启表单登录后,当访问一个未认证的接口时,会被重定向到登录页,因为开启表单认证后,默认的AuthenticationEntryPoint实现是LoginUrlAuthenticationEntryPoint, 20221223100757

    3、关闭表单认证

    除了我自定义的TokenAuthenticationFilter过滤器外还有11个,与上面相比刚好少了3个和表单相关的过滤器(UsernamePassworkAuthenticationFilter,DefaultLoginPageGeneratingFilter,DefaultLogoutPageGeneratingFilter) 20221223100930

    并且关闭表单验证后,默认的AuthenticationEntryPoint变成了Http403ForbiddenEntryPoint,当用户未认证时,去访问一个接口就会被此类处理,返回403异常,不会跳转到登录表单。

    + diff --git a/java/framework/security/CustomTokenAuthentication.html b/java/framework/security/CustomTokenAuthentication.html index 742233d7cd..5a325e103e 100644 --- a/java/framework/security/CustomTokenAuthentication.html +++ b/java/framework/security/CustomTokenAuthentication.html @@ -30,8 +30,8 @@ 前后分离项目自定义token认证 | ChenSino - - + +
    跳至主要內容

    前后分离项目自定义token认证

    chensino大约 2 分钟

    注意

    网上很难精准找到一个前后端分离项目自定义Token认证的教程,找了很久终于找到,特此记录

    1、Security配置注意事项

    1. 我在很多教程中都看到他们有讲解自定义登录页面,但是我想说的是,都21世纪了,早都前后分离了,
    @@ -136,7 +136,7 @@
            filterChain.doFilter(request, response);
        }
     }
    -
    - +
    + diff --git a/java/framework/security/DelegatingFilterProxy.html b/java/framework/security/DelegatingFilterProxy.html index 59ed1dd00b..353db960a0 100644 --- a/java/framework/security/DelegatingFilterProxy.html +++ b/java/framework/security/DelegatingFilterProxy.html @@ -30,11 +30,11 @@ DelegatingFilterProxy介绍 | ChenSino - - + + -
    跳至主要內容

    DelegatingFilterProxy介绍

    chensino原创大约 2 分钟

    作用

    在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。

    具体解析:

    1. ContextLoaderListener:
    • ContextLoaderListener 是 Spring 中常见的监听器,负责在应用启动时加载 Spring 的 ApplicationContext,从而初始化应用中的所有 Spring Bean。这通常是在应用启动的较晚阶段完成的。
    • ContextLoaderListener 会监听 Web 应用的启动事件,并在合适的时机加载和管理 Spring 上下文。
    1. Servlet 过滤器的加载顺序:
    • 在典型的 Servlet 容器(如 Tomcat)中,过滤器的实例化和注册会在应用启动的早期阶段进行,即在应用的主业务逻辑启动之前。
    • 这些过滤器的初始化是 Servlet 容器的一部分,不依赖于 Spring 的 ApplicationContext。换句话说,过滤器的生命周期由 Servlet 容器本身管理,而不受 Spring 管理的影响。
    1. 问题所在:
    • 因为过滤器(Filter)的实例需要在 Spring 上下文加载之前注册,而 Spring 的 Bean 管理是由 ContextLoaderListener 在较后阶段完成的,这就导致了过滤器实例在创建时无法直接依赖于 Spring 的 Bean 或上下文。如果直接在 web.xml 中定义过滤器,过滤器实例会在 Spring 上下文加载完成之前被创建。

    解决方案——DelegatingFilterProxy:

    • DelegatingFilterProxy 可以解决这个问题。它允许 Servlet 容器在初始化过滤器时,并不直接创建过滤器的实际实例,而是通过代理的方式,将过滤器的执行逻辑委托给 Spring 上下文中的某个 Bean。这样,过滤器的真正逻辑就可以依赖于 Spring 容器管理的 Bean。
    • 具体来说,DelegatingFilterProxy 本身是一个标准的 Servlet 过滤器,在容器启动时被注册。但是它在处理过滤时,会去查找 Spring 上下文中已经定义好的过滤器 Bean(例如 Spring Security 的 FilterChainProxy),并将请求委托给这个 Spring 管理的过滤器 Bean。

    总结:

    • 问题:在传统的 Servlet 应用中,过滤器的初始化顺序早于 Spring 上下文的加载,这导致无法将 Spring 的依赖注入直接应用于过滤器。
    • 解决:通过使用 DelegatingFilterProxy,可以将过滤器的创建延迟到 Spring 上下文加载完成之后,由 Spring 容器管理过滤器实例的生命周期,使得过滤器可以正常依赖 Spring Bean。 因此,DelegatingFilterProxy 是一个桥梁,它确保即使 Servlet 容器在 Spring 上下文加载之前注册过滤器,过滤器的实际执行逻辑依然能够使用 Spring 管理的 Bean 和资源。
    - +
    跳至主要內容

    DelegatingFilterProxy介绍

    chensino原创大约 2 分钟

    作用

    在典型的 Spring 应用中,ContextLoaderListener 通常用于加载 Spring 的应用上下文(ApplicationContext),并管理其中定义的 Spring Bean。然而,Servlet 容器在启动时,会优先初始化过滤器(Filter)实例,而此时 Spring 上下文可能还没有完全加载。

    具体解析:

    1. ContextLoaderListener:
    • ContextLoaderListener 是 Spring 中常见的监听器,负责在应用启动时加载 Spring 的 ApplicationContext,从而初始化应用中的所有 Spring Bean。这通常是在应用启动的较晚阶段完成的。
    • ContextLoaderListener 会监听 Web 应用的启动事件,并在合适的时机加载和管理 Spring 上下文。
    1. Servlet 过滤器的加载顺序:
    • 在典型的 Servlet 容器(如 Tomcat)中,过滤器的实例化和注册会在应用启动的早期阶段进行,即在应用的主业务逻辑启动之前。
    • 这些过滤器的初始化是 Servlet 容器的一部分,不依赖于 Spring 的 ApplicationContext。换句话说,过滤器的生命周期由 Servlet 容器本身管理,而不受 Spring 管理的影响。
    1. 问题所在:
    • 因为过滤器(Filter)的实例需要在 Spring 上下文加载之前注册,而 Spring 的 Bean 管理是由 ContextLoaderListener 在较后阶段完成的,这就导致了过滤器实例在创建时无法直接依赖于 Spring 的 Bean 或上下文。如果直接在 web.xml 中定义过滤器,过滤器实例会在 Spring 上下文加载完成之前被创建。

    解决方案——DelegatingFilterProxy:

    • DelegatingFilterProxy 可以解决这个问题。它允许 Servlet 容器在初始化过滤器时,并不直接创建过滤器的实际实例,而是通过代理的方式,将过滤器的执行逻辑委托给 Spring 上下文中的某个 Bean。这样,过滤器的真正逻辑就可以依赖于 Spring 容器管理的 Bean。
    • 具体来说,DelegatingFilterProxy 本身是一个标准的 Servlet 过滤器,在容器启动时被注册。但是它在处理过滤时,会去查找 Spring 上下文中已经定义好的过滤器 Bean(例如 Spring Security 的 FilterChainProxy),并将请求委托给这个 Spring 管理的过滤器 Bean。

    总结:

    • 问题:在传统的 Servlet 应用中,过滤器的初始化顺序早于 Spring 上下文的加载,这导致无法将 Spring 的依赖注入直接应用于过滤器。
    • 解决:通过使用 DelegatingFilterProxy,可以将过滤器的创建延迟到 Spring 上下文加载完成之后,由 Spring 容器管理过滤器实例的生命周期,使得过滤器可以正常依赖 Spring Bean。 因此,DelegatingFilterProxy 是一个桥梁,它确保即使 Servlet 容器在 Spring 上下文加载之前注册过滤器,过滤器的实际执行逻辑依然能够使用 Spring 管理的 Bean 和资源。
    + diff --git a/java/framework/security/OAuth2Authentication.html b/java/framework/security/OAuth2Authentication.html index 312564962c..826e30ab02 100644 --- a/java/framework/security/OAuth2Authentication.html +++ b/java/framework/security/OAuth2Authentication.html @@ -30,11 +30,11 @@ Ruoyi使用oauth对接pig | ChenSino - - + + -
    跳至主要內容

    Ruoyi使用oauth对接pig

    chenkunSecurityOAuth大约 2 分钟

    1、时序图

    时序图

    2、流程解析

    本流程是以使用Ruoyi对接Pig授权中心为例,进行讲解,其他网站的的oauth的原理都和这个一样,所以只要把这个流程搞懂了即可,接下来就按照真实的流程进行逐步解析。

    2.1 第1步

    用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页
    20230104105625

    2.2 第2步

    点击SSO登录会访问如下这样子的一个url,申请授权,(127.0.0.1:3000就是授权服务器) https://127.0.0.1:3000/oauth/authorize?client_id=ruyi&response_type=code&scope=server&redirect_uri=http://127.0.0.1:1024/sso&TENANT-ID=1,当授权服务收到这个请求时会发现用户还未登录授权服务器,会重定向到授权服务器的登录页面http://127.0.0.1:3000/token/login,大概就是下面这样

    20230104105039

    2.3 第3步

    用户输入帐号密码提交后pig授权中心会校验ruoyi客户端是否合法,就是验证你这个客户端是否在pigx中已经注册,验证通过再验证你输入的用户名密码是否正确,验证通过后再生成一个授权页面,就是和平时我们微信授权给第三方小程序弹出一个让你确认授权的那个界面差不多,参考下一步

    2.4 第4步

    用户点击授权, 20230104105954

    2.5 第5步

    pig授权中心生成授权码

    2.6 第6步

    pig产生授权码后,会带着这个授权码重定向到注册客户端时填的那个地址,这里就是http://127.0.0.1:1024/sso?code=U1wLD7 这个地址是在数据库注册好的,它是ruoyi前端的一个页面,并不是后端接口

    20230104110557

    20230104110449

    2.7 第7步

    ruoyi前端构造一个请求,并且携带code请求ruoyi后端接口, 2023010411090920230104110921

    2.8 第8步

    ruoyi后端拿到code,发送post请求到http://127.0.0.1:3000/oauth/token获取token,注意这是个oauth的默认端点,不是用户写的,在TokenEndPoint类中 20230104111117

    2.9 第9步

    pig授权中心生成token,这里生成token的逻辑可以自定义实现,具体的请参考pig的源码TokenService类 20230104111420

    2.10 第10步

    pig授权中心生成的token返回到ruoyi后端,ruoyi后端拿到token 20230104111504

    2.11 第11步

    有了token后面的就不再赘述,可以拿token访问资源了,这里其实仅仅用了oauth2的特性来做了一个sso单点登录,并没有用到oauth2的核心来进行资源服务器的访问,oauth2的核心其实是用来无需帐号密码即可访问资源服务器的资源。

    - +
    跳至主要內容

    Ruoyi使用oauth对接pig

    chenkunSecurityOAuth大约 2 分钟

    1、时序图

    时序图

    2、流程解析

    本流程是以使用Ruoyi对接Pig授权中心为例,进行讲解,其他网站的的oauth的原理都和这个一样,所以只要把这个流程搞懂了即可,接下来就按照真实的流程进行逐步解析。

    2.1 第1步

    用户还未登录,访问ruoyi前端,ruoyi会自动跳转到自己的登录首页
    20230104105625

    2.2 第2步

    点击SSO登录会访问如下这样子的一个url,申请授权,(127.0.0.1:3000就是授权服务器) https://127.0.0.1:3000/oauth/authorize?client_id=ruyi&response_type=code&scope=server&redirect_uri=http://127.0.0.1:1024/sso&TENANT-ID=1,当授权服务收到这个请求时会发现用户还未登录授权服务器,会重定向到授权服务器的登录页面http://127.0.0.1:3000/token/login,大概就是下面这样

    20230104105039

    2.3 第3步

    用户输入帐号密码提交后pig授权中心会校验ruoyi客户端是否合法,就是验证你这个客户端是否在pigx中已经注册,验证通过再验证你输入的用户名密码是否正确,验证通过后再生成一个授权页面,就是和平时我们微信授权给第三方小程序弹出一个让你确认授权的那个界面差不多,参考下一步

    2.4 第4步

    用户点击授权, 20230104105954

    2.5 第5步

    pig授权中心生成授权码

    2.6 第6步

    pig产生授权码后,会带着这个授权码重定向到注册客户端时填的那个地址,这里就是http://127.0.0.1:1024/sso?code=U1wLD7 这个地址是在数据库注册好的,它是ruoyi前端的一个页面,并不是后端接口

    20230104110557

    20230104110449

    2.7 第7步

    ruoyi前端构造一个请求,并且携带code请求ruoyi后端接口, 2023010411090920230104110921

    2.8 第8步

    ruoyi后端拿到code,发送post请求到http://127.0.0.1:3000/oauth/token获取token,注意这是个oauth的默认端点,不是用户写的,在TokenEndPoint类中 20230104111117

    2.9 第9步

    pig授权中心生成token,这里生成token的逻辑可以自定义实现,具体的请参考pig的源码TokenService类 20230104111420

    2.10 第10步

    pig授权中心生成的token返回到ruoyi后端,ruoyi后端拿到token 20230104111504

    2.11 第11步

    有了token后面的就不再赘述,可以拿token访问资源了,这里其实仅仅用了oauth2的特性来做了一个sso单点登录,并没有用到oauth2的核心来进行资源服务器的访问,oauth2的核心其实是用来无需帐号密码即可访问资源服务器的资源。

    + diff --git a/java/framework/security/OncePerRequestFilter.html b/java/framework/security/OncePerRequestFilter.html index b2f85bcafc..141b70ef35 100644 --- a/java/framework/security/OncePerRequestFilter.html +++ b/java/framework/security/OncePerRequestFilter.html @@ -30,8 +30,8 @@ OnecePerRequestFilter | ChenSino - - + +
    跳至主要內容

    OnecePerRequestFilter

    chenkunSecurity大约 3 分钟

    1、OnecePerRequestFilter初识

    第一次接触这个类,在SpringSecurity中,大概百度了一下,知道此类是限制一次请求只
    @@ -141,7 +141,7 @@
     			throws ServletException, IOException;
     
     }
    -

    3、总结

    OncePerRequestFilter类的作用就是解决不同版本(或者不同厂家的Servlet容器)容器可能存在的一次请求重复进入到同一个过滤器的问题。 其解决方法是在request对象中添加一个属性标识符,每次调用本过滤器处理逻辑前先判断request的标识符,保证只执行一次。

    - +

    3、总结

    OncePerRequestFilter类的作用就是解决不同版本(或者不同厂家的Servlet容器)容器可能存在的一次请求重复进入到同一个过滤器的问题。 其解决方法是在request对象中添加一个属性标识符,每次调用本过滤器处理逻辑前先判断request的标识符,保证只执行一次。

    + diff --git a/java/framework/security/PreAuthorize.html b/java/framework/security/PreAuthorize.html index 52da5fe7d2..80afa8a40c 100644 --- a/java/framework/security/PreAuthorize.html +++ b/java/framework/security/PreAuthorize.html @@ -30,11 +30,11 @@ PreAuthorize注解 | ChenSino - - + + - - + + diff --git a/java/framework/security/SSO.html b/java/framework/security/SSO.html index 2cab5bdb8c..28b548c041 100644 --- a/java/framework/security/SSO.html +++ b/java/framework/security/SSO.html @@ -30,11 +30,11 @@ SSO协议 | ChenSino - - + + -
    跳至主要內容

    SSO协议

    ChenSinooauthsso大约 4 分钟

    1、写在前面

    一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。

    • 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录
    • 授权(Authorization),即用户要访问某资源,必须要拥有对应权限

    也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源时,系统会校验其拥有的权限是否可以访问这个资源。

    2、SSO和OAUTH2介绍

    SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。

    oauth是一个协议,它们定义了一套流程规范,你要实现我这个协议,就得按照我的规范来,和java中jdbc一样,sun公司负责规定一套规范,提供一套接口,下游的用户自行负责实现,你具体怎么实现我不关心,你要做的就是实现我规定的一套规范。

    OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。OAuth强调的是授权,当然要想获取授权前你必须认证,就像第一节说的,用户只有登录后才能获得对应用户的权限,这一步是无法绕开的。

    3、SSO实现

    单点登录实现中,系统之间的协议对接是非常重要的一环,一般涉及的标准协议类型有 CAS、OAuth、OpenID Connect、SAML。

    另外国产的SA-TOKEN项目也有sso和oauth实现

    3.1 CAS

    Central Authentication Service简称CAS,是一种常见的B/S架构的SSO协议。和其他任何SSO协议一样,用户仅需登陆一次,访问其他应用则无需再次登陆。顾名思义,CAS是一种仅用于Authentication的服务,它和OAuth/OIDC协议不一样,并不能作为一种Authorization的协议。当前CAS协议包括CAS 1.0、CAS2.0、CAS3.0版本,这三个版本的认证流程基本类似。CAS的认证流程通过包括几部分参与者:

    Client: 通常为使用浏览器的用户 CAS Client: 实现CAS协议的Web应用 CAS Server: 作为统一认证的CAS服务器

    4、各种协议对比

    单纯的单点登录,其实无需授权中心有一套自己的用户和权限,单点登录是只管登录,登录后从业务系统自行获取自己的权限,所以从这个角度来说sso也有它自己的优势,它不像oauth那样还要维护一套用户权限系统,如果仅仅作单点登录不用权限的话用cas显然更有优势。

    以下图片是各种实现的对比图来自知乎open in new window,我并不赞同他的观点,仅作参考 20230104171826

    以下对比来自sa-token官网open in new window

    功能点SSO单点登录OAuth2.0
    统一认证支持度高支持度高
    统一注销支持度高支持度低
    多个系统会话一致性强一致弱一致
    第三方应用授权管理不支持支持度高
    自有系统授权管理支持度高支持度低
    Client级的权限校验不支持支持度高
    集成简易度比较简单难度中等
    - +
    跳至主要內容

    SSO协议

    ChenSinooauthsso大约 4 分钟

    1、写在前面

    一个系统一般都会有用户角色权限相关的概念,用户需要登录系统并且拥有相应的权限后才能访问对应的资源。

    • 认证(Authentication),即确认用户的身份,也就是上面说的要输入帐号密码登录
    • 授权(Authorization),即用户要访问某资源,必须要拥有对应权限

    也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源时,系统会校验其拥有的权限是否可以访问这个资源。

    2、SSO和OAUTH2介绍

    SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。

    oauth是一个协议,它们定义了一套流程规范,你要实现我这个协议,就得按照我的规范来,和java中jdbc一样,sun公司负责规定一套规范,提供一套接口,下游的用户自行负责实现,你具体怎么实现我不关心,你要做的就是实现我规定的一套规范。

    OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。OAuth强调的是授权,当然要想获取授权前你必须认证,就像第一节说的,用户只有登录后才能获得对应用户的权限,这一步是无法绕开的。

    3、SSO实现

    单点登录实现中,系统之间的协议对接是非常重要的一环,一般涉及的标准协议类型有 CAS、OAuth、OpenID Connect、SAML。

    另外国产的SA-TOKEN项目也有sso和oauth实现

    3.1 CAS

    Central Authentication Service简称CAS,是一种常见的B/S架构的SSO协议。和其他任何SSO协议一样,用户仅需登陆一次,访问其他应用则无需再次登陆。顾名思义,CAS是一种仅用于Authentication的服务,它和OAuth/OIDC协议不一样,并不能作为一种Authorization的协议。当前CAS协议包括CAS 1.0、CAS2.0、CAS3.0版本,这三个版本的认证流程基本类似。CAS的认证流程通过包括几部分参与者:

    Client: 通常为使用浏览器的用户 CAS Client: 实现CAS协议的Web应用 CAS Server: 作为统一认证的CAS服务器

    4、各种协议对比

    单纯的单点登录,其实无需授权中心有一套自己的用户和权限,单点登录是只管登录,登录后从业务系统自行获取自己的权限,所以从这个角度来说sso也有它自己的优势,它不像oauth那样还要维护一套用户权限系统,如果仅仅作单点登录不用权限的话用cas显然更有优势。

    以下图片是各种实现的对比图来自知乎open in new window,我并不赞同他的观点,仅作参考 20230104171826

    以下对比来自sa-token官网open in new window

    功能点SSO单点登录OAuth2.0
    统一认证支持度高支持度高
    统一注销支持度高支持度低
    多个系统会话一致性强一致弱一致
    第三方应用授权管理不支持支持度高
    自有系统授权管理支持度高支持度低
    Client级的权限校验不支持支持度高
    集成简易度比较简单难度中等
    + diff --git a/java/framework/security/SecurityFilterChain.html b/java/framework/security/SecurityFilterChain.html index 7aabb81df3..97d3671621 100644 --- a/java/framework/security/SecurityFilterChain.html +++ b/java/framework/security/SecurityFilterChain.html @@ -30,8 +30,8 @@ Security配置类 | ChenSino - - + +
    跳至主要內容

    Security配置类

    chensinosecurity大约 5 分钟

    1、配置类

    1. 5.7版本后Security把WebSecurityConfigurerAdapter标记为废弃,鼓励程序员使用SecurityFilterChain进行配置,如果看过官网Security的架构图对SecurityFilterChain一定不会陌生,此类是Security过滤器的核心,所以用它来配置寓意更为明显。
    @@ -229,7 +229,7 @@
                     .and()
                     .build();
         }
    -
    - +
    + diff --git a/java/framework/security/Session.html b/java/framework/security/Session.html index 39d36dec10..54fbc93c1e 100644 --- a/java/framework/security/Session.html +++ b/java/framework/security/Session.html @@ -30,8 +30,8 @@ Security中的Session | ChenSino - - + +
    跳至主要內容

    Security中的Session

    chenkunSecurity大约 2 分钟

    1、背景(问题复现)

    
    @@ -39,7 +39,7 @@
     2. 随便访问一个接口比如:http://localhost:8080/hello/aa,此时由于接口被security默认保护,会重定向到登录页面(如图一),此时查看sessionid(也就是name为JSESSIONID的cookie)是91E9629F748637154F86CCB44FB2B23D
     3. 然后输入用户名:user,密码:控制台随机生成的,登录后会重定向到之前访问的接口,但此时
        查看JSESSIONID发现变了,变成87957B71A3CEA4FA375CFFACA6AD425D
    -

    图一图二

    2、sessionid变化的原因

    Session Fixation Attack Protectionopen in new window

    3、源码查看

    Security的基本原理就是过滤器,这里不懂的请查看官方文档open in new window。当输入用户名和密码后,最终经过一系列的过滤器会到达UsernamePasswordAuthenticationFilter,此类会校验 用户名密码,然后校验通过会调用this.sessionStrategy.onAuthentication(authenticationResult, request, response);跟踪这里面的代码就会找到答案。

    时序图

    4、基于Session的认证

    了解Security认证原理的应该知道,如果不认证,就直接访问一个受保护的接口,会被重定向到默认登录页面,提示登录,登录成功后会重定向到之前访问的接口。 Security本身是一系列的过滤器在工作,其特有的Filter我们把他叫做SecurityFilter,第一次走到UsernamePasswordAuthenticationFilter时,认证成功 后最终会重定向到之前访问的接口,后面的过滤器不会执行了,我之前调试发现第一次登录执行到 UsernamePasswordAuthenticationFilter后,就直接跳转到接口处理逻辑了,没有继续执行后面的过滤器,跟踪发现UsernamePasswordAuthenticationFilter认证成功后有个方法,吧请求重定向了。

    20230103161747

    认证成功会会吧SecurityContext设置到Session中,所以后面在请求时从session获取的认证信息。

    - +

    图一图二

    2、sessionid变化的原因

    Session Fixation Attack Protectionopen in new window

    3、源码查看

    Security的基本原理就是过滤器,这里不懂的请查看官方文档open in new window。当输入用户名和密码后,最终经过一系列的过滤器会到达UsernamePasswordAuthenticationFilter,此类会校验 用户名密码,然后校验通过会调用this.sessionStrategy.onAuthentication(authenticationResult, request, response);跟踪这里面的代码就会找到答案。

    时序图

    4、基于Session的认证

    了解Security认证原理的应该知道,如果不认证,就直接访问一个受保护的接口,会被重定向到默认登录页面,提示登录,登录成功后会重定向到之前访问的接口。 Security本身是一系列的过滤器在工作,其特有的Filter我们把他叫做SecurityFilter,第一次走到UsernamePasswordAuthenticationFilter时,认证成功 后最终会重定向到之前访问的接口,后面的过滤器不会执行了,我之前调试发现第一次登录执行到 UsernamePasswordAuthenticationFilter后,就直接跳转到接口处理逻辑了,没有继续执行后面的过滤器,跟踪发现UsernamePasswordAuthenticationFilter认证成功后有个方法,吧请求重定向了。

    20230103161747

    认证成功会会吧SecurityContext设置到Session中,所以后面在请求时从session获取的认证信息。

    + diff --git a/java/framework/security/index.html b/java/framework/security/index.html index 5a1204c7c2..ba829a35aa 100644 --- a/java/framework/security/index.html +++ b/java/framework/security/index.html @@ -30,11 +30,11 @@ Security学习 | ChenSino - - + + - - + + diff --git a/java/framework/security/note.html b/java/framework/security/note.html index e4589a2473..f3accaae49 100644 --- a/java/framework/security/note.html +++ b/java/framework/security/note.html @@ -30,8 +30,8 @@ Security入门笔记 | ChenSino - - + +
    跳至主要內容

    Security入门笔记

    chenkun大约 4 分钟

    1、加密解密流程

    在项目中引入security依赖后,项目启动会自动生成一个账号密码,账号固定为user,密码为控制台随机生成的。账号密码可以自定义加解密方式,一般情况我们的账号密码都是放在数据库中,一个正常的业务流程是,先定义好密码加解密方式,加密方式需要实现PasswordEncoder接口,比如这里我用默认的加解密open in new window,然后在新增用户插入到数据库时,需要调用encode方法来加密密码。在请求后端接口时如果需要登陆权限,则会自动跳转到登陆页面,登陆接口会自动调用PasswordEncodermatches方法拿明文密码加密后和密文密码(根据用户名获取已经存到系统的密文)进行匹配,匹配成功则登陆通过。

    总结一下: 加密:是新建用户时调用加密方法,把密文写入数据库或者内存; 解密:登陆时,根据用户名从系统查找对应密文A,然后在把登陆时用户传递过来的明文密码使用相同加密方式加密得到密文B,把A和B对比。(这里体现了密文不可反向解密,只能正向加密,再做对比)

    public interface PasswordEncoder {
    @@ -106,7 +106,7 @@
     	//账号因为一些原因被管理员禁用,如果把enabled设置为true,security默认会提示用户已失效
     	private final boolean enabled;
     }
    -

    4、开启方法级别的权限控制

    @EnableGlobalMethodSecurity(prePostEnabled=true)在配置类要开启方法级别权限控制后,就可以使用@PreAuthorize, @PostAuthorize, @Secured等配合上el表达式,对方法进行鉴权。

    - +

    4、开启方法级别的权限控制

    @EnableGlobalMethodSecurity(prePostEnabled=true)在配置类要开启方法级别权限控制后,就可以使用@PreAuthorize, @PostAuthorize, @Secured等配合上el表达式,对方法进行鉴权。

    + diff --git a/java/framework/security/spring-security-oauth2-authorization-server.html b/java/framework/security/spring-security-oauth2-authorization-server.html index 6ae47a6102..b5cf3c42e7 100644 --- a/java/framework/security/spring-security-oauth2-authorization-server.html +++ b/java/framework/security/spring-security-oauth2-authorization-server.html @@ -30,8 +30,8 @@ spring-security-oauth2-authorization-server | ChenSino - - + +
    跳至主要內容

    spring-security-oauth2-authorization-server

    ChenSino原创大约 1 分钟

    介绍

    spring-security-oauth2已经被废弃,采用Security5.7 之后就用spring-security-oauth2-authorization-server,

    /oauth2/token

    在之前的老版本中请求token的端点是在一个叫做TokenEndpoint的类中,此类可以处理/oauth/token请求,这个类可看成是一个Controller,而在新版本中已经没有这个类,新版本中请求token的端点是/oauth2/token,我看源码没有找到对应的controller,直到我debug源码,才发现根本没有类似之前的专门处理/oauth/token的controller,其实新版本在Filter(实际是OAuth2TokenEndpointFilter)中就已经直接响应给客户端了, 20230509201806

    下面写个demo测试在filter中直接响应,新建一个springboot的web项目,添加一个filter,这个过滤器仅仅用来测试,所有请求都给他返回一个“hello world",我们请求任意路径发现确实如此,根本没有进入Controller,我甚至都没定义Controller,打破了我的惯性思维,潜意识认为只要有请求,最终都要进入Controller,谬矣。

    @Component
    @@ -41,7 +41,7 @@
             response.getWriter().write("hello world");
         }
     }
    -

    20230509201658

    - +

    20230509201658

    + diff --git a/java/framework/spring/Annotation.html b/java/framework/spring/Annotation.html index dd955b4a67..326750bf5f 100644 --- a/java/framework/spring/Annotation.html +++ b/java/framework/spring/Annotation.html @@ -30,8 +30,8 @@ 注解 | ChenSino - - + +
    跳至主要內容

    注解

    ChenSinoSpring大约 1 分钟

    1、依赖注入

     1. @Autowired
    @@ -51,7 +51,7 @@
         }
     }
     

    启动报错:

    Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Constructor threw exception; nested exception is java.lang.NullPointerException
    -

    要搞明白报错原因,需要对bean的生命周期有所了解,参考bean生命周期,Spring容器创建UserServiceImpl的实例正常过程如下:

    1. 反射调用构造方法,创建一个对象
    2. 使用反射给person赋值
    3. 调用BeanPostProcessor的前置方法
    4. 调用初始化方法
    5. BeanPostProcessor后置方法
    6. 得到对象

    实际上的过程是: 使用反射调用构造方法,构造方法执行this.company = person.getCompany();发现此时person是空,直接报空指针。

    - +

    要搞明白报错原因,需要对bean的生命周期有所了解,参考bean生命周期,Spring容器创建UserServiceImpl的实例正常过程如下:

    1. 反射调用构造方法,创建一个对象
    2. 使用反射给person赋值
    3. 调用BeanPostProcessor的前置方法
    4. 调用初始化方法
    5. BeanPostProcessor后置方法
    6. 得到对象

    实际上的过程是: 使用反射调用构造方法,构造方法执行this.company = person.getCompany();发现此时person是空,直接报空指针。

    + diff --git a/java/framework/spring/BeanPostProcessor.html b/java/framework/spring/BeanPostProcessor.html index f4a35d0ce0..a853d55c3b 100644 --- a/java/framework/spring/BeanPostProcessor.html +++ b/java/framework/spring/BeanPostProcessor.html @@ -30,8 +30,8 @@ BeanPostProcessor介绍 | ChenSino - - + + - +
    + diff --git a/java/framework/spring/CircularDependency.html b/java/framework/spring/CircularDependency.html index 090b877af2..0f6451605f 100644 --- a/java/framework/spring/CircularDependency.html +++ b/java/framework/spring/CircularDependency.html @@ -30,8 +30,8 @@ Spring循环依赖 | ChenSino - - + +
    跳至主要內容

    Spring循环依赖

    chensinoSpring大约 1 分钟

    1、循环依赖的产生

    A依赖B,B也依赖于A

    public class A{
    @@ -65,7 +65,7 @@
         2.4 B对象接着执行后续的逻辑,BeanPostProcessor前置处理——>初始化——>BeanPostProcessor后置处理等
         2.5 处理玩以后返回B的bean给A
     3. A拿到B的Bean后接着setter,然后继续执行后续逻辑,和上面B的过程一样
    -
    - +
    + diff --git a/java/framework/spring/DesignPatternInSpring.html b/java/framework/spring/DesignPatternInSpring.html index 85224f844d..57271074e5 100644 --- a/java/framework/spring/DesignPatternInSpring.html +++ b/java/framework/spring/DesignPatternInSpring.html @@ -30,8 +30,8 @@ Spring中的设计模式 | ChenSino - - + +
    跳至主要內容

    Spring中的设计模式

    ChenSino大约 2 分钟

    1、工厂模式

    BeanFactory是典型的工厂方法模式,其有多个实现,不同的实现有不同的getBean方法,默认实现是DefaultListalbeBeanFactory,我们也可以定义自己的工厂实现BeanFactory接口,重写里面的getBean方法

    2、单例模式

    在spring中使用singleton修饰的bean都是单例模式,org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton,在spring实例化出真正的对象后,会把这个对象加到容器中

    修正,以上说singleton=单例模式,是错误的,singleton指的是容器中该对象的bean只有一个,和单例模式不是一回事,单例模式有:Mybatis的连接工厂、redis的连接工厂等

    
    @@ -79,7 +79,7 @@
     	}
     
     
    -

    另外BeanDefinitionHolder也是包装了BeanDefinition,也是装饰者设计模式

    8、原型模式

    spring中prototype类型的bean采用的原型模式

    9、建造者模式

    各种框架中的builder基本都是建造者模式,一般建造这模式都是通过链式写法来设置字,并且设置完字段后返回Builder本身,最后调用build方法,最终生成一个我们需要的对象。比如SpringSecurity中AuthenticationManagerBuilder,源码如下:

    20221228145658

    - +

    另外BeanDefinitionHolder也是包装了BeanDefinition,也是装饰者设计模式

    8、原型模式

    spring中prototype类型的bean采用的原型模式

    9、建造者模式

    各种框架中的builder基本都是建造者模式,一般建造这模式都是通过链式写法来设置字,并且设置完字段后返回Builder本身,最后调用build方法,最终生成一个我们需要的对象。比如SpringSecurity中AuthenticationManagerBuilder,源码如下:

    20221228145658

    + diff --git a/java/framework/spring/OncePerRequestFilter.html b/java/framework/spring/OncePerRequestFilter.html index 2e38b89867..97c667f26b 100644 --- a/java/framework/spring/OncePerRequestFilter.html +++ b/java/framework/spring/OncePerRequestFilter.html @@ -30,11 +30,11 @@ OncePerRequestFilter | ChenSino - - + + -
    跳至主要內容

    OncePerRequestFilter

    chensino大约 1 分钟

    1、OncePerRequestFilter

    org.springframework.web.filter.OncePerRequestFilter是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次? 其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤。因为Springweb无法保证每个用户使用的Servlet容器是一样的,因此,在有些场景如果我们要确保同一个请求只能被过滤器处理一次,那就需要spring自己来实现了,因此有了这个类。其注释下写的很清楚,就是为了要陈any servlet container都确保只执行一次。

    20221103172449

    最开始被误导是因为看了这篇open in new window20221103173249 看完这篇,我就做实验想复现他说的这个,因为我用的springboot,内嵌的tomcat是9.x版本,对应的servlet是4.0,这个版本servlet不存在它说的问题,所以一直无法复现,导致我理解出现了偏差。后来看了别人博客,才知道怎么回事。

    2、参考

    Spring的OncePerRequestFilter过滤器open in new window
    Spring的OncePerRequestFilter的作用open in new window

    - +
    跳至主要內容

    OncePerRequestFilter

    chensino大约 1 分钟

    1、OncePerRequestFilter

    org.springframework.web.filter.OncePerRequestFilter是springweb中的一个过滤器,是为了确保一个请求只被过滤器执行一次。什么?难道一个请求还能被同一个过滤器执行多次? 其实是有这中可能的,在不同版本下的servlet下,过滤器的行为是不一样的。比如在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤。因为Springweb无法保证每个用户使用的Servlet容器是一样的,因此,在有些场景如果我们要确保同一个请求只能被过滤器处理一次,那就需要spring自己来实现了,因此有了这个类。其注释下写的很清楚,就是为了要陈any servlet container都确保只执行一次。

    20221103172449

    最开始被误导是因为看了这篇open in new window20221103173249 看完这篇,我就做实验想复现他说的这个,因为我用的springboot,内嵌的tomcat是9.x版本,对应的servlet是4.0,这个版本servlet不存在它说的问题,所以一直无法复现,导致我理解出现了偏差。后来看了别人博客,才知道怎么回事。

    2、参考

    Spring的OncePerRequestFilter过滤器open in new window
    Spring的OncePerRequestFilter的作用open in new window

    + diff --git a/java/framework/spring/SpringAOP.html b/java/framework/spring/SpringAOP.html index db42056a05..6a462f670c 100644 --- a/java/framework/spring/SpringAOP.html +++ b/java/framework/spring/SpringAOP.html @@ -30,8 +30,8 @@ SpringAOP | ChenSino - - + +
    跳至主要內容

    SpringAOP

    ChenSino大约 4 分钟

    1、SpringAOP

    > SpringAOP的本质就是动态代理,底层使用JDK动态代理或者CGlib动态代理,通过代理框架生成代理类,实现对目标类的增强,Spring代理是方法级别的代理,是对方法增强,
    @@ -199,7 +199,7 @@
             return new JdkDynamicAopProxy(config);
         }
     }
    -

    4.3 时序图

    - +

    4.3 时序图

    + diff --git a/java/framework/spring/SpringCache.html b/java/framework/spring/SpringCache.html index 56ca7d5a84..f248ca73cb 100644 --- a/java/framework/spring/SpringCache.html +++ b/java/framework/spring/SpringCache.html @@ -30,8 +30,8 @@ Spring缓存 | ChenSino - - + +
    跳至主要內容

    Spring缓存

    ChenSino大约 2 分钟

    Spring缓存大揭秘

    注意

    使用springboot测试springcache切记需要在启动类加上@EnableCache

    1、背景

    使用springboot项目时看到很多对spring缓存注解的使用,比如@Cacheable、@Evict、@CachePut等,之前一直都是知道个大概,使用方式也就局限于配置好redisTemplate然后直接开始使用,但是一直不明白为什么我配置好redisTemplate就可以用了,甚至不配置redis也能有缓存效果。之前还一直有个疑问,新增数据spring把返回结果缓存后,那么下次如果更新了数据,如何同时更新缓存列表呢?

    2、spring-cache介绍

    springcache不是一个单独的jar包,它位于spring-context的org.springframework.cache包下,spring提供了各种注解来使用缓存,并且提供了多种缓存实现,比如常见的redis,EhCache等。

    image-20211014105423666

    3、springcache的实现

    如果不引入redis等第三方包,则spring默认采用的是ConcurrentMapCache来管理缓存,它里面有个ConcurrentMap(具体实现是ConcurrentHashMap,在ConcurrentMapCacheManager类中传入的),如果引入第三方比如redis,则会自动使用redis

    image-20211014105857365

    image-20211014110150052

    在上图中可以看到ConcurrentMapCacheManager创建了一个ConcurrentHashMap对象,用来初始化ConcurrentMapCache,所以由此我们可以知道spring默认缓存实际上是一个Map对象,占用的是JVM内存,一旦创建后就永不消失,因为它不像redis有过期时间,所以使用默认缓存要慎重,注意OOM

    总结一下spring缓存默认实现:

    1. SimpleCacheConfiguration是一个springboot的配置类,类中会实例化一个ConcurrentMapCacheManager的Bean,
    @Configuration(proxyBeanMethods = false)
    @@ -57,7 +57,7 @@
     		SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
     		return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256), isAllowNullValues(), actualSerialization);
     	}
    -

    4、springcache的注解使用

    4.1 Cacheable注解

    此注解可以用于类或方法上,表示开启注解功能,开启后,方法的返回值会放到缓存里面

    - +

    4、springcache的注解使用

    4.1 Cacheable注解

    此注解可以用于类或方法上,表示开启注解功能,开启后,方法的返回值会放到缓存里面

    + diff --git a/java/framework/spring/SpringExtensionPoint.html b/java/framework/spring/SpringExtensionPoint.html index 0289693744..bc859286e6 100644 --- a/java/framework/spring/SpringExtensionPoint.html +++ b/java/framework/spring/SpringExtensionPoint.html @@ -30,8 +30,8 @@ Spring框架扩展点 | ChenSino - - + +
    跳至主要內容

    Spring框架扩展点

    ChenSino大约 4 分钟

    [TOC]

    1. spring生命周期

    spring容器实例化一个对象往大说主要是分为两步

    1.1 第一步:根据配置生成BeanDefinition

    根据配置文件(properties、xml)、注解等生成Bean的定义,BeanDefinition的作用是用来描述如何生成真正的对象,对象的生成是通过反射实现,这里的关键词是“描述”,它的作用是告诉容器如何去生成一个真正的对象,它不是真正的对象!!!!

    BeanDefinition里包含信息如下:

    1. class:有了bean的class用反射就可以创建出一个真正的对象;
    2. scope :可取singleton、prototype,即单例还是多例;
    3. lazyInit:表明此bean是否为懒加载,如果是true代表容器启动后自动创建出bean真实对象,若为false,则代表需要显示的调用容器的getBean()方法时才会实例化出对象;
    4. 其它字段不再介绍

    1.2 第二步:根据BeanDefinition来生成真正的对象

    image-20210526182642991

    2. 扩展点介绍

    2.1 扩展点1-BeanFactoryPostProcessor

    此接口在容器启动后,并且BeanDefinition已经注册到容器中以后,调用其回调函数,作用就是能拿到ConfigurableListableBeanFactory,然后操作里面的容器里面的BeanDefinition

    前面说了第一步是生成BeanDefinition此时真实的对象还未生成,所以可以用spring预留的扩展接口做一些事情,比如在代码中修改一个bean的BeanDefinition

    举例说明(此场景可能没啥卵用,只是为了证明可以在实例化出对象之前可以修改它的BeanDefinition):

    第一次测试:在xml随便配一个bean,bean标签有个lazyInit,默认是false

      <bean id="address" class="com.chen.bean.Address">
    @@ -87,7 +87,7 @@
             return bean;
         }
     }
    -

    通过以上代码修改address对象的属性后,再通过getBean方法获取对象,会发现对象的值是我们修改后的,而非xml中定义的那个值

    2.3 扩展点3-BeanNameAware

    在BeanPostProcessor之前执行,获取Bean的名字

    - +

    通过以上代码修改address对象的属性后,再通过getBean方法获取对象,会发现对象的值是我们修改后的,而非xml中定义的那个值

    2.3 扩展点3-BeanNameAware

    在BeanPostProcessor之前执行,获取Bean的名字

    + diff --git a/java/framework/spring/SpringIOC.html b/java/framework/spring/SpringIOC.html index 464883101c..2e4ce4bdc3 100644 --- a/java/framework/spring/SpringIOC.html +++ b/java/framework/spring/SpringIOC.html @@ -30,8 +30,8 @@ Spring IOC 容器源码分析 | ChenSino - - + +
    跳至主要內容

    Spring IOC 容器源码分析

    ChenSinoopen-source大约 62 分钟

    Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器。既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文并不能让你成为 Spring 专家,不过一定有助于大家理解 Spring 的很多概念,帮助大家排查应用中和 Spring 相关的一些问题。

    本文采用的源码版本是 4.3.11.RELEASE,算是 5.0.x 前比较新的版本了。为了降低难度,本文所说的所有的内容都是基于 xml 的配置的方式,实际使用已经很少人这么做了,至少不是纯 xml 配置,不过从理解源码的角度来看用这种方式来说无疑是最合适的。

    阅读建议:读者至少需要知道怎么配置 Spring,了解 Spring 中的各种概念,少部分内容我还假设读者使用过 SpringMVC。本文要说的 IOC 总体来说有两处地方最重要,一个是创建 Bean 容器,一个是初始化 Bean,如果读者觉得一次性看完本文压力有点大,那么可以按这个思路分两次消化。读者不一定对 Spring 容器的源码感兴趣,也许附录部分介绍的知识对读者有些许作用。

    希望通过本文可以让读者不惧怕阅读 Spring 源码,也希望大家能反馈表述错误或不合理的地方。

    引言

    先看下最基本的启动 Spring 容器的例子:

    public static void main(String[] args) {
    @@ -2012,7 +2012,7 @@
        Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
     
     }
    -

    看这个接口中的两个方法名字我们大体上可以猜测 bean 在初始化之前会执行 postProcessBeforeInitialization 这个方法,初始化完成之后会执行 postProcessAfterInitialization 这个方法。但是,这么理解是非常片面的。

    首先,我们要明白,除了我们自己定义的 BeanPostProcessor 实现外,Spring 容器在启动时自动给我们也加了几个。如在获取 BeanFactory 的 obtainFactory() 方法结束后的 prepareBeanFactory(factory),大家仔细看会发现,Spring 往容器中添加了这两个 BeanPostProcessor:ApplicationContextAwareProcessor、ApplicationListenerDetector。

    我们回到这个接口本身,读者请看第一个方法,这个方法接受的第一个参数是 bean 实例,第二个参数是 bean 的名字,重点在返回值将会作为新的 bean 实例,所以,没事的话这里不能随便返回个 null。

    那意味着什么呢?我们很容易想到的就是,我们这里可以对一些我们想要修饰的 bean 实例做一些事情。但是对于 Spring 框架来说,它会决定是不是要在这个方法中返回 bean 实例的代理,这样就有更大的想象空间了。

    最后,我们说说如果我们自己定义一个 bean 实现 BeanPostProcessor 的话,它的执行时机是什么时候?

    如果仔细看了代码分析的话,其实很容易知道了,在 bean 实例化完成、属性注入完成之后,会执行回调方法,具体请参见类 AbstractAutowireCapableBeanFactory#initBean 方法。

    首先会回调几个实现了 Aware 接口的 bean,然后就开始回调 BeanPostProcessor 的 postProcessBeforeInitialization 方法,之后是回调 init-method,然后再回调 BeanPostProcessor 的 postProcessAfterInitialization 方法。

    总结

    按理说,总结应该写在附录前面,我就不讲究了。

    在花了那么多时间后,这篇文章终于算是基本写完了,大家在惊叹 Spring 给我们做了那么多的事的时候,应该透过现象看本质,去理解 Spring 写得好的地方,去理解它的设计思想。

    本文的缺陷在于对 Spring 预初始化 singleton beans 的过程分析不够,主要是代码量真的比较大,分支旁路众多。同时,虽然附录条目不少,但是庞大的 Spring 真的引出了很多的概念,希望日后有精力可以慢慢补充一些。

    (全文完)

    - +

    看这个接口中的两个方法名字我们大体上可以猜测 bean 在初始化之前会执行 postProcessBeforeInitialization 这个方法,初始化完成之后会执行 postProcessAfterInitialization 这个方法。但是,这么理解是非常片面的。

    首先,我们要明白,除了我们自己定义的 BeanPostProcessor 实现外,Spring 容器在启动时自动给我们也加了几个。如在获取 BeanFactory 的 obtainFactory() 方法结束后的 prepareBeanFactory(factory),大家仔细看会发现,Spring 往容器中添加了这两个 BeanPostProcessor:ApplicationContextAwareProcessor、ApplicationListenerDetector。

    我们回到这个接口本身,读者请看第一个方法,这个方法接受的第一个参数是 bean 实例,第二个参数是 bean 的名字,重点在返回值将会作为新的 bean 实例,所以,没事的话这里不能随便返回个 null。

    那意味着什么呢?我们很容易想到的就是,我们这里可以对一些我们想要修饰的 bean 实例做一些事情。但是对于 Spring 框架来说,它会决定是不是要在这个方法中返回 bean 实例的代理,这样就有更大的想象空间了。

    最后,我们说说如果我们自己定义一个 bean 实现 BeanPostProcessor 的话,它的执行时机是什么时候?

    如果仔细看了代码分析的话,其实很容易知道了,在 bean 实例化完成、属性注入完成之后,会执行回调方法,具体请参见类 AbstractAutowireCapableBeanFactory#initBean 方法。

    首先会回调几个实现了 Aware 接口的 bean,然后就开始回调 BeanPostProcessor 的 postProcessBeforeInitialization 方法,之后是回调 init-method,然后再回调 BeanPostProcessor 的 postProcessAfterInitialization 方法。

    总结

    按理说,总结应该写在附录前面,我就不讲究了。

    在花了那么多时间后,这篇文章终于算是基本写完了,大家在惊叹 Spring 给我们做了那么多的事的时候,应该透过现象看本质,去理解 Spring 写得好的地方,去理解它的设计思想。

    本文的缺陷在于对 Spring 预初始化 singleton beans 的过程分析不够,主要是代码量真的比较大,分支旁路众多。同时,虽然附录条目不少,但是庞大的 Spring 真的引出了很多的概念,希望日后有精力可以慢慢补充一些。

    (全文完)

    + diff --git a/java/framework/spring/SpringMVC.html b/java/framework/spring/SpringMVC.html index b467c46035..3b09729e41 100644 --- a/java/framework/spring/SpringMVC.html +++ b/java/framework/spring/SpringMVC.html @@ -30,8 +30,8 @@ SpringMVC基本原理 | ChenSino - - + +
    跳至主要內容

    SpringMVC基本原理

    ChenSino原创大约 4 分钟

    SpringMVC处理请求的流程

    20230625180034

    前端控制器源码

    
    @@ -164,7 +164,7 @@
     # HandlerMapping为什么有多个?
     
     Spring MVC中有多个HandlerMapping是为了支持不同的URL映射策略。每个HandlerMapping实现都有自己的URL映射策略,可以根据具体的需求选择使用不同的HandlerMapping实现。例如,BeanNameUrlHandlerMapping将请求URL映射到bean名称,这个bean名称就是处理请求的controller类名;而RequestMappingHandlerMapping将请求URL映射到带有@RequestMapping注解的方法。因此,Spring MVC中有多个HandlerMapping是为了提供更灵活的URL映射策略,以满足不同的需求。希望这能回答你的问题。
    -
    - +
    + diff --git a/java/framework/spring/SpringSourceAnalize.html b/java/framework/spring/SpringSourceAnalize.html index 47dd2753a9..f57dee09e2 100644 --- a/java/framework/spring/SpringSourceAnalize.html +++ b/java/framework/spring/SpringSourceAnalize.html @@ -30,11 +30,11 @@ SpringIOC源码分析 | ChenSino - - + + - - + + diff --git a/java/framework/spring/Validator.html b/java/framework/spring/Validator.html index fbcd26874b..3fcae53b22 100644 --- a/java/framework/spring/Validator.html +++ b/java/framework/spring/Validator.html @@ -30,8 +30,8 @@ 自定义validator分组检验 | ChenSino - - + + - +
    + diff --git a/java/framework/spring/index.html b/java/framework/spring/index.html index 67df9cb414..4786bef7d0 100644 --- a/java/framework/spring/index.html +++ b/java/framework/spring/index.html @@ -30,11 +30,11 @@ Spring | ChenSino - - + + - - + + diff --git a/java/framework/springboot/AOPLog.html b/java/framework/springboot/AOPLog.html index accd54570c..cf753e1eac 100644 --- a/java/framework/springboot/AOPLog.html +++ b/java/framework/springboot/AOPLog.html @@ -30,8 +30,8 @@ 使用aop记录请求log | ChenSino - - + +
    跳至主要內容

    使用aop记录请求log

    ChenSino大约 2 分钟

    1. 博客背景

    最近业务提了一个需求,让记录每个用户的每个操作请求到数据库,保证每个操作都可追溯,这个需求很典型,实现起来也不难,一个自定义注解就搞定了。

    2. 实现

    实现思路比较简单,采用AOP,先自定义一个注解,在需要记录的地方就使用注解

    2.1 自定义注解SysLog
    @Target(ElementType.METHOD)
    @@ -191,7 +191,7 @@
     
     }
     
    -
    - +
    + diff --git a/java/framework/springboot/CollectionInject.html b/java/framework/springboot/CollectionInject.html index 32f0d979d7..b393c61949 100644 --- a/java/framework/springboot/CollectionInject.html +++ b/java/framework/springboot/CollectionInject.html @@ -30,8 +30,8 @@ 注入集合 | ChenSino - - + +
    跳至主要內容

    注入集合

    ChenSinoSpring大约 1 分钟

    1、测试

    加入有以下代码,~MyProcessor~是一个接口,没有提供任何实现,然后启动容器会发现执行Bean1的构造方法时并不会空指针,容器会自动提供一个Collection的实现类~LinkedHashMap$LinkedValues~,那么容器如何注入我自己的MyProcessor呢?

    @Component
    @@ -79,7 +79,7 @@
     public class MyProcessorImpl2 implements MyProcessor{
     }
     
    -
    - +
    + diff --git a/java/framework/springboot/Http2.html b/java/framework/springboot/Http2.html index ff149a72de..4e30131815 100644 --- a/java/framework/springboot/Http2.html +++ b/java/framework/springboot/Http2.html @@ -30,11 +30,11 @@ springboot开启http2.0 | ChenSino - - + + - - + + diff --git a/java/framework/springboot/SpringBootAutoConfiguration.html b/java/framework/springboot/SpringBootAutoConfiguration.html index 1086af2e5d..f051fcf6eb 100644 --- a/java/framework/springboot/SpringBootAutoConfiguration.html +++ b/java/framework/springboot/SpringBootAutoConfiguration.html @@ -30,8 +30,8 @@ springboot自动配置原理 | ChenSino - - + +
    跳至主要內容

    springboot自动配置原理

    chenkun大约 9 分钟

    1. springboot自动配置的原理初探

    ​以下注解都在springboot的自动化配置包中:spring-boot-autoconfigure

    1. springboot程序的入口是在启动类,该类有个关键注解SpringBootApplication

      @Target(ElementType.TYPE)
      @@ -239,7 +239,7 @@
       	}
       
       }
      -

      接下来我们在org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry方法打上断点准备debug,因springboot项目启动时会自动调用它,至于为什么,以后再研究。

      先说一下debug时方法调用链(只列举关键方法),大家先在调用这些方法的位置打好断点:

      org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry

      org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.ConfigurationClassFilter#filter

      org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match

      org.springframework.boot.autoconfigure.condition.OnBeanCondition#getOutcomes

      org.springframework.boot.autoconfigure.condition.OnBeanCondition#getOutcome

    - +

    接下来我们在org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry方法打上断点准备debug,因springboot项目启动时会自动调用它,至于为什么,以后再研究。

    先说一下debug时方法调用链(只列举关键方法),大家先在调用这些方法的位置打好断点:

    org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry

    org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.ConfigurationClassFilter#filter

    org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition#match

    org.springframework.boot.autoconfigure.condition.OnBeanCondition#getOutcomes

    org.springframework.boot.autoconfigure.condition.OnBeanCondition#getOutcome

    + diff --git a/java/framework/springboot/Swagger.html b/java/framework/springboot/Swagger.html index 454c97353b..688bd350f1 100644 --- a/java/framework/springboot/Swagger.html +++ b/java/framework/springboot/Swagger.html @@ -30,8 +30,8 @@ Swagger | ChenSino - - + +
    跳至主要內容

    Swagger

    ChenSino原创大约 2 分钟

    Swagger和SpringFox关系

    java项目在引入swagger的时候,一般会引入如下依赖,当时只知道照着博客抄,也有好奇为啥叫springfox,怎么没有swagger,不知你是否有同样的疑问?直到今天问了一下GPT

        <dependency>
    @@ -48,7 +48,7 @@
         而 Swagger 是一套规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。它包括一些工具,可以帮助开发人员设计、构建、编写文档和消费 RESTful APIs。
         Springfox 是基于 Swagger 规范的产物,他是在 Swagger 的基础上实现了对 Spring 等相关框架的集成,使得我们可以更方便地在 Spring 应用中使用 Swagger 来生成 API 文档,因此间接实现了无状态的 API 设计与文档生成。Springfox 让生成和维护 API 文档变得简单,只需要加上几个注解,即可自动生成 Swagger API 文档。
         总结来说,Swagger为一种API文档规范和一套API开发框架;而Springfox是为了更方便Spring Boot集成Swagger而存在的一个开源库。两者的关系可以理解为Springfox是基于Swagger框架和规范的一种工具。
    -

    从以上回到明显可看出,springfox和swagger是两个东西,springfox不能代表swagger,其实也好理解,因为swagger不是java专有的,比如php也有swagger,总不能让php也用springfox吧。 还有一点,springfox官方已经放弃了维护,转为了springdoc,新项目使用springdoc。以下截图也可以看到springfox依赖于swagger。

    20230509092040

    - +

    从以上回到明显可看出,springfox和swagger是两个东西,springfox不能代表swagger,其实也好理解,因为swagger不是java专有的,比如php也有swagger,总不能让php也用springfox吧。 还有一点,springfox官方已经放弃了维护,转为了springdoc,新项目使用springdoc。以下截图也可以看到springfox依赖于swagger。

    20230509092040

    + diff --git a/java/framework/springboot/index.html b/java/framework/springboot/index.html index 2d560f9e7c..485ece4a62 100644 --- a/java/framework/springboot/index.html +++ b/java/framework/springboot/index.html @@ -30,11 +30,11 @@ SpringBoot | ChenSino - - + + - - + + diff --git "a/java/framework/springboot/\351\203\250\347\275\262.html" "b/java/framework/springboot/\351\203\250\347\275\262.html" index a32db7f8d0..50cfd6fa9e 100644 --- "a/java/framework/springboot/\351\203\250\347\275\262.html" +++ "b/java/framework/springboot/\351\203\250\347\275\262.html" @@ -30,11 +30,11 @@ springboot部署 | ChenSino - - + + - - + + diff --git a/java/framework/springcloud/SpringCloudGateway.html b/java/framework/springcloud/SpringCloudGateway.html index 3e1c4b1c9f..37ca44b7df 100644 --- a/java/framework/springcloud/SpringCloudGateway.html +++ b/java/framework/springcloud/SpringCloudGateway.html @@ -30,8 +30,8 @@ 网关路由失效 | ChenSino - - + +
    跳至主要內容

    网关路由失效

    ChenSino原创大约 2 分钟

    问题描述

    有一个业务服务,启动了两个做成负载均衡,分别为10.6.6.11:2221,10.6.6.11:5221,为了调试,所以把route修改为只路由到5221,但是网关服务配置好route后,发送请求无法路由到指定的10.6.6.11:5221服务,一直路由到另一个服务10.6.6.11:2221上,并且连自定义的gatewayfilter都失效了

    请求路径为:http://gateway:port/mcs/test 配置如下

    spring:
    @@ -52,7 +52,7 @@
                 - Path=/mcs/**
               filters:
                 - StripPrefix=1
    -

    原因

    还是对gateway不够熟悉,其实是因为配置了spring.cloud.gateway.locator.enable=true,这个配置会开启服务自动发现,从而实现动态路由。意思就是当你开启了这个配置,就会自动去到你的请求路径去找服务名,这里我的服务名就是mcs,当请求http://gateway:port/mcs/test,发现有mcs,就会自动把这个请求路由到服务名为mcs的服务上(去注册中心找这个服务),如果找不到这个服务,那么就会继续从下面配置的route中去看看有没有匹配的。所以这里可以在nacos手动把msc下线,你就会发现下面的route生效了,可以路由到5221了。

    动态路由:
    动态路由说白了就是每新注册一个服务,都可以通过http://gateway:port/serviceName/xxx去访问,而无需手动配置路由,这种情况在大多数情况是很方便,但是遇到需要自定义路由就非常不好,比如我们上面需要把所有请求都路由到5221.。

    针对本次问题的解决方法

    方法1

    把动态路由配置关闭(默认就是关闭)

    方法2

    到nacos把服务下线,route中的路由就会生效,当然uri只能用具体ip的形式了,不能使用lb://servicename这种负载均衡写法了

    - +

    原因

    还是对gateway不够熟悉,其实是因为配置了spring.cloud.gateway.locator.enable=true,这个配置会开启服务自动发现,从而实现动态路由。意思就是当你开启了这个配置,就会自动去到你的请求路径去找服务名,这里我的服务名就是mcs,当请求http://gateway:port/mcs/test,发现有mcs,就会自动把这个请求路由到服务名为mcs的服务上(去注册中心找这个服务),如果找不到这个服务,那么就会继续从下面配置的route中去看看有没有匹配的。所以这里可以在nacos手动把msc下线,你就会发现下面的route生效了,可以路由到5221了。

    动态路由:
    动态路由说白了就是每新注册一个服务,都可以通过http://gateway:port/serviceName/xxx去访问,而无需手动配置路由,这种情况在大多数情况是很方便,但是遇到需要自定义路由就非常不好,比如我们上面需要把所有请求都路由到5221.。

    针对本次问题的解决方法

    方法1

    把动态路由配置关闭(默认就是关闭)

    方法2

    到nacos把服务下线,route中的路由就会生效,当然uri只能用具体ip的形式了,不能使用lb://servicename这种负载均衡写法了

    + diff --git a/java/framework/springcloud/index.html b/java/framework/springcloud/index.html index b43a6d14d8..8b440843ca 100644 --- a/java/framework/springcloud/index.html +++ b/java/framework/springcloud/index.html @@ -30,11 +30,11 @@ Springcloud | ChenSino - - + + - - + + diff --git a/java/index.html b/java/index.html index b666df96d5..49e971e164 100644 --- a/java/index.html +++ b/java/index.html @@ -30,11 +30,11 @@ Java入门到放弃 | ChenSino - - + + - - + + diff --git a/java/jvm/ClassLoader.html b/java/jvm/ClassLoader.html index 2c4e65f981..845d8fa9fc 100644 --- a/java/jvm/ClassLoader.html +++ b/java/jvm/ClassLoader.html @@ -30,8 +30,8 @@ 自定义类加载器 | ChenSino - - + + - +
    + diff --git a/java/jvm/MemoryModel.html b/java/jvm/MemoryModel.html index 35a04b88f9..5498e82e26 100644 --- a/java/jvm/MemoryModel.html +++ b/java/jvm/MemoryModel.html @@ -30,8 +30,8 @@ 内存模型 | ChenSino - - + +
    跳至主要內容

    内存模型

    ChenSino大约 2 分钟

    环境jdk8

    1、元空间

    jdk8中用元空间取代了原来的方法区,元空间是没有上限,只要系统有可用内存那么jvm就能一直申请,那么元空间里放的是啥?比如类元信息,就是类加载器加载的类,就放在元空间。

    以下进行测试,测试之前,需要了解一个知识点,两个class相同的前提是同一个类加载器加载的同一个类,这样得到的才是同一个Class.

    import java.io.IOException;
    @@ -124,7 +124,7 @@
     
     }
     
    -

    启动主程序,使用visualvm监视,可以看到Metaspace一直在线性增长

    20230131181628

    - +

    启动主程序,使用visualvm监视,可以看到Metaspace一直在线性增长

    20230131181628

    + diff --git a/java/jvm/NewObject.html b/java/jvm/NewObject.html index 23993239fa..a939b9e8bc 100644 --- a/java/jvm/NewObject.html +++ b/java/jvm/NewObject.html @@ -30,8 +30,8 @@ 对象创建过程 | ChenSino - - + + - +
    + diff --git a/java/jvm/ObjectReference.html b/java/jvm/ObjectReference.html index c599238edb..0e8658fecd 100644 --- a/java/jvm/ObjectReference.html +++ b/java/jvm/ObjectReference.html @@ -30,8 +30,8 @@ 对象引用类型 | ChenSino - - + +
    跳至主要內容

    对象引用类型

    ChenSino大约 2 分钟

    1、强引用

    StrongReference,java中默认的引用类型都是强引用,比如`Objectg obj = new Object()`,这个obj就是强引用。强引用的特性是只要引用存在,被引用的对象就不会被垃圾回收器回收,这个比较简单,不做测试。
    @@ -58,7 +58,7 @@
             //再创建一个大对象,让老年代内存不够容纳此对象从而触发垃圾回收
             byte[] DATA_15M = new byte[15 * 1024 * 1024];
         }
    -

    下图可以清晰看到,最终老年代有75%占用,差不多刚好15m,就是后来创建的大对象,说明之前那个被回收了 20230131112554

    3、弱引用

    WeakReference,下次垃圾回收时,弱引用的对象一定会被回收

    4、虚引用

    - +

    下图可以清晰看到,最终老年代有75%占用,差不多刚好15m,就是后来创建的大对象,说明之前那个被回收了 20230131112554

    3、弱引用

    WeakReference,下次垃圾回收时,弱引用的对象一定会被回收

    4、虚引用

    + diff --git a/java/jvm/SetObjectNull.html b/java/jvm/SetObjectNull.html index 6a27d7d424..64a5d4c3e1 100644 --- a/java/jvm/SetObjectNull.html +++ b/java/jvm/SetObjectNull.html @@ -30,8 +30,8 @@ 给对象设置null的意义 | ChenSino - - + +
    跳至主要內容

    给对象设置null的意义

    ChenSinojavajvm大约 4 分钟

    前言:是否需要把不用的对象设置为null?

    1、开始写代码测试(所有测试都要加上以下指令)

    jvm参数-Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=2097152

    简单解释一下:

    • -Xms20m -Xmx20m这两个指令限制堆内存固定为20m不允许扩容
    • -Xmn10m代表分配给新生代的总内存为10m
    • -XX:SurvivorRatio=8代表Eden区和Survivor的比例8:1,即新生代被分为3部分,分别8m,1m,1m
    • XX:PretenureSizeThreshold=2097152,这个指令用的比较少,在虚拟机中,普通对象都在新生代分配内存,但是大对象是直接在老年代分配,至于多大算大对象,就是这个参数来设置的,我设置的是2m用来测试(2097152 =2 * 1024 * 1024 ),设置2m是方便我测试,保证我在下面代码设置1m的MB_1对象,内存是在新生代分配,而不是直接进入老年代
    • -XX:+PrintGCDetails打印垃圾回收日志

    1.1 第一次测试,直接创建一个512kb的数组,调用回收

      public static void main(String[] args) throws InterruptedException {
    @@ -67,7 +67,7 @@
             //模拟一些耗时代码
             xxxxxx
         }
    -
  • 栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。

    在第三次测试中我们用代码块包围了KB_512,来限制其作用于,我们发现也能达到在main方法执行前就释放内存的目的。原因就是KB_512这个变量的作用于限制在代码块,代码快执行完后,就用不上它了,那么它在栈帧中的局部变量那个地址是可被别的局部变量复用,即temp复用了。当temp把引用指向new byte[0];时,原来的new byte[1 * 1024 * 512];就没有变量去引用它了,所以它可以被回收。

  • 3、参考

    jvm内存结构open in new window

    - +
  • 栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。

    在第三次测试中我们用代码块包围了KB_512,来限制其作用于,我们发现也能达到在main方法执行前就释放内存的目的。原因就是KB_512这个变量的作用于限制在代码块,代码快执行完后,就用不上它了,那么它在栈帧中的局部变量那个地址是可被别的局部变量复用,即temp复用了。当temp把引用指向new byte[0];时,原来的new byte[1 * 1024 * 512];就没有变量去引用它了,所以它可以被回收。

  • 3、参考

    jvm内存结构open in new window

    + diff --git a/java/jvm/StringAdd.html b/java/jvm/StringAdd.html index 1328f964b5..3590560159 100644 --- a/java/jvm/StringAdd.html +++ b/java/jvm/StringAdd.html @@ -30,8 +30,8 @@ 通过反汇编来看String的拼接 | ChenSino - - + +
    跳至主要內容

    通过反汇编来看String的拼接

    ChenSinojavajvm字节码反汇编大约 3 分钟

    1、先看问题,以下结果是什么?

    String s1 = "Hello";
    @@ -117,7 +117,7 @@
            3: return
     }
     
    -

    为了验证以上说的不是信口开河,我们找到JvmTest.class文件,用十六进制编辑器打开,如下,可以清楚的看到有一个拼接好的字符窜hello

    在这里插入图片描述

    - +

    为了验证以上说的不是信口开河,我们找到JvmTest.class文件,用十六进制编辑器打开,如下,可以清楚的看到有一个拼接好的字符窜hello

    在这里插入图片描述

    + diff --git a/java/jvm/index.html b/java/jvm/index.html index 496c837f8e..928f0eca14 100644 --- a/java/jvm/index.html +++ b/java/jvm/index.html @@ -30,11 +30,11 @@ JVM | ChenSino - - + + - - + + diff --git a/java/jvm/volatile.html b/java/jvm/volatile.html index c3a963770a..ade50a3d4e 100644 --- a/java/jvm/volatile.html +++ b/java/jvm/volatile.html @@ -30,8 +30,8 @@ volatile关键字 | ChenSino - - + + - +

    4、参考

    Java并发编程之volatile关键字open in new window

    + diff --git "a/java/jvm/\345\220\215\350\257\215\350\247\243\351\207\212.html" "b/java/jvm/\345\220\215\350\257\215\350\247\243\351\207\212.html" index 62a93b39ca..31f45aa59d 100644 --- "a/java/jvm/\345\220\215\350\257\215\350\247\243\351\207\212.html" +++ "b/java/jvm/\345\220\215\350\257\215\350\247\243\351\207\212.html" @@ -30,11 +30,11 @@ 名词解释 | ChenSino - - + + -
    跳至主要內容

    名词解释

    chenkun大约 2 分钟

    vm中说的字面量和符号引用是什么

    在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。

    字面量(Literal)

    字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。

    • 整数字面量: 100, -50
    • 浮点数字面量: 3.14, -0.001
    • 字符字面量: 'A', 'z'
    • 字符串字面量: "Hello, World!", "Java"

    在Java编译后的字节码中,字面量通常被存储在常量池(constant pool)中,并且在运行时直接使用这些值。

    符号引用(Symbolic Reference)

    符号引用是在Java编译过程中生成的一种引用,它表示的是某个类、接口、方法或字段的符号名称,而不是直接的内存地址或值。在编译时,Java编译器并不直接解析符号引用,而是将这些符号引用保留在字节码中,直到程序运行时才解析。

    常见的符号引用有:

    • 类或接口的全限定名: java/lang/String

    • 字段名称: System.out

    • 方法名称和描述符: java/io/PrintStream.println:(Ljava/lang/String;)V

    • 这些符号引用在运行时通过类加载器和链接器解析为实际的内存地址或对象引用。

    区别与联系

    • 字面量直接对应实际的数据值,在运行时不需要进一步解析。
    • 符号引用是对类、方法或字段的间接引用,在运行时需要解析以获取实际的内存地址或对象引用。

    总结来说,字面量是编译时直接确定的常量值,而符号引用则是编译时生成的一种间接引用,需在运行时解析以获取具体的内存地址或实例。

    - +
    跳至主要內容

    名词解释

    chenkun大约 2 分钟

    vm中说的字面量和符号引用是什么

    在Java虚拟机(JVM)中,字面量和符号引用是与Java程序的编译和执行密切相关的两个概念。它们在Java类文件中表示变量、方法、常量等的不同方式。

    字面量(Literal)

    字面量是程序代码中直接书写的固定值,比如整数、浮点数、字符、字符串等。它们在程序运行时直接表示实际的数据值。

    • 整数字面量: 100, -50
    • 浮点数字面量: 3.14, -0.001
    • 字符字面量: 'A', 'z'
    • 字符串字面量: "Hello, World!", "Java"

    在Java编译后的字节码中,字面量通常被存储在常量池(constant pool)中,并且在运行时直接使用这些值。

    符号引用(Symbolic Reference)

    符号引用是在Java编译过程中生成的一种引用,它表示的是某个类、接口、方法或字段的符号名称,而不是直接的内存地址或值。在编译时,Java编译器并不直接解析符号引用,而是将这些符号引用保留在字节码中,直到程序运行时才解析。

    常见的符号引用有:

    • 类或接口的全限定名: java/lang/String

    • 字段名称: System.out

    • 方法名称和描述符: java/io/PrintStream.println:(Ljava/lang/String;)V

    • 这些符号引用在运行时通过类加载器和链接器解析为实际的内存地址或对象引用。

    区别与联系

    • 字面量直接对应实际的数据值,在运行时不需要进一步解析。
    • 符号引用是对类、方法或字段的间接引用,在运行时需要解析以获取实际的内存地址或对象引用。

    总结来说,字面量是编译时直接确定的常量值,而符号引用则是编译时生成的一种间接引用,需在运行时解析以获取具体的内存地址或实例。

    + diff --git a/java/other/JdkVersion.html b/java/other/JdkVersion.html index e66c7f36a4..e519fe5b92 100644 --- a/java/other/JdkVersion.html +++ b/java/other/JdkVersion.html @@ -30,11 +30,11 @@ Jdk版本 | ChenSino - - + + -
    跳至主要內容

    Jdk版本

    chenkun大约 2 分钟

    开发者注意

    Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。

    1、Jdk11对比jdk1.8

    1.1 Java9

    1. 模块系统 模块是一个包的容器,Java 9 最大的变化之一是引入模块系统。(Jigsaw 项目) 作用:解决大型项目模块化开发的需求,更适合客户端软件,在打包时可以自定义依赖模块,根据所依赖的模块打包出一个包含jdk环境的软件,
      这样的好处是打包比较小,用户下载快,并且无需自己安装jdk环境。个人认为这项新特性对于web应用并不是那么重要,web应用无需考虑包的大小。

    2. 支持 HTTP/2 标准 HTTP/2 标准是 HTTP 协议的最新版本,新的 HTTPClient API 支持 Websocket 和 HTTP2 流以及服务器推送特性。

    3. 提供创建不可变集合的静态工厂方法 List、Set、Map 接口中,提供新的静态工厂方法直接创建不可变的集合实例。 作用:创建不可变集合更方便,一行代码就搞定,节省了开销。

    4. 私有接口方法 在接口中也允许编写 private 修饰的私有方法了。 作用:增强了接口的功能,提高了可扩展性。

    5. 轻量级的 JSON API 内置了一个轻量级的 JSON API。

    6. 引入响应式流 API Java 9 引入了新的响应式流 API。

    1.2 Java10

    1. 增加var类型推断

    1.3 Java11(LTS)

    1. HttpClient在java9引入,但是在java11进行优化升级

    - +
    跳至主要內容

    Jdk版本

    chenkun大约 2 分钟

    开发者注意

    Java版本现在6个月发布一个大版本,更新很快,但是对于web开发人员来说,代码层面其实新增的功能并不是那么多,开发人员只需要掌握几个主要的更新内容即可,重点关注LTS版本。

    1、Jdk11对比jdk1.8

    1.1 Java9

    1. 模块系统 模块是一个包的容器,Java 9 最大的变化之一是引入模块系统。(Jigsaw 项目) 作用:解决大型项目模块化开发的需求,更适合客户端软件,在打包时可以自定义依赖模块,根据所依赖的模块打包出一个包含jdk环境的软件,
      这样的好处是打包比较小,用户下载快,并且无需自己安装jdk环境。个人认为这项新特性对于web应用并不是那么重要,web应用无需考虑包的大小。

    2. 支持 HTTP/2 标准 HTTP/2 标准是 HTTP 协议的最新版本,新的 HTTPClient API 支持 Websocket 和 HTTP2 流以及服务器推送特性。

    3. 提供创建不可变集合的静态工厂方法 List、Set、Map 接口中,提供新的静态工厂方法直接创建不可变的集合实例。 作用:创建不可变集合更方便,一行代码就搞定,节省了开销。

    4. 私有接口方法 在接口中也允许编写 private 修饰的私有方法了。 作用:增强了接口的功能,提高了可扩展性。

    5. 轻量级的 JSON API 内置了一个轻量级的 JSON API。

    6. 引入响应式流 API Java 9 引入了新的响应式流 API。

    1.2 Java10

    1. 增加var类型推断

    1.3 Java11(LTS)

    1. HttpClient在java9引入,但是在java11进行优化升级

    + diff --git a/java/other/gradle/index.html b/java/other/gradle/index.html index 4fb7499c9c..4135d8c63b 100644 --- a/java/other/gradle/index.html +++ b/java/other/gradle/index.html @@ -30,11 +30,11 @@ Gradle | ChenSino - - + + - - + + diff --git a/java/other/gradle/wrapper.html b/java/other/gradle/wrapper.html index e80381ad48..c95de13a7b 100644 --- a/java/other/gradle/wrapper.html +++ b/java/other/gradle/wrapper.html @@ -30,11 +30,11 @@ Wrapper | ChenSino - - + + - - + + diff --git a/java/other/index.html b/java/other/index.html index 591f90808a..56e273e39a 100644 --- a/java/other/index.html +++ b/java/other/index.html @@ -30,11 +30,11 @@ 其他 | ChenSino - - + + - - + + diff --git a/java/other/locateproblem/TooManyOpenFiles.html b/java/other/locateproblem/TooManyOpenFiles.html index 9c06e6b7ad..91e032a45f 100644 --- a/java/other/locateproblem/TooManyOpenFiles.html +++ b/java/other/locateproblem/TooManyOpenFiles.html @@ -31,8 +31,8 @@ TooManyOpenFiles | ChenSino - - + +
    跳至主要內容

    TooManyOpenFiles

    chenkun @@ -65,8 +65,7 @@ -N 15: unlimited
    $ ulimit -n
     65535
    -

    根据ulimit -n得知系统设置的最大打开文件为65535,然后通过lsof|wc -l发现,打开的文件已经超过65535,所以报错Too many files open是正常的。 接下来使用jps -lv查看报错的java服务进程id,再使用lsof -p <pid>查看打开的文件都有哪些,通过查看发现有一个文件ip2region.db被打开几万次,然后 到代码查找此文件名,定位到代码,发现一段代码打开文件后未调用close方法导致程序运行到一段时间就抛出Too many files open异常。

    注意

    lsof查看到的结果数和实际并不一致,以上描述不正确,定位Too many open files问题仍然可按照上述思路找到被打开多次的文件,然后根据文件名去定位问题。

    - +

    根据ulimit -n得知系统设置的最大打开文件为65535,然后通过lsof|wc -l发现,打开的文件已经超过65535,所以报错Too many files open是正常的。 接下来使用jps -lv查看报错的java服务进程id,再使用lsof -p <pid>查看打开的文件都有哪些,通过查看发现有一个文件ip2region.db被打开几万次,然后 到代码查找此文件名,定位到代码,发现一段代码打开文件后未调用close方法导致程序运行到一段时间就抛出Too many files open异常。

    注意

    lsof查看到的结果数和实际并不一致,以上描述不正确,定位Too many open files问题仍然可按照上述思路找到被打开多次的文件,然后根据文件名去定位问题。

    + diff --git a/java/other/locateproblem/Undertow.xxxNotFount.html b/java/other/locateproblem/Undertow.xxxNotFount.html index c619a48ae4..fd721bc830 100644 --- a/java/other/locateproblem/Undertow.xxxNotFount.html +++ b/java/other/locateproblem/Undertow.xxxNotFount.html @@ -30,15 +30,15 @@ undertow.xxx not found | ChenSino - - + +
    跳至主要內容

    undertow.xxx not found

    ChenSino原创大约 2 分钟

    背景

    用户反馈上传图片失败,查看日志报错上传文件提示:java.nio.file.NoSuchFileException: /tmp/undertow.xxxx.xxxxx,解决方案,问题是这个问题之前用户就反馈过,之前是直接重启就好了,之后也没深究。最近用户又反馈不行了,所以花了点时间深究一下从根源解决问题。

    原因

    在 Linux 系统中,Spring Boot 应用以 java -jar 命令启动时,会在操作系统的 /tmp 目录下随机生成一个 tomcat(或 undertow )临时目录,上传的文件先要转换成临时文件保存在这个文件夹中。 由于临时 /tmp 目录下的文件,在长时间(默认10天)没有使用的情况下,操作系统会执行 tmp 目录清理服务(systemd-tmpfiles-clean.service),导致 /tmp/undertow.xxxx.xxxxxxx 文件被清理; 导致在上传文件时,java调用 Files.createFile(…) 在目录/tmp/undertow.xxxx.xxxxxxx下创建临时文件时,发现找不到目录,就会抛出以上的错误。

    解决方法

    方法一

    可以根据报错信息,新建 /tmp/undertow.xxxx.xxxxxxxx 目录,不影响用户正常使用。 执行 mkdir -p /tmp/undertow.8760.570269926767628882命令;

    方法二

    先创建好文件,配置问yml中

    spring:
       servlet:
         multipart:
           location: xxxx
    -

    方法三

    java -jar -Dspring.servlet.multipart.location=xxxx

    验证是否生效

    方式1

    使用以上几个方法发现创建的目录在程序启动后里面还是空白,是因为还没有上传文件当然就没有东西了,可以使用watch -n 1 'ls -l'来查看,方法如下:

    1. 先在对应路径下执行watch -n 1 'ls -l',每秒打印一次
    2. 上传文件
    3. 运气好会看到watch的命令会打印出一个tmp文件,但是会马上被删除

    方式2

    删除你新建的目录,上传文件,再次报之前的错误,说明我们的配置是生效的

    - +

    方法三

    java -jar -Dspring.servlet.multipart.location=xxxx

    验证是否生效

    方式1

    使用以上几个方法发现创建的目录在程序启动后里面还是空白,是因为还没有上传文件当然就没有东西了,可以使用watch -n 1 'ls -l'来查看,方法如下:

    1. 先在对应路径下执行watch -n 1 'ls -l',每秒打印一次
    2. 上传文件
    3. 运气好会看到watch的命令会打印出一个tmp文件,但是会马上被删除

    方式2

    删除你新建的目录,上传文件,再次报之前的错误,说明我们的配置是生效的

    + diff --git a/java/other/locateproblem/index.html b/java/other/locateproblem/index.html index bbf2a1c1a5..24b176e572 100644 --- a/java/other/locateproblem/index.html +++ b/java/other/locateproblem/index.html @@ -30,11 +30,11 @@ 线上问题定位 | ChenSino - - + + - - + + diff --git "a/java/other/locateproblem/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html" "b/java/other/locateproblem/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html" index 273ec786be..4fe143f58a 100644 --- "a/java/other/locateproblem/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html" +++ "b/java/other/locateproblem/\345\220\204\347\247\215\346\241\206\346\236\266\347\211\210\346\234\254\347\232\204\345\235\221.html" @@ -30,11 +30,11 @@ 各种版本的坑 | ChenSino - - + + -
    跳至主要內容

    各种版本的坑

    ChenSino原创大约 2 分钟

    1、Spring系列

    1.1 坑1 spring.factories使用方式变更

    2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,同时在此文件中写入类全限定名字,多个类,每行一个,不用符号隔开

    1.2 坑2 Spring Security OAuth2已被新版本security弃用

    Spring Security OAuth2是为构建OAuth2系统提供安全支持的Spring Security子项目。在Spring Security OAuth2中,您可以使用Authorization Server来创建和管理授权服务器,同时也可以使用Resource Server来保护受保护的资源。然而,从Spring Security 5.3开始,Spring Security OAuth2已被弃用,取而代之的是Spring Authorization Server和Spring Security 5的核心Oauth2支持。

    与Spring Security OAuth2相比,Spring Authorization Server是一个独立的模块,它提供了完整的授权服务器,无需其他模块或库的支持,可以轻松地与现有的Spring Security应用程序集成。此外,Spring Authorization Server不依赖于Spring Security的其他功能,它有自己独立的API和文档。

    同时,Spring Authorization Server在实现OAuth2规范方面更加严格,对于OAuth2规范中的各项要求都有很好的支持,并且提供了更加灵活和易于扩展的配置方式。在使用Spring Authorization Server时,您需要将spring-security-oauth2依赖关系修改为spring-security-oauth2-authorization-server。

    总之,如果您想在您的应用程序中实现OAuth2,并且正在使用Spring Security 5.3或更高版本,那么您应该使用Spring Authorization Server而不是Spring Security OAuth2。

    2、Oauth2.0

    2.1 移除password授权类型

    oauth2.0的最新规范已经移除了grant_type类型为password的,并且在spring-authorization-server 明确标记password类型为废弃,强行使用会报grant_type不对。因为新版本Security使用的是spring-authorization-server,所以也意味着在新版本security无法使用password授权类型 z

    - +
    跳至主要內容

    各种版本的坑

    ChenSino原创大约 2 分钟

    1、Spring系列

    1.1 坑1 spring.factories使用方式变更

    2.7版本已经不推荐使用spring.factories,在3.0废弃了,以前的spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,同时在此文件中写入类全限定名字,多个类,每行一个,不用符号隔开

    1.2 坑2 Spring Security OAuth2已被新版本security弃用

    Spring Security OAuth2是为构建OAuth2系统提供安全支持的Spring Security子项目。在Spring Security OAuth2中,您可以使用Authorization Server来创建和管理授权服务器,同时也可以使用Resource Server来保护受保护的资源。然而,从Spring Security 5.3开始,Spring Security OAuth2已被弃用,取而代之的是Spring Authorization Server和Spring Security 5的核心Oauth2支持。

    与Spring Security OAuth2相比,Spring Authorization Server是一个独立的模块,它提供了完整的授权服务器,无需其他模块或库的支持,可以轻松地与现有的Spring Security应用程序集成。此外,Spring Authorization Server不依赖于Spring Security的其他功能,它有自己独立的API和文档。

    同时,Spring Authorization Server在实现OAuth2规范方面更加严格,对于OAuth2规范中的各项要求都有很好的支持,并且提供了更加灵活和易于扩展的配置方式。在使用Spring Authorization Server时,您需要将spring-security-oauth2依赖关系修改为spring-security-oauth2-authorization-server。

    总之,如果您想在您的应用程序中实现OAuth2,并且正在使用Spring Security 5.3或更高版本,那么您应该使用Spring Authorization Server而不是Spring Security OAuth2。

    2、Oauth2.0

    2.1 移除password授权类型

    oauth2.0的最新规范已经移除了grant_type类型为password的,并且在spring-authorization-server 明确标记password类型为废弃,强行使用会报grant_type不对。因为新版本Security使用的是spring-authorization-server,所以也意味着在新版本security无法使用password授权类型 z

    + diff --git "a/java/other/locateproblem/\345\220\204\347\247\215\351\227\256\351\242\230.html" "b/java/other/locateproblem/\345\220\204\347\247\215\351\227\256\351\242\230.html" index c86ea4e412..f38fd79856 100644 --- "a/java/other/locateproblem/\345\220\204\347\247\215\351\227\256\351\242\230.html" +++ "b/java/other/locateproblem/\345\220\204\347\247\215\351\227\256\351\242\230.html" @@ -30,8 +30,8 @@ 开发中遇到的各种问题 | ChenSino - - + +
    跳至主要內容

    开发中遇到的各种问题

    ChenSino原创小于 1 分钟

    1. springcloudgateway负载均衡配置不生效

    配置如下,使用lb/serviceid配置负载均衡,一直报错,服务找不到

    spring:
    @@ -50,7 +50,7 @@
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-loadbalancer</artifactId>
     </dependency>
    -
    - +
    + diff --git a/java/other/log/index.html b/java/other/log/index.html index 16a33b775e..a59dae26e8 100644 --- a/java/other/log/index.html +++ b/java/other/log/index.html @@ -30,11 +30,11 @@ Log | ChenSino - - + + - - + + diff --git a/java/other/log/logback.html b/java/other/log/logback.html index 78b599e95b..5fbdf1b6ab 100644 --- a/java/other/log/logback.html +++ b/java/other/log/logback.html @@ -30,8 +30,8 @@ LogBack | ChenSino - - + +
    跳至主要內容

    LogBack

    ChenSino原创日志大约 4 分钟

    配置

    <?xml version="1.0" encoding="UTF-8"?>
    @@ -154,7 +154,7 @@
             <level>debug</level>
         </filter>
     </appender>
    -

    我们配置了一个logger,类名为com.example,级别为info,它引用了名为console的appender。在console这个appender中,我们定义了一个Filter,用于过滤掉级别为debug以外的日志事件。

    假如我们在代码中输出了一条debug级别的日志事件,因为logger的级别是info,所以这条日志事件会被过滤掉,不会经过appender和filter的处理而直接被忽略掉。如果代码中输出的日志级别是info,这条日志事件就会被appender处理,并且filter也会检查该条日志是否符合要求,最终决定是否输出。

    == 所以,并不是说在logger配置了一个info级别的日志,然后在代码打印一个info级别日志,就一定会输出这个日志,还要看最终输出时appender的日志级别,如果appender日志级别不满足照样不会输出

    - +

    我们配置了一个logger,类名为com.example,级别为info,它引用了名为console的appender。在console这个appender中,我们定义了一个Filter,用于过滤掉级别为debug以外的日志事件。

    假如我们在代码中输出了一条debug级别的日志事件,因为logger的级别是info,所以这条日志事件会被过滤掉,不会经过appender和filter的处理而直接被忽略掉。如果代码中输出的日志级别是info,这条日志事件就会被appender处理,并且filter也会检查该条日志是否符合要求,最终决定是否输出。

    == 所以,并不是说在logger配置了一个info级别的日志,然后在代码打印一个info级别日志,就一定会输出这个日志,还要看最终输出时appender的日志级别,如果appender日志级别不满足照样不会输出

    + diff --git "a/java/other/maven/build\346\240\207\347\255\276.html" "b/java/other/maven/build\346\240\207\347\255\276.html" index bf340ab2c4..d5f8d53416 100644 --- "a/java/other/maven/build\346\240\207\347\255\276.html" +++ "b/java/other/maven/build\346\240\207\347\255\276.html" @@ -30,8 +30,8 @@ build标签 | ChenSino - - + +
    跳至主要內容

    build标签

    ChenSino原创maven小于 1 分钟

    配置解释

    resource标签的操作是通过maven-resource-plugin插件来实现的,通过配置标签来实现资源文件的过滤替换。这个标签的作用是告诉Maven,在拷贝源文件到目标路径之前,对源文件内容进行参数替换。

        <build>
    @@ -66,7 +66,7 @@
             </resources>
         </build>
     
    -

    比如以下图中就是指定了targetPath的路径在项目下的一个叫做targetPath的目录,执行编译就可以看到确实把文件复制过去了

    20230608173626

    - +

    比如以下图中就是指定了targetPath的路径在项目下的一个叫做targetPath的目录,执行编译就可以看到确实把文件复制过去了

    20230608173626

    + diff --git a/java/other/maven/import.html b/java/other/maven/import.html index 643a0c0ba3..4dbf0557e7 100644 --- a/java/other/maven/import.html +++ b/java/other/maven/import.html @@ -30,8 +30,8 @@ import使用 | ChenSino - - + +
    跳至主要內容

    import使用

    chenkun大约 2 分钟

    1. import介绍

    官方介绍open in new window

    maven中的import是解决maven只能单个继承的问题,有时候我们的maven项目已经有了一个公司所自定义parent,但是我们想引入另一个项目中的dependencyManagement,此时可以用import,可以理解为把另一个项目中的dependencyManagement内容直接复制到本项目。

    2. 使用方式

    scope=import只能用在dependencyManagement并且type必须为pom,作用就是把另一个项目中dependencyManagement内容直接复制到本项目。以下就是一个最经典的使用方式,把springboot的版本管理直接引入本项目,在idea工具中可以直接点开看看里面管理了很多依赖,对每个依赖设置了version,以后在子项目引入依赖就不要version了。不仅仅springboot家族本身项目可以这样引入,包括其他依赖,只要在spring-boot-dependencies定义了的都可以无需版本引入,我在实际项目开发时有一次导入jedis就遇到过依赖问题,起初我是手动指定jedis版本,一直报错class undefine,后来去掉版本号,直接用spring-boot-dependencies中的就解决了问题。

    <dependencyManagement>
    @@ -69,7 +69,7 @@
             </plugins>
         </build>
     </project>
    -
    - +
    + diff --git a/java/other/maven/index.html b/java/other/maven/index.html index cb6f96ff31..4bbb236b43 100644 --- a/java/other/maven/index.html +++ b/java/other/maven/index.html @@ -30,11 +30,11 @@ maven | ChenSino - - + + - - + + diff --git a/java/other/maven/multiModule.html b/java/other/maven/multiModule.html index 6c4f5dd3f8..11262ea8c3 100644 --- a/java/other/maven/multiModule.html +++ b/java/other/maven/multiModule.html @@ -30,8 +30,8 @@ 多模块maven项目的搭建 | ChenSino - - + +
    跳至主要內容

    多模块maven项目的搭建

    chenkun小于 1 分钟

    1、一个真实的多模块maven项目

    $ tree -d -L 2
    @@ -77,7 +77,7 @@
         ├── ccs-weibao-api
         └── ccs-weibao-biz
     
    -

    说明:
    通用模块common是一个多模块聚合的,其中有个common-bom是专门负责管理整个项目jar包版本。其他的data和weibao等是业务模块,业务模块一般分为两个子模块聚合在一起,分别是api和biz

    - +

    说明:
    通用模块common是一个多模块聚合的,其中有个common-bom是专门负责管理整个项目jar包版本。其他的data和weibao等是业务模块,业务模块一般分为两个子模块聚合在一起,分别是api和biz

    + diff --git a/java/other/maven/problem.html b/java/other/maven/problem.html index 1cfc7efa11..f9a19fa125 100644 --- a/java/other/maven/problem.html +++ b/java/other/maven/problem.html @@ -30,8 +30,8 @@ 使用问题记录 | ChenSino - - + +
    跳至主要內容

    使用问题记录

    chenkun大约 3 分钟

    1、继承

    继承使用场景
    把公共的依赖都放在父模块,在子模块通过parent引用,使用了parent后,在父模块中dependencies定义的依赖会自动传递到子模块。

      <parent>
    @@ -116,7 +116,7 @@
                 </plugin>
             </plugins>
         </build>
    -
    - +
    + diff --git "a/java/other/maven/\347\224\237\345\221\275\345\221\250\346\234\237.html" "b/java/other/maven/\347\224\237\345\221\275\345\221\250\346\234\237.html" index ae17a94f3b..3d51d5e669 100644 --- "a/java/other/maven/\347\224\237\345\221\275\345\221\250\346\234\237.html" +++ "b/java/other/maven/\347\224\237\345\221\275\345\221\250\346\234\237.html" @@ -30,8 +30,8 @@ Maven的生命周期 | ChenSino - - + +
    跳至主要內容

    Maven的生命周期

    ChenSino原创maven大约 5 分钟

    Maven的生命周期

    There are three built-in build lifecycles: default, clean and site. The default lifecycle handles your project deployment, the clean lifecycle handles project cleaning, while the site lifecycle handles the creation of your project's web site.

    各个生命周期包含的phase

    官方说明open in new window

    default lifecycle包含的phase

    <phases>
    @@ -149,7 +149,7 @@
                         </execution>
                     </executions>
                 </plugin>
    -

    上面虽然是用clean生命周期进行举例,但是最重要的是build生命周期,原理都一样,无非build周期的phase多了一些而已。

    写在最后

    以前一直对maven的生命周期理解的很模糊,对clean/build/site三条生命周期稀里糊涂的,可能是被mvn clean install类似这种连写的方式误导了,最近有心得理解。

    其实字面意思很明显了,“生命周期”,什么是“生命周期”,其实就是一条完整的流程,或者叫流水线,每条流水线上已经按照顺序定义好了phase。那clean/build/site其实就是三条独立的流水线。

    每次执行一条生命周期时,其实就是按照某一个生命周期上已经绑定好的phase进行顺序扫描,扫描到有插件绑定在这个phase上时,就去执行配置好的goal,仅此而已。

    另外补充一下,在idea插件中,有个lifecycle,显示的其实是phase,在使用spring-boot-maven-plugin插件时,发现并没有配置phase,其实插件默认绑定在了package极端

    spring-boot-maven-plugin是不是默认绑定在package阶段?

    是的,Spring Boot Maven插件默认绑定在Maven的package阶段,它会在执行该阶段时自动运行。在默认的情况下,spring-boot-maven-plugin会使用repackage目标来创建可执行的jar或war文件。

    以下是Spring Boot官方文档对repackage目标的说明:

    repackage 目标是 spring-boot-maven-plugin 的一个目标,它用于重新打包当前的JAR或WAR文件。在处理普通的JAR或WAR文件时,这通常会将依赖项复制到 BOOT-INF/lib 目录中,并将所有类文件复制到 BOOT-INF/classes 目录中。同时,此目标还重命名了原始的JAR或WAR文件,以 *-original.jar 或 *-original.war 命名,并创建一个新的可执行的JAR或WAR文件,以便在命令行上运行应用程序。

    因此,在大多数情况下,您不需要在 pom.xml 文件中为 spring-boot-maven-plugin 插件指定任何额外的配置,它将在默认的package阶段正常工作。只有在您需要更改默认行为时,才需要根据需要更改 spring-boot-maven-plugin 插件的配置或执行计划。

    最后再画一个图,图示用的default生命周期进行说明。

    20230608144107

    - +

    上面虽然是用clean生命周期进行举例,但是最重要的是build生命周期,原理都一样,无非build周期的phase多了一些而已。

    写在最后

    以前一直对maven的生命周期理解的很模糊,对clean/build/site三条生命周期稀里糊涂的,可能是被mvn clean install类似这种连写的方式误导了,最近有心得理解。

    其实字面意思很明显了,“生命周期”,什么是“生命周期”,其实就是一条完整的流程,或者叫流水线,每条流水线上已经按照顺序定义好了phase。那clean/build/site其实就是三条独立的流水线。

    每次执行一条生命周期时,其实就是按照某一个生命周期上已经绑定好的phase进行顺序扫描,扫描到有插件绑定在这个phase上时,就去执行配置好的goal,仅此而已。

    另外补充一下,在idea插件中,有个lifecycle,显示的其实是phase,在使用spring-boot-maven-plugin插件时,发现并没有配置phase,其实插件默认绑定在了package极端

    spring-boot-maven-plugin是不是默认绑定在package阶段?

    是的,Spring Boot Maven插件默认绑定在Maven的package阶段,它会在执行该阶段时自动运行。在默认的情况下,spring-boot-maven-plugin会使用repackage目标来创建可执行的jar或war文件。

    以下是Spring Boot官方文档对repackage目标的说明:

    repackage 目标是 spring-boot-maven-plugin 的一个目标,它用于重新打包当前的JAR或WAR文件。在处理普通的JAR或WAR文件时,这通常会将依赖项复制到 BOOT-INF/lib 目录中,并将所有类文件复制到 BOOT-INF/classes 目录中。同时,此目标还重命名了原始的JAR或WAR文件,以 *-original.jar 或 *-original.war 命名,并创建一个新的可执行的JAR或WAR文件,以便在命令行上运行应用程序。

    因此,在大多数情况下,您不需要在 pom.xml 文件中为 spring-boot-maven-plugin 插件指定任何额外的配置,它将在默认的package阶段正常工作。只有在您需要更改默认行为时,才需要根据需要更改 spring-boot-maven-plugin 插件的配置或执行计划。

    最后再画一个图,图示用的default生命周期进行说明。

    20230608144107

    + diff --git "a/myserver/Jellyfin\346\220\255\345\273\272.html" "b/myserver/Jellyfin\346\220\255\345\273\272.html" index 3694484e89..b4a1a6a24e 100644 --- "a/myserver/Jellyfin\346\220\255\345\273\272.html" +++ "b/myserver/Jellyfin\346\220\255\345\273\272.html" @@ -30,11 +30,11 @@ jellyfin搭建 | ChenSino - - + + - - + + diff --git a/myserver/index.html b/myserver/index.html index a6d30260d2..4f913ec5ea 100644 --- a/myserver/index.html +++ b/myserver/index.html @@ -30,11 +30,11 @@ 个人服务器 | ChenSino - - + + - - + + diff --git a/myserver/x86_openwrt.html b/myserver/x86_openwrt.html index 9313ffc65e..4fa9974489 100644 --- a/myserver/x86_openwrt.html +++ b/myserver/x86_openwrt.html @@ -30,11 +30,11 @@ 旁路由网关 | ChenSino - - + + -
    跳至主要內容

    旁路由网关

    chensino原创小于 1 分钟

    vmware中使用openwrt做旁路由网关

    注意!!

    在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下: vmware中其他系统可以正常使用openwrt做旁路由网关,但是局域网内其他终端(包括宿主机)是无法使用这个openwrt作为网关的。 解决方式: 要么不要用windows系统,要么把宿主机切换为有线连接

    参考

    关于vmware搭openwrt旁路由 无线网卡设备无法上网open in new window

    - +
    跳至主要內容

    旁路由网关

    chensino原创小于 1 分钟

    vmware中使用openwrt做旁路由网关

    注意!!

    在windows下,如果宿主机是通过无线网卡连接的网络,vmware中的openwrt是无法作为一个正常的旁路由网关的,具体表现如下: vmware中其他系统可以正常使用openwrt做旁路由网关,但是局域网内其他终端(包括宿主机)是无法使用这个openwrt作为网关的。 解决方式: 要么不要用windows系统,要么把宿主机切换为有线连接

    参考

    关于vmware搭openwrt旁路由 无线网卡设备无法上网open in new window

    + diff --git "a/myserver/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html" "b/myserver/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html" index 75c4aae2c5..836129b34e 100644 --- "a/myserver/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html" +++ "b/myserver/\346\227\201\350\267\257\347\224\261\347\275\221\345\205\263.html" @@ -30,12 +30,12 @@ 旁路由网关 | ChenSino - - + + - +

    参考: 参考open in new window

    + diff --git "a/myserver/\347\275\221\347\273\234\350\256\276\347\275\256.html" "b/myserver/\347\275\221\347\273\234\350\256\276\347\275\256.html" index 3fd630eef3..8238495af7 100644 --- "a/myserver/\347\275\221\347\273\234\350\256\276\347\275\256.html" +++ "b/myserver/\347\275\221\347\273\234\350\256\276\347\275\256.html" @@ -30,14 +30,14 @@ 网络设置 | ChenSino - - + +
    跳至主要內容

    网络设置

    chensino原创大约 6 分钟

    本篇介绍我的网络配置过程,如何获得公网ip,以及升级我的家庭路由,mesh组网,我的网络是湖北电信500m,光猫一开始是天翼网关3.0, 后来咸鱼买了一个二手的4.0的花了50,曾打电话给电信客服升级4.0的是不是免费,他说199元。 家庭组网章节适合所有用户阅读,公网部分适合程序员朋友参考。

    家庭组网

    为什么要组网

    我家套内93平,一个无线路由器无法覆盖整个房间,起初是在每个房间放一个路由器,每次从一个房间到另一房间都要切换网络,即使手机有
     自动切换网络功能,但是总是不丝滑,我想做到wifi切换无感知,所以就生出组网的想法。
     

    家庭网线图

    下图我虽然在每个房间留了网口,但是电信的猫只有3个网口,还有一个是iptv的口,所以我有个房间是用不了(确实有需要的可以加交换机)

    20240322142629

    网上的组网教程比较多,会让你看的眼花缭乱,并且有的组网是要求你预埋网线,并且走线还有要求,比如如果你想通过有线组网,那么你的各个网孔节点要能实现串联,就是“有线回程”,这个是 组网效果最好的,另外还有各种无线组网等的等,但是我的网线布局是从弱电箱分别分一个网口到各个房间,并没有串联,我想90%的家庭装修应该都是这样留的。如果你这种走线有个简单的方法实现组网, 可以使用电信的elink协议实现组网,天翼网关3.0以及4.0都有这个功能,你要做的就是买几个电信路由器直接插在光猫的网口,注意别插到iptv的口了,至于需要几个,根据自己的情况,一般情况下买 两个就够了,我是在咸鱼买的二手路由器,tplink和电信合作的(型号wta301 ax3000 支持wifi6+),60块钱买的。

    所以组网后我家实际拓扑图如下,买了两个二手的wta301总共花了120,实现全屋wifi漫游

    20240322144108

    wta301设置也比较简单,买回来后先重置一下,再用网线到光猫,然后手机连接到它发射的wifi根据提示设置elink组网就行了,设置好以后手机 会掉线,因为天翼网关会把主路由的wifi名字同步过来,两个wta301设置方式一样,组网完成以后整个家里形成一主两副的拓扑结构,一共三个无线 路由器,120平的房间够用了。

    桥接拨号还是光猫拨号

    一开始我不知道光猫下直连几个路由器也可以组网,所以我一直在研究自己的路由器桥接到光猫,然后拨号,但是一直没搞定,一直无法进入天翼网关的超级管理界面, 只能进入普通路由器设置界面。后来无意间从咸鱼买了一个4.0版本的网关,神奇的是它居然可以直接进入管理界面,也不像网上说的超级管理员密码是动态变化的, 就很神奇。后来发现电信本身elink也可以组网,我就没有折腾桥接拨号了

    开通公网

    公网适合有主机的朋友折腾,如果你仅仅是用一个nas,家庭影院简单看看电影,感觉没必要折腾,耗时耗力。
    -

    我的网络是湖北电信,打电话给客服要求开通公网,客户问你干嘛,你就说家庭nas,我打完电话就给我开了,包括ipv4和ipv6公网。 开通公网后可以在外面访问到家里的服务,对我个人而言,我有一个小mini主机,在上面部署了各种服务,家庭影院、nas、博客、 chatgpt等等,我开外网就可以随时访问,比较方便,有时候晚上没看完的电影,在公司午休的时候还可以接着看,以下是我的几个 服务:

    20240322145826

    ipv4玩法

    申请一个ddns

    众所周知,ipv4地址是很稀缺的,所以运营商即使给你开通了公网ip,也不会给你一个固定ip,它是一直i动态变化的,隔几天就要变,这对我们外网访问 很不友好,所以你需要一个ddns,它的作用就是把你的动态的ip和一个域名绑定,其实原理也很简单,就是一直轮询,定时发送你的ip到ddns运营商,然后 进行绑定。最简单的ddns就是买个带ddns的路由器,tplink一般都有,或者你自己找一个免费ddns运营商,我用的是dynv6他家的,免费的,自己写了一个 脚本定时获取我本地的ip发送到服务器进行绑定。

    ipv6玩法

    我的ipv6地址是固定的,天翼网关4.0里面可以设置ipv6前缀,然后在客户端(你的服务器)绑定一个ipv6地址,这个我依然用的是ddns,毕竟免费,如果要 绑定到一个标准域名还得备案,麻烦

    要想使用ipv6从外部访问到家里的服务器,需要天翼网关4.0关闭ipv6防火墙,这里切忌要把服务器防火墙打开,不必要的端口一律关闭访问权限,一旦在天翼 网关关闭了防火墙,那么你的电脑如果通过ipv6联网,那么它就暴露在公网之上了,所有的人都可以直接访问你的电脑了,所以如果关闭天翼网关这道防火墙,务必 把电脑本身防火墙开启,不然就是裸奔。

    天翼网关4.0关闭ipv6防火墙的方法(前提是你已经能进入超级管理页面):

    访问你的路由管理页面192.168.1.1,点击快速装维入口,输入超级管理员的账号密码登录:

    20240322151706

    登录后,点击安全---防火墙----ipv6防火墙:

    20240322151834

    正常情况下你的 “使能IPv6防火墙控制转发报文”是勾选上的,我们要给他去掉勾选,但是它是灰色的,无法点击,所以需要懂点前端知识, 也比较简单,按F12(或者ctrl+shift+i)打开浏览器调试界面,使用元素选择器定位到复选框以及“应用”按钮,把disabled去掉,然后就可以点击应用了。

    20240322152203

    然后让天翼网关给客户端分配ipv6地址,设置如下,主要是前缀来源选择static,启用dhcpv6就行了,其他你的界面是什么不重要,不要动它。

    20240322152428

    - +

    我的网络是湖北电信,打电话给客服要求开通公网,客户问你干嘛,你就说家庭nas,我打完电话就给我开了,包括ipv4和ipv6公网。 开通公网后可以在外面访问到家里的服务,对我个人而言,我有一个小mini主机,在上面部署了各种服务,家庭影院、nas、博客、 chatgpt等等,我开外网就可以随时访问,比较方便,有时候晚上没看完的电影,在公司午休的时候还可以接着看,以下是我的几个 服务:

    20240322145826

    ipv4玩法

    申请一个ddns

    众所周知,ipv4地址是很稀缺的,所以运营商即使给你开通了公网ip,也不会给你一个固定ip,它是一直i动态变化的,隔几天就要变,这对我们外网访问 很不友好,所以你需要一个ddns,它的作用就是把你的动态的ip和一个域名绑定,其实原理也很简单,就是一直轮询,定时发送你的ip到ddns运营商,然后 进行绑定。最简单的ddns就是买个带ddns的路由器,tplink一般都有,或者你自己找一个免费ddns运营商,我用的是dynv6他家的,免费的,自己写了一个 脚本定时获取我本地的ip发送到服务器进行绑定。

    ipv6玩法

    我的ipv6地址是固定的,天翼网关4.0里面可以设置ipv6前缀,然后在客户端(你的服务器)绑定一个ipv6地址,这个我依然用的是ddns,毕竟免费,如果要 绑定到一个标准域名还得备案,麻烦

    要想使用ipv6从外部访问到家里的服务器,需要天翼网关4.0关闭ipv6防火墙,这里切忌要把服务器防火墙打开,不必要的端口一律关闭访问权限,一旦在天翼 网关关闭了防火墙,那么你的电脑如果通过ipv6联网,那么它就暴露在公网之上了,所有的人都可以直接访问你的电脑了,所以如果关闭天翼网关这道防火墙,务必 把电脑本身防火墙开启,不然就是裸奔。

    天翼网关4.0关闭ipv6防火墙的方法(前提是你已经能进入超级管理页面):

    访问你的路由管理页面192.168.1.1,点击快速装维入口,输入超级管理员的账号密码登录:

    20240322151706

    登录后,点击安全---防火墙----ipv6防火墙:

    20240322151834

    正常情况下你的 “使能IPv6防火墙控制转发报文”是勾选上的,我们要给他去掉勾选,但是它是灰色的,无法点击,所以需要懂点前端知识, 也比较简单,按F12(或者ctrl+shift+i)打开浏览器调试界面,使用元素选择器定位到复选框以及“应用”按钮,把disabled去掉,然后就可以点击应用了。

    20240322152203

    然后让天翼网关给客户端分配ipv6地址,设置如下,主要是前缀来源选择static,启用dhcpv6就行了,其他你的界面是什么不重要,不要动它。

    20240322152428

    + diff --git "a/myserver/\350\207\252\345\273\272nas.html" "b/myserver/\350\207\252\345\273\272nas.html" index f500987f9f..4f0972e8f3 100644 --- "a/myserver/\350\207\252\345\273\272nas.html" +++ "b/myserver/\350\207\252\345\273\272nas.html" @@ -30,11 +30,11 @@ 自建nas | ChenSino - - + + - - + + diff --git a/other/books/ebooks.html b/other/books/ebooks.html index 2338d39faf..8e75c0ecff 100644 --- a/other/books/ebooks.html +++ b/other/books/ebooks.html @@ -30,11 +30,11 @@ 电子书资源汇总 | ChenSino - - + + - - + + diff --git a/other/books/index.html b/other/books/index.html index 55ea1d47e4..6eca732bef 100644 --- a/other/books/index.html +++ b/other/books/index.html @@ -30,11 +30,11 @@ 电子书资源 | ChenSino - - + + - - + + diff --git a/other/computerprinciple/TCP-IP.html b/other/computerprinciple/TCP-IP.html index 375ad46d79..ebcf1bdf0e 100644 --- a/other/computerprinciple/TCP-IP.html +++ b/other/computerprinciple/TCP-IP.html @@ -30,8 +30,8 @@ 深入理解TCP/IP | ChenSino - - + +
    跳至主要內容

    深入理解TCP/IP

    ChenSino大约 8 分钟

    1、TCP/IP与OSI的关系

    网上学习网络模型时一会是七层osi模型,一会又五层,一会又是四层模型,着实五花八门让人费解,实际上七层模型是osi的标准,目前大家所讲的tcp/ip也是符合osi的标准的,不过是把其中几层合并到一层了而已,点击查看tcp/ip和osi的关系open in new window。TCP/IP实际上是一个协议簇,包含很多协议,比如TCP、IP、UDP、ICMP、RIP、TELNETFTP、SMTP、ARP、TFTP等,这些协议一起称为TCP/IP协议。常见协议简单介绍:

    TCP(Transport Control Protocol)传输控制协议

    IP(Internetworking Protocol)网间网协议

    UDP(User Datagram Protocol)用户数据报协议

    ICMP(Internet Control Message Protocol)互联网控制信息协议

    SMTP(Simple Mail Transfer Protocol)简单邮件传输协议

    SNMP(Simple Network manage Protocol)简单网络管理协议

    FTP(File Transfer Protocol)文件传输协议

    ARP(Address Resolation Protocol)地址解析协议

    20230130104228

    20230130103942

    2、TCP/IP

    完整的osi七层模型,在tcp/ip中被简化为了4层模型,其中应用层、表示层、会话层被压缩成了一个应用层,数据链路层、物理层被压缩为数据链路层。

    flowchart LR
    @@ -100,7 +100,7 @@
     12:02:01.497715 ARP, Request who-has 192.168.189.36 tell 192.168.189.137, length 28
     12:02:01.497733 ARP, Reply 192.168.189.36 is-at 00:93:37:25:27:0e, length 28
     
    -

    3、参考资料

    bilibli教程open in new window

    - +

    3、参考资料

    bilibli教程open in new window

    + diff --git a/other/computerprinciple/index.html b/other/computerprinciple/index.html index f68f6ba5bf..c8a5dd9f5d 100644 --- a/other/computerprinciple/index.html +++ b/other/computerprinciple/index.html @@ -30,11 +30,11 @@ Computerprinciple | ChenSino - - + + - - + + diff --git a/other/database/CPUOverLoad.html b/other/database/CPUOverLoad.html index b88cc3cd67..572c784119 100644 --- a/other/database/CPUOverLoad.html +++ b/other/database/CPUOverLoad.html @@ -30,8 +30,8 @@ Mysql CPU负载过高 | ChenSino - - + +
    跳至主要內容

    Mysql CPU负载过高

    chenkun数据库大约 2 分钟

    问题

    某天突然收到预警邮件,服务器CPU超过阈值,并且一直持续居高不下

    分析原因

    1. 使用htop查看资源消耗,按照CPU使用率降序排列,发现都是mysqld进程占用CPU很高
    2. 进入mysql命令行使用show processlist;查看当前正在执行的命令,经过多次执行show processlist发现有几条固定的sql一直在执行,并且每次传递的参数害不一样
    mysql> show processlist;
    @@ -76,7 +76,7 @@
     
  • 光有以上代码还不能完全确定,这个循环在线上一直在执行,所以我决定使用arthas来进一步确定,于是我把arths attach到生产环境的项目上使用trace分别跟踪循环中的三个查询,看看是否正在执行,结果三个全在执行,印证了循环一直在执行,所以cpu问题就出现在这里了。

  • trace com.sonoscape.ccs.data.mapper.InstallRecordMapper queryById  -n 5 --skipJDKMethod false 
     
    trace com.sonoscape.ccs.data.mapper.InstallRecordAccessoryMapper queryByHostId  -n 5 --skipJDKMethod false 
     
    trace com.sonoscape.ccs.data.mapper.SysFileMapper queryByBusinessInfo  -n 5 --skipJDKMethod false 
    -
    1. 最后我找到这部分代码负责人,他的ids参数本应该是个过滤后的,理论上不会太多,结果因为调用函数不严谨导致查询出ids实际上是全表的,吐

    结论

    不要轻易在循环执行sql

    - +
    1. 最后我找到这部分代码负责人,他的ids参数本应该是个过滤后的,理论上不会太多,结果因为调用函数不严谨导致查询出ids实际上是全表的,吐

    结论

    不要轻易在循环执行sql

    + diff --git a/other/database/MysqlCollate.html b/other/database/MysqlCollate.html index cc20efc088..c5fa7b9ea8 100644 --- a/other/database/MysqlCollate.html +++ b/other/database/MysqlCollate.html @@ -30,8 +30,8 @@ Mysql中的Collate和charset | ChenSino - - + +
    跳至主要內容

    Mysql中的Collate和charset

    ChenSino原创mysql大约 6 分钟

    本文转载自此处open in new window

    在mysql中执行show create table &lt tablename>指令,可以看到一张表的建表语句,example如下:

    CREATE TABLE `table1` (
    @@ -56,7 +56,7 @@
     

    如果列级别没有设置CHARSET和COLATE,则列级别会继承表级别的CHARSET与COLLATE。

    最后,你也可以在写SQL查询的时候显示声明COLLATE来覆盖任何库表列的COLLATE设置,不太常用,了解即可:

    SELECT DISTINCT field1 COLLATE utf8mb4_general_ci FROM table1;
     
     SELECT field1, field2 FROM table1 ORDER BY field1 COLLATE utf8mb4_unicode_ci;
    -

    如果全都显示设置了,那么优先级顺序是 SQL语句 > 列级别设置 > 表级别设置 > 库级别设置 > 实例级别设置。也就是说列上所指定的COLLATE可以覆盖表上指定的COLLATE,表上指定的COLLATE可以覆盖库级别的COLLATE。如果没有指定,则继承下一级的设置。即列上面没有指定COLLATE,则该列的COLLATE和表上设置的一样。

    以上就是关于mysql的COLLATE相关知识。不过,在系统设计中,我们还是要尽量避免让系统严重依赖中文字段的排序结果,在mysql的查询中也应该尽量避免使用中文做查询条件。

    - +

    如果全都显示设置了,那么优先级顺序是 SQL语句 > 列级别设置 > 表级别设置 > 库级别设置 > 实例级别设置。也就是说列上所指定的COLLATE可以覆盖表上指定的COLLATE,表上指定的COLLATE可以覆盖库级别的COLLATE。如果没有指定,则继承下一级的设置。即列上面没有指定COLLATE,则该列的COLLATE和表上设置的一样。

    以上就是关于mysql的COLLATE相关知识。不过,在系统设计中,我们还是要尽量避免让系统严重依赖中文字段的排序结果,在mysql的查询中也应该尽量避免使用中文做查询条件。

    + diff --git a/other/database/MysqlNote.html b/other/database/MysqlNote.html index 33d9953295..611af76181 100644 --- a/other/database/MysqlNote.html +++ b/other/database/MysqlNote.html @@ -30,8 +30,8 @@ Mysql知识点记录 | ChenSino - - + +
    跳至主要內容

    Mysql知识点记录

    chenkun大约 2 分钟

    1、批量插入速度慢

    项目使用的MyBatis-plus批量插入效率很低,遂百度一下原因

    在jdbc的链接上加上rewriteBatchedStatements=true参数,可以解决此问题。

    默认情况下rewriteBatchedStatements=false,jdbc批量插入会判断rewriteBatchedStatements,当为true才会执行批量语句,以下从源码(以下jdbc驱动源码版本为8.0.20)角度分析:

    1. com.mysql.cj.jdbc.StatementImpl#executeBatch
      @Override
    @@ -82,7 +82,7 @@
                 }
             }
         }
    -
    1. com.mysql.cj.jdbc.ClientPreparedStatement#executeBatchSerially

    打开源码可看到注释写的很清楚了,是逐条插入

    image-20220607182512020

    结论:

    使用Mybatis时,批量插入慢,需要在jdbc的url上拼接rewriteBatchedStatements=true

    2、唯一索引

    唯一索引指的是一个字段的值在表中只能有一个,唯一索引可以有多个字段,比如用户表有姓名、年龄、性别等,当新建一个唯一索引,把姓名、年龄放在同一个唯一索引,代表用户表不允许存在姓名和年龄同时相等记录,姓名和年龄可以有一个相同。

    - +
    1. com.mysql.cj.jdbc.ClientPreparedStatement#executeBatchSerially

    打开源码可看到注释写的很清楚了,是逐条插入

    image-20220607182512020

    结论:

    使用Mybatis时,批量插入慢,需要在jdbc的url上拼接rewriteBatchedStatements=true

    2、唯一索引

    唯一索引指的是一个字段的值在表中只能有一个,唯一索引可以有多个字段,比如用户表有姓名、年龄、性别等,当新建一个唯一索引,把姓名、年龄放在同一个唯一索引,代表用户表不允许存在姓名和年龄同时相等记录,姓名和年龄可以有一个相同。

    + diff --git a/other/database/MysqlRemoteConnect.html b/other/database/MysqlRemoteConnect.html index 250ed4ba4f..6cb269fe4d 100644 --- a/other/database/MysqlRemoteConnect.html +++ b/other/database/MysqlRemoteConnect.html @@ -30,13 +30,13 @@ Mysql开启远程连接权限 | ChenSino - - + +
    跳至主要內容

    Mysql开启远程连接权限

    chenkun小于 1 分钟

    默认情况下,mysql只允许本地登录,如果要开启远程连接,则需要修改/etc/mysql/my.conf文件。Mariadb可以去/etc/my.cnf看看引用的目录配置

    1、修改/etc/mysql/my.conf

    找到bind-address = 127.0.0.1这一行 改为bind-address = 0.0.0.0即可

    二、为需要远程登录的用户赋予权限

    1、新建用户远程连接mysql数据库 grant all on . to admin@'%' identified by '123456' with grant option; flush privileges; 允许任何ip地址(%表示允许任何ip地址)的电脑用admin帐户和密码(123456)来访问这个mysql server。 注意admin账户不一定要存在。

    2、支持root用户允许远程连接mysql数据库 grant all privileges on . to 'root'@'%' identified by '123456' with grant option; flush privileges;

    三、查看系统用户

    use mysql;
     select user,host from user;
    -

    四、放开服务器对应端口

    - +

    四、放开服务器对应端口

    + diff --git a/other/database/Recurse.html b/other/database/Recurse.html index 7e0f4073ed..eced72fb05 100644 --- a/other/database/Recurse.html +++ b/other/database/Recurse.html @@ -30,8 +30,8 @@ 递归下钻 | ChenSino - - + +
    跳至主要內容

    递归下钻

    ChenSino原创大约 1 分钟

    注意!!!

    只有在mysql8.0之后才有递归,5.7及之前是不支持的

    1、递归下钻

    MySQL中的递归查询通常用于处理树形结构数据,如组织架构、文件目录、级联地址、多级菜单等。

    2、示例

    WITH RECURSIVE cte (id, parent_id, level) AS (
    @@ -51,7 +51,7 @@
     3. 在递归查询中,我们使用JOIN将另一个实例的表连接到我们正在创建的表中,然后根据两个表之间共享的信息递归向下查找树形结构中的每个节点。这就是递归下钻查询的核心部分。
     4. 最后的SELECT将返回递归查询的结果。
     5. 注意递归中参数个数要和select个数一致
    -
    - +
    + diff --git a/other/database/SQLOptimization.html b/other/database/SQLOptimization.html index c6bb32e0a8..09892016c1 100644 --- a/other/database/SQLOptimization.html +++ b/other/database/SQLOptimization.html @@ -30,8 +30,8 @@ 联合查询sql优化 | ChenSino - - + +
    跳至主要內容

    联合查询sql优化

    chenkun数据库大约 1 分钟

    背景:

    最近在改一个老项目,其中使用到框架是mybatis,有一个业务表install_record,代表装机记录,一个accessory代表备件表,一个sys_file代表文件表,业务关系是一个install_record对应多个accessory、以及多个sys_file,在一开始使用的是mybatis的嵌套查询的方式,但此方式有N+1的问题,比如一个装机表对应10个accessory、20个sys_file,则就要查询1+10+20 = 31次数据库,效率是很低的,因此想改成嵌套查询的方式。

    1、改造后的语句如下

    SELECT xxx   FROM
    @@ -40,7 +40,7 @@
     	LEFT JOIN ccsx.sys_file f ON f.business_type = 1 
     	AND f.business_id = ir.id 	LIMIT 0,1000;
     

    install_record表中有8000数据,accessory表20000数据,sys_file 表240000数据,执行sql直接卡死

    2、原因分析

    数据量其实很小,执行慢的原因是没有索引,使用explain分析sql

    explain xxx
    -

    image-20220428164618054

    可以看到key那一列,只有两个PRIMARY被索引,所以所有用于关联字段加上索引即可,此处给business_idhost_id加上索引就ok了

    - +

    image-20220428164618054

    可以看到key那一列,只有两个PRIMARY被索引,所以所有用于关联字段加上索引即可,此处给business_idhost_id加上索引就ok了

    + diff --git a/other/database/index.html b/other/database/index.html index 9f5659d74e..1ef7321f46 100644 --- a/other/database/index.html +++ b/other/database/index.html @@ -30,11 +30,11 @@ 数据库 | ChenSino - - + + - - + + diff --git "a/other/database/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html" "b/other/database/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html" index 0663e39127..5ff075a301 100644 --- "a/other/database/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html" +++ "b/other/database/\346\225\260\346\215\256\345\272\223\345\244\207\344\273\275.html" @@ -30,8 +30,8 @@ 定时备份数据库 | ChenSino - - + + - +
    + diff --git a/other/distributeservice/DistributeLock.html b/other/distributeservice/DistributeLock.html index e4a2265bff..8f14ce41ab 100644 --- a/other/distributeservice/DistributeLock.html +++ b/other/distributeservice/DistributeLock.html @@ -30,8 +30,8 @@ 分布式锁 | ChenSino - - + +
    跳至主要內容

    分布式锁

    ChenSino大约 10 分钟

    1、写在前面

    以前一直没搞清楚分布式锁和分布式事务,对其概念以及使用场景很模糊,今天查查资料,好好总结一下分布式事务和分布式锁。另外提前说一句,使用redis来解决分布式、高并发问题存在一些困难,Redis 分布式锁只能作为一种缓解并发的手段,如果要完全解决并发问题,仍需要数据库的防并发手段。

    2、锁和分布式锁解决了什么问题?

    单机锁:解决单进程中多线程同时操作共有数据(比如java堆数据)带来的安全问题,强调的是单机服务中线程安全问题,这种场景很常见,比如单机多线程售票的例子。这种锁直接通过java的本地锁实现即可,可以使用java自带的synchroized和Lock

    分布式锁:解决集群服务中,多个相同服务操作同一个资源数据的安全问题。比如淘宝双十一抢购,为了支持高并发,下订单的服务肯定是集群模式而非单机,比如有一个商品促销,数量共有1000个,下订单的集群服务是10个,如果抢购开始,这10个集群服务应该是共同拥有这1000个商品,应当避免在并发情况下销售的总量超过1000的情况,这种就是典型的分布式锁需要处理的问题。

    3、分布式锁特性

    • 互斥性: 同一时刻只能有一个服务(进程)持有锁
    • 可重入性: 同一服务节点上的同一个线程如果获取了锁之后能够再次获取锁
    • 锁超时:和J.U.C中的锁一样支持锁超时,防止死锁
    • 高性能和高可用: 加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效
    • 具备阻塞和非阻塞性:能够及时从阻塞状态中被唤醒

    4、分布式锁的实现

    4.1 redis实现

    实现思路:利用redis处理网络请求是单线程(其他模块是多线程),并且其操作具有原子性。

    4.1.1 单机redis实现(采用逐步升级的方式来进行分析)
    1. 采用redis 的SETNX(SET IF NOT EXIST),key和value我们可以随意指定,若key不存在此操作成功返回1,如果key已存在则set失败返回0。正常情况第一个抢占锁的服务设置肯定是返回1的,如果此时其他服务(JVM进程)再次来set相同的key就返回0,代表抢锁失败。

      SETNX lock_source_key lock_value # 加锁
      @@ -53,7 +53,7 @@
       	return redis.call("DEL",KEYS[1])          #如果判断相等则用lua调用del释放锁
       else
       	return 0;                                 #如果当前服务不是锁拥有者直接返回0,即不释放锁,因为此时锁的所有者不是当前服务
      -

      基于 Redis 单节点的分布式锁基本完成了,但是这并不是一个完美的方案,只是相对完全一点,因为它并没有完全解决当前线程执行超时锁被提前释放后,其它线程乘虚而入的问题。要完美解决此问题需要使用 Redisson 的分布式锁,具体实现比较复杂以后补充。

      4.1.2 多机redis实现

      在4.1.1中,是单机部署,如果用在多机redis部署又会产生新的问题。加锁时只作用在一个 Redis 节点上,即使 Redis 通过 Sentinel 保证了高可用,但由于 Redis 的复制是异步的,Master 节点获取到锁后在未完成数据同步的情况下发生故障转移,此时其他客户端上的线程依然可以获取到锁,因此会丧失锁的安全性。

      整个过程如下:

      • 客户端 A 从 Master 节点获取锁。
      • Master 节点出现故障,主从复制过程中,锁对应的 key 没有同步到 Slave 节点。
      • Slave 升 级为 Master 节点,但此时的 Master 中没有锁数据。
      • 客户端 B 请求新的 Master 节点,并获取到了对应同一个资源的锁。
      • 出现多个客户端同时持有同一个资源的锁,不满足锁的互斥性。

      正因为如此,在 Redis 的分布式环境中,Redis 的作者 antirez 提供了 RedLock 的算法来实现一个分布式锁,该算法大概是这样的:

      假设有 N(N>=5)个 Redis 节点,这些节点完全互相独立,不存在主从复制或者其他集群协调机制,确保在这 N 个节点上使用与在 Redis 单实例下相同的方法获取和释放锁。

      获取锁的过程,客户端应执行如下操作:

      • 获取当前 Unix 时间,以毫秒为单位。
      • 按顺序依次尝试从 5 个实例使用相同的 key 和具有唯一性的 value(例如 UUID)获取锁。当向 Redis 请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如锁自动失效时间为 10 秒,则超时时间应该在 5-50 毫秒之间。这样可以避免服务器端 Redis 已经挂掉的情况下,客户端还在一直等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试去另外一个 Redis 实例请求获取锁。
      • 客户端使用当前时间减去开始获取锁时间(步骤 1 记录的时间)就得到获取锁使用的时间。当且仅当从大多数(N/2+1,这里是 3 个节点)的 Redis 节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
      • 如果取到了锁,key 的真正有效时间等于有效时间减去获取锁所使用的时间(步骤 3 计算的结果)。
      • 如果因为某些原因,获取锁失败(没有在至少 N/2+1 个 Redis 实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的 Redis 实例上进行解锁(使用 Redis Lua 脚本)。

      释放锁的过程相对比较简单:客户端向所有 Redis 节点发起释放锁的操作,包括加锁失败的节点,也需要执行释放锁的操作,antirez 在算法描述中特别强调这一点,这是为什么呢?

      原因是可能存在某个节点加锁成功后返回客户端的响应包丢失了,这种情况在异步通信模型中是有可能发生的:客户端向服务器通信是正常的,但反方向却是有问题的。虽然对客户端而言,由于响应超时导致加锁失败,但是对 Redis 节点而言,SET 指令执行成功,意味着加锁成功。因此,释放锁的时候,客户端也应该对当时获取锁失败的那些 Redis 节点同样发起请求。

      除此之外,为了避免 Redis 节点发生崩溃重启后造成锁丢失,从而影响锁的安全性,antirez 还提出了延时重启的概念,即一个节点崩溃后不要立即重启,而是等待一段时间后再进行重启,这段时间应该大于锁的有效时间。

      4.2 zookeeper实现

      TODO

      4.3 基于数据库实现

      TODO

      4.4 MemCached 实现

      TODO

      5、参考博客

      1. 浅析分布式锁redis解决方案open in new window

      2. 分布式锁的实现之 redis 篇open in new window

    - +

    基于 Redis 单节点的分布式锁基本完成了,但是这并不是一个完美的方案,只是相对完全一点,因为它并没有完全解决当前线程执行超时锁被提前释放后,其它线程乘虚而入的问题。要完美解决此问题需要使用 Redisson 的分布式锁,具体实现比较复杂以后补充。

    4.1.2 多机redis实现

    在4.1.1中,是单机部署,如果用在多机redis部署又会产生新的问题。加锁时只作用在一个 Redis 节点上,即使 Redis 通过 Sentinel 保证了高可用,但由于 Redis 的复制是异步的,Master 节点获取到锁后在未完成数据同步的情况下发生故障转移,此时其他客户端上的线程依然可以获取到锁,因此会丧失锁的安全性。

    整个过程如下:

    正因为如此,在 Redis 的分布式环境中,Redis 的作者 antirez 提供了 RedLock 的算法来实现一个分布式锁,该算法大概是这样的:

    假设有 N(N>=5)个 Redis 节点,这些节点完全互相独立,不存在主从复制或者其他集群协调机制,确保在这 N 个节点上使用与在 Redis 单实例下相同的方法获取和释放锁。

    获取锁的过程,客户端应执行如下操作:

    释放锁的过程相对比较简单:客户端向所有 Redis 节点发起释放锁的操作,包括加锁失败的节点,也需要执行释放锁的操作,antirez 在算法描述中特别强调这一点,这是为什么呢?

    原因是可能存在某个节点加锁成功后返回客户端的响应包丢失了,这种情况在异步通信模型中是有可能发生的:客户端向服务器通信是正常的,但反方向却是有问题的。虽然对客户端而言,由于响应超时导致加锁失败,但是对 Redis 节点而言,SET 指令执行成功,意味着加锁成功。因此,释放锁的时候,客户端也应该对当时获取锁失败的那些 Redis 节点同样发起请求。

    除此之外,为了避免 Redis 节点发生崩溃重启后造成锁丢失,从而影响锁的安全性,antirez 还提出了延时重启的概念,即一个节点崩溃后不要立即重启,而是等待一段时间后再进行重启,这段时间应该大于锁的有效时间。

    4.2 zookeeper实现

    TODO

    4.3 基于数据库实现

    TODO

    4.4 MemCached 实现

    TODO

    5、参考博客

    1. 浅析分布式锁redis解决方案open in new window

    2. 分布式锁的实现之 redis 篇open in new window

    + diff --git a/other/distributeservice/Nacos.html b/other/distributeservice/Nacos.html index ff75b1d93b..66f0cfff18 100644 --- a/other/distributeservice/Nacos.html +++ b/other/distributeservice/Nacos.html @@ -30,11 +30,11 @@ Nacos学习 | ChenSino - - + + -
    跳至主要內容

    Nacos学习

    chenkun小于 1 分钟

    1、配置管理

    命名空间——>Group——>Data Id——>配置项

    命名空间:

    默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。

    如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM代表bom系统。

    image-20220602103505994

    Group

    group是应用程序(服务)粒度的隔离,我在dev环境有两个微服务,中控微服务使用CCS分组下的配置,bom微服务使用bom分组下的配置

    DataId

    配置集,就是一个配置文件,类似springboot中的application.yml

    - +
    跳至主要內容

    Nacos学习

    chenkun小于 1 分钟

    1、配置管理

    命名空间——>Group——>Data Id——>配置项

    命名空间:

    默认使用的命名空间是public,一个命名空间下可以有多个Group,多个服务。命名空间使用场景是租户粒度的隔离,可以给不同租户不同的命名空间,还可以用于隔离不同环境比如dev、test、prod。

    如下图,我给dev环境设置了两个Group,CCS代表中控系统,BOM代表bom系统。

    image-20220602103505994

    Group

    group是应用程序(服务)粒度的隔离,我在dev环境有两个微服务,中控微服务使用CCS分组下的配置,bom微服务使用bom分组下的配置

    DataId

    配置集,就是一个配置文件,类似springboot中的application.yml

    + diff --git a/other/distributeservice/index.html b/other/distributeservice/index.html index d5c48d3dab..dc028b3f9b 100644 --- a/other/distributeservice/index.html +++ b/other/distributeservice/index.html @@ -30,11 +30,11 @@ 分布式微服务 | ChenSino - - + + - - + + diff --git a/other/docker/Docker.html b/other/docker/Docker.html index 514772bb5e..34a57162c1 100644 --- a/other/docker/Docker.html +++ b/other/docker/Docker.html @@ -30,11 +30,11 @@ Docker常用命令 | ChenSino - - + + - - + + diff --git a/other/docker/ServiceInstall.html b/other/docker/ServiceInstall.html index b63d413069..bd89a8efc7 100644 --- a/other/docker/ServiceInstall.html +++ b/other/docker/ServiceInstall.html @@ -30,8 +30,8 @@ 使用docker安装常见服务 | ChenSino - - + +
    跳至主要內容

    使用docker安装常见服务

    ChenSinodocker小于 1 分钟

    1. docker安装Redis

    起初的需求是用docker启动一个redis,并且指定一个配置,死活不成功,主要是少设置了data目录的映射

    注意

    使用docker-compose安装redis时,若指定外置redis.conf配置文件,要切记同时设置data存储目录,默认docker中的数据存在/data下,所以 使用卷映射时需要映射到容器内的/data docker-compose.yml

    目录结构如下,需要事先准备一个redis.conf放到项目suc/redis/conf,另外需要创建目录suc/redis/data

    suc
    @@ -55,7 +55,7 @@
           - 6379:6379
         networks:
           - custom
    -
    - +
    + diff --git a/other/docker/index.html b/other/docker/index.html index c4c10bdaa4..8427f4c2ea 100644 --- a/other/docker/index.html +++ b/other/docker/index.html @@ -30,11 +30,11 @@ Docker | ChenSino - - + + - - + + diff --git a/other/essay/2022-04-12.html b/other/essay/2022-04-12.html index 455912c36e..4bbb3d03fb 100644 --- a/other/essay/2022-04-12.html +++ b/other/essay/2022-04-12.html @@ -30,8 +30,8 @@ 2022-04-02 | ChenSino - - + +
    跳至主要內容

    2022-04-02

    chenkun数据库数据库大约 1 分钟

    背景:

    我有一个库A,这个库同时被两个服务使用(serviceA、serviceB),某天,因serviceA业务需要,必须更改数据库结构,导致serviceB无法使用,但是serviceB又不能停机,所以就考虑把数据库克隆一份给serviceB使用。在克隆使用的是mysqldump直接导出,然后再新建另一个库B,在库B导入sql,结果就悲剧了。

    1、库A导出

    # 在服务器把写了个定时备份脚本,这个databaseA是未更新前备份的库
    @@ -41,7 +41,7 @@
     # 2. source命令导入
     use databaseB;
     source databaseA.sql;  # 这一步就杯具了
    -

    这里对导出的sql文件不熟悉,导出的时候里面会自动有个use 原数据库名,所以即使你在导入到新库之前切换到了库B,但是当使用source命令时,它会自动切换到原库,所以实际上直接source databaseA.sql是导入到了原来的A库中,这就很苦比了,这个库会直接覆盖databaseA,前面我们说过,此时的A库因业务需要表结构已经发生变化了,然后source时又被覆盖,之前修改的表结构相当于白改了。

    image-20220412100355169

    - +

    这里对导出的sql文件不熟悉,导出的时候里面会自动有个use 原数据库名,所以即使你在导入到新库之前切换到了库B,但是当使用source命令时,它会自动切换到原库,所以实际上直接source databaseA.sql是导入到了原来的A库中,这就很苦比了,这个库会直接覆盖databaseA,前面我们说过,此时的A库因业务需要表结构已经发生变化了,然后source时又被覆盖,之前修改的表结构相当于白改了。

    image-20220412100355169

    + diff --git a/other/essay/BTree.html b/other/essay/BTree.html index 084d9f7917..c22b57cc2b 100644 --- a/other/essay/BTree.html +++ b/other/essay/BTree.html @@ -30,11 +30,11 @@ 二叉树 | ChenSino - - + + - - + + diff --git a/other/essay/CDN.html b/other/essay/CDN.html index b412651dd1..dd607777a4 100644 --- a/other/essay/CDN.html +++ b/other/essay/CDN.html @@ -30,15 +30,15 @@ CDN静态资源加速 | ChenSino - - + +
    跳至主要內容

    CDN静态资源加速

    John运维大约 7 分钟

    1、什么是CDN?

    CDN(Content Delivery Network,内容分发网络)是构建在现有互联网基础之上的一层智能虚拟网络,通过在网络各处部署节点服务器,实现将源站内容分发至所有CDN节点,使用户可以就近获得所需的内容。CDN服务缩短了用户查看内容的访问延迟,提高了用户访问网站的响应速度与网站的可用性,解决了网络带宽小、用户访问量大、网点分布不均等问题。
     

    2、工作原理

    cdn原理open in new window

    当用户访问使用CDN服务的网站时,本地DNS服务器通过CNAME方式将最终域名请求重定向到CDN服务。CDN通过一组预先定义好的策略(如内容类型、地理区域、网络负载状况等),将当时能够最快响应用户的CDN节点IP地址提供给用户,使用户可以以最快的速度获得网站内容。使用CDN后的HTTP请求处理流程如下。

    img

    HTTP请求流程说明:

    1. 用户在浏览器输入要访问的网站域名www.example.com,向本地DNS发起域名解析请求。
    2. 本地DNS检查缓存中是否有www.example.com的IP地址记录。如果有,则直接返回给终端用户;如果没有,则向网站授权DNS查询。
    3. 网站DNS服务器解析发现域名已经解析到了CNAME:www.example.com.c.cdnhwc1.com。
    4. 请求被指向CDN服务。
    5. CDN对域名进行智能解析,将响应速度最快的CDN节点IP地址返回给本地DNS。
    6. 用户获取响应速度最快的CDN节点IP地址。
    7. 浏览器在得到最佳节点的IP地址以后,向CDN节点发出访问请求。
      • 如果该IP地址对应的节点已缓存该资源,节点将数据直接返回给用户,如图中步骤7和8,请求结束。
      • 如果该IP地址对应的节点未缓存该资源,节点回源请求资源。获取资源后,结合用户自定义配置的缓存策略,将资源缓存至节点,如图中的北京节点,并返回给用户,请求结束。

    3、CDN应用场景

    华为云关于CDN应用场景介绍open in new window

    3.1 网站加速

    适用于有加速需求的网站,包括门户网站、电商平台、资讯APP、UGC应用(User Generated Content,用户原创内容)等。CDN网络能够对加速域名下的静态内容提供良好的加速服务。支持自定义缓存规则,用户可以根据数据需求设置缓存过期时间,缓存格式包括但不限于zip、exe、wmv、gif、png、bmp、wma、rar、jpeg、jpg等。

    img

    3.2 文件下载加速

    适用于使用http/https文件下载业务的网站、下载工具、游戏客户端、APP商店等。现在越来越多的新业务需要通过网络对客户端软件进行实时更新,包括APP更新,手游更新等,传统的下载类业务也需要支持更多的文件数量和更大的文件,如果所有的请求都通过源站服务器来处理,服务器和网络会成为很大的瓶颈,导致下载体验变差。使用CDN下载加速可以将下载量大的内容分发到各地的CDN节点,有效减轻源站的压力,同时保证了客户端高速下载的需求。

    img

    3.3 点播加速

    适用于提供音视频点播服务的客户。例如:在线教育类网站、在线视频分享网站、互联网电视点播平台、音乐视频点播APP等。传统的点播服务会加大服务器的负载,并消耗巨大的带宽资源,同时又无法保证终端用户访问时需要的高速体验,CDN点播加速可以提供快速、稳定和安全的点播加速服务,通过分布在各个区域的CDN节点,将音视频内容扩展到距离用户较近的地方,随时随地为用户提供高品质的访问体验。

    点击放大

    3.4 全站加速

    适用于各行业动静态内容混合,含较多动态资源请求(如asp、jsp、php等格式的文件)的网站。全站加速融合了动态和静态加速,用户请求资源时,静态内容从边缘节点就近获取,动态内容通过动态加速技术智能选择较优路由回源获取。CDN全站加速有效提升动态页面的加载速度,避开网络拥堵路由,提高访问成功率,实现网站整体加速与实时优化。

    4、CDN加速原理

    当用户访问使用CDN服务的网站时,本地DNS服务器通过CNAME方式将最终域名请求重定向到CDN服务。CDN通过一组预先定义好的策略(如内容类型、地理区域、网络负载状况等),将当时能够最快响应用户的CDN节点IP地址提供给用户,使用户可以以最快的速度获得网站内容。
     
    4.1 CDN节点无缓存场景

    CDN节点无缓存场景

    HTTP请求流程说明:

    1、用户在浏览器输入要访问的网站域名,向本地DNS发起域名解析请求。

    2、域名解析的请求被发往网站授权DNS服务器。

    3、网站DNS服务器解析发现域名已经CNAME到了www.example.com.c.cdnhwc1.com。

    4、请求被指向CDN服务。

    5、CDN对域名进行智能解析,将响应速度最快的CDN节点IP地址返回给本地DNS。

    6、用户获取响应速度最快的CDN节点IP地址。

    7、浏览器在得到速度最快节点的IP地址以后,向CDN节点发出访问请求。

    8、CDN节点回源站拉取用户所需资源。(==无缓存时,第一次请求要回源地址获取资源,因此第一次请求会感觉仍然慢==)

    9、将回源拉取的资源缓存至节点。

    10、将用户所需资源返回给用户。

    4.2 CDN节点有缓存场景

    CDN节点有缓存场景

    HTTP请求流程说明:

    1、用户在浏览器输入要访问的网站域名,向本地DNS发起域名解析请求。

    2、域名解析的请求被发往网站授权DNS服务器。

    3、网站DNS服务器解析发现域名已经CNAME到了www.example.com.c.cdnhwc1.com。

    4、请求被指向CDN服务。

    5、CDN对域名进行智能解析,将响应速度最快的CDN节点IP地址返回给本地DNS。

    6、用户获取响应速度最快的CDN节点IP地址。

    7、浏览器在得到速度最快节点的IP地址以后,向CDN节点发出访问请求。

    8、CDN节点将用户所需资源返回给用户。

    5、如何使用华为云CDN?

    华为云CDN配置open in new window

    举例说明:我要开通加速的地址是h.sonoscape.com,是公司下的一个子域名,则开通cdn的步骤如下:

    1. 在华为云花钱开通CDN服务;

    2. 添加加速域名,就是把你要加速的域名到华为控制台配置以下,配置好后,它会根据你的域名生成一个CNAME,类似h.sonoscape.com.7bf9428.c.cdnhwc1.com这样;

    3. 配置CNAME解析,要看你的域名是哪里来的,这个h.sonoscape.com是从公司申请的,因此要找公司IT部门去配置一个CNAME,把h.sonoscape.com下配置一个cname,h.sonoscape.com.7bf9428.c.cdnhwc1.com,配置好一般两小时生效;

    4. 验证cname是否生效

      nslookup -qt=cname h.sonoscape.com
       

      如果回显CNAME,则表示CNAME配置已经生效,如下图:

      点击放大

    6、问题

    1. 为何第一次请求感觉CDN没用?

      正常,由于首次访问时,CDN未对源站的相关资源进行缓存,需要回源拉取资源。您可以在首次访问前,进行缓存预热,将访问频率高的资源预热到CDN。
      -
    - +
    + diff --git a/other/essay/ChromeDevTools.html b/other/essay/ChromeDevTools.html index 7ba7c9e916..2c76739e3e 100644 --- a/other/essay/ChromeDevTools.html +++ b/other/essay/ChromeDevTools.html @@ -30,11 +30,11 @@ ChromeDevTools学习 | ChenSino - - + + - - + + diff --git a/other/essay/CloudService.html b/other/essay/CloudService.html index 42f1ed04ee..9d18cbaa46 100644 --- a/other/essay/CloudService.html +++ b/other/essay/CloudService.html @@ -30,8 +30,8 @@ 云服务问题分析及总结 | ChenSino - - + +
    跳至主要內容

    云服务问题分析及总结

    陈老师运维大约 8 分钟

    1、我的云服务使用场景

    1. OBS用来存储图片,而CDN用来加速图片的访问速度;

    2. ECS服务器上有个导出pdf服务需要访问obs的图片;

    2、遇到的问题

    1. 一开始没有上cdn,用户反应慢,后来配置了cdn加速obs中的图片,这样第一次打开图片会把图片资源拉到就近的服务器,问题就是第一次打开依然慢;
    2. 导出PDF很慢,经常出现504Timeout

    3、问题分析

    先上cdn原理图,对照原理图来进行分析

    img

    3.1 使用cdn 第一次访问慢原因分析

    image-20220620164944526

    以上是我的服务访问线路:

    1. 客户端通过加速域名obs.sonoscapecloud.com去请求图片,以下是我 在云服务管理台做的配置,使用obs.sonoscapecloud.com加速桶域名ccs.obs.ap-southeast-1.myhuaweicloud.com

      image-20220620163303675

    2. cdn通过cname去查找是否缓存过此文件,若没有缓存,则回源到原来的obs存储服务器,先把图片从obs服务器拉到就近的cdn服务器,比如我在武汉访问图片,可能就把图片拉到了华中区的服务器,缓存到华中区后,再把图片返回到客户端。


      第二步因为要回源拉取,所以第一次打开会慢点,第二次就直接就近到华中区去获取,就会快一些。这里有个地方要注意,虽然使用cdn后第一次拉取会稍慢,但是比不使用cdn还是快很多的,原因就是从obs源服务器把文件拉取到就近的cdn服务器应该是走的华为内网,所以速度相对来说还是要快一些,具体还要分析从源到cdn是否走了内网。

    3.2 解决第一次访问慢的问题

    防止第一次访问图片,数据回源,只需要在新增文件时,在新增接口添加缓存预热一下即可。具体的代码参考华为云提供的demo即可。

    华为云CDN缓存预热open in new window

    创建刷新预热open in new window

    private void preHeatingFile(String fileName) {
    @@ -175,7 +175,7 @@
             +---[0.18% 1.628766ms ] java.io.ByteArrayOutputStream:toByteArray() #88
             `---[0.00% 0.002498ms ] java.io.ByteArrayOutputStream:close() #95
     
    -

    根据以上arthas日志看到,渲染pdf总耗时不到1s,大大提升了速率,再打开浏览器控制台看到后端总耗时1.26s,经过我多次测试都很稳定。

    image-20220620184752843

    问题二:如何实现一个加速域名既能保证加速效果,又能保证ecs的内网访问呢?

    方法1: 配置内网dns,ecs访问obs时使用桶域名访问

    方法2(推荐):先配置内网dns,ping桶域名拿到内网ip,然后手动配置hosts把加速域名指向内网ip(不配置host就会解析到cname了)

    - +

    根据以上arthas日志看到,渲染pdf总耗时不到1s,大大提升了速率,再打开浏览器控制台看到后端总耗时1.26s,经过我多次测试都很稳定。

    image-20220620184752843

    问题二:如何实现一个加速域名既能保证加速效果,又能保证ecs的内网访问呢?

    方法1: 配置内网dns,ecs访问obs时使用桶域名访问

    方法2(推荐):先配置内网dns,ping桶域名拿到内网ip,然后手动配置hosts把加速域名指向内网ip(不配置host就会解析到cname了)

    + diff --git a/other/essay/DeployGithubPage.html b/other/essay/DeployGithubPage.html index fe883242e8..a87dbbfeac 100644 --- a/other/essay/DeployGithubPage.html +++ b/other/essay/DeployGithubPage.html @@ -30,8 +30,8 @@ 如何在github部署静态网站 | ChenSino - - + +
    跳至主要內容

    如何在github部署静态网站

    Sino部署搭建大约 2 分钟

    1、 GitHubPage介绍

    1.1 ok

    1.2 搭建个人githubpage

    个人page和项目page的区别就是个人page只有一个,所谓的个人Page说白了也是一个特殊的项目Page,无非就是它的仓库名字比较特殊,必须为<username>.github.io,比如java框架`spring-cloud.github.io`、`facebook.github.io`,注意个人page的仓库名一定要加上 `.github.io`才算个人Page,不加的话就是一个普通项目了。
    @@ -94,7 +94,7 @@
               # 部署目录为 VuePress 的默认输出目录
               FOLDER: public
     
    -

    注意

    1. 在项目根路径新建一个.github/workflows/main.yml,yml中放以上内容
    2. 现在github主分支改成了main,所以需要在本地把分支修改一下git branch -M main
    3. 源码和打包后的public(一般是dist)文件夹分别放在两个分支,我这里源码在main分支,部署是在gh-pages分支,部署脚本会自动创建一个gh-pages分支。
    4. ACCESS_TOKEN这里不要直接放密钥,而是放你生成的密钥对应的名字(github设置中那个note字段)
    - +

    注意

    1. 在项目根路径新建一个.github/workflows/main.yml,yml中放以上内容
    2. 现在github主分支改成了main,所以需要在本地把分支修改一下git branch -M main
    3. 源码和打包后的public(一般是dist)文件夹分别放在两个分支,我这里源码在main分支,部署是在gh-pages分支,部署脚本会自动创建一个gh-pages分支。
    4. ACCESS_TOKEN这里不要直接放密钥,而是放你生成的密钥对应的名字(github设置中那个note字段)
    + diff --git "a/other/essay/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html" "b/other/essay/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html" index c39624dcb6..0b45f254f8 100644 --- "a/other/essay/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html" +++ "b/other/essay/IM\345\215\263\346\227\266\351\200\232\344\277\241\346\212\200\346\234\257\351\200\211\345\236\213.html" @@ -30,12 +30,12 @@ IM即时通信选型 | ChenSino - - + +
    跳至主要內容

    IM即时通信选型

    chensino原创大约 19 分钟

    1. 需求

    服务端开发语言:Java

    终端:Android、iOS、Web、小程序

    核心需求:发送文字、图片、文件、语音、视频、消息缓存、消息存储、消息未读、已读、撤回,离线消息、历史消息、单聊、群聊,多端同步,以及其他一些需求。

    用户管理:添加好友、查看好友列表、删除好友、查看好友信息、创建群聊、加入群聊、查看群成员信息、退出群聊、修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注以及其他用户相关的需求等。

    权限管理: 针对群聊不同用户有不同的权限,比如群主、管理员、普通成员,普通成员可以发送消息、撤回消息、删除消息、修改消息、查看消息等,管理员可以管理群成员,修改群昵称、拉人进群、踢人出群、解散群聊、填写群公告、修改群备注等。

    2. 原有方案评估

    2.1 webrtc即时通信

    优点:

    • 实时性强:WebRTC 的设计初衷就是为实时通信提供低延迟的解决方案,适合需要快速响应的应用场景。
    • P2P通信:WebRTC 支持点对点(P2P)通信,这可以减少延迟和服务器压力,适用于小规模的直接通信场景。
    • 跨平台支持:WebRTC 在现代浏览器中原生支持,适用于多平台的应用开发。 缺点
    • 复杂性:WebRTC 需要处理信令(比如通过 WebSocket 或其他方式),而且连接的建立和维护(如 NAT 穿透、ICE 候选等)较为复杂。
    • 消息持久化和历史记录:IM 通常需要消息持久化和历史记录功能,而 WebRTC 本身不具备这些功能,需要额外的服务器支持。
    • 扩展性问题:WebRTC 的 P2P 特性使其更适合于小规模的通信,如果用户量大或者需要支持大规模群聊,P2P 方式的扩展性会受到限制。
    • 服务器压力:虽然 P2P 减少了服务器的带宽负担,但信令服务器和消息的中转(在一些情况下)仍然需要服务器支持,尤其是当点对点连接不可用时。

    总结
    WebRTC 可以用于即时通信,但它的优缺点使其更适合特定的场景。例如,如果你需要实现一个注重实时性的小规模视频通话或音频通话的应用, WebRTC 是一个很好的选择。但如果要构建一个大规模的、需要强大消息持久化和历史记录支持的 IM 应用,那么传统的基于 WebSocket更加适合。

    远程会诊的通信技术使用的是WebRTC技术SFU架构,聊天功能模块是基于WebSocket的通信,WebRTC的优势是音视频通信,其即时聊天的多方建立连接需要依赖于信令服务器。 原有方案业务代码和会诊业务耦合度高,im通信不是其核心功能,很多功能需要从头实现,改造成本较大。

    3. IM通信的难点

    20240829105122

    3.1 消息传递可靠性问题

    离线消息需要被可靠地存储在服务器端,可能涉及大规模的数据存储需求。如何在不影响系统性能的情况下高效存储和检索离线消息? 用户从离线到在线的状态切换时,如何处理未同步的离线消息与新接收的实时消息,并确保两者的有序合并?

    3.2 消息重复问题

    20240829115908 由于网络的不可靠性,如果在如上图的环节5(ack)丢包,则会出现服务端认为消息发送成功,但是客户端认为消息发送失败的问题。当客户端重试后,如果不进行处理, 则会在数据库出现2条相同的消息。

    3.3 消息时序问题

    IM类系统中,都需要考虑消息时序问题,如果后发送的消息先显示,可能严重扰乱聊天消息所要表达的意义。

    消息时序是分布式系统架构设计中非常难的问题,一个分布式的IM系统必须要解决这个问题。

    IM系统中主要有两类消息 (1)单聊消息,两个人之间的聊天。需要确保发送方和接收方消息时序展示一致。 (2)群聊消息,一群人在一起聊天。需要确保所有接收方消息顺序一致。

    一、为什么会出现时序问题 1、时间不一致。 IM系统存在大量的客户端、IM服务器集群、长连接接入层集群、短连接接入层集群、数据库集群,这些应用分布在不同的机器上,时间很可能 不一致,时区也可能不一致。

    2、网络传输 网络传输延迟不同。同一用户后发送的消息可能早与先发送的消息到达服务器;不同用户的发送的消息到达服务器的延时差异可能更大。如下 图,msg1先发送,msg2后发送。由于网络原因,可能msg2先到达消息服务器

    3、服务集群时差 由于IM服务器分布式部署,不同的消息可能路由到不同的逻辑层处理。路由到不同logic的时延不同(尤其是跨机房),且不同logic之间存 在微量时差。

    4、消息处理速度不一致 服务器收到消息后,不同logic,不同线程对消息的处理速度可能不同,导致投递消息的时序出现错乱。

    3.4 音视频QoS问题

    网络抖动、视频延迟、丢包问题... 当同时有多路音视频通话时,如何节流,如何保证通话质量,一般云服务器的出站(上行)带宽价格是非常高昂的。

    跨网络如何保证质量? M应用需要在不同的网络环境下(如Wi-Fi、4G/5G、宽带等)保持一致的服务质量,这需要解决不同网络类型的QoS兼容问题。

    3.5 消息推送问题

    可靠的推送机制:在移动设备上,确保应用能够在后台接收消息,并及时推送通知给用户,即使应用未启动或网络不佳。 推送服务选择:根据不同平台(如Android、iOS)选择合适的推送服务,如Firebase Cloud Messaging(FCM)、APNs等,并确保推送消息的延迟和成功率。 推送策略优化:在推送大量消息时,设计合理的推送策略,避免过度消耗设备电量或造成用户干扰,同时提供灵活的推送设置供用户选择。

    3.6 技术栈问题

    音视频通话都是基于C语言或者C++开发的,超出了java技术范围。 20240830103446

    4. 技术选型目标

    20240812175557

    1)业务目标:满足需求分析篇章中的各类需求场景;
    2)技术目标:支持扩容,前期最大要能支持万级别用户同时在线聊天;
    3)架构目标:高性能、高可用、可监控、可预警、可伸缩,支持扩展。
    4)价格目标:免费开源或一次买断源码,再做二次开发

    4. 候选方案

    4.1 uni-im

    4.1.1 简介

    地址:https://doc.dcloud.net.cn/uniCloud/uni-im.htmlopen in new window

    uni-im是云端一体的、全平台的、免费的、开源即时通讯系统。

    • 基于uni-app,App、小程序、web全端兼容
    • 基于uniCloud,前后端都使用js开发
    • 基于uni-push2,专业稳定的全端推送系统
    • 基于uni-id,完善的账户体系
    • 支持服务端为非uniCloud(比如:应用服务端的开发语言是php、java、go、.net、python、c#等)或 不基于uni-id-pages 开发的项目接入

    4.1.2 优势

    • 比较详细的文档支持
    • 前端工作量大大减少
    • 全端可用
    • App端支持nvue,更好的长列表性能。list组件性能优势详情参考
    • 中心化响应式数据管理,切换会话无需重新加载数据,更流畅的体验
    • App端聚合多个手机厂商推送通道,app不在线也可以收到消息

    4.1.3 劣势

    • 服务收费,且价格不便宜
    • 无法私有化部署

    CDN流量、出网流量、存储费用比较贵

    4.1.4 费用

    按量计费:

    • 调用10000次云函数0.0133元
    • 调用10000次数据库查询仅0.015元

    私聊一次: 1次云函数请求、2次数据库读操作、2次数据库写操作、1次uni-push2推送操作,即 (1 * 0.0133 + 2 * 0.015 + 2 * 0.05 + 1 * 0.0283)/10000 ≈ 0.000017元

    500人的群聊:1次云函数请求、4次数据库读操作、2次数据库写操作、1次uni-push2推送操作,即 (1 * 0.0133 + 4 * 0.015 + 2 * 0.05 + 1 * 0.0283)/10000 ≈ 0.000020元

    资源分类 资源细项 售价(元)
    云函数 资源使用量(GBs) 0.000110592
    调用次数(万次) 0.0133
    出网流量(GB) 0.8
    云数据库 容量(GB/天) 0.07
    读操作使用量(万RU) 0.015
    写操作使用量(万RU) 0.05
    云存储 容量(GB/天) 0.0043
    下载操作次数(万次) 0.01
    上传操作次数(万次) 0.01
    CDN 流量(GB) 0.18
    前端网站托管 容量(GB/天) 0.0043
    流量(GB) 0.18

    套餐计费:

    资源分类 资源细项 免费版 基础版 标准版 专业版 企业版 旗舰版
    云函数 资源使用量(GBs/月) 1000 1万 20万 40万 150万 400万
    调用次数(万次/月) 1.5 15 300 600 2400 6000
    出网流量(GB/月) 1 1 20 40 160 500
    云数据库 容量(GB) 2 2 3 5 10 10
    读操作使用量(万RU/天) 0.05 5 25 50 150 500
    写操作使用量(万WU/天) 0.03 3 15 30 100 300
    集合数量 100 100 100 100 100 100
    索引数量 400 400 400 400 400 400
    云存储 容量(GB) 5 8 10 50 100 500
    下载操作次数(万次/月) 0.2 10 200 750 1500 3750
    上传操作次数(万次/月) 0.1 5 100 300 600 1500
    CDN流量(GB/月) 1 2 10 50 150 500
    前端网页托管 容量(GB) 5 8 10 50 100 500
    CDN流量(GB/月) 1 2 10 50 150 500
    售价(元/月) 免费 5 24 82 316 688

    名词解释:

    资源分类 资源细项 说明 数据更新延迟时间
    云函数 资源使用量(GBs) 资源使用量GBs = 函数配置内存GB × 运行计费时长s。 例如,配置为256MB的函数,单次运行了1760ms,计费时长为1760ms,则单次运行的资源使用量为(256 / 1024) × (1760 / 1000) = 0.44GBs 20分钟
    调用次数 - 20分钟
    出网流量(GB) 在云函数中访问外网时产生的出网流量,包含请求三方服务器发送的数据和返回给客户端的数据。 20分钟
    云数据库 容量(GB) - 1小时
    读操作使用量(RU) 读操作使用量(Read Unit)= ceil(查询数据量KB / 4),即从数据表中读取一条4 KB数据(向上取整)计作1RU,例如读取7.6 KB的数据计作2RU。 20分钟
    写操作使用量(WU) 写操作使用量(Write Unit)= ceil(写入数据量KB / 1),即向数据表中写入一条1 KB数据(向上取整)计作1WU,例如写入1.8 KB的数据计作2WU。 20分钟
    云存储 容量(GB) - 6小时
    下载操作次数 通过CDN加速访问的次数,回源次数暂不收费。 6小时
    上传操作次数 - 6小时
    CDN 流量(GB) 通过CDN加速产生的流量,回源流量暂不收费。 6小时
    前端网站托管 容量(GB) - 6小时
    CDN 流量(GB) 通过CDN加速产生的流量,回源流量暂不收费。 6小时

    4.2 J-IM

    GIT地址:https://gitee.com/xchao/j-imopen in new window

    4.2.1 简介

    J-IM 是用JAVA语言开发的轻量、高性能、单机支持几十万至百万在线用户IM,主要目标降低即时通讯门槛,快速打造低成本接入在线IM系统,通过极简洁的消息格式就可以实现多端不同协议间的消息发送如内置(Http、Websocket、Tcp自定义IM协议)等,并提供通过http协议的api接口进行消息发送无需关心接收端属于什么协议,一个消息格式搞定一切!

    4.2.2 特性

    1、高性能(单机可支持几十万至百万人同时在线) 2、轻量、可扩展性极强 3、支持集群多机部署 4、支持SSL/TLS加密传输 5、消息格式极其简洁(JSON) 6、一端口支持可插拔多种协议(Socket自定义IM协议、Websocket、Http),各协议可分别独立部署。 7、内置消息持久化(离线、历史、漫游),保证消息可靠性,高性能存储 8、各种丰富的API接口。 9、零成本部署,一键启动。

    4.2.3 劣势

    1. 项目不活跃长期不更新
    2. 没有完善的文档支持,官网打不开

    4.3 cim

    GIT地址: https://gitee.com/farsunset/cimopen in new window

    4.3.1 简介

    CIM是一套完善的消息推送框架,可应用于信令推送,即时聊天,移动设备指令推送等领域。开发者可沉浸于业务开发,不用关心消息通道长连接、消息编解码协议等繁杂处理。

    CIM采用业内主流开源技术构建,易于扩展和使用,并完美支持集群部署支持海量链接,目前支持websocket,android,ios,桌面应用,系统应用等多端接入持,可应用于移动应用,物联网,智能家居,嵌入式开发,桌面应用,WEB应用即时消服务。

    4.3.2 优势

    1. 维护时间比较长,10年了 ,目前还在活跃提交代码
    2. 提供多个客户端SDK(android/.net/fluttter/ios/swift/uniapp/web)
    3. 有web/vue/android的demo,有后台管理
    4. 文档详细

    4.3.3 劣势

    1. 按照平台SDK收费,uni-app(h5,android,ios)

    20240813112230

    4.4 V-IM

    GIT地址:https://gitee.com/alyouge/V-IMopen in new window

    4.4.1 简介

    开源与企业版功能点对比 20240813112349

    企业版优势

    多终端支持:PC(windows、linux、mac、web) 手机(安卓、IOS、H5、小程序); 上传支持两种方案(直接存服务器和minio); 私有云代码仓库永久更新,无加密部分,不依赖第三方。 一对一技术支持。 bug修复优先级最高。 支持付费定制化需求。 功能更新频率高。 聊天记录存储在mongoDB; 支持国产化部署,服务端已对接到snowy开源项目(分支版本)。

    4.4.2 企业版收费情况

    2980元,交付源码
    -

    4.4.3 劣势

    • 文档不够详细
    • git不活跃

    4.5 MobileIMSDK

    GIT地址:https://gitee.com/jackjiang/MobileIMSDKopen in new window

    2024083010352720240830103538

    4.5.1 简介

    历经10年、久经考验; 超轻量级、高度提炼,lib包50KB以内; 精心封装,一套API同时支持UDP、TCP、WebSocket三种协议(可能是全网唯一开源的); 客户端支持iOS、Android、标准Java、H5(暂未开源)、微信小程序(暂未开源)、Uniap(暂未开源); 服务端基于Netty,性能卓越、易于扩展 new; 可与姊妹工程 MobileIMSDK-Web 无缝互通实现网页端聊天或推送等; 可应用于跨设备、跨网络的聊天APP、企业OA、消息推送等各种场景。

    20240813114025

    4.5.2 优势

    1. 轻量级,可支持多端多协议,可扩展性极强
    2. 有自己的论坛,社区活跃,有开发者提供支持,作者响应及时
    3. 文档详细,收费版提供详细的代码注释
    4. 覆盖所有平台,所有平台客户端都提供源码,若需要定制可参考他的源码

    2024083009021120240830090249

    4.5.3 劣势

    1. 仅支持单机部署(单机支持10万人同时在线)
    2. 按照终端收费,安卓/ios/web端分开收费

    4.5.4 费用

    20240821173051

    4.6 野火IM/im-server

    GIT地址:https://gitee.com/wfchat/im-serveropen in new window

    4.6.1 简介

    野火IM是一套通用的即时通讯和实时音视频组件,能够更加容易地赋予客户IM和RTC能力,使客户可以快速的在自有产品上添加聊天和通话功能或者直接使用野火提供的应用。使用野火可以替代云通讯产品或减少自研即时通讯和实时音视频的工作量,降低客户研发成本和难度。

    4.6.2 提供产品

    1. 即时通讯服务(IM Server)
    2. 应用服务
    3. 推送服务
    4. Android 客户端
    5. iOS 客户端
    6. PC 客户端
    7. Web 客户端
    8. 小程序 Demo
    9. uni-app Demo
    10. 机器人服务
    11. 开发平台
    12. 频道(公众号)管理系统
    13. IM 管理后台系统

    4.6.3 优势

    1. 文档齐全
    2. 支持多端

    4.6.4 劣势

    1. 价格偏贵且后续升级需要付费(原价10% )
    2. 购买SDK要绑定IP/域名,无法部署到不同机器
    3. 只通过github提供技术支持,响应会很慢
    4. 即使付费购买,产品的功能库也是闭源的,不提供源码,只提供demo源码

    4.6.4 体验

    20240830090803

    4.6.5 收费

    20240830091017

    20240830091029

    20240830091040

    5. 总结

    大厂的im比如腾讯im,都是卖服务卖套餐收费高昂,包括uniapp也是,所以不考虑。 其他都是提供SDK,基于SDK进行业务开发,SDK提供的功能差不多, 其中MobileIMSDK的资料最详细, 支持所有平台的部署,可单机10万人同时在线,10万人对我们业务来说绰绰有余了。另外MobileIMSDK有自己的社区,他的的文档是 最详细的,从社区数据来看作者比较活跃,能及时响应。 综合从费用、开发成本、文档支持、社区支持几个维度来看,MobileIMSDK比较适合。

    - +

    4.4.3 劣势

    4.5 MobileIMSDK

    GIT地址:https://gitee.com/jackjiang/MobileIMSDKopen in new window

    2024083010352720240830103538

    4.5.1 简介

    历经10年、久经考验; 超轻量级、高度提炼,lib包50KB以内; 精心封装,一套API同时支持UDP、TCP、WebSocket三种协议(可能是全网唯一开源的); 客户端支持iOS、Android、标准Java、H5(暂未开源)、微信小程序(暂未开源)、Uniap(暂未开源); 服务端基于Netty,性能卓越、易于扩展 new; 可与姊妹工程 MobileIMSDK-Web 无缝互通实现网页端聊天或推送等; 可应用于跨设备、跨网络的聊天APP、企业OA、消息推送等各种场景。

    20240813114025

    4.5.2 优势

    1. 轻量级,可支持多端多协议,可扩展性极强
    2. 有自己的论坛,社区活跃,有开发者提供支持,作者响应及时
    3. 文档详细,收费版提供详细的代码注释
    4. 覆盖所有平台,所有平台客户端都提供源码,若需要定制可参考他的源码

    2024083009021120240830090249

    4.5.3 劣势

    1. 仅支持单机部署(单机支持10万人同时在线)
    2. 按照终端收费,安卓/ios/web端分开收费

    4.5.4 费用

    20240821173051

    4.6 野火IM/im-server

    GIT地址:https://gitee.com/wfchat/im-serveropen in new window

    4.6.1 简介

    野火IM是一套通用的即时通讯和实时音视频组件,能够更加容易地赋予客户IM和RTC能力,使客户可以快速的在自有产品上添加聊天和通话功能或者直接使用野火提供的应用。使用野火可以替代云通讯产品或减少自研即时通讯和实时音视频的工作量,降低客户研发成本和难度。

    4.6.2 提供产品

    1. 即时通讯服务(IM Server)
    2. 应用服务
    3. 推送服务
    4. Android 客户端
    5. iOS 客户端
    6. PC 客户端
    7. Web 客户端
    8. 小程序 Demo
    9. uni-app Demo
    10. 机器人服务
    11. 开发平台
    12. 频道(公众号)管理系统
    13. IM 管理后台系统

    4.6.3 优势

    1. 文档齐全
    2. 支持多端

    4.6.4 劣势

    1. 价格偏贵且后续升级需要付费(原价10% )
    2. 购买SDK要绑定IP/域名,无法部署到不同机器
    3. 只通过github提供技术支持,响应会很慢
    4. 即使付费购买,产品的功能库也是闭源的,不提供源码,只提供demo源码

    4.6.4 体验

    20240830090803

    4.6.5 收费

    20240830091017

    20240830091029

    20240830091040

    5. 总结

    大厂的im比如腾讯im,都是卖服务卖套餐收费高昂,包括uniapp也是,所以不考虑。 其他都是提供SDK,基于SDK进行业务开发,SDK提供的功能差不多, 其中MobileIMSDK的资料最详细, 支持所有平台的部署,可单机10万人同时在线,10万人对我们业务来说绰绰有余了。另外MobileIMSDK有自己的社区,他的的文档是 最详细的,从社区数据来看作者比较活跃,能及时响应。 综合从费用、开发成本、文档支持、社区支持几个维度来看,MobileIMSDK比较适合。

    + diff --git a/other/essay/Jenkins.html b/other/essay/Jenkins.html index c7e5b36411..da528fa2eb 100644 --- a/other/essay/Jenkins.html +++ b/other/essay/Jenkins.html @@ -30,8 +30,8 @@ jenkins部署及使用 | ChenSino - - + +
    跳至主要內容

    jenkins部署及使用

    chenkun运维大约 5 分钟

    1、jenkins插件更新报错

    1.1 报错如下,ssl证书问题

    image-20220415144539319

    Jenkins(2020年及以后版本,2.260以上)安装后,插件下载时失败,网上找了各种解决方法,修改jenkins插件的下载源地址:

    找到菜单Manage Jenkins → Manage Plugins → Advanced → Update Site,

    把URL改为 https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

    或把默认地址 https://updates.jenkins.io/update-center.json 的https改为http再重启。

    我使用的是最新版本(2022-04-15),使用以上方式无效。

    1.2 解决办法

    1. 新建一个java源文件InstallCert.java,内容如下。
    2. 编译javac InstallCert.java
    3. 执行java InstallCert <hostname>出现提示后按1,回车。会生成jssecacerts 文件。hostname是mirrors.tuna.tsinghua.edu.cn,记得在jenkins中把插件仓库地址设置为https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
    4. 将第3步生成的jssecacerts 拷贝到$JAVA_HOME/jre/lib/security
    5. 重启jenkins
    6. 重新更新插件
    /*
    @@ -265,7 +265,7 @@
         nohup java -jar -server -Xms1000m -Xmx1000m ${JAR_FILE}|tee ${CCS_DATA_LOG}|sed -e '/send-msg->/Q' &
     fi
     exit 0
    -

    关键是后面这个启动命令nohup java -jar -server -Xms1000m -Xmx1000m ${JAR_FILE}|tee ${CCS_DATA_LOG}|sed -e '/send-msg->/Q' &

    使用tee命令把输出源写入文件${CCS_DATA_LOG},同时在控制台也要显示,sed -e '/send-msg->/Q'这个很重要,代表当日志出现内容send-msg->时结束,一定要给启动命令一个结束标志,不然会把jenkins流水阻塞。

    - +

    关键是后面这个启动命令nohup java -jar -server -Xms1000m -Xmx1000m ${JAR_FILE}|tee ${CCS_DATA_LOG}|sed -e '/send-msg->/Q' &

    使用tee命令把输出源写入文件${CCS_DATA_LOG},同时在控制台也要显示,sed -e '/send-msg->/Q'这个很重要,代表当日志出现内容send-msg->时结束,一定要给启动命令一个结束标志,不然会把jenkins流水阻塞。

    + diff --git a/other/essay/TyporaPicgo.html b/other/essay/TyporaPicgo.html index da448e00f5..a18fe13276 100644 --- a/other/essay/TyporaPicgo.html +++ b/other/essay/TyporaPicgo.html @@ -30,8 +30,8 @@ 在Typora中使用Picgo | ChenSino - - + +
    跳至主要內容

    在Typora中使用Picgo

    ChenSino大约 2 分钟

    前言

    平时用MarkDown写博客少不了需要截图,我用的是Typora,刚开始截图是保存在本地,有时想把博客分享到网上,就发现各种图全挂了,需要手动一个一个再复制一下,着实麻烦,今天无意间发现有个叫PicGo的工具,此工具专门上传图到各大图床,着实方便。

    picgo配置open in new window

    picgo-coreopen in new window

    ==picgo的ui剪贴板上传功能,需要win10+==

    1、搭建Typora + PicGo + gitee

    我的电脑环境如下,

    								  OS: Manjaro 21.2.4 Qonos
    @@ -70,7 +70,7 @@
         "lastSync": "2022-03-09 02:50:05"
       }
     }
    -

    1.5 、 第五步

    测试上传图片

    image-20220309145521751

    2、给typora中picgo设置代理

    最简单的方法是用gui先设置好代理,然后通过ui打开配置,再复制到typora中picgo配置文件下

    PicGo的配置文件在不同系统里是不一样的。

    Windows: %APPDATA%\picgo\data.json Linux: $XDG_CONFIG_HOME/picgo/data.json or ~/.config/picgo/data.json macOS: ~/Library/Application\ Support/picgo/data.json

    - +

    1.5 、 第五步

    测试上传图片

    image-20220309145521751

    2、给typora中picgo设置代理

    最简单的方法是用gui先设置好代理,然后通过ui打开配置,再复制到typora中picgo配置文件下

    PicGo的配置文件在不同系统里是不一样的。

    Windows: %APPDATA%\picgo\data.json Linux: $XDG_CONFIG_HOME/picgo/data.json or ~/.config/picgo/data.json macOS: ~/Library/Application\ Support/picgo/data.json

    + diff --git "a/other/essay/elasticSearch\346\223\215\344\275\234.html" "b/other/essay/elasticSearch\346\223\215\344\275\234.html" index aa3756b355..fbf8bc9f7a 100644 --- "a/other/essay/elasticSearch\346\223\215\344\275\234.html" +++ "b/other/essay/elasticSearch\346\223\215\344\275\234.html" @@ -30,8 +30,8 @@ elasticsearch操作 | ChenSino - - + +
    跳至主要內容

    elasticsearch操作

    chensino原创大约 21 分钟

    1.索引库操作

    索引库就类似数据库表,mapping映射就类似表的结构。

    我们要向es中存储数据,必须先创建“库”和“表”

    1.1 Mapping映射属性

    mapping是对索引库中文档的约束,常见的mapping属性包括:

    • type:字段数据类型,常见的简单类型有:

      • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
      • keyword类型只能整体搜索,不支持搜索部分内容
      • 数值:long、integer、short、byte、double、float、
      • 布尔:boolean
      • 日期:date
      • 对象:object
    • index:是否创建索引,默认为true

    • analyzer:使用哪种分词器

    • properties:该字段的子字段

    例如以下json文档:

    {
    @@ -416,7 +416,7 @@
         <artifactId>elasticsearch-java</artifactId>
         <version>8.14.3</version>
       </dependency>
    -

    推荐直接使用第4个ava API Client

    - +

    推荐直接使用第4个ava API Client

    + diff --git "a/other/essay/elk\351\203\250\347\275\262.html" "b/other/essay/elk\351\203\250\347\275\262.html" index a6aa4542f0..6d6a9d65f8 100644 --- "a/other/essay/elk\351\203\250\347\275\262.html" +++ "b/other/essay/elk\351\203\250\347\275\262.html" @@ -30,8 +30,8 @@ ELK 部署 | ChenSino - - + + - +
    + diff --git "a/other/essay/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html" "b/other/essay/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html" index 532c88aef0..594be747b6 100644 --- "a/other/essay/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html" +++ "b/other/essay/im\345\215\263\346\227\266\351\200\232\344\277\241\347\232\204\351\234\200\346\261\202.html" @@ -30,11 +30,11 @@ 即时通信软件需求 | ChenSino - - + + -
    跳至主要內容

    即时通信软件需求

    chenkun原创大约 10 分钟

    即时通信需求文档

    1. 概述

    1.1 项目背景

    随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。

    1.2 项目目标

    开发一款满足万人级别同时在线的即时通信软件,专门为医院内的医生、护士、行政人员等设计。该软件旨在提升院内信息传递的效率,确保信息的及时性和安全性, 从而提高医疗服务的质量和工作效率。

    1.3 用户群体

    • 医生:科室主任、主治医师、住院医师等。
    • 护士:护士长、责任护士、实习护士等。
    • 行政人员:医院管理层、科室秘书、后勤人员等。

    2. 功能需求

    2.1 基础功能

    2.1.1 单聊

    1. 消息发送与接收

    • 文本消息:

      • 用户能够发送和接收文字消息。
      • 支持多行文本,自动换行显示。
      • 支持文字消息的格式化(如粗体、斜体、链接)。
    • 语音消息:

      • 用户能够录制、发送和接收语音消息。
      • 支持语音消息的播放、暂停、停止、重听功能。
      • 显示语音消息的时长和发送/接收状态。
    • 语音通话

      • 用户能够发起和接受语音通话、挂断语音。
      • 支持语音通话的开关麦克风、开关静音、免提功能。
    • 视频聊天

      • 用户能够发起和接受视频通话、挂断视频。
      • 支持视频通话的开关摄像头、开关麦克风、开关静音、免提功能。
    • 视频消息:

      • 用户能够录制并发送视频消息。
      • 支持视频消息的播放、暂停、停止。
      • 显示视频消息的时长和缩略图。
    • 图片和文件:

      • 用户能够发送和接收图片、音频文件、文档等。
      • 支持查看、下载、分享图片和文件。
      • 支持图片的预览、放大和缩小功能。
    • 消息状态:

      • 显示消息的发送状态(如已发送、已接收、已读)。
      • 支持消息的发送时间和接收时间显示。

    2. 聊天界面

    • 聊天窗口:

      • 支持显示完整的聊天记录,自动加载历史消息。
      • 支持消息的上下滚动查看。
      • 显示对方和自己的头像、昵称及状态(如在线/离线)。
    • 消息气泡:

      • 消息以气泡形式显示,发送和接收消息使用不同的气泡样式。
      • 支持长按消息气泡进行操作(如复制、转发、删除)。
    • 聊天记录:

      • 支持按日期、联系人等条件搜索聊天记录。
      • 支持聊天记录的备份和恢复。

    3. 消息互动

    • 回复和引用:

      • 支持对消息进行回复和引用功能。
      • 被回复的消息显示为线程或嵌套形式。
    • 转发消息:

      • 支持将消息转发至其他聊天窗口或联系人。
      • 支持批量转发功能。
    • 表情和贴图:

      • 支持发送和接收表情、贴图。
    • 消息撤回和删除:

      • 支持在一定时间内撤回已发送的消息。
      • 支持删除聊天记录(包括单条消息和整个聊天记录)。

    4. 通知和提醒

    • 消息通知:
      • 支持新消息的推送通知(包括声音、震动、横幅)。
      • 支持自定义通知设置(如通知声音、提醒方式)。
    • 聊天提示:
      • 支持未读消息的提示(如红点、消息提醒)。

    5. 离线消息

    • 离线消息接收:

      • 用户在离线状态时能够接收并查看离线期间的消息。
      • 支持离线消息的同步显示。
    • 消息同步:

      • 支持消息在不同设备之间的同步(如手机、电脑)。

    2.1.2 群聊

    用户可以创建群聊,邀请其他用户加入,方便科室或项目组内部的多人沟通。

    1. 群组创建与管理

    • 创建群组:

      • 用户能够创建新的群组,并设置群组名称、头像、简介。
      • 提供选择群组类型(如公开、私密)的选项。
    • 群组邀请:

      • 群主和管理员能够邀请联系人加入群组。
      • 支持通过邀请链接、二维码等方式邀请用户。
    • 群组设置:

      • 群主能够修改群组的基本信息(名称、头像、简介)。
      • 群主和管理员设置群公告。
    • 成员管理:

      • 群主和管理员能够添加、移除群组成员。
    • 权限管理

      • 群主能够设置群成员的权限级别(普通成员、管理员)。
      • 群主可解散群组,转让群主。
      • 除群主外的所有成员可以退群。
      • 非群内成员可以申请加入,需要管理审批通过方可进群
    • 加入群组

      • 用户搜索群组,选择群组,点击申请加入

    2. 群组聊天

    • 消息发送:

      • 支持在群聊中发送文字、语音、图片、视频和文件。
      • 支持群内消息的“@”功能,提醒特定成员。
    • 消息显示:

      • 支持显示群聊中每条消息的发送者头像、昵称、时间等。
      • 消息气泡以不同颜色标识发送者和接收者。
    • 消息通知:

      • 支持群聊消息的推送通知。
      • 用户能够设置接收所有消息、仅接收@消息或静音通知。
    • 消息管理:

      • 支持查看消息的发送状态(已发送、已接收、已读)。
      • 支持消息的回复和引用功能。
    • 群聊搜索:

      • 支持按关键词、发送者、日期等搜索群聊记录。
      • 提供搜索结果高亮显示功能。
    • 消息撤回:

      • 支持在一定时间内撤回已发送的群聊消息。
    • 历史消息

      • 群默认显示最近的历史消息
      • 可根据关键字、时间段搜索群消息

    3. 群组互动功能

    • 群成员列表:

      • 显示群组成员的列表,包括头像和昵称。
      • 支持查看成员的在线状态和活动情况。
    • 群内互动:

      • 支持群成员之间的私聊功能。
      • 支持在群聊中进行投票、问卷调查等互动功能。
    • 群组公告:

      • 群主能够发布群组公告,所有成员都能看到。
      • 支持公告的编辑和删除。
    • 文件共享与管理:

      • 支持在群组内上传、下载和分享文件。
      • 支持群组文件的分类和管理(如按日期、类型)。

    2.1.3 消息通知

    针对重要信息进行通知提醒,用户可以在设置中调整通知方式(如铃声、震动、弹窗等)。

    • 支持紧急消息提醒功能,必要时医生可以发送高优先级消息,并强制提醒对方。
    • 支持离线消息推送功能,即使用户不在线也能接收到消息提醒。
    • 广播通知

    2.1.4 好友管理

    1. 好友添加和删除

    • 添加好友:

      • 通过搜索:用户可以通过搜索功能查找和添加好友。搜索方式包括用户名、手机号、邮箱等。
      • 通过二维码:用户可以通过扫描对方的二维码添加好友。
      • 通过邀请链接:用户可以发送邀请链接给他人,通过链接添加好友。
      • 通过联系人导入:支持从手机或其他社交网络导入联系人并添加为好友。
    • 删除好友:

      • 用户可以从好友列表中删除好友。
      • 删除好友后,双方将不再互相显示在线状态和消息。
    • 好友请求管理:

      • 接收请求:用户能够接收好友请求,查看请求者的基本信息(如头像、昵称)。
      • 接受/拒绝请求:用户可以选择接受或拒绝好友请求。
      • 请求记录:支持查看已发送和已接收的好友请求记录。

    2. 好友列表管理

    • 好友列表显示:

      • 显示好友的基本信息,包括头像、昵称、在线状态。
      • 支持按字母排序、按最近聊天排序或按自定义分组查看好友列表。
    • 好友备注:

      • 用户可以为好友设置备注,以便于识别和管理。
      • 支持编辑和删除好友备注。

    3. 好友互动

    • 查看好友状态:
      • 用户可以查看好友的在线状态(如在线、离线、隐身)。
      • 显示好友的最近活动状态(如最后登录时间、最近聊天记录)。

    4. 隐私和安全设置

    • 隐私设置:

      • 谁可以联系我:用户可以设置谁可以向我发送消息(如所有人、好友、仅群组成员)。
      • 谁可以查看我的状态:用户可以设置谁可以查看自己的在线状态、最后登录时间。
      • 谁可以查看我的资料:设置好友或非好友的资料查看权限。
    • 拉黑与举报: 拉黑好友:用户可以将某些好友拉黑,拉黑后对方将无法向用户发送消息和查看用户状态。 举报好友:用户可以举报好友的异常行为或违规行为,系统将进行审查。

    5. 好友同步与备份

    • 好友同步:
      • 支持好友列表在不同设备间同步(如手机、电脑)。
      • 支持在设备更换时快速恢复好友列表。
      • 消息在不同设备同步

    2.1.5 聊天记录

    1. 支持聊天记录的搜索功能(根据关键字、日期、联系人等)。
    2. 支持聊天记录的备份和恢复。

    2.2 自动回复

    • 自动回复内容管理
    • 支持在特定情况下使用自动回复或机器人处理消息。

    2.3 安全与合规

    • 消息加密:

      • 支持端到端加密,确保消息内容的安全性。
    • 隐私设置:

      • 支持设置隐私选项(如谁可以向我发送消息、消息通知设置)。
    • 账号保护:

      • 支持账号的安全设置(如2FA两步验证、密码保护)。
    • 日志记录与审计:

      • 记录敏感操作日志,供管理人员审计和追踪。
    • 系统设计需符合医疗行业的合规性要求

    • 数据要有备份

    2.4 高级功能

    2.4.1 病例讨论

    为医生提供病例讨论的专属功能,支持在群聊中讨论具体病历,并对讨论内容进行结构化保存。

    • 支持病例资料上传(如CT图像、检验报告等)。
    • 支持标记重点内容,方便后期查阅。

    2.4.2 文件共享与存储

    提供云盘功能,支持在群聊和单聊中直接分享文件,并进行分类存储和权限管理。

    • 支持文件的在线预览及下载。
    • 支持对文件的版本管理,方便查看历史版本。

    2.5 跨平台支持

    软件应支持在多种设备上使用,包括但不限于PC、手机、平板等。

    • 支持iOS和Android系统的手机应用。
    • 支持Windows和macOS的桌面应用。

    2.6 其他需求

    2.6.1 扩展性

    系统设计需具有良好的扩展性,支持未来新增功能的扩展。

    • 支持API接口,便于与其他系统进行数据交互。
    • 支持不同语言客户端

    3. 非功能需求

    3.1 性能要求

    • 系统应能够在十万级用户同时在线时保持流畅运行。
    • 消息的平均延迟应控制在1秒以内。

    3.2 安全要求

    • 系统需通过严格的安全测试,防止数据泄露和外部攻击。
    • 所有数据存储和传输均需符合国家信息安全标准。

    3.3 可维护性

    • 系统架构需清晰,代码需具备良好的可读性和注释。
    • 提供详细的技术文档和用户手册,便于后续维护与升级。

    4. 风险管理

    • 技术风险:可能遇到性能瓶颈,需提前进行压力测试。
    • 安全风险:需重点关注数据安全问题,确保所有安全措施到位。
    • 用户接受度风险:需在上线前进行广泛的用户培训,确保软件顺利推广。
    - +
    跳至主要內容

    即时通信软件需求

    chenkun原创大约 10 分钟

    即时通信需求文档

    1. 概述

    1.1 项目背景

    随着医院规模的扩大及医疗服务的复杂化,医疗人员之间的信息交流需求日益增加。传统的电话、短信等方式已难以满足医院内部高效、及时、保密的沟通需求。因此,开发一款专门面向医院人员的即时通信软件势在必行。

    1.2 项目目标

    开发一款满足万人级别同时在线的即时通信软件,专门为医院内的医生、护士、行政人员等设计。该软件旨在提升院内信息传递的效率,确保信息的及时性和安全性, 从而提高医疗服务的质量和工作效率。

    1.3 用户群体

    • 医生:科室主任、主治医师、住院医师等。
    • 护士:护士长、责任护士、实习护士等。
    • 行政人员:医院管理层、科室秘书、后勤人员等。

    2. 功能需求

    2.1 基础功能

    2.1.1 单聊

    1. 消息发送与接收

    • 文本消息:

      • 用户能够发送和接收文字消息。
      • 支持多行文本,自动换行显示。
      • 支持文字消息的格式化(如粗体、斜体、链接)。
    • 语音消息:

      • 用户能够录制、发送和接收语音消息。
      • 支持语音消息的播放、暂停、停止、重听功能。
      • 显示语音消息的时长和发送/接收状态。
    • 语音通话

      • 用户能够发起和接受语音通话、挂断语音。
      • 支持语音通话的开关麦克风、开关静音、免提功能。
    • 视频聊天

      • 用户能够发起和接受视频通话、挂断视频。
      • 支持视频通话的开关摄像头、开关麦克风、开关静音、免提功能。
    • 视频消息:

      • 用户能够录制并发送视频消息。
      • 支持视频消息的播放、暂停、停止。
      • 显示视频消息的时长和缩略图。
    • 图片和文件:

      • 用户能够发送和接收图片、音频文件、文档等。
      • 支持查看、下载、分享图片和文件。
      • 支持图片的预览、放大和缩小功能。
    • 消息状态:

      • 显示消息的发送状态(如已发送、已接收、已读)。
      • 支持消息的发送时间和接收时间显示。

    2. 聊天界面

    • 聊天窗口:

      • 支持显示完整的聊天记录,自动加载历史消息。
      • 支持消息的上下滚动查看。
      • 显示对方和自己的头像、昵称及状态(如在线/离线)。
    • 消息气泡:

      • 消息以气泡形式显示,发送和接收消息使用不同的气泡样式。
      • 支持长按消息气泡进行操作(如复制、转发、删除)。
    • 聊天记录:

      • 支持按日期、联系人等条件搜索聊天记录。
      • 支持聊天记录的备份和恢复。

    3. 消息互动

    • 回复和引用:

      • 支持对消息进行回复和引用功能。
      • 被回复的消息显示为线程或嵌套形式。
    • 转发消息:

      • 支持将消息转发至其他聊天窗口或联系人。
      • 支持批量转发功能。
    • 表情和贴图:

      • 支持发送和接收表情、贴图。
    • 消息撤回和删除:

      • 支持在一定时间内撤回已发送的消息。
      • 支持删除聊天记录(包括单条消息和整个聊天记录)。

    4. 通知和提醒

    • 消息通知:
      • 支持新消息的推送通知(包括声音、震动、横幅)。
      • 支持自定义通知设置(如通知声音、提醒方式)。
    • 聊天提示:
      • 支持未读消息的提示(如红点、消息提醒)。

    5. 离线消息

    • 离线消息接收:

      • 用户在离线状态时能够接收并查看离线期间的消息。
      • 支持离线消息的同步显示。
    • 消息同步:

      • 支持消息在不同设备之间的同步(如手机、电脑)。

    2.1.2 群聊

    用户可以创建群聊,邀请其他用户加入,方便科室或项目组内部的多人沟通。

    1. 群组创建与管理

    • 创建群组:

      • 用户能够创建新的群组,并设置群组名称、头像、简介。
      • 提供选择群组类型(如公开、私密)的选项。
    • 群组邀请:

      • 群主和管理员能够邀请联系人加入群组。
      • 支持通过邀请链接、二维码等方式邀请用户。
    • 群组设置:

      • 群主能够修改群组的基本信息(名称、头像、简介)。
      • 群主和管理员设置群公告。
    • 成员管理:

      • 群主和管理员能够添加、移除群组成员。
    • 权限管理

      • 群主能够设置群成员的权限级别(普通成员、管理员)。
      • 群主可解散群组,转让群主。
      • 除群主外的所有成员可以退群。
      • 非群内成员可以申请加入,需要管理审批通过方可进群
    • 加入群组

      • 用户搜索群组,选择群组,点击申请加入

    2. 群组聊天

    • 消息发送:

      • 支持在群聊中发送文字、语音、图片、视频和文件。
      • 支持群内消息的“@”功能,提醒特定成员。
    • 消息显示:

      • 支持显示群聊中每条消息的发送者头像、昵称、时间等。
      • 消息气泡以不同颜色标识发送者和接收者。
    • 消息通知:

      • 支持群聊消息的推送通知。
      • 用户能够设置接收所有消息、仅接收@消息或静音通知。
    • 消息管理:

      • 支持查看消息的发送状态(已发送、已接收、已读)。
      • 支持消息的回复和引用功能。
    • 群聊搜索:

      • 支持按关键词、发送者、日期等搜索群聊记录。
      • 提供搜索结果高亮显示功能。
    • 消息撤回:

      • 支持在一定时间内撤回已发送的群聊消息。
    • 历史消息

      • 群默认显示最近的历史消息
      • 可根据关键字、时间段搜索群消息

    3. 群组互动功能

    • 群成员列表:

      • 显示群组成员的列表,包括头像和昵称。
      • 支持查看成员的在线状态和活动情况。
    • 群内互动:

      • 支持群成员之间的私聊功能。
      • 支持在群聊中进行投票、问卷调查等互动功能。
    • 群组公告:

      • 群主能够发布群组公告,所有成员都能看到。
      • 支持公告的编辑和删除。
    • 文件共享与管理:

      • 支持在群组内上传、下载和分享文件。
      • 支持群组文件的分类和管理(如按日期、类型)。

    2.1.3 消息通知

    针对重要信息进行通知提醒,用户可以在设置中调整通知方式(如铃声、震动、弹窗等)。

    • 支持紧急消息提醒功能,必要时医生可以发送高优先级消息,并强制提醒对方。
    • 支持离线消息推送功能,即使用户不在线也能接收到消息提醒。
    • 广播通知

    2.1.4 好友管理

    1. 好友添加和删除

    • 添加好友:

      • 通过搜索:用户可以通过搜索功能查找和添加好友。搜索方式包括用户名、手机号、邮箱等。
      • 通过二维码:用户可以通过扫描对方的二维码添加好友。
      • 通过邀请链接:用户可以发送邀请链接给他人,通过链接添加好友。
      • 通过联系人导入:支持从手机或其他社交网络导入联系人并添加为好友。
    • 删除好友:

      • 用户可以从好友列表中删除好友。
      • 删除好友后,双方将不再互相显示在线状态和消息。
    • 好友请求管理:

      • 接收请求:用户能够接收好友请求,查看请求者的基本信息(如头像、昵称)。
      • 接受/拒绝请求:用户可以选择接受或拒绝好友请求。
      • 请求记录:支持查看已发送和已接收的好友请求记录。

    2. 好友列表管理

    • 好友列表显示:

      • 显示好友的基本信息,包括头像、昵称、在线状态。
      • 支持按字母排序、按最近聊天排序或按自定义分组查看好友列表。
    • 好友备注:

      • 用户可以为好友设置备注,以便于识别和管理。
      • 支持编辑和删除好友备注。

    3. 好友互动

    • 查看好友状态:
      • 用户可以查看好友的在线状态(如在线、离线、隐身)。
      • 显示好友的最近活动状态(如最后登录时间、最近聊天记录)。

    4. 隐私和安全设置

    • 隐私设置:

      • 谁可以联系我:用户可以设置谁可以向我发送消息(如所有人、好友、仅群组成员)。
      • 谁可以查看我的状态:用户可以设置谁可以查看自己的在线状态、最后登录时间。
      • 谁可以查看我的资料:设置好友或非好友的资料查看权限。
    • 拉黑与举报: 拉黑好友:用户可以将某些好友拉黑,拉黑后对方将无法向用户发送消息和查看用户状态。 举报好友:用户可以举报好友的异常行为或违规行为,系统将进行审查。

    5. 好友同步与备份

    • 好友同步:
      • 支持好友列表在不同设备间同步(如手机、电脑)。
      • 支持在设备更换时快速恢复好友列表。
      • 消息在不同设备同步

    2.1.5 聊天记录

    1. 支持聊天记录的搜索功能(根据关键字、日期、联系人等)。
    2. 支持聊天记录的备份和恢复。

    2.2 自动回复

    • 自动回复内容管理
    • 支持在特定情况下使用自动回复或机器人处理消息。

    2.3 安全与合规

    • 消息加密:

      • 支持端到端加密,确保消息内容的安全性。
    • 隐私设置:

      • 支持设置隐私选项(如谁可以向我发送消息、消息通知设置)。
    • 账号保护:

      • 支持账号的安全设置(如2FA两步验证、密码保护)。
    • 日志记录与审计:

      • 记录敏感操作日志,供管理人员审计和追踪。
    • 系统设计需符合医疗行业的合规性要求

    • 数据要有备份

    2.4 高级功能

    2.4.1 病例讨论

    为医生提供病例讨论的专属功能,支持在群聊中讨论具体病历,并对讨论内容进行结构化保存。

    • 支持病例资料上传(如CT图像、检验报告等)。
    • 支持标记重点内容,方便后期查阅。

    2.4.2 文件共享与存储

    提供云盘功能,支持在群聊和单聊中直接分享文件,并进行分类存储和权限管理。

    • 支持文件的在线预览及下载。
    • 支持对文件的版本管理,方便查看历史版本。

    2.5 跨平台支持

    软件应支持在多种设备上使用,包括但不限于PC、手机、平板等。

    • 支持iOS和Android系统的手机应用。
    • 支持Windows和macOS的桌面应用。

    2.6 其他需求

    2.6.1 扩展性

    系统设计需具有良好的扩展性,支持未来新增功能的扩展。

    • 支持API接口,便于与其他系统进行数据交互。
    • 支持不同语言客户端

    3. 非功能需求

    3.1 性能要求

    • 系统应能够在十万级用户同时在线时保持流畅运行。
    • 消息的平均延迟应控制在1秒以内。

    3.2 安全要求

    • 系统需通过严格的安全测试,防止数据泄露和外部攻击。
    • 所有数据存储和传输均需符合国家信息安全标准。

    3.3 可维护性

    • 系统架构需清晰,代码需具备良好的可读性和注释。
    • 提供详细的技术文档和用户手册,便于后续维护与升级。

    4. 风险管理

    • 技术风险:可能遇到性能瓶颈,需提前进行压力测试。
    • 安全风险:需重点关注数据安全问题,确保所有安全措施到位。
    • 用户接受度风险:需在上线前进行广泛的用户培训,确保软件顺利推广。
    + diff --git a/other/essay/index.html b/other/essay/index.html index 7c4b43e67f..e21b7da1a3 100644 --- a/other/essay/index.html +++ b/other/essay/index.html @@ -30,11 +30,11 @@ 随笔分享 | ChenSino - - + + - - + + diff --git "a/other/essay/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html" "b/other/essay/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html" index b7cca80900..f2ef46f838 100644 --- "a/other/essay/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html" +++ "b/other/essay/windows\344\270\213\346\234\215\345\212\241\346\263\250\345\206\214.html" @@ -30,8 +30,8 @@ windows下服务注册 | ChenSino - - + + - +
    + diff --git "a/other/essay/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html" "b/other/essay/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html" index c3867e94ae..18ecda7ef8 100644 --- "a/other/essay/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html" +++ "b/other/essay/\346\223\215\344\275\234\347\263\273\347\273\237\345\274\225\345\257\274.html" @@ -30,8 +30,8 @@ 系统引导基本名词BIOS/EFI/MBR/GPT/GRUB | ChenSino - - + +
    跳至主要內容

    系统引导基本名词BIOS/EFI/MBR/GPT/GRUB

    chensino原创大约 5 分钟

    对应关系

    备注
    固件UEFI/EFIBIOS
    分区格式GPTMBR
    启动引导程序windows:Windows Boot Managerlinux:GRUB

    老版本的组合是BIOS+MBR启动引导,BIOS是写在主板中的程序,老版本的引导方式有诸多限制,

    # BIOS和uefi区别
    @@ -121,7 +121,7 @@
     包含冗余的分区表,有助于数据恢复。
     分区和分区表中包含的数据结构更加先进和灵活。
     因此,在GPT分区表中,你可以直接创建多个分区,而不需要像在MBR分区表中那样划分扩展分区和逻辑分区。这使得磁盘管理更为简单和高效。
    -
    - +
    + diff --git a/other/git/BatchDeleteGitHubRepo.html b/other/git/BatchDeleteGitHubRepo.html index 83fd6189e4..c164d12ea7 100644 --- a/other/git/BatchDeleteGitHubRepo.html +++ b/other/git/BatchDeleteGitHubRepo.html @@ -30,8 +30,8 @@ 批量删除github仓库 | ChenSino - - + +
    跳至主要內容

    批量删除github仓库

    ChenSino原创小于 1 分钟

    背景

    github上fork了很多仓库,但是平时又没看,所以索性删除,一个个删除又很慢,所以搞个脚本批量删除

    方法

    1. 到github配置一个token,https://github.com/settings/tokens
    @@ -49,7 +49,7 @@
         echo "Delete "$repName
         curl -XDELETE -H "Authorization: token ${DELETE_KOKEN}" https://api.github.com/repos/${GithubName}/${repName}
     done
    -
    - +
    + diff --git a/other/git/GitCommands.html b/other/git/GitCommands.html index 985bcfc8a2..faaf438289 100644 --- a/other/git/GitCommands.html +++ b/other/git/GitCommands.html @@ -30,8 +30,8 @@ git命令 | ChenSino - - + + - +
    + diff --git a/other/git/branch01.html b/other/git/branch01.html index db3f1d46bc..108d0f1da6 100644 --- a/other/git/branch01.html +++ b/other/git/branch01.html @@ -30,15 +30,15 @@ git 拉取远程分支到本地 | ChenSino - - + +
    跳至主要內容

    git 拉取远程分支到本地

    ChenSinogit 操作必会小于 1 分钟

    1,查看远程分支

    可以查看远程分支名,查看要切换的远程分支是否存在。

    git branch -a
     

    2, 查看本地分支

    查看本地是否已经有了要切换的分支,如果有,可以直接git checkout 分支名来切换分支。

    git branch 
     

    3,拉取远程分支

    比如我把远程dev分支拉下来。

    git fetch origin dev
     

    4,新建本地dev分支,并关联远程dev分支

    git checkout -b dev origin/dev
    -
    - +
    + diff --git a/other/git/branch02.html b/other/git/branch02.html index 7dffb35565..4907959277 100644 --- a/other/git/branch02.html +++ b/other/git/branch02.html @@ -30,8 +30,8 @@ git分支操作 | ChenSino - - + + - +
    + diff --git a/other/git/fatal.html b/other/git/fatal.html index f5877bdd33..4f2c971207 100644 --- a/other/git/fatal.html +++ b/other/git/fatal.html @@ -30,8 +30,8 @@ Git克隆出现连接错误 | ChenSino - - + + - +
    + diff --git a/other/git/gitConflict.html b/other/git/gitConflict.html index b68ad060fa..9c294a5ced 100644 --- a/other/git/gitConflict.html +++ b/other/git/gitConflict.html @@ -30,11 +30,11 @@ git冲突出现的原因 | ChenSino - - + + -
    跳至主要內容

    git冲突出现的原因

    zxfgit 操作必会大约 3 分钟

    一,git冲突出现的原因及解决方案

    简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。

    1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。

    • 修改component.html本地文件第11行,既没有add,也没有commit
    • 然后修改远程component.html远程文件第14行
    • 本地执行git pull,拉取代码提示,同时修改component.html,提示发生冲突

    image-20220723162517914

    此时,有以下3种解决办法。

    1. 丢弃本地的发生冲突的文件(git checkout -- component.html),然后重新git pull代码

    2. 按照提示,先commit本地修改,然后再次执行pull操作,得到如下丑陋的提交记录。此时本地commit记录便会显示如下的丑陋的commit记录。由于发生了冲突,但是由于是修改的不同行,git直接帮我们把本地修改和远程修改直接合并了,并产生了一个新的提交节点 Merge branch.....

      img

    3. 采用git stash先暂存本地代码->然后git pull 代码->git stash pop 把暂存文件恢复->git add xxx->git commit -m “xxx”-> git push origin master

      本地没有commit的情况下此时采用第3种解决方式会更好,commit记录不会产生一个丑陋的合并节点。

    1.2 本地代码已经commit但是还没有push. 有时候经常会忘记先pull远程代码,直接push代码.如果本地和远程同时修改了同一文件,会产生提示

    image-20220723172630686

    看到这个提示:远程代码有修改,本地代码先要合并远程的代码才可以push.

    不敢随便点按钮,毕竟不知道编辑器不知道隐藏了什么诡异的操作,给代码搞丢了。

    我的操作,先关闭提示框。先执行git pull 同步远程代码,此时编辑器提示我有文件发生了冲突。

    image-20220723173039634

    点击Merge,选择手动合并冲突的代码。此时也会产生一个合并节点。

    image-20220723173207361

    然后git add ->git commit -> git push.

    1.3 就算本地和远程没有同时修改同一个文件。只要出现本地分支落后远程分支,就会出现该提示 push rejected的提示,此时我们就要先pull远程代码,然后再push代码即可。

    例如,我在远程新增一个文件,本地没有pull代码,此时本地分支是落后于远程分支的。如果你此时不知情,commit一些代码并准备push代码到远程会收到如下提示:

    image-20220726104412089

    此时我的操作是,依旧是关闭这个弹框,执行git pull 同步远程代码,这时会产生一个丑陋合并节点,因为远程和本地其实并没有发生冲突,这个merge节点也不会有任何提交内容。

    image-20220726105156403

    image-20220726105424098

    - +
    跳至主要內容

    git冲突出现的原因

    zxfgit 操作必会大约 3 分钟

    一,git冲突出现的原因及解决方案

    简单来说就是本地修改的文件和目标远程库的同一个文件都有修改,这时无论是pull,push,merge时都会产生冲突。

    1.1 本地代码没有commit. 修改同一文件的不同处,依旧会产生冲突。

    • 修改component.html本地文件第11行,既没有add,也没有commit
    • 然后修改远程component.html远程文件第14行
    • 本地执行git pull,拉取代码提示,同时修改component.html,提示发生冲突

    image-20220723162517914

    此时,有以下3种解决办法。

    1. 丢弃本地的发生冲突的文件(git checkout -- component.html),然后重新git pull代码

    2. 按照提示,先commit本地修改,然后再次执行pull操作,得到如下丑陋的提交记录。此时本地commit记录便会显示如下的丑陋的commit记录。由于发生了冲突,但是由于是修改的不同行,git直接帮我们把本地修改和远程修改直接合并了,并产生了一个新的提交节点 Merge branch.....

      img

    3. 采用git stash先暂存本地代码->然后git pull 代码->git stash pop 把暂存文件恢复->git add xxx->git commit -m “xxx”-> git push origin master

      本地没有commit的情况下此时采用第3种解决方式会更好,commit记录不会产生一个丑陋的合并节点。

    1.2 本地代码已经commit但是还没有push. 有时候经常会忘记先pull远程代码,直接push代码.如果本地和远程同时修改了同一文件,会产生提示

    image-20220723172630686

    看到这个提示:远程代码有修改,本地代码先要合并远程的代码才可以push.

    不敢随便点按钮,毕竟不知道编辑器不知道隐藏了什么诡异的操作,给代码搞丢了。

    我的操作,先关闭提示框。先执行git pull 同步远程代码,此时编辑器提示我有文件发生了冲突。

    image-20220723173039634

    点击Merge,选择手动合并冲突的代码。此时也会产生一个合并节点。

    image-20220723173207361

    然后git add ->git commit -> git push.

    1.3 就算本地和远程没有同时修改同一个文件。只要出现本地分支落后远程分支,就会出现该提示 push rejected的提示,此时我们就要先pull远程代码,然后再push代码即可。

    例如,我在远程新增一个文件,本地没有pull代码,此时本地分支是落后于远程分支的。如果你此时不知情,commit一些代码并准备push代码到远程会收到如下提示:

    image-20220726104412089

    此时我的操作是,依旧是关闭这个弹框,执行git pull 同步远程代码,这时会产生一个丑陋合并节点,因为远程和本地其实并没有发生冲突,这个merge节点也不会有任何提交内容。

    image-20220726105156403

    image-20220726105424098

    + diff --git a/other/git/gitRebase.html b/other/git/gitRebase.html index 743f28cc64..11736fe09c 100644 --- a/other/git/gitRebase.html +++ b/other/git/gitRebase.html @@ -30,8 +30,8 @@ git rebase的使用 | ChenSino - - + +
    跳至主要內容

    git rebase的使用

    zxfgit 操作必会大约 3 分钟

    一,用于合并当前分支的多个commit记录

    应用场景,如下第2-4次提交是对同一功能的代码提交记录,完全可以合并成一次提交记录。这个时候rebase就很有用了。

    image-20220724180044950

    1. 找到想要合并的commit, 使用rebase -i

      git rebase -i bd0d758(第一次提交的commitId)
      @@ -46,7 +46,7 @@
       git rebase --continue
       

      执行完git rebase --continue 进入以下界面 image-20220724220336167

      这里是修改commit 信息

      修改完按:wq保存退出。

      image-20220724220742643

      此时dev分支和master分支已经重合

      image-20220724221217031

      4,合并dev代码到master上

      git switch master
       git merge dev
      -

      此时master分支与dev分支在同一个提交节点了。

      image-20220724221734501

    - +

    此时master分支与dev分支在同一个提交节点了。

    image-20220724221734501

    + diff --git a/other/git/gitwork.html b/other/git/gitwork.html index 736e5cb831..d2bf01ce83 100644 --- a/other/git/gitwork.html +++ b/other/git/gitwork.html @@ -30,11 +30,11 @@ git工作区、暂存区、和版本库 | ChenSino - - + + -
    跳至主要內容

    git工作区、暂存区、和版本库

    zxfgit 操作必会大约 2 分钟

    一,基本概念

    • 工作区:就是你在电脑里能看到的目录。
    • 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
    • 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。

    下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:

    image-20220803144042656

    • 图中左侧为工作区,右侧为版本库。在版本库中标记为 "index" 的区域是暂存区(stage/index),标记为 "master" 的是 master 分支所代表的目录树。
    • 图中我们可以看出此时 "HEAD" 实际是指向 master 分支的一个"游标"。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
    • 图中的 objects 标识的区域为 Git 的对象库,实际位于 ".git/objects" 目录下,里面包含了创建的各种对象及内容。
    • 当对工作区修改(或新增)的文件执行 ==git add 命令时,暂存区的目录树被更新== ,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
    • 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
    • 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
    • 当执行 git rm --cached <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。
    • 当执行 git checkout . 或者 git checkout -- <file> 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。
    • 当执行 git checkout HEAD . 或者 git checkout HEAD <file> 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。
    - +
    跳至主要內容

    git工作区、暂存区、和版本库

    zxfgit 操作必会大约 2 分钟

    一,基本概念

    • 工作区:就是你在电脑里能看到的目录。
    • 暂存区:英文叫 stage 或 index。一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。
    • 版本库:工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。

    下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:

    image-20220803144042656

    • 图中左侧为工作区,右侧为版本库。在版本库中标记为 "index" 的区域是暂存区(stage/index),标记为 "master" 的是 master 分支所代表的目录树。
    • 图中我们可以看出此时 "HEAD" 实际是指向 master 分支的一个"游标"。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
    • 图中的 objects 标识的区域为 Git 的对象库,实际位于 ".git/objects" 目录下,里面包含了创建的各种对象及内容。
    • 当对工作区修改(或新增)的文件执行 ==git add 命令时,暂存区的目录树被更新== ,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
    • 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
    • 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。
    • 当执行 git rm --cached <file> 命令时,会直接从暂存区删除文件,工作区则不做出改变。
    • 当执行 git checkout . 或者 git checkout -- <file> 命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区中的改动。
    • 当执行 git checkout HEAD . 或者 git checkout HEAD <file> 命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。
    + diff --git a/other/git/index.html b/other/git/index.html index f6c056913b..835e2fd5e0 100644 --- a/other/git/index.html +++ b/other/git/index.html @@ -30,11 +30,11 @@ Git | ChenSino - - + + - - + + diff --git a/other/git/mergeBranch.html b/other/git/mergeBranch.html index 298487def5..d70ac7212c 100644 --- a/other/git/mergeBranch.html +++ b/other/git/mergeBranch.html @@ -30,8 +30,8 @@ git pull产生临时Merge branch的问题 | ChenSino - - + +
    跳至主要內容

    git pull产生临时Merge branch的问题

    ChenSinogit 操作必会大约 3 分钟

    一,问题

    ​ 在git log中往往会发现在log中出现Merge branch 'master of .....'这种合并节点,造成日志的污染

    image-20220726115529360

    二,产生的原因

    当多人合作开发一个项目时,本地仓库落后于远程仓库是一个非常正常的事情,可参考下图 在这里插入图片描述

    具体情境如下:

    我当前拉取的远端版本为 B,此时修改了代码,并在本地仓库 commit 一次,但并未 push 到远端仓库。 另一位开发者在 B 的基础上,同样 commit 了一次并 push 到远端仓库。那么这个时候,我再 push 自己的代码就会发生错误。

    To github.com:xxx/xxx.git
    @@ -42,7 +42,7 @@
     hint: to the same ref. You may want to first integrate the remote changes
     hint: (e.g., 'git pull ...') before pushing again.
     
    -

    这个时候我们会选择,先 pull(如果有冲突需要先解决冲突,再 push。push 成功,但是此时我们查看 log 就会发现除了我们自己提交的那条日志之外,会多出一条 “Merge branch ‘master’ of …”。

    那么,为什么会出现这种现象呢?其实是与 Git 的工作原理有关,对 Git 比较了解的人应该会知道,无论是 pull、push 亦或是 merge 操作,其实背后都是有很多的不同的模式的。

    在进行 pull 操作的同时,其实就是 fetch+merge 的一个过程。我们从 remote 分支中拉取新的更新,然后再合并到本地分支中去。

    如果 remote 分支超前于本地分支,并且本地分支没有任何 commit 的,直接从 remote 进行 pull 操作,默认会采用 fast-forward 模式,这种模式下,并 不会产生合并节点,也就是说不会产生多余的那条 log 信息。

    如果本地先commit后再去pull,那么此时,remote分支和本地分支会出现分叉,这个时候使用pull操作拉取更新时,就会进行分支合并,产生合并节点和log信息。(总之如果本地有commit记录,只要出现本地分支落后远程分支,那么无论本地文件与远程文件是否有冲突,pull代码都会产生merge节点,本地分支落后于远程分支是无法push代码的)

    三,解决方法

    方法一: 在执行git pull的时候加上–rebase参数。这参数的意思就是在合并代码之前,先执行变基操作,成功后在进行真正的merge操作。(如果有冲突需要手动解决)

    方法二: 在你的git bash里执行git config --global pull.rebase true。这个配置就是告诉git在每次pull前先进行rebase操作。这种方法和方法1原理一样,只不过方法1是每次pull前都要手动操作。

    方法三:修改任何代码之前,先pull代码到本地。

    - +

    这个时候我们会选择,先 pull(如果有冲突需要先解决冲突,再 push。push 成功,但是此时我们查看 log 就会发现除了我们自己提交的那条日志之外,会多出一条 “Merge branch ‘master’ of …”。

    那么,为什么会出现这种现象呢?其实是与 Git 的工作原理有关,对 Git 比较了解的人应该会知道,无论是 pull、push 亦或是 merge 操作,其实背后都是有很多的不同的模式的。

    在进行 pull 操作的同时,其实就是 fetch+merge 的一个过程。我们从 remote 分支中拉取新的更新,然后再合并到本地分支中去。

    如果 remote 分支超前于本地分支,并且本地分支没有任何 commit 的,直接从 remote 进行 pull 操作,默认会采用 fast-forward 模式,这种模式下,并 不会产生合并节点,也就是说不会产生多余的那条 log 信息。

    如果本地先commit后再去pull,那么此时,remote分支和本地分支会出现分叉,这个时候使用pull操作拉取更新时,就会进行分支合并,产生合并节点和log信息。(总之如果本地有commit记录,只要出现本地分支落后远程分支,那么无论本地文件与远程文件是否有冲突,pull代码都会产生merge节点,本地分支落后于远程分支是无法push代码的)

    三,解决方法

    方法一: 在执行git pull的时候加上–rebase参数。这参数的意思就是在合并代码之前,先执行变基操作,成功后在进行真正的merge操作。(如果有冲突需要手动解决)

    方法二: 在你的git bash里执行git config --global pull.rebase true。这个配置就是告诉git在每次pull前先进行rebase操作。这种方法和方法1原理一样,只不过方法1是每次pull前都要手动操作。

    方法三:修改任何代码之前,先pull代码到本地。

    + diff --git a/other/git/proxy.html b/other/git/proxy.html index 4fc74ef75f..fedb242376 100644 --- a/other/git/proxy.html +++ b/other/git/proxy.html @@ -30,8 +30,8 @@ git代理 | ChenSino - - + + - +

    3、参考

    git代理open in new window

    + diff --git a/other/git/rebaseAndMerge.html b/other/git/rebaseAndMerge.html index 150cca47a0..9362f4a6ae 100644 --- a/other/git/rebaseAndMerge.html +++ b/other/git/rebaseAndMerge.html @@ -30,11 +30,11 @@ git rebase与merge的区别 | ChenSino - - + + -
    跳至主要內容

    git rebase与merge的区别

    ChenSinogit 操作必会大约 1 分钟

    在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的)

    img

    如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetch),需要将本地远程分支代码更新到个人工作分支,这时有两种方法,rebaseopen in new window和merge。

    merge的方法是合并库时推荐使用的方法。

    如下图,merge操作会将绿色的所有修改合并,解决冲突后在蓝色分支的后面新建一个ID,成为蓝色分支的最新提交,实际上就是基于父节点ID7的提交,入库后将蓝色分支库上代码更新。

    img

    如下图,rebase的方法是将提蓝色分支放到绿色分支后面提交,ID3\ID4\ID5\ID7都会转换为新的ID并进行提交,由于父节点改变,每次提交都需要解决一次冲突,因此会大大增加分支合并的难度。

    img

    - +
    跳至主要內容

    git rebase与merge的区别

    ChenSinogit 操作必会大约 1 分钟

    在codehub上有多个分支,每次的提交都会生成一个新的ID。如下图,假设开始各个分支都是根据ID2的提交更新后的代码进行修改,(ID号仅代表生成的时间顺序,实际的ID号是根据算法生成的)

    img

    如果我们需要将绿色分支修改的代码更新到蓝色分支,本地远程分支内与个人工作分支已经是蓝色分支对应库内最新代码,那么在绿色远程分支代码更新到个人的库后(fetch),需要将本地远程分支代码更新到个人工作分支,这时有两种方法,rebaseopen in new window和merge。

    merge的方法是合并库时推荐使用的方法。

    如下图,merge操作会将绿色的所有修改合并,解决冲突后在蓝色分支的后面新建一个ID,成为蓝色分支的最新提交,实际上就是基于父节点ID7的提交,入库后将蓝色分支库上代码更新。

    img

    如下图,rebase的方法是将提蓝色分支放到绿色分支后面提交,ID3\ID4\ID5\ID7都会转换为新的ID并进行提交,由于父节点改变,每次提交都需要解决一次冲突,因此会大大增加分支合并的难度。

    img

    + diff --git a/other/git/reset.html b/other/git/reset.html index 4c915ce709..9b17584085 100644 --- a/other/git/reset.html +++ b/other/git/reset.html @@ -30,8 +30,8 @@ git reset命令使用 | ChenSino - - + +
    跳至主要內容

    git reset命令使用

    chenkun大约 2 分钟

    1、几种reset介绍

    git reset 命令用于回退版本,可以指定退回某一次提交的版本。

    git reset 命令语法格式如下:

    git reset [--soft | --mixed | --hard] [HEAD]
    @@ -43,7 +43,7 @@
     

    执行改命令的效果 为e2c809aa88之后的commit文件都回到暂存状态,代码都不会丢失,之前commit的状态状态都没了,暂存区和工作区不变。

    --hard 参数撤销工作区中所有未提交的修改内容,将暂存区与工作区都回到上一次版本,并删除之前的所有提交信息

    $ git reset --hard HEAD~3  # 回退上上上一个版本  
     $ git reset –hard bae128  # 回退到某个版本回退点之前的所有信息。 
     $ git reset --hard origin/master    # 将本地的状态回退到和远程的一样 
    -

    注意:谨慎使用 –hard 参数,它会删除回退点之前的所有信息。

    HEAD 说明:

    • HEAD 表示当前版本

    • HEAD^ 上一个版本

    • HEAD^^ 上上一个版本

    • HEAD^^^ 上上上一个版本

    • 以此类推...

    也可以使用 ~数字表示

    • HEAD~0 表示当前版本
    • HEAD~1 上一个版本
    • HEAD^2 上上一个版本
    • HEAD^3 上上上一个版本
    • 以此类推...
    - +

    注意:谨慎使用 –hard 参数,它会删除回退点之前的所有信息。

    HEAD 说明:

    • HEAD 表示当前版本

    • HEAD^ 上一个版本

    • HEAD^^ 上上一个版本

    • HEAD^^^ 上上上一个版本

    • 以此类推...

    也可以使用 ~数字表示

    • HEAD~0 表示当前版本
    • HEAD~1 上一个版本
    • HEAD^2 上上一个版本
    • HEAD^3 上上上一个版本
    • 以此类推...
    + diff --git a/other/git/stash.html b/other/git/stash.html index dd34f1b722..48ad141709 100644 --- a/other/git/stash.html +++ b/other/git/stash.html @@ -30,11 +30,11 @@ git stash 暂存 | ChenSino - - + + -
    跳至主要內容

    git stash 暂存

    ChenSinogit 操作必会大约 2 分钟

    一,使用场景

    在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。

    二,stash的作用

    stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。

    三,使用命令

    1. git status查看哪些文件发生变化

    2. git stash把所有改动暂存起来,git stash等同于git stash push,可以给当前stash增加message方便后续查看

      git stash push -m "message",执行完,会发现git status当前分支变得跟没有改动时一样干净

    3. git stash list 查看当前栈中有多少暂存记录

    4. 恢复栈最新的一个暂存可以git stash pop,取暂存中的某一份 git stash apply stash@{1}

    5. popapply都可以恢复暂存,但是,apply执行后,暂存记录还存在。pop`则会从栈中移除

    6. git stash clear 删除所有缓存的stash

    7. git stash drop stash@{$num} :丢弃stash@{$num}存储,从列表中删除这个存储

    四,本地解决冲突

    1. 把自己开发的代码储藏起来git stash
    2. git pull 拉取最新代码
    3. git stash pop 把暂存文件恢复
    4. 查看冲突,解决冲突 git status -s
    5. 解决完冲突后就可以像正常提交代码一样操作了,
    6. git add xxx
    7. git commit -m “xxx”
    8. git push origin master:my_branch
    - +
    跳至主要內容

    git stash 暂存

    ChenSinogit 操作必会大约 2 分钟

    一,使用场景

    在开发的过程中,经常会遇到,几个分支并行进行。当在A分支开发,突然发现有个线上bug,需要临时切换到B分支进行处理,同时,A分支上的代码还未编写完整,不想提交上去。这个时候,git stash的好处就提现出来了。

    二,stash的作用

    stash会跟踪文件的修改与暂存的改动——然后将未完成的修改保存到一个栈上, 而你可以在任何时候重新应用这些改动(甚至在不同的分支上)。

    三,使用命令

    1. git status查看哪些文件发生变化

    2. git stash把所有改动暂存起来,git stash等同于git stash push,可以给当前stash增加message方便后续查看

      git stash push -m "message",执行完,会发现git status当前分支变得跟没有改动时一样干净

    3. git stash list 查看当前栈中有多少暂存记录

    4. 恢复栈最新的一个暂存可以git stash pop,取暂存中的某一份 git stash apply stash@{1}

    5. popapply都可以恢复暂存,但是,apply执行后,暂存记录还存在。pop`则会从栈中移除

    6. git stash clear 删除所有缓存的stash

    7. git stash drop stash@{$num} :丢弃stash@{$num}存储,从列表中删除这个存储

    四,本地解决冲突

    1. 把自己开发的代码储藏起来git stash
    2. git pull 拉取最新代码
    3. git stash pop 把暂存文件恢复
    4. 查看冲突,解决冲突 git status -s
    5. 解决完冲突后就可以像正常提交代码一样操作了,
    6. git add xxx
    7. git commit -m “xxx”
    8. git push origin master:my_branch
    + diff --git a/other/hardware/CPU.html b/other/hardware/CPU.html index 2eb0574684..e863709b96 100644 --- a/other/hardware/CPU.html +++ b/other/hardware/CPU.html @@ -30,11 +30,11 @@ cpu介绍 | ChenSino - - + + -
    跳至主要內容

    cpu介绍

    ChenSino原创大约 1 分钟

    intel cpu型号

    官方说明open in new window

    cpu型号命名

    20230809094135

    20230809094205

    20230809094220

    cpu 后缀

    外形/功能类型/细分市场后缀优化/设计
    台式机K高性能,未锁频
    Φ需要独立显卡
    S特别版
    T功耗优化生活方式
    X/XE最高性能,未锁频
    移动设备(笔记本电脑 2、2 合 1 电脑)HX最高性能,所有 SKU 未锁频
    HK高性能,未锁频
    H高性能
    P提供轻薄型设备所需的性能
    U能效更高
    Y功耗极低
    G1-G7显卡级别(采用较新集成显卡技术的处理器)
    嵌入式E嵌入式
    UE能效更高
    HE高性能
    UL高能效,采用 LGA 封装
    HL高性能,采用 LGA 封装
    git
    - +
    跳至主要內容

    cpu介绍

    ChenSino原创大约 1 分钟

    intel cpu型号

    官方说明open in new window

    cpu型号命名

    20230809094135

    20230809094205

    20230809094220

    cpu 后缀

    外形/功能类型/细分市场后缀优化/设计
    台式机K高性能,未锁频
    Φ需要独立显卡
    S特别版
    T功耗优化生活方式
    X/XE最高性能,未锁频
    移动设备(笔记本电脑 2、2 合 1 电脑)HX最高性能,所有 SKU 未锁频
    HK高性能,未锁频
    H高性能
    P提供轻薄型设备所需的性能
    U能效更高
    Y功耗极低
    G1-G7显卡级别(采用较新集成显卡技术的处理器)
    嵌入式E嵌入式
    UE能效更高
    HE高性能
    UL高能效,采用 LGA 封装
    HL高性能,采用 LGA 封装
    git
    + diff --git a/other/hardware/index.html b/other/hardware/index.html index bb4a7c31d2..90043c63fa 100644 --- a/other/hardware/index.html +++ b/other/hardware/index.html @@ -30,11 +30,11 @@ Hardware | ChenSino - - + + - - + + diff --git a/other/index.html b/other/index.html index ebece6686d..6a50399406 100644 --- a/other/index.html +++ b/other/index.html @@ -30,11 +30,11 @@ 其他 | ChenSino - - + + -
    跳至主要內容

    其他

    chenkun小于 1 分钟

    - + + diff --git a/other/linux/CentOS.html b/other/linux/CentOS.html index 89f94472e2..956c82bb16 100644 --- a/other/linux/CentOS.html +++ b/other/linux/CentOS.html @@ -30,13 +30,13 @@ RedHat系 | ChenSino - - + + - +
    + diff --git a/other/linux/CommonUsedCMD.html b/other/linux/CommonUsedCMD.html index b575262320..f2a97a3123 100644 --- a/other/linux/CommonUsedCMD.html +++ b/other/linux/CommonUsedCMD.html @@ -30,8 +30,8 @@ 常用命令 | ChenSino - - + + - +
    + diff --git a/other/linux/Curl.html b/other/linux/Curl.html index ac0efe0b43..43ee253a28 100644 --- a/other/linux/Curl.html +++ b/other/linux/Curl.html @@ -30,8 +30,8 @@ Curl命令 | ChenSino - - + +
    跳至主要內容

    Curl命令

    chenkunlinuxlinux大约 5 分钟

    1、使用CURL分析接口请求耗时

    在服务器上发送请求一般用四字命令curl,本博客记录一下如何用curl测试接口耗时

    1.1 构造curl命令

    浏览器提供了快速构建各种curl请求的方式,直接复制,有需要再编辑即可 构造curl

    1.2、分析耗时

    使用curl的-w选项,其手册如下:

    -w, --write-out <format>
    @@ -137,7 +137,7 @@
      time_starttransfer:  0.027
                         ----------
              time_total:  0.384
    -

    可以看到 time_appconnect 和 time_redirect 都不是 0 了,其中 SSL 协议处理时间为 328-18=310ms。而且 pretransfer 和 starttransfer 的时间都缩短了,这是重定向之后请求的时间。

    - +

    可以看到 time_appconnect 和 time_redirect 都不是 0 了,其中 SSL 协议处理时间为 328-18=310ms。而且 pretransfer 和 starttransfer 的时间都缩短了,这是重定向之后请求的时间。

    + diff --git a/other/linux/InstallMysqlWithDocker.html b/other/linux/InstallMysqlWithDocker.html index cbfafc53e2..f495cbe36f 100644 --- a/other/linux/InstallMysqlWithDocker.html +++ b/other/linux/InstallMysqlWithDocker.html @@ -30,8 +30,8 @@ docker安装mysql | ChenSino - - + +
    跳至主要內容

    docker安装mysql

    chenkun小于 1 分钟

    1. 不要用太新的版本

    安装一定要选对版本,刚开始我使用的8.0.30镜像,一直无法启动,日志报错大概意思是我映射的目录有问题,应该是新版本mysql的安装文件有变动, 我也懒得去深究,直接换了一个版本就好了了。

    2 安装

    # 1. 下载镜像
    @@ -46,7 +46,7 @@
     -d mysql:8.0.23
     

    3 报错

    在本机命令行无法使用mysql -uroot -proot直接连到mysql报错如下:

    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
     

    但是使用别的机器(mysql -h 192.168.92.31 -uroot -proot)居然可以连接上,在本机添加本机地址也可以连接上

    mysql -h 127.0.0.1 -uroot -proot
    -
    - +
    + diff --git a/other/linux/Manjaro.html b/other/linux/Manjaro.html index 50a391f7c9..be736b913f 100644 --- a/other/linux/Manjaro.html +++ b/other/linux/Manjaro.html @@ -30,8 +30,8 @@ Manjaro问题搜集 | ChenSino - - + +
    跳至主要內容

    Manjaro问题搜集

    chenkun大约 12 分钟

    1、降级软件包

    安装downgrade程序 sudo pacman -S downgrade 降级 sudo DOWNGRADE_FROM_ALA=1 downgrade xxx包 注意DOWNGRADE_FROM_ALA=1一定要按照我上边这样写,不能单独export DOWNGRADE_FROM_ALA=1 设置忽略升级的包 第二步会让你选择更新的时候是否要忽略更新,选择y的话,它会在/etc/pacman.conf添加一个忽略,如果不想湖绿,把下面的IgnorePkg注释即可

    image-20220322171440300

    2、开机报错failed to start rotate log files

    2.1 分析问题

    1. logrotate是什么 按照老套路分析,先百度了一下logrotate是什么,参考,说白了就是个日志切割,和java里面的差不多。就是防止单文件日志过大,按照一定的规则切割成多个日志,或者删除,比如设置超过一个月直接删除,或者超过10M直接删除等等。
    2. 查看logrotate是什么时候启动,以及启动后的状态。首先我们知道它是一个systemctl启动的service服务。那就到/lib/systemd/system下看一下ll |grep rotate
     ll|grep rota
    @@ -192,7 +192,7 @@
     vncpasswd 
     
    1. 启动服务端
    #启动一个服务,监听5901,以此类推vncserver :2 监听5902……
     vncserver :1
    -
    1. 客户端使用任何一个vnc客户端都可以连接,可以用realvnc,tigervnc,ultravnc等客户端都行,需要注意的是一定要开启键盘鼠标

    20231013103250

    12、Remmina远程windows字体发虚

    使用remmina远程windows,字体发虚,尤其chrome浏览器,在视觉效果自定义中选择“平滑屏幕字体边缘”重新打开chrome发现就好了,但是断开remmina重新连接发现 “平滑屏幕字体边缘”又被去掉了勾选。

    20240416112834

    解决方法: 在remmina设置——高级——画质,选择最好(最慢)就可以了

    20240416112927

    13、manjaro-kde下idea新版本UI问题

    新版本IDEA的UI在manjaro-kde下显示没有使用KDE桌面设置的样式, 20240808112352

    解决方案如下: 20240808112509

    设置后重启idea,效果如下,终于和我设置的样式一致了: 20240808112611

    12、GTK3主题设置

    gtk开发的软件在kde桌面下,默认的窗口样式不好看,设置好全局主题后对GTK软件是不生效的,需要单独设置GTK3主题,在kde桌面下,右键点击桌面,选择设置,选择主题,选择GTK3主题,有的主题会专门提供GTK, 设置方法如下: 20240815143416

    下载对应主题的gtk包,然后选择应用,当然有的主题作者没有提供gtk包,那就随便选择一个其他主题的gtk包就行了, 20240815143437

    - +
    1. 客户端使用任何一个vnc客户端都可以连接,可以用realvnc,tigervnc,ultravnc等客户端都行,需要注意的是一定要开启键盘鼠标

    20231013103250

    12、Remmina远程windows字体发虚

    使用remmina远程windows,字体发虚,尤其chrome浏览器,在视觉效果自定义中选择“平滑屏幕字体边缘”重新打开chrome发现就好了,但是断开remmina重新连接发现 “平滑屏幕字体边缘”又被去掉了勾选。

    20240416112834

    解决方法: 在remmina设置——高级——画质,选择最好(最慢)就可以了

    20240416112927

    13、manjaro-kde下idea新版本UI问题

    新版本IDEA的UI在manjaro-kde下显示没有使用KDE桌面设置的样式, 20240808112352

    解决方案如下: 20240808112509

    设置后重启idea,效果如下,终于和我设置的样式一致了: 20240808112611

    12、GTK3主题设置

    gtk开发的软件在kde桌面下,默认的窗口样式不好看,设置好全局主题后对GTK软件是不生效的,需要单独设置GTK3主题,在kde桌面下,右键点击桌面,选择设置,选择主题,选择GTK3主题,有的主题会专门提供GTK, 设置方法如下: 20240815143416

    下载对应主题的gtk包,然后选择应用,当然有的主题作者没有提供gtk包,那就随便选择一个其他主题的gtk包就行了, 20240815143437

    + diff --git a/other/linux/MountDisk.html b/other/linux/MountDisk.html index a25ca728e1..9d2ef91542 100644 --- a/other/linux/MountDisk.html +++ b/other/linux/MountDisk.html @@ -30,8 +30,8 @@ 系统挂载磁盘 | ChenSino - - + +
    跳至主要內容

    系统挂载磁盘

    chenkun大约 8 分钟

    参考

    初始化Linux数据盘(fdisk)open in new window

    挂载

    划分分区并挂载磁盘

    本操作以该场景为例,当云服务器挂载了一块新的数据盘时,使用fdisk分区工具将该数据盘设为主分区,分区形式默认设置为MBR,文件系统设为ext4格式,挂载在“/mnt/sdc”下,并设置开机启动自动挂载。

    1. fdisk -l 回显类似如下信息:
    [root@ecs-test-0001 ~]# fdisk -l
    @@ -116,7 +116,7 @@
     Writing inode tables: done
     Creating journal (32768 blocks): done
     Writing superblocks and filesystem accounting information: done
    -

    格式化需要等待一段时间,请观察系统运行状态,不要退出。

    须知

    不同文件系统支持的分区大小不同,请根据您的业务需求选择合适的文件系统。

    1. 执行以下命令,新建挂载目录。 mkdir 挂载目录

    2. 以新建挂载目录“/mnt/sdc”为例:

    mkdir /mnt/sdc

    说明

    Linux系统默认带有/mnt目录,如果创建失败,可能是/mnt被误删除了,可以执行命令mkdir -p /mnt/sdc创建。

    执行以下命令,将新建分区挂载到12中创建的目录下。 mount 磁盘分区 挂载目录

    以挂载新建分区“/dev/vdb1”至“/mnt/sdc”为例:

    mount /dev/vdb1 /mnt/sdc

    1. 执行以下命令,查看挂载结果。 df -TH

    回显类似如下信息:

    [root@ecs-test-0001 ~]# df -TH Filesystem Type Size Used Avail Use% Mounted on /dev/vda1 ext4 43G 1.9G 39G 5% / devtmpfs devtmpfs 2.0G 0 2.0G 0% /dev tmpfs tmpfs 2.0G 0 2.0G 0% /dev/shm tmpfs tmpfs 2.0G 9.1M 2.0G 1% /run tmpfs tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup tmpfs tmpfs 398M 0 398M 0% /run/user/0 /dev/vdb1 ext4 106G 63M 101G 1% /mnt/sdc

    表示新建分区“/dev/vdb1”已挂载至“/mnt/sdc”。

    说明

    云服务器重启后,挂载会失效。您可以修改“/etc/fstab”文件,将新建磁盘分区设置为开机自动挂载,请参见设置开机自动挂载磁盘分区。

    设置开机自动挂载磁盘分区

    您可以通过配置fstab文件,设置云服务器系统启动时自动挂载磁盘分区。已有数据的云服务器也可以进行设置,该操作不会影响现有数据。

    本文介绍如何在fstab文件中使用UUID来设置自动挂载磁盘分区。不建议采用在“/etc/fstab”直接指定设备名(比如/dev/vdb1)的方法,因为云中设备的顺序编码在关闭或者开启云服务器过程中可能发生改变,例如/dev/vdb1可能会变成/dev/vdb2,可能会导致云服务器重启后不能正常运行。

    说明

    UUID(universally unique identifier)是Linux系统为磁盘分区提供的唯一的标识字符串。

    1. 执行如下命令,查询磁盘分区的UUID。 blkid 磁盘分区

    2. 以查询磁盘分区“/dev/vdb1”的UUID为例:

    blkid /dev/vdb1

    回显类似如下信息:

    [root@ecs-test-0001 ~]# blkid /dev/vdb1 /dev/vdb1: UUID="0b3040e2-1367-4abb-841d-ddb0b92693df" TYPE="ext4"

    表示“/dev/vdb1”的UUID。

    执行以下命令,使用VI编辑器打开“fstab”文件。 vi /etc/fstab

    1. 按“i”,进入编辑模式。
    2. 将光标移至文件末尾,按“Enter”,添加如下内容。 UUID=0b3040e2-1367-4abb-841d-ddb0b92693df /mnt/sdc ext4 defaults 0 2

    以上内容仅为示例,具体请以实际情况为准,参数说明如下: 第一列为UUID,此处填写1中查询到的磁盘分区的UUID。 第二列为磁盘分区的挂载目录,可以通过df -TH命令查询。 第三列为磁盘分区的文件系统格式, 可以通过df -TH命令查询。 第四列为磁盘分区的挂载选项,此处通常设置为defaults即可。 第五列为Linux dump备份选项。 0表示不使用Linux dump备份。现在通常不使用dump备份,此处设置为0即可。 1表示使用Linux dump备份。 第六列为fsck选项,即开机时是否使用fsck检查磁盘。 0表示不检验。 挂载点为(/)根目录的分区,此处必须填写1。 根分区设置为1,其他分区只能从2开始,系统会按照数字从小到大依次检查下去。

    1. 按“ESC”后,输入“:wq”,按“Enter”。 保存设置并退出编辑器。

    2. 执行以下步骤,验证自动挂载功能。 执行如下命令,卸载已挂载的分区。 a. umount 磁盘分区

    命令示例:

    b. umount /dev/vdb1

    执行如下命令,将“/etc/fstab”文件所有内容重新加载。 mount -a

    c. 执行如下命令,查询文件系统挂载信息。 mount | grep 挂载目录

    命令示例:

    mount | grep /mnt/sdc

    回显类似如下信息,说明自动挂载功能生效:

    说明

    root@ecs-test-0001 ~]# mount | grep /mnt/sdc /dev/vdb1 on /mnt/sdc type ext4 (rw,relatime,data=ordered)

    - +

    格式化需要等待一段时间,请观察系统运行状态,不要退出。

    须知

    不同文件系统支持的分区大小不同,请根据您的业务需求选择合适的文件系统。

    1. 执行以下命令,新建挂载目录。 mkdir 挂载目录

    2. 以新建挂载目录“/mnt/sdc”为例:

    mkdir /mnt/sdc

    说明

    Linux系统默认带有/mnt目录,如果创建失败,可能是/mnt被误删除了,可以执行命令mkdir -p /mnt/sdc创建。

    执行以下命令,将新建分区挂载到12中创建的目录下。 mount 磁盘分区 挂载目录

    以挂载新建分区“/dev/vdb1”至“/mnt/sdc”为例:

    mount /dev/vdb1 /mnt/sdc

    1. 执行以下命令,查看挂载结果。 df -TH

    回显类似如下信息:

    [root@ecs-test-0001 ~]# df -TH Filesystem Type Size Used Avail Use% Mounted on /dev/vda1 ext4 43G 1.9G 39G 5% / devtmpfs devtmpfs 2.0G 0 2.0G 0% /dev tmpfs tmpfs 2.0G 0 2.0G 0% /dev/shm tmpfs tmpfs 2.0G 9.1M 2.0G 1% /run tmpfs tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup tmpfs tmpfs 398M 0 398M 0% /run/user/0 /dev/vdb1 ext4 106G 63M 101G 1% /mnt/sdc

    表示新建分区“/dev/vdb1”已挂载至“/mnt/sdc”。

    说明

    云服务器重启后,挂载会失效。您可以修改“/etc/fstab”文件,将新建磁盘分区设置为开机自动挂载,请参见设置开机自动挂载磁盘分区。

    设置开机自动挂载磁盘分区

    您可以通过配置fstab文件,设置云服务器系统启动时自动挂载磁盘分区。已有数据的云服务器也可以进行设置,该操作不会影响现有数据。

    本文介绍如何在fstab文件中使用UUID来设置自动挂载磁盘分区。不建议采用在“/etc/fstab”直接指定设备名(比如/dev/vdb1)的方法,因为云中设备的顺序编码在关闭或者开启云服务器过程中可能发生改变,例如/dev/vdb1可能会变成/dev/vdb2,可能会导致云服务器重启后不能正常运行。

    说明

    UUID(universally unique identifier)是Linux系统为磁盘分区提供的唯一的标识字符串。

    1. 执行如下命令,查询磁盘分区的UUID。 blkid 磁盘分区

    2. 以查询磁盘分区“/dev/vdb1”的UUID为例:

    blkid /dev/vdb1

    回显类似如下信息:

    [root@ecs-test-0001 ~]# blkid /dev/vdb1 /dev/vdb1: UUID="0b3040e2-1367-4abb-841d-ddb0b92693df" TYPE="ext4"

    表示“/dev/vdb1”的UUID。

    执行以下命令,使用VI编辑器打开“fstab”文件。 vi /etc/fstab

    1. 按“i”,进入编辑模式。
    2. 将光标移至文件末尾,按“Enter”,添加如下内容。 UUID=0b3040e2-1367-4abb-841d-ddb0b92693df /mnt/sdc ext4 defaults 0 2

    以上内容仅为示例,具体请以实际情况为准,参数说明如下: 第一列为UUID,此处填写1中查询到的磁盘分区的UUID。 第二列为磁盘分区的挂载目录,可以通过df -TH命令查询。 第三列为磁盘分区的文件系统格式, 可以通过df -TH命令查询。 第四列为磁盘分区的挂载选项,此处通常设置为defaults即可。 第五列为Linux dump备份选项。 0表示不使用Linux dump备份。现在通常不使用dump备份,此处设置为0即可。 1表示使用Linux dump备份。 第六列为fsck选项,即开机时是否使用fsck检查磁盘。 0表示不检验。 挂载点为(/)根目录的分区,此处必须填写1。 根分区设置为1,其他分区只能从2开始,系统会按照数字从小到大依次检查下去。

    1. 按“ESC”后,输入“:wq”,按“Enter”。 保存设置并退出编辑器。

    2. 执行以下步骤,验证自动挂载功能。 执行如下命令,卸载已挂载的分区。 a. umount 磁盘分区

    命令示例:

    b. umount /dev/vdb1

    执行如下命令,将“/etc/fstab”文件所有内容重新加载。 mount -a

    c. 执行如下命令,查询文件系统挂载信息。 mount | grep 挂载目录

    命令示例:

    mount | grep /mnt/sdc

    回显类似如下信息,说明自动挂载功能生效:

    说明

    root@ecs-test-0001 ~]# mount | grep /mnt/sdc /dev/vdb1 on /mnt/sdc type ext4 (rw,relatime,data=ordered)

    + diff --git a/other/linux/MultiNetworkCard.html b/other/linux/MultiNetworkCard.html index 7e8bc7bc68..aae5095cce 100644 --- a/other/linux/MultiNetworkCard.html +++ b/other/linux/MultiNetworkCard.html @@ -30,8 +30,8 @@ 双网卡问题 | ChenSino - - + +
    跳至主要內容

    双网卡问题

    chenkun大约 2 分钟

    1.1 kde桌面双网卡内外网设置

    环境如下:

    $ screenfetch   
    @@ -57,7 +57,7 @@
     nameserver 10.10.102.8
     nameserver 192.168.222.1
     nameserver fe80::aa5a:e0ff:fe3d:6d1b%wlp5s0
    -

    当有多个dns解析服务时,会自上而下依次调用,当第一个解析失败会继续从第二个dns解析。以上配置10.10.102.8是我公司内网dns,192.168.222.1这个是外网网关。 举例说明:

    1. 解析rdm.sonoscape.com时,会先从10.10.102.8解析,这个域名是我司内网服务,肯定能解析到;

    2. 解析huaweicloud.com这个是在我们内网能够的10.10.102.8是没有的,会走到192.168.222.1解析;

    总结

    如果有这种场景,只需要在/etc/resolv.conf把内网dns设置在第一行,外网dns放置在后面,优先走内网解析

    3、搜索域

    搜索域用作域搜索列表的一部分域。解析程序使用域搜索列表以及本地域名。当dns查询不到你所输入的域名时,会将dns搜索域拼接在你所查询的域名后面再次进行查询。 假设内网中有一台服务器名为adgk.local当我们需要访问它时需要在浏览器输入http://adgk.local,如果把dns搜索域设置成local那么你可以通过http://adgk来访问这台服务器也可以通过http://adgk.local来访问这台服务器。

    - +

    当有多个dns解析服务时,会自上而下依次调用,当第一个解析失败会继续从第二个dns解析。以上配置10.10.102.8是我公司内网dns,192.168.222.1这个是外网网关。 举例说明:

    1. 解析rdm.sonoscape.com时,会先从10.10.102.8解析,这个域名是我司内网服务,肯定能解析到;

    2. 解析huaweicloud.com这个是在我们内网能够的10.10.102.8是没有的,会走到192.168.222.1解析;

    总结

    如果有这种场景,只需要在/etc/resolv.conf把内网dns设置在第一行,外网dns放置在后面,优先走内网解析

    3、搜索域

    搜索域用作域搜索列表的一部分域。解析程序使用域搜索列表以及本地域名。当dns查询不到你所输入的域名时,会将dns搜索域拼接在你所查询的域名后面再次进行查询。 假设内网中有一台服务器名为adgk.local当我们需要访问它时需要在浏览器输入http://adgk.local,如果把dns搜索域设置成local那么你可以通过http://adgk来访问这台服务器也可以通过http://adgk.local来访问这台服务器。

    + diff --git a/other/linux/Samba.html b/other/linux/Samba.html index 00c3d06f71..a19fe69b59 100644 --- a/other/linux/Samba.html +++ b/other/linux/Samba.html @@ -30,8 +30,8 @@ 部署Samba | ChenSino - - + +
    跳至主要內容

    部署Samba

    chenkun小于 1 分钟

    1、安装过程省略

    2、配置

    2.1 配置文件

    配置目录在 /etc/samba,修改smb.conf在最后加一组[test],同时修改[global]在里面加上ntlm auth = yes,最终加完如下

    # See smb.conf.example for a more detailed config file or
    @@ -74,7 +74,7 @@
             guest ok = yes
     

    2.2 创建samba用户

    useadd test  #添加系统用户
     smbpaswd -a test  #给新加的系统用户设置samba密码,这个密码和系统密码不是一回事
    -

    2.3 windows连接samba

    windows开启samba过程省略……

    连接时的路径格式为\\IP\test,IP是你的samba地址,test是在smb.conf配置的那个 [test]

    image-20220627134822676

    - +

    2.3 windows连接samba

    windows开启samba过程省略……

    连接时的路径格式为\\IP\test,IP是你的samba地址,test是在smb.conf配置的那个 [test]

    image-20220627134822676

    + diff --git a/other/linux/ShareBetweenWindowsAndLinux.html b/other/linux/ShareBetweenWindowsAndLinux.html index 0e72d90352..f13ec1d269 100644 --- a/other/linux/ShareBetweenWindowsAndLinux.html +++ b/other/linux/ShareBetweenWindowsAndLinux.html @@ -30,15 +30,15 @@ Linux挂载windows共享目录 | ChenSino - - + +
    跳至主要內容

    Linux挂载windows共享目录

    chenkun小于 1 分钟

    1、在windows设置共享目录

    设置过程省略……

    2、在linux下挂载

        ## 1. 创建空白目录
             mkdir /home/data/share
         ## 2. 修改/etc/fstab开机自动挂载
         //10.10.102.97/tempfile    /home/data/share  cifs    defaults,user=xxx,password=xxx,uid=1000,gid=1000  0 0
    -

    解释: //10.10.102.97/tempfile是在windows设置的共享目录,设置好了可以用其他windows电脑测试下看是否共享成功 /home/data/share 挂载的位置,一定要是个空目录, cifs 不要修改 user windows帐户, password windows密码 uid 挂载的目录所属用户,在linux终端用id 你的用户名查看 gid 用户组,和uid一个道理,这两个一定要指定,不然就被挂到root了

    参考博客

    参考open in new window

    - +

    解释: //10.10.102.97/tempfile是在windows设置的共享目录,设置好了可以用其他windows电脑测试下看是否共享成功 /home/data/share 挂载的位置,一定要是个空目录, cifs 不要修改 user windows帐户, password windows密码 uid 挂载的目录所属用户,在linux终端用id 你的用户名查看 gid 用户组,和uid一个道理,这两个一定要指定,不然就被挂到root了

    参考博客

    参考open in new window

    + diff --git a/other/linux/TcpDump.html b/other/linux/TcpDump.html index 6b3eb304ed..26a9607484 100644 --- a/other/linux/TcpDump.html +++ b/other/linux/TcpDump.html @@ -30,8 +30,8 @@ tcpdump抓包 | ChenSino - - + +
    跳至主要內容

    tcpdump抓包

    chenkun小于 1 分钟

    简要介绍Linux抓报

    作为web开发我抓报主要是针对http请求,主要是看请求行,请求头,请求体,以及响应

    1、所用抓报命令

    sudo tcpdump tcp -i eth1 -t -s 0 -c 100 and dst port ! 22 and src net 192.168.1.0/24 -w ./target.cap
    @@ -39,7 +39,7 @@
     strings target.cap>temp.txt
     

    3、其他使用实例

    # port指定要抓包端口,
     sudo tcpdump tcp -i enp3s0f1  -t -s 0 -c 100 and port 6061 -w  ./target.cap
    -
    - +
    + diff --git a/other/linux/Wifi.html b/other/linux/Wifi.html index 10943f4bbe..4cb9aa1140 100644 --- a/other/linux/Wifi.html +++ b/other/linux/Wifi.html @@ -30,11 +30,11 @@ Linux下加装wifi模块 | ChenSino - - + + -
    跳至主要內容

    Linux下加装wifi模块

    ChenSino小于 1 分钟

    1、需求场景

    场景1:

    有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡, 但此方法有个问题是一般USB无线网卡不会对Linux系统进行适配,即使有适配也是对系统条件要求很苛刻,比如只能用ubuntu,系统内核限制为指定版本等;

    场景2:

    有一个笔记本电脑,自带的有wifi模块,但是此无线网卡不支持5G、WIFI6等,此时可以根据情况升级无线网卡;

    2、解决方案——使用板载无线网卡

    使用板载无线网卡相对usb无线网卡来说,不会存在驱动的问题,驱动由Intel提供了支持,具体可以参考此处open in new window

    使用板载网卡一般使用的是NGFF接口,也就是常见的M2接口,一般2015年以后的主板都有这个扩展口。

    - +
    跳至主要內容

    Linux下加装wifi模块

    ChenSino小于 1 分钟

    1、需求场景

    场景1:

    有一个没有wifi模块的电脑,想给他加装一个wifi模块,要求不仅支持windows系统,还需要支持Linux系统,最容易想到的方式是买一个USB无线网卡, 但此方法有个问题是一般USB无线网卡不会对Linux系统进行适配,即使有适配也是对系统条件要求很苛刻,比如只能用ubuntu,系统内核限制为指定版本等;

    场景2:

    有一个笔记本电脑,自带的有wifi模块,但是此无线网卡不支持5G、WIFI6等,此时可以根据情况升级无线网卡;

    2、解决方案——使用板载无线网卡

    使用板载无线网卡相对usb无线网卡来说,不会存在驱动的问题,驱动由Intel提供了支持,具体可以参考此处open in new window

    使用板载网卡一般使用的是NGFF接口,也就是常见的M2接口,一般2015年以后的主板都有这个扩展口。

    + diff --git a/other/linux/firewall.html b/other/linux/firewall.html index 87cc5d3c49..f4829006f7 100644 --- a/other/linux/firewall.html +++ b/other/linux/firewall.html @@ -30,8 +30,8 @@ linux中防火墙 | ChenSino - - + +
    跳至主要內容

    linux中防火墙

    ChenSino原创大约 13 分钟

    集中常用的防火墙配置工具

    1. netfilter
    @@ -150,7 +150,7 @@
     是的,从 UFW (Uncomplicated Firewall) 版本 0.35 开始,它开始支持 nftables 作为底层技术来实现防火墙功能。在之前的版本中,UFW 使用的是 iptables。
     
     通过在 UFW 的配置文件中设置 IPV6=yes 和 IPT_BACKEND=nftables,可以启用 nftables 支持。这样,UFW 将使用 nftables 来管理 IPv6 规则,而不再使用 iptables。
    -

    结论

    1. 如果想用界面操作,直接选择gufw,另外firewall也提供有界面
    2. 命令行操作可以使用ufw,或者wirewall,二选一就行了,他俩都是可以操作iptables或者nftables,具体使用哪个要自行配置
    3. 如果你是大神,或者你的要求比较高,那么直接使用iptables或者nftables

    参考

    iptables详解open in new window

    ufwopen in new window

    - +

    结论

    1. 如果想用界面操作,直接选择gufw,另外firewall也提供有界面
    2. 命令行操作可以使用ufw,或者wirewall,二选一就行了,他俩都是可以操作iptables或者nftables,具体使用哪个要自行配置
    3. 如果你是大神,或者你的要求比较高,那么直接使用iptables或者nftables

    参考

    iptables详解open in new window

    ufwopen in new window

    + diff --git a/other/linux/index.html b/other/linux/index.html index 07b5dc5624..2899586c6e 100644 --- a/other/linux/index.html +++ b/other/linux/index.html @@ -30,11 +30,11 @@ Linux | ChenSino - - + + - - + + diff --git a/other/linux/wsl.html b/other/linux/wsl.html index 76b2189e3b..33eaff5df7 100644 --- a/other/linux/wsl.html +++ b/other/linux/wsl.html @@ -30,8 +30,8 @@ windows子系统wsl | ChenSino - - + +
    跳至主要內容

    windows子系统wsl

    chenkun小于 1 分钟

    一、wsl安装

    二、wsl使用

    2.1 更换国内镜像源

    2.2 从windows进入wsl

    在我的电脑地址栏直接输入:\wsl$ 即可进入wsl文件管理系统。

    C:\Users\xiaoPeng\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc\LocalState\rootfs

    2.3 wsl域名解析慢的问题

    域名解析慢会导致apt命令无法在线安装软件,更新等

    查看/etc/resolv.conf发现nameserver是默认的172.x.x.x,即使你修改了,重启终端他也会被还原,此文件的注释也说的很明白了,如下

    # This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
    @@ -42,7 +42,7 @@
     generateResolvConf = false
     
    1. 关闭wsl,管理员方式打开powershell执行wsl --shutdown

    2. 重新进入wsl,删除sudo rm /etc/resolv.conf,注意是resolv.conf

    3. 新建/etc/resolv.conf,添加自定义nameserver

       $ cat /etc/resolv.conf 
        nameserver 10.10.102.8
      -
    - +
    + diff --git "a/other/linux/\346\210\252\345\233\276.html" "b/other/linux/\346\210\252\345\233\276.html" index 0f71c54415..efbf130876 100644 --- "a/other/linux/\346\210\252\345\233\276.html" +++ "b/other/linux/\346\210\252\345\233\276.html" @@ -30,8 +30,8 @@ 使用Flameshot截图 | ChenSino - - + + - +![20240402111155](https://ddns.chensina.cn:29000/afatpig/blog/20240402111155.png)
    + diff --git a/other/markdown/index.html b/other/markdown/index.html index 085719fb7a..df287a3f3d 100644 --- a/other/markdown/index.html +++ b/other/markdown/index.html @@ -30,11 +30,11 @@ MarkDown资源 | ChenSino - - + + - - + + diff --git a/other/oauth2/01.html b/other/oauth2/01.html index 14e4d6a6e9..45f026b4e3 100644 --- a/other/oauth2/01.html +++ b/other/oauth2/01.html @@ -30,11 +30,11 @@ 01 | OAuth 2.0是要通过什么方式解决什么问题? | ChenSino - - + + -
    跳至主要內容

    01 | OAuth 2.0是要通过什么方式解决什么问题?

    王新栋大约 10 分钟

    你好,我是王新栋。 在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。

    到这里,我估计你会问,这是怎么实现的?微信把我的个人信息给了极客时间,它又是怎么保证我的数据安全的呢?

    其实,微信这一系列授权背后的原理都可以归到一个词上,那就是 OAuth 2.0。今天这节课,我们就来看看 OAuth 2.0 到底是什么、能干什么以及它是怎么干的。

    OAuth 2.0 是什么?

    用一句话总结来说,OAuth 2.0 就是一种授权协议。那如何理解这里的“授权”呢?

    我举个咱们生活中的例子。假如你是一名销售人员,你想去百度拜访你的大客户王总。到了百度的大楼之后,保安拦住了你,问你要工牌。你说:“保安大哥啊,我是来拜访王总的,哪里有什么工牌”。保安大哥说:“那你要去前台做个登记”。

    然后你就赶紧来到前台,前台美女问你是不是做了登记。你说王总秘书昨天有要你的手机号,说是已经做过预约。小姐姐确认之后往你的手机发了个验证码,你把验证码告诉了前台小姐姐之后,她给了你一张门禁卡,于是你就可以开心地去见王总了。

    你看,这个例子里面就有一次授权。本来你是没有权限进入百度大楼的,但是经过前台小姐姐一系列的验证之后,她发现你确实是来拜访客户的,于是给了你一张临时工牌。这整个过程就是授权。

    我再举一个电商的场景,你估计更有感觉。假如你是一个卖家,在京东商城开了一个店铺,日常运营中你要将订单打印出来以便给用户发货。但打印这事儿也挺繁琐的,之前你总是手工操作,后来发现有个叫“小兔”的第三方软件,它可以帮你高效率地处理这事。

    但你想想,小兔是怎么访问到这些订单数据的呢?其实是这样,京东商城提供了开放平台,小兔通过京东商家开放平台的 API 就能访问到用户的订单数据。只要你在软件里点击同意,小兔就可以拿到一个访问令牌,通过访问令牌来获取到你的订单数据帮你干活儿了。你看,这里也是有一次授权。你要是不同意,平台肯定不敢把这些数据给到第三方软件。

    为什么用 OAuth 2.0?

    基于上面两种场景的解决方案,关于授权我们最容易想到的方案就是提供钥匙。比如,你要去百度拜访王总,那前台小姐姐就给你张百度的工牌;小兔要获取你的订单信息,那你就把你的用户名和密码给它。但稍微有些安全意识,我们都不会这样做。

    因为你有了百度工牌,那以后都可以随时自由地进出了,这显然不是百度想要的。所以,百度有一套完整的机制,通过给你一张临时工牌,实现在保证安全的情况下,还能让你去大楼里面见到王总。相应地,小兔软件请求访问你的订单数据的过程,也会涉及这样一套授权机制,那就是 OAuth 2.0。它通过给小兔软件一个访问令牌,而不是让小兔软件拿着你的用户名和密码,去获取你的订单数据帮你干活儿。

    其实,除了小兔软件这个场景,在如今的互联网世界里用到 OAuth 2.0 的地方非常多,只是因为它隐藏了实现细节,需要我们多做分析才能发现它。比如,当你使用微信登录其他网站或者 App 的时候,当你开始使用某个小程序的时候,你都在无感知的情况下用到了 OAuth 2.0。

    那总结来说, ==OAuth 2.0 这种授权协议,就是保证第三方(软件)只有在获得授权之后,才可以进一步访问授权者的数据。== 因此,我们常常还会听到一种说法,OAuth 2.0 是一种安全协议。现在你知道了,这种说法也是正确的。

    现在访问授权者的数据主要是通过 Web API,所以凡是要保护这种对外的 API 时,都需要这样授权的方式。而 OAuth 2.0 的这种颁发访问令牌的机制,是再合适不过的方法了。同时,这样的 Web API 还在持续增加,所以 OAuth 2.0 是目前 Web 上重要的安全手段之一了。

    OAuth 2.0 是怎样运转的?

    现在,我相信你已经对 OAuth 2.0 有了一个整体印象,接下来咱们再看看它是怎么运转的。

    我们还是来看上面提到的小兔打单软件的例子吧。假如小明在京东上面开了一个店铺,小明要管理他的店铺里面的订单,于是选择了使用小兔软件。

    现在,让我们把“小明”“小兔软件”“京东商家开放平台”放到一个对话里面,看看“他们”是怎么沟通的吧。

    小明:“你好,小兔软件。我正在 Google 浏览器上面,需要访问你来帮我处理我在京东商城店铺的订单。

    ”小兔软件:“好的,小明,我需要你给我授权。现在我把你引导到京东商家开放平台上,你在那里给我授权吧。

    ”京东商家开放平台:“你好,小明。我收到了小兔软件跳转过来的请求,现在已经准备好了一个授权页面。你登录并确认后,点击授权页面上面的授权按钮即可。

    小明:“好的,京东商家开放平台。我看到了这个授权页面,已经点授权按钮啦😄”

    京东商家开放平台:“你好,小兔打单软件。我收到了小明的授权,现在要给你生成一个授权码 code 值,我通过浏览器重定向到你的回调 URL 地址上面了。

    ”小兔软件:“好的,京东商家开放平台。我现在从浏览器上拿到了授权码,现在就用这个授权码来请求你,请给我一个访问令牌 access_token 吧。”

    京东商家开放平台:“好的,小兔打单软件,访问令牌已经发送给你了。”

    小兔打单软件:“太好了,我现在就可以使用访问令牌来获取小明店铺的订单了。”

    小明:“我已经能够看到我的订单了,现在就开始打单操作了。”下

    面,为了帮助你理解,我再用一张图来描述整个过程:

    20221108112829

    再分析下这个流程,我们不难发现小兔软件最终的目的,是要获取一个叫做“访问令牌”的东西。从最后一步也能够看出来,在小兔软件获取到访问令牌之后,才有足够的 “能力” 去请求小明的店铺的订单,也就是才能够帮助小明打印订单。

    那么,小兔软件是怎么获取访问令牌的值的呢?我们会发现还有一个叫做“授权码”的东西,也就是说小兔软件是拿授权码换取的访问令牌。

    小兔软件又是怎么拿到授权码的呢?从图中流程刚开始的那一步,我们就会发现,是在小明授权之后,才产生的授权码,上面流程中后续的一切动作,实际上都是在小明对小兔软件授权发生以后才产生的。其中主要的动作,就是生成授权码–> 生成访问令牌–> 使用访问令牌。

    到这里,我们不难发现,OAuth 2.0 授权的核心就是颁发访问令牌、使用访问令牌,而且不管是哪种类型的授权流程都是这样。你一定要理解,或者记住这句话,它是整个流程的核心。你也可以再回想下,去百度拜访王总的例子。如果你是百度这套机制的设计者的话,会怎么设计这套授权机制呢。想清楚了这个问题,你再去理解令牌、授权码啥的也就简单了。

    在小兔软件这个例子中呢,我们使用的就是授权码许可(Authorization Code)类型。它是 OAuth 2.0 中最经典、最完备、最安全、应用最广泛的许可类型。除了授权码许可类型外,OAuth 2.0 针对不同的使用场景,还有 3 种基础的许可类型,分别是隐式许可(Implicit)、客户端凭据许可(Client Credentials)、资源拥有者凭据许可(Resource Owner Password Credentials)。相对而言,这 3 种授权许可类型的流程,在流程复杂度和安全性上都有所减弱(我会在第 6 讲,与你详细分析)。

    因此,在这个课程中,我会频繁用授权码许可类型来举例。至于为什么称它为授权码许可,为什么有两次重定向,以及这种许可类型更详细的通信流程又是怎样的,我会在第 2 讲给你深入分析,你可以先不用关注。

    总结

    好了,今天这节课就到这里。这节课咱们知识点不多,我来回给你举例子,其实就是希望你能理解 OAuth 到底是什么,为什么需要它,以及它大概的运行逻辑是怎样的。总结来说,我需要你记住以下这 3 个关键点:

    1. OAuth 2.0 的核心是授权许可,更进一步说就是令牌机制。也就是说,像小兔软件这样的第三方软件只有拿到了京东商家开放平台颁发的访问令牌,也就是得到了授权许可,然后才可以代表用户访问他们的数据。
    2. 互联网中所有的受保护资源,几乎都是以 Web API 的形式来提供访问的,比如极客时间 App 要获取用户的头像、昵称,小兔软件要获取用户的店铺订单,我们说 OAuth 2.0 与安全相关,是用来保护 Web API 的。另外,第三方软件通过 OAuth 2.0 取得访问权限之后,用户便把这些权限委托给了第三方软件,我们说 OAuth 2.0 是一种委托协议,也没问题。
    3. 也正因为像小兔这样的第三方软件,每次都是用访问令牌而不是用户名和密码来请求用户的数据,才大大减少了安全风险上的“攻击面”。不然,我们试想一下,每次都带着用户名和密码来访问数量众多的 Web API ,是不是增加了这个“攻击面”。因此,我们说 OAuth 2.0 的核心,就是颁发访问令牌和使用访问令牌。

    思考题

    好了,今天这一讲我们马上要结束了,我给你留个思考题。你可以再花时间想下小兔软件获取用户订单信息的那个场景,如果让你来设计整个的授权流程,你会怎么设计?还有没有更好的方式?欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。

    - +
    跳至主要內容

    01 | OAuth 2.0是要通过什么方式解决什么问题?

    王新栋大约 10 分钟

    你好,我是王新栋。 在课程正式开始之前,我想先问你个问题。第一次使用极客时间 App 的时候,你是直接使用了第三方帐号(比如微信、微博)登录,还是选择了重新注册新用户?如果你选择了重新注册用户,那你还得上传头像、输入用户名等信息。但如果你选择了使用第三方帐号微信来登录,那极客时间会直接使用你微信的这些信息作为基础信息,你就能省心很多。

    到这里,我估计你会问,这是怎么实现的?微信把我的个人信息给了极客时间,它又是怎么保证我的数据安全的呢?

    其实,微信这一系列授权背后的原理都可以归到一个词上,那就是 OAuth 2.0。今天这节课,我们就来看看 OAuth 2.0 到底是什么、能干什么以及它是怎么干的。

    OAuth 2.0 是什么?

    用一句话总结来说,OAuth 2.0 就是一种授权协议。那如何理解这里的“授权”呢?

    我举个咱们生活中的例子。假如你是一名销售人员,你想去百度拜访你的大客户王总。到了百度的大楼之后,保安拦住了你,问你要工牌。你说:“保安大哥啊,我是来拜访王总的,哪里有什么工牌”。保安大哥说:“那你要去前台做个登记”。

    然后你就赶紧来到前台,前台美女问你是不是做了登记。你说王总秘书昨天有要你的手机号,说是已经做过预约。小姐姐确认之后往你的手机发了个验证码,你把验证码告诉了前台小姐姐之后,她给了你一张门禁卡,于是你就可以开心地去见王总了。

    你看,这个例子里面就有一次授权。本来你是没有权限进入百度大楼的,但是经过前台小姐姐一系列的验证之后,她发现你确实是来拜访客户的,于是给了你一张临时工牌。这整个过程就是授权。

    我再举一个电商的场景,你估计更有感觉。假如你是一个卖家,在京东商城开了一个店铺,日常运营中你要将订单打印出来以便给用户发货。但打印这事儿也挺繁琐的,之前你总是手工操作,后来发现有个叫“小兔”的第三方软件,它可以帮你高效率地处理这事。

    但你想想,小兔是怎么访问到这些订单数据的呢?其实是这样,京东商城提供了开放平台,小兔通过京东商家开放平台的 API 就能访问到用户的订单数据。只要你在软件里点击同意,小兔就可以拿到一个访问令牌,通过访问令牌来获取到你的订单数据帮你干活儿了。你看,这里也是有一次授权。你要是不同意,平台肯定不敢把这些数据给到第三方软件。

    为什么用 OAuth 2.0?

    基于上面两种场景的解决方案,关于授权我们最容易想到的方案就是提供钥匙。比如,你要去百度拜访王总,那前台小姐姐就给你张百度的工牌;小兔要获取你的订单信息,那你就把你的用户名和密码给它。但稍微有些安全意识,我们都不会这样做。

    因为你有了百度工牌,那以后都可以随时自由地进出了,这显然不是百度想要的。所以,百度有一套完整的机制,通过给你一张临时工牌,实现在保证安全的情况下,还能让你去大楼里面见到王总。相应地,小兔软件请求访问你的订单数据的过程,也会涉及这样一套授权机制,那就是 OAuth 2.0。它通过给小兔软件一个访问令牌,而不是让小兔软件拿着你的用户名和密码,去获取你的订单数据帮你干活儿。

    其实,除了小兔软件这个场景,在如今的互联网世界里用到 OAuth 2.0 的地方非常多,只是因为它隐藏了实现细节,需要我们多做分析才能发现它。比如,当你使用微信登录其他网站或者 App 的时候,当你开始使用某个小程序的时候,你都在无感知的情况下用到了 OAuth 2.0。

    那总结来说, ==OAuth 2.0 这种授权协议,就是保证第三方(软件)只有在获得授权之后,才可以进一步访问授权者的数据。== 因此,我们常常还会听到一种说法,OAuth 2.0 是一种安全协议。现在你知道了,这种说法也是正确的。

    现在访问授权者的数据主要是通过 Web API,所以凡是要保护这种对外的 API 时,都需要这样授权的方式。而 OAuth 2.0 的这种颁发访问令牌的机制,是再合适不过的方法了。同时,这样的 Web API 还在持续增加,所以 OAuth 2.0 是目前 Web 上重要的安全手段之一了。

    OAuth 2.0 是怎样运转的?

    现在,我相信你已经对 OAuth 2.0 有了一个整体印象,接下来咱们再看看它是怎么运转的。

    我们还是来看上面提到的小兔打单软件的例子吧。假如小明在京东上面开了一个店铺,小明要管理他的店铺里面的订单,于是选择了使用小兔软件。

    现在,让我们把“小明”“小兔软件”“京东商家开放平台”放到一个对话里面,看看“他们”是怎么沟通的吧。

    小明:“你好,小兔软件。我正在 Google 浏览器上面,需要访问你来帮我处理我在京东商城店铺的订单。

    ”小兔软件:“好的,小明,我需要你给我授权。现在我把你引导到京东商家开放平台上,你在那里给我授权吧。

    ”京东商家开放平台:“你好,小明。我收到了小兔软件跳转过来的请求,现在已经准备好了一个授权页面。你登录并确认后,点击授权页面上面的授权按钮即可。

    小明:“好的,京东商家开放平台。我看到了这个授权页面,已经点授权按钮啦😄”

    京东商家开放平台:“你好,小兔打单软件。我收到了小明的授权,现在要给你生成一个授权码 code 值,我通过浏览器重定向到你的回调 URL 地址上面了。

    ”小兔软件:“好的,京东商家开放平台。我现在从浏览器上拿到了授权码,现在就用这个授权码来请求你,请给我一个访问令牌 access_token 吧。”

    京东商家开放平台:“好的,小兔打单软件,访问令牌已经发送给你了。”

    小兔打单软件:“太好了,我现在就可以使用访问令牌来获取小明店铺的订单了。”

    小明:“我已经能够看到我的订单了,现在就开始打单操作了。”下

    面,为了帮助你理解,我再用一张图来描述整个过程:

    20221108112829

    再分析下这个流程,我们不难发现小兔软件最终的目的,是要获取一个叫做“访问令牌”的东西。从最后一步也能够看出来,在小兔软件获取到访问令牌之后,才有足够的 “能力” 去请求小明的店铺的订单,也就是才能够帮助小明打印订单。

    那么,小兔软件是怎么获取访问令牌的值的呢?我们会发现还有一个叫做“授权码”的东西,也就是说小兔软件是拿授权码换取的访问令牌。

    小兔软件又是怎么拿到授权码的呢?从图中流程刚开始的那一步,我们就会发现,是在小明授权之后,才产生的授权码,上面流程中后续的一切动作,实际上都是在小明对小兔软件授权发生以后才产生的。其中主要的动作,就是生成授权码–> 生成访问令牌–> 使用访问令牌。

    到这里,我们不难发现,OAuth 2.0 授权的核心就是颁发访问令牌、使用访问令牌,而且不管是哪种类型的授权流程都是这样。你一定要理解,或者记住这句话,它是整个流程的核心。你也可以再回想下,去百度拜访王总的例子。如果你是百度这套机制的设计者的话,会怎么设计这套授权机制呢。想清楚了这个问题,你再去理解令牌、授权码啥的也就简单了。

    在小兔软件这个例子中呢,我们使用的就是授权码许可(Authorization Code)类型。它是 OAuth 2.0 中最经典、最完备、最安全、应用最广泛的许可类型。除了授权码许可类型外,OAuth 2.0 针对不同的使用场景,还有 3 种基础的许可类型,分别是隐式许可(Implicit)、客户端凭据许可(Client Credentials)、资源拥有者凭据许可(Resource Owner Password Credentials)。相对而言,这 3 种授权许可类型的流程,在流程复杂度和安全性上都有所减弱(我会在第 6 讲,与你详细分析)。

    因此,在这个课程中,我会频繁用授权码许可类型来举例。至于为什么称它为授权码许可,为什么有两次重定向,以及这种许可类型更详细的通信流程又是怎样的,我会在第 2 讲给你深入分析,你可以先不用关注。

    总结

    好了,今天这节课就到这里。这节课咱们知识点不多,我来回给你举例子,其实就是希望你能理解 OAuth 到底是什么,为什么需要它,以及它大概的运行逻辑是怎样的。总结来说,我需要你记住以下这 3 个关键点:

    1. OAuth 2.0 的核心是授权许可,更进一步说就是令牌机制。也就是说,像小兔软件这样的第三方软件只有拿到了京东商家开放平台颁发的访问令牌,也就是得到了授权许可,然后才可以代表用户访问他们的数据。
    2. 互联网中所有的受保护资源,几乎都是以 Web API 的形式来提供访问的,比如极客时间 App 要获取用户的头像、昵称,小兔软件要获取用户的店铺订单,我们说 OAuth 2.0 与安全相关,是用来保护 Web API 的。另外,第三方软件通过 OAuth 2.0 取得访问权限之后,用户便把这些权限委托给了第三方软件,我们说 OAuth 2.0 是一种委托协议,也没问题。
    3. 也正因为像小兔这样的第三方软件,每次都是用访问令牌而不是用户名和密码来请求用户的数据,才大大减少了安全风险上的“攻击面”。不然,我们试想一下,每次都带着用户名和密码来访问数量众多的 Web API ,是不是增加了这个“攻击面”。因此,我们说 OAuth 2.0 的核心,就是颁发访问令牌和使用访问令牌。

    思考题

    好了,今天这一讲我们马上要结束了,我给你留个思考题。你可以再花时间想下小兔软件获取用户订单信息的那个场景,如果让你来设计整个的授权流程,你会怎么设计?还有没有更好的方式?欢迎你在留言区分享你的观点,也欢迎你把今天的内容分享给其他朋友,我们一起交流。

    + diff --git a/other/oauth2/02.html b/other/oauth2/02.html index 4b4dec30e3..7d2f96b595 100644 --- a/other/oauth2/02.html +++ b/other/oauth2/02.html @@ -30,12 +30,12 @@ 02 | 授权码许可类型中,为什么一定要有授权码? | ChenSino - - + +
    跳至主要內容

    02 | 授权码许可类型中,为什么一定要有授权码?

    ChenSino大约 13 分钟

    你好,我是王新栋。

    在上一讲,我提到了 OAuth 2.0 的授权码许可类型,在小兔打单软件的例子里面,小兔最终是通过访问令牌请求到小明的店铺里的订单数据。同时呢,我还提到了,这个访问令牌是通过授权码换来的。到这里估计你会问了,为什么要用授权码来换令牌?为什么不能直接颁发访问令牌呢?

    你可以先停下来想想这个问题。今天咱们这节课,我会带着你深入探究下其中的逻辑。

    为什么需要授权码?

    在讲这个问题之前,我先要和你同步下,在 OAuth 2.0 的体系里面有 4 种角色,按照官方的称呼它们分别是资源拥有者、客户端、授权服务和受保护资源。不过,这里的客户端,我更愿意称其为第三方软件,而且在咱们这个课程中,都是以第三方软件在举例子。所以,在后续的讲解中我统一把它称为第三方软件。

    所以,你在看官方资料的时候,可以自己对应下。为了便于你理解,我还是拿小兔软件来举例子,将官方的称呼 “照进现实”,对应关系就是,资源拥有者 -> 小明,第三方软件 -> 小兔软件,授权服务 -> 京东商家开放平台的授权服务,受保护资源 -> 小明店铺在京东上面的订单。

    在理解了这些概念以后,让我们继续。

    你知道,OAuth 诞生之初就是为了解决 Web 浏览器场景下的授权问题,所以我基于浏览器的场景,在上一讲的小明使用小兔软件打印订单的整体流程的基础上,画了一个授权码许可类型的序列图。

    当然了,这里还是有小兔软件来继续陪伴着我们,不过这次为了能够更好地表述授权码许可流程,我会把小兔软件的前端和后端分开展示,并把京东商家开放平台的系统按照 OAuth 2.0 的组件拆分成了授权服务和受保护资源服务。如下图所示: 图1 以小兔软件为例,授权码许可类型的序列图

    突然看到这个序列图增加了这么多步骤的时候,你是不是有些紧张?那如果我告诉你再细分的话步骤还要更多,你是不是就更困惑了?

    不过,别紧张,这没啥关系。一方面,咱们这一讲的重点就是跟授权码相关的流程,你只需关注这里的重点步骤,也就是两次重定向相关的步骤就够了。在下一讲中,我再教你如何将这些步骤进一步拆解。另一方面,我接下来还会用另一种视角来帮助你分析这个流程。

    我们继续来看这张序列图。从图中看到,在第 4 步授权服务生成了授权码 code,按照一开始我们提出来的问题,如果不要授权码,这一步实际上就可以直接返回访问令牌 access_token 了。

    按着这个没有授权码的思路继续想,如果这里直接返回访问令牌,那我们肯定不能使用重定向的方式。因为这样会把安全保密性要求极高的访问令牌暴露在浏览器上,从而将会面临访问令牌失窃的安全风险。显然,这是不能被允许的。

    也就是说,如果没有授权码的话,我们就只能把访问令牌发送给第三方软件小兔的后端服务。按照这样的逻辑,上面的流程图就会变成下面这样: 图2 如果没有授权码,直接把访问令牌发送给第三方软件小兔的后端服务

    到这里,看起来天衣无缝。小明访问小兔软件,小兔软件说要打单你得给我授权,不然京东不干,然后小兔软件就引导小明跳转到了京东的授权服务。到授权服务之后,京东商家开放平台验证了小兔的合法性以及小明的登录状态后,生成了授权页面。紧接着,小明赶紧点击同意授权,这时候,京东商家开放平台知道可以把小明的订单数据给小兔软件。

    于是,京东商家开放平台没含糊,赶紧生成访问令牌 access_token,并且通过后端服务的方式返回给了小兔软件。这时候,小兔软件就能正常工作了。

    这样,问题就来了,什么问题呢?当小明被浏览器重定向到授权服务上之后,小明跟小兔软件之间的 “连接” 就断了,相当于此时此刻小明跟授权服务建立了“连接”后,将一直“停留在授权服务的页面上”。你会看到图 2 中问号处的时序上,小明再也没有重新“连接”到小兔软件。

    但是,这个时候小兔软件已经拿到了小明授权之后的访问令牌,也使用访问令牌获取到了小明店铺里的订单数据。这时,考虑到“小明的感受”,小兔软件应该要通知到小明,但是如何做呢?现在“连接断了”,这事儿恐怕就没那么容易了。

    OK,为了让小兔软件能很容易地通知到小明,还必须让小明跟小兔软件重新建立起 “连接”。这就是我们看到的第二次重定向,小明授权之后,又重新重定向回到了小兔软件的地址上,这样小明就跟小兔软件有了新的 “连接”。

    到这里,你就能理解在授权码许可的流程中,为什么需要两次重定向了吧。

    为了重新建立起这样的一次连接,我们又不能让访问令牌暴露出去,就有了这样一个临时的、间接的凭证:授权码。因为小兔软件最终要拿到的是安全保密性要求极高的访问令牌,并不是授权码,而授权码是可以暴露在浏览器上面的。这样有了授权码的参与,访问令牌可以在后端服务之间传输,同时呢还可以重新建立小明与小兔软件之间的“连接”。这样通过一个授权码,既“照顾”到了小明的体验,又“照顾”了通信的安全。

    这下,你就知道为什么要有授权码了吧。

    那么,在执行授权码流程的时候,授权码和访问令牌在小兔软件和授权服务之间到底是怎么流转的呢?要回答这个问题,就需要继续分析一下授权码许可类型的通信过程了。

    授权码许可类型的通信过程

    图 1 的通信过程中标识出来的步骤就有 9 个,一步步地去分析看似会很复杂,所以我会用另一个维度来分析以帮助你理解,也就是从直接通信和间接通信的维度来分析。这里所谓的间接通信就是指获取授权码的交互,而直接通信就是指通过授权码换取访问令牌的交互。

    接下来,我们就一起分析下吧,看看哪些是间接通信,哪些又是直接通信。

    间接通信我们先分析下为什么是“间接”。我们把图 1 中获取授权码 code 的流程 “放大”,并换个角度来看一看,也就是将浏览器这个代理放到第三方软件小兔和授权服务中间。于是,我们来到了下面这张图: 图3 获取授权码的交互过程

    这个过程,仿佛有这样的一段对话。

    小明:“你好,小兔软件,我要访问你了。 ”小兔软件:“好的,我把你引到授权服务那里,我需要授权服务给我一个授权码。” 授权服务:“小兔软件,我把授权码发给浏览器了。” 小兔软件:“好的,我从浏览器拿到了授权码。”

    不知道你注意到没有,第三方软件小兔和授权服务之间,并没有发生直接的通信,而是 ==通过浏览器这个“中间人” 来 “搭线”的。== 因此,我们说这是一个间接通信的方式。

    直接通信

    那我们再分析下,授权码换取访问令牌的交互,为什么是“直接”的。我们再把图 1 中获取访问令牌的流程“放大”,就得到了下面的图示: 图4 授权码换取访问令牌的交互过程

    相比获取授权码过程的间接通信,获取访问令牌的直接通信就比较容易理解了,就是第三方软件小兔获取到授权码 code 值后,向授权服务发起获取访问令牌 access_token 的通信请求。这个请求是第三方软件服务器跟授权服务的服务器之间的通信,都是在后端服务器之间的请求和响应,因此也叫作后端通信。

    两个 “一伙”

    了解了上面的通信方式之后,不知道你有没有意识到,OAuth 2.0 中的 4 个角色是 “两两站队” 的:资源拥有者和第三方软件“站在一起”,因为第三方软件要代表资源拥有者去访问受保护资源;授权服务和受保护资源“站在一起”,因为授权服务负责颁发访问令牌,受保护资源负责接收并验证访问令牌。 图5 OAuth 2.0 中的4个角色是“两两站队”

    讲到这里的时候,你会发现在这一讲,介绍授权码流程的时候我都是以浏览器参与的场景来讲的,那么浏览器一定要参与到这个流程中吗?其实,授权码许可流程,不一定要有浏览器的参与。接下来,我们就继续分析下其中的逻辑。

    一定要有浏览器吗?

    OAuth 2.0 发展之初,开放生态环境相对单薄,以浏览器为代理的 Web 应用居多,授权码许可类型 “理所当然” 地被应用到了通过浏览器才能访问的 Web 应用中。

    但实际上,OAuth 2.0 是一个授权理念,或者说是一种授权思维。它的授权码模式的思维可以移植到很多场景中,比如微信小程序。在开发微信小程序应用时,我们通过授权码模式获取用户登录信息,官方文档的地址示例open in new window中给出的 grant_type=authorization_code ,就没有用到浏览器。

    根据微信官方文档描述,开发者获取用户登录态信息的过程正是一个授权码的许可流程:

    • 首先,开发者通过 wx.login(Object object) 方法获取到登录凭证 code 值,这一步的流程是在小程序内部通过调用微信提供的 SDK 实现;
    • 然后,再通过该 code 值换取用户的 session_key 等信息,也就是官方文档的 auth.code2Session 方法,同时该方法也是被强烈建议通过开发者的后端服务来调用的。

    你可以看到,这个过程并没有使用到浏览器,但确实按照授权码许可的思想走了一个完整的授权码许可流程。也就是说,先通过小程序前端获取到 code 值,再通过小程序的后端服务使用 code 值换取 session_key 等信息,只不过是访问令牌 access_token 的值被换成了 session_key。

    GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
    -

    你看,这整个过程体现的就是授权码许可流程的思想。

    总结

    这节课又接近尾声了,我再带你回顾下重点内容。

    今天,我从为什么需要授权码这个问题开始讲起,并通过授权码把授权码许可流程整体的通信过程串了一遍,提到了授权码这种方式解决的问题,也提到了授权码流程的通信方式。总结来说,我需要你记住以下两点。

    1. 授权码许可流程有两种通信方式。一种是前端通信,因为它通过浏览器促成了授权码的交互流程,比如京东商家开放平台的授权服务生成授权码发送到浏览器,第三方软件小兔从浏览器获取授权码。正因为获取授权码的时候小兔软件和授权服务并没有发生直接的联系,也叫做间接通信。另外一种是后端通信,在小兔软件获取到授权码之后,在后端服务直接发起换取访问令牌的请求,也叫做直接通信。
    2. 在 OAuth 2.0 中,访问令牌被要求有极高的安全保密性,因此我们不能让它暴露在浏览器上面,只能通过第三方软件(比如小兔)的后端服务来获取和使用,以最大限度地保障访问令牌的安全性。正因为访问令牌的这种安全要求特性,当需要前端通信,比如浏览器上面的流转的时候,OAuth 2.0 才又提供了一个临时的凭证:授权码。通过授权码的方式,可以让用户小明在授权服务上给小兔授权之后,还能重新回到小兔的操作页面上。这样,在保障安全性的情况下,提升了小明在小兔上的体验。

    从授权码许可流程中就可以看出来,它完美地将 OAuth 2.0 的 4 个角色组织了起来,并保证了它们之间的顺畅通信。它提出的这种结构和思想都可以被迁移到其他环境或者协议上,比如在微信小程序中使用授权码许可。

    不过,也正是因为有了授权码的参与,才使得授权码许可要比其他授权许可类型,在授权的流程上多出了好多步骤,让授权码许可类型成为了 OAuth 2.0 体系中迄今流程最完备、安全性最高的授权流程。在接下来的两讲中,我还会为你重点讲解授权码许可类型下的授权服务。

    思考题

    好了,今天这一讲我们马上要结束了,我给你留个思考题。关于不需要浏览器参与的授权码许可流程,你还能列举出更多的应用场景吗?

    - +

    你看,这整个过程体现的就是授权码许可流程的思想。

    总结

    这节课又接近尾声了,我再带你回顾下重点内容。

    今天,我从为什么需要授权码这个问题开始讲起,并通过授权码把授权码许可流程整体的通信过程串了一遍,提到了授权码这种方式解决的问题,也提到了授权码流程的通信方式。总结来说,我需要你记住以下两点。

    1. 授权码许可流程有两种通信方式。一种是前端通信,因为它通过浏览器促成了授权码的交互流程,比如京东商家开放平台的授权服务生成授权码发送到浏览器,第三方软件小兔从浏览器获取授权码。正因为获取授权码的时候小兔软件和授权服务并没有发生直接的联系,也叫做间接通信。另外一种是后端通信,在小兔软件获取到授权码之后,在后端服务直接发起换取访问令牌的请求,也叫做直接通信。
    2. 在 OAuth 2.0 中,访问令牌被要求有极高的安全保密性,因此我们不能让它暴露在浏览器上面,只能通过第三方软件(比如小兔)的后端服务来获取和使用,以最大限度地保障访问令牌的安全性。正因为访问令牌的这种安全要求特性,当需要前端通信,比如浏览器上面的流转的时候,OAuth 2.0 才又提供了一个临时的凭证:授权码。通过授权码的方式,可以让用户小明在授权服务上给小兔授权之后,还能重新回到小兔的操作页面上。这样,在保障安全性的情况下,提升了小明在小兔上的体验。

    从授权码许可流程中就可以看出来,它完美地将 OAuth 2.0 的 4 个角色组织了起来,并保证了它们之间的顺畅通信。它提出的这种结构和思想都可以被迁移到其他环境或者协议上,比如在微信小程序中使用授权码许可。

    不过,也正是因为有了授权码的参与,才使得授权码许可要比其他授权许可类型,在授权的流程上多出了好多步骤,让授权码许可类型成为了 OAuth 2.0 体系中迄今流程最完备、安全性最高的授权流程。在接下来的两讲中,我还会为你重点讲解授权码许可类型下的授权服务。

    思考题

    好了,今天这一讲我们马上要结束了,我给你留个思考题。关于不需要浏览器参与的授权码许可流程,你还能列举出更多的应用场景吗?

    + diff --git a/other/oauth2/03.html b/other/oauth2/03.html index 7ce02acf7c..ff69178676 100644 --- a/other/oauth2/03.html +++ b/other/oauth2/03.html @@ -30,8 +30,8 @@ 03 | 授权服务:授权码和访问令牌的颁发流程是怎样 | ChenSino - - + +
    跳至主要內容

    03 | 授权服务:授权码和访问令牌的颁发流程是怎样

    王新栋大约 19 分钟

    你好,我是王新栋。

    在上一讲,我从为什么需要授权码这个问题开始,为你串了一遍授权码许可流程整体的通信过程。在接下来的三讲中,我会着重为你讲解关于授权服务的工作流程、授权过程中的令牌,以及如何接入 OAuth 2.0。这样一来,你就可以吃透授权码许可这一最经典、最完备、最常用的授权流程了,以后再处理授权相关的逻辑就更得心应手了。现在呢,让我们开始这一讲。

    在介绍授权码许可类型时,我提到了很多次 “授权服务”。一句话概括,授权服务就是负责颁发访问令牌的服务。更进一步地讲,OAuth 2.0 的核心是授权服务,而授权服务的核心

    为什么这么说呢?当第三方软件比如小兔,要想获取小明在京东店铺的订单,就必须先从京东商家开放平台的授权服务那里获取访问令牌,进而通过访问令牌来 “代表” 小明去请求小明的订单数据。这不恰恰就是整个 OAuth 2.0 授权体系的核心吗?

    那么,授权服务到底是怎么生成访问令牌的,这其中包含了哪些操作呢?还有一个问题是,访问令牌过期了而用户又不在场的情况下,又如何重新生成访问令牌呢?

    带着这两个问题,我们就以授权码许可类型为例,一起深入探索下授权服务这个核心组件吧。

    授权服务的工作过程

    开始之前,你还是要先回想下小明给小兔软件授权订单数据的整个流程。

    我们说小兔软件先要让小明去京东商家开放平台那里给它授权数据,那这里是不是你觉得很奇怪?你总不能说,“嘿,京东,你把数据给小兔用吧”,那京东肯定会回复说,“小明,小兔是谁啊,没在咱家备过案,我不能给他,万一是骗子呢?”

    对吧,你想想是不是这个逻辑。所以,授权这个大动作的前提,肯定是小兔要去平台那里“备案”,也就是注册。注册完后,京东商家开放平台就会给小兔软件 app_id 和 app_secret 等信息,以方便后面授权时的各种身份校验。

    同时,注册的时候,第三方软件也会请求受保护资源的可访问范围。比如,小兔能否获取小明店铺 3 个月以前的订单,能否获取每条订单的所有字段信息等等。这个权限范围,就是 scope。后面呢,我还会详细讲述范围控制。

    文字说起来有点抽象,咱们还是直接上代码吧。关于注册后的数据存储,我们使用如下 Java 代码来模拟:

    
    @@ -145,7 +145,7 @@
     if(!appStr.startsWith(appId+"|"+"USERTEST")){
         //该refresh_token值不是颁发给该第三方软件的
     }
    -

    需要注意,一个刷新令牌被使用以后,授权服务需要将其废弃,并重新颁发一个刷新令牌。

    第二步,重新生成访问令牌。

    生成访问令牌的处理流程,与颁发访问令牌环节的生成流程是一致的。授权服务会将新的访问令牌和新的刷新令牌,一起返回给第三方软件。这里就不再赘述了。

    总结

    今天的课马上又要结束了,我和你讲了授权码许可类型下授权服务的工作原理。授权服务可以说是整个 OAuth 2.0 体系中的 “灵魂” 组件,任何一种许可类型都离不开它的支持,它也是最复杂的组件。这是因为它将复杂性尽可能地“揽在了自己身上”,才使得诸如小兔这样的第三方软件接入 OAuth 2.0 的时候更加便捷。那关于如何快速地接入 OAuth 2.0,我在第 5 讲中和你详细展开。

    授权服务的步骤流程比较多,因此我把这节课配套的代码放到了GitHubopen in new window上,可以帮助你更好地理解授权服务的流程。

    总结来讲,关于这一讲,我希望你能记住以下 3 点。

    1. 授权服务的核心就是,先颁发授权码 code 值,再颁发访问令牌 access_token 值。
    2. 在颁发访问令牌的同时还会颁发刷新令牌 refresh_token 值,这种机制可以在无须用户参与的情况下用于生成新的访问令牌。正如我们讲到的小明使用小兔软件的例子,当访问令牌过期的时候,刷新令牌的存在可以大大提高小明使用小兔软件的体验。
    3. 授权还要有授权范围,不能让第三方软件获得比注册时权限范围还大的授权,也不能获得超出了用户授权的权限范围,始终确保最小权限安全原则。比如,小明只为小兔软件授予了获取当天订单的权限,那么小兔软件就不能访问小明店铺里面的历史订单数据。
    - +

    需要注意,一个刷新令牌被使用以后,授权服务需要将其废弃,并重新颁发一个刷新令牌。

    第二步,重新生成访问令牌。

    生成访问令牌的处理流程,与颁发访问令牌环节的生成流程是一致的。授权服务会将新的访问令牌和新的刷新令牌,一起返回给第三方软件。这里就不再赘述了。

    总结

    今天的课马上又要结束了,我和你讲了授权码许可类型下授权服务的工作原理。授权服务可以说是整个 OAuth 2.0 体系中的 “灵魂” 组件,任何一种许可类型都离不开它的支持,它也是最复杂的组件。这是因为它将复杂性尽可能地“揽在了自己身上”,才使得诸如小兔这样的第三方软件接入 OAuth 2.0 的时候更加便捷。那关于如何快速地接入 OAuth 2.0,我在第 5 讲中和你详细展开。

    授权服务的步骤流程比较多,因此我把这节课配套的代码放到了GitHubopen in new window上,可以帮助你更好地理解授权服务的流程。

    总结来讲,关于这一讲,我希望你能记住以下 3 点。

    1. 授权服务的核心就是,先颁发授权码 code 值,再颁发访问令牌 access_token 值。
    2. 在颁发访问令牌的同时还会颁发刷新令牌 refresh_token 值,这种机制可以在无须用户参与的情况下用于生成新的访问令牌。正如我们讲到的小明使用小兔软件的例子,当访问令牌过期的时候,刷新令牌的存在可以大大提高小明使用小兔软件的体验。
    3. 授权还要有授权范围,不能让第三方软件获得比注册时权限范围还大的授权,也不能获得超出了用户授权的权限范围,始终确保最小权限安全原则。比如,小明只为小兔软件授予了获取当天订单的权限,那么小兔软件就不能访问小明店铺里面的历史订单数据。
    + diff --git a/other/oauth2/04.html b/other/oauth2/04.html index ca504b97cb..cd9d96e4c2 100644 --- a/other/oauth2/04.html +++ b/other/oauth2/04.html @@ -30,8 +30,8 @@ 04 | 在OAuth 2.0中,如何使用JWT结构化令牌? | ChenSino - - + +
    跳至主要內容

    04 | 在OAuth 2.0中,如何使用JWT结构化令牌?

    王新栋大约 15 分钟

    在上一讲,我们讲到了授权服务的核心就是颁发访问令牌,而 OAuth 2.0 规范并没有约束访问令牌内容的生成规则,只要符合唯一性、不连续性、不可猜性就够了。这就意味着,我们可以灵活选择令牌的形式,既可以是没有内部结构且不包含任何信息含义的随机字符串,也可以是具有内部结构且包含有信息含义的字符串。

    随机字符串这样的方式我就不再介绍了,之前课程中我们生成令牌的方式都是默认一个随机字符串。而在结构化令牌这方面,目前用得最多的就是 JWT 令牌了。

    接下来,我就要和你详细讲讲,JWT 是什么、原理是怎样的、优势是什么,以及怎么使用,同时我还会讲到令牌生命周期的问题。

    JWT 结构化令牌

    关于什么是 JWT,官方定义是这样描述的:

    JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。

    这个定义是不是很费解?我们简单理解下,JWT 就是用一种结构化封装的方式来生成 token 的技术。结构化后的 token 可以被赋予非常丰富的含义,这也是它与原先毫无意义的、随机的字符串形式 token 的最大区别。

    结构化之后,令牌本身就可以被“塞进”一些有用的信息,比如小明为小兔软件进行了授权的信息、授权的范围信息等。或者,你可以形象地将其理解为这是一种“自编码”的能力,而这些恰恰是无结构化令牌所不具备的。

    JWT 这种结构化体可以分为 HEADER(头部)、PAYLOAD(数据体)和 SIGNATURE(签名)三部分。经过签名之后的 JWT 的整体结构,是被句点符号分割的三段内容,结构为 header.payload.signature 。比如下面这个示例:

    
    @@ -52,7 +52,7 @@
     JwsHeader header = claimsJws.getHeader();
     Claims body = claimsJws.getBody();  
     

    使用 JJWT 解析 JWT 令牌时包含了验证签名的动作,如果签名不正确就会抛出异常信息。我们可以借助这一点来对签名做校验,从而判断是否是一个没有被伪造过的、合法的 JWT 令牌。异常信息,一般是如下的样子:

    JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
    -

    以上就是借助开源工具,将 JWT 令牌应用到授权服务流程中的方法了。到这里,你是不是一直都有一个疑问:为什么要绕这么大一个弯子,使用 JWT,而不是使用没有啥内部结构,也不包含任何信息的随机字符串呢?JWT 到底有什么好处?

    为什么要使用 JWT 令牌?

    别急,我这就和你总结下使用 JWT 格式令牌的三大好处。

    第一,JWT 的核心思想,就是用计算代替存储,有些 “时间换空间” 的 “味道”。当然,这种经过计算并结构化封装的方式,也减少了“共享数据库” 因远程调用而带来的网络传输消耗,所以也有可能是节省时间的。

    第二,也是一个重要特性,是加密。因为 JWT 令牌内部已经包含了重要的信息,所以在整个传输过程中都必须被要求是密文传输的,这样被强制要求了加密也就保障了传输过程中的安全性。这里的加密算法,既可以是对称加密,也可以是非对称加密。

    第三,使用 JWT 格式的令牌,有助于增强系统的可用性和可伸缩性。这一点要怎么理解呢?我们前面讲到了,这种 JWT 格式的令牌,通过“自编码”的方式包含了身份验证需要的信息,不再需要服务端进行额外的存储,所以每次的请求都是无状态会话。这就符合了我们尽可能遵循无状态架构设计的原则,也就是增强了系统的可用性和伸缩性。

    但,万物皆有两面性,JWT 令牌也有缺点。

    JWT 格式令牌的最大问题在于 “覆水难收”,也就是说,没办法在使用过程中修改令牌状态。我们还是借助小明使用小兔软件例子,先停下来想一下。

    小明在使用小兔软件的时候,是不是有可能因为某种原因修改了在京东的密码,或者是不是有可能突然取消了给小兔的授权?这时候,令牌的状态是不是就要有相应的变更,将原来对应的令牌置为无效。

    但,使用 JWT 格式令牌时,每次颁发的令牌都不会在服务端存储,这样我们要改变令牌状态的时候,就无能为力了。因为服务端并没有存储这个 JWT 格式的令牌。这就意味着,JWT 令牌在有效期内,是可以“横行无止”的。

    为了解决这个问题,我们可以把 JWT 令牌存储到远程的分布式内存数据库中吗?显然不能,因为这会违背 JWT 的初衷(将信息通过结构化的方式存入令牌本身)。因此,我们通常会有两种做法:

    • 一是,将每次生成 JWT 令牌时的秘钥粒度缩小到用户级别,也就是一个用户一个秘钥。这样,当用户取消授权或者修改密码后,就可以让这个密钥一起修改。一般情况下,这种方案需要配套一个单独的密钥管理服务。
    • 二是,在不提供用户主动取消授权的环境里面,如果只考虑到修改密码的情况,那么我们就可以把用户密码作为 JWT 的密钥。当然,这也是用户粒度级别的。这样一来,用户修改密码也就相当于修改了密钥。

    令牌的生命周期

    我刚才讲了 JWT 令牌有效期的问题,讲到了它的失效处理,另外咱们在第 3 讲中提到,授权服务颁发访问令牌的时候,都会设置一个过期时间,其实这都属于令牌的生命周期的管理问题。接下来,我便向你讲一讲令牌的生命周期。万物皆有周期,这是自然规律,令牌也不例外,无论是 JWT 结构化令牌还是普通的令牌。它们都有有效期,只不过,JWT 令牌可以把有效期的信息存储在本身的结构体中。具体到 OAuth 2.0 的令牌生命周期,通常会有三种情况。

    第一种情况是令牌的自然过期过程,这也是最常见的情况。这个过程是,从授权服务创建一个令牌开始,到第三方软件使用令牌,再到受保护资源服务验证令牌,最后再到令牌失效。同时,这个过程也不排除主动销毁令牌的事情发生,比如令牌被泄露,授权服务可以做主让令牌失效。

    生命周期的第二种情况,也就是上一讲提到的,访问令牌失效之后可以使用刷新令牌请求新的访问令牌来代替失效的访问令牌,以提升用户使用第三方软件的体验。

    生命周期的第三种情况,就是让第三方软件比如小兔,主动发起令牌失效的请求,然后授权服务收到请求之后让令牌立即失效。我们来想一下,什么情况下会需要这种机制,也就是想一下第三方软件这样做的 “动机”,毕竟一般情况下 “我们很难放弃已经拥有的事物”。

    比如有些时候,用户和第三方软件之间存在一种订购关系,比如小明购买了小兔软件,那么在订购时长到期或者退订,且小明授权的 token 还没有到期的情况下,就需要有这样的一种令牌撤回协议,来支持小兔软件主动发起令牌失效的请求。作为平台一方比如京东商家开放平台,也建议有责任的第三方软件比如小兔软件,遵守这样的一种令牌撤回协议。

    我将以上三种情况整理成了一份序列图,以便帮助你理解。同时,为了突出令牌,我将访问令牌和刷新令牌,特意用深颜色标识出来,并单独作为两个角色放进了整个序列图中。

    img

    总结

    OAuth 2.0 的核心是授权服务,更进一步讲是令牌,没有令牌就没有 OAuth,令牌表示的是授权行为之后的结果。

    一般情况下令牌对第三方软件来说是一个随机的字符串,是不透明的。大部分情况下,我们提及的令牌,都是一个无意义的字符串。

    但是,人们“不甘于”这样的满足,于是开始探索有没有其他生成令牌的方式,也就有了 JWT 令牌,这样一来既不需要通过共享数据库,也不需要通过授权服务提供接口的方式来做令牌校验了。这就相当于通过 JWT 这种结构化的方式,我们在做令牌校验的时候多了一种选择。

    通过这一讲呢,我希望你能记住以下几点内容:

    1. 我们有了新的令牌生成方式的选择,这就是 JWT 令牌。这是一种结构化、信息化令牌,结构化可以组织用户的授权信息,信息化就是令牌本身包含了授权信息。
    2. 虽然我们这讲的重点是 JWT 令牌,但是呢,不论是结构化的令牌还是非结构化的令牌,对于第三方软件来讲,它都不关心,因为令牌在 OAuth 2.0 系统中对于第三方软件都是不透明的。需要关心令牌的,是授权服务和受保护资源服务。
    3. 我们需要注意 JWT 令牌的失效问题。我们使用了 JWT 令牌之后,远程的服务端上面是不存储的,因为不再有这个必要,JWT 令牌本身就包含了信息。那么,如何来控制它的有效性问题呢?本讲中,我给出了两种建议,一种是建立一个秘钥管理系统,将生成秘钥的粒度缩小到用户级别,另外一种是直接将用户密码当作密钥。

    现在,你已经对 JWT 有了更深刻的认识,也知道如何来使用它了。当你构建并生成令牌的时候除了使用随机的、“任性的”字符串,还可以采用这样的结构化的令牌,以便在令牌校验的时候能解析出令牌的内容信息直接进行校验处理。

    我把今天用到的代码放到了 GitHub 上,你可以点击这个链接open in new window查看。

    思考题

    你还知道有哪些场景适合 JWT 令牌,又有哪些场景不适合 JWT 令牌吗?

    - +

    以上就是借助开源工具,将 JWT 令牌应用到授权服务流程中的方法了。到这里,你是不是一直都有一个疑问:为什么要绕这么大一个弯子,使用 JWT,而不是使用没有啥内部结构,也不包含任何信息的随机字符串呢?JWT 到底有什么好处?

    为什么要使用 JWT 令牌?

    别急,我这就和你总结下使用 JWT 格式令牌的三大好处。

    第一,JWT 的核心思想,就是用计算代替存储,有些 “时间换空间” 的 “味道”。当然,这种经过计算并结构化封装的方式,也减少了“共享数据库” 因远程调用而带来的网络传输消耗,所以也有可能是节省时间的。

    第二,也是一个重要特性,是加密。因为 JWT 令牌内部已经包含了重要的信息,所以在整个传输过程中都必须被要求是密文传输的,这样被强制要求了加密也就保障了传输过程中的安全性。这里的加密算法,既可以是对称加密,也可以是非对称加密。

    第三,使用 JWT 格式的令牌,有助于增强系统的可用性和可伸缩性。这一点要怎么理解呢?我们前面讲到了,这种 JWT 格式的令牌,通过“自编码”的方式包含了身份验证需要的信息,不再需要服务端进行额外的存储,所以每次的请求都是无状态会话。这就符合了我们尽可能遵循无状态架构设计的原则,也就是增强了系统的可用性和伸缩性。

    但,万物皆有两面性,JWT 令牌也有缺点。

    JWT 格式令牌的最大问题在于 “覆水难收”,也就是说,没办法在使用过程中修改令牌状态。我们还是借助小明使用小兔软件例子,先停下来想一下。

    小明在使用小兔软件的时候,是不是有可能因为某种原因修改了在京东的密码,或者是不是有可能突然取消了给小兔的授权?这时候,令牌的状态是不是就要有相应的变更,将原来对应的令牌置为无效。

    但,使用 JWT 格式令牌时,每次颁发的令牌都不会在服务端存储,这样我们要改变令牌状态的时候,就无能为力了。因为服务端并没有存储这个 JWT 格式的令牌。这就意味着,JWT 令牌在有效期内,是可以“横行无止”的。

    为了解决这个问题,我们可以把 JWT 令牌存储到远程的分布式内存数据库中吗?显然不能,因为这会违背 JWT 的初衷(将信息通过结构化的方式存入令牌本身)。因此,我们通常会有两种做法:

    令牌的生命周期

    我刚才讲了 JWT 令牌有效期的问题,讲到了它的失效处理,另外咱们在第 3 讲中提到,授权服务颁发访问令牌的时候,都会设置一个过期时间,其实这都属于令牌的生命周期的管理问题。接下来,我便向你讲一讲令牌的生命周期。万物皆有周期,这是自然规律,令牌也不例外,无论是 JWT 结构化令牌还是普通的令牌。它们都有有效期,只不过,JWT 令牌可以把有效期的信息存储在本身的结构体中。具体到 OAuth 2.0 的令牌生命周期,通常会有三种情况。

    第一种情况是令牌的自然过期过程,这也是最常见的情况。这个过程是,从授权服务创建一个令牌开始,到第三方软件使用令牌,再到受保护资源服务验证令牌,最后再到令牌失效。同时,这个过程也不排除主动销毁令牌的事情发生,比如令牌被泄露,授权服务可以做主让令牌失效。

    生命周期的第二种情况,也就是上一讲提到的,访问令牌失效之后可以使用刷新令牌请求新的访问令牌来代替失效的访问令牌,以提升用户使用第三方软件的体验。

    生命周期的第三种情况,就是让第三方软件比如小兔,主动发起令牌失效的请求,然后授权服务收到请求之后让令牌立即失效。我们来想一下,什么情况下会需要这种机制,也就是想一下第三方软件这样做的 “动机”,毕竟一般情况下 “我们很难放弃已经拥有的事物”。

    比如有些时候,用户和第三方软件之间存在一种订购关系,比如小明购买了小兔软件,那么在订购时长到期或者退订,且小明授权的 token 还没有到期的情况下,就需要有这样的一种令牌撤回协议,来支持小兔软件主动发起令牌失效的请求。作为平台一方比如京东商家开放平台,也建议有责任的第三方软件比如小兔软件,遵守这样的一种令牌撤回协议。

    我将以上三种情况整理成了一份序列图,以便帮助你理解。同时,为了突出令牌,我将访问令牌和刷新令牌,特意用深颜色标识出来,并单独作为两个角色放进了整个序列图中。

    img

    总结

    OAuth 2.0 的核心是授权服务,更进一步讲是令牌,没有令牌就没有 OAuth,令牌表示的是授权行为之后的结果。

    一般情况下令牌对第三方软件来说是一个随机的字符串,是不透明的。大部分情况下,我们提及的令牌,都是一个无意义的字符串。

    但是,人们“不甘于”这样的满足,于是开始探索有没有其他生成令牌的方式,也就有了 JWT 令牌,这样一来既不需要通过共享数据库,也不需要通过授权服务提供接口的方式来做令牌校验了。这就相当于通过 JWT 这种结构化的方式,我们在做令牌校验的时候多了一种选择。

    通过这一讲呢,我希望你能记住以下几点内容:

    1. 我们有了新的令牌生成方式的选择,这就是 JWT 令牌。这是一种结构化、信息化令牌,结构化可以组织用户的授权信息,信息化就是令牌本身包含了授权信息。
    2. 虽然我们这讲的重点是 JWT 令牌,但是呢,不论是结构化的令牌还是非结构化的令牌,对于第三方软件来讲,它都不关心,因为令牌在 OAuth 2.0 系统中对于第三方软件都是不透明的。需要关心令牌的,是授权服务和受保护资源服务。
    3. 我们需要注意 JWT 令牌的失效问题。我们使用了 JWT 令牌之后,远程的服务端上面是不存储的,因为不再有这个必要,JWT 令牌本身就包含了信息。那么,如何来控制它的有效性问题呢?本讲中,我给出了两种建议,一种是建立一个秘钥管理系统,将生成秘钥的粒度缩小到用户级别,另外一种是直接将用户密码当作密钥。

    现在,你已经对 JWT 有了更深刻的认识,也知道如何来使用它了。当你构建并生成令牌的时候除了使用随机的、“任性的”字符串,还可以采用这样的结构化的令牌,以便在令牌校验的时候能解析出令牌的内容信息直接进行校验处理。

    我把今天用到的代码放到了 GitHub 上,你可以点击这个链接open in new window查看。

    思考题

    你还知道有哪些场景适合 JWT 令牌,又有哪些场景不适合 JWT 令牌吗?

    + diff --git a/other/oauth2/05.html b/other/oauth2/05.html index d41ea24248..f481962506 100644 --- a/other/oauth2/05.html +++ b/other/oauth2/05.html @@ -30,8 +30,8 @@ 05 | 如何安全、快速地接入OAuth 2.0? | ChenSino - - + +
    跳至主要內容

    05 | 如何安全、快速地接入OAuth 2.0?

    王新栋大约 15 分钟

    第 3 讲,我已经讲了授权服务的流程,如果你还记得的话,当时我特意强调了一点,就是授权服务将 OAuth 2.0 的复杂性都揽在了自己身上,这也是授权服务为什么是 OAuth 2.0 体系的核心的原因之一。

    虽然授权服务做了大部分工作,但是呢,在 OAuth 2.0 的体系里面,除了资源拥有者是作为用户参与,还有另外两个系统角色,也就是第三方软件和受保护资源服务。那么今天这一讲,我们就站在这两个角色的角度,看看它们应该做哪些工作,才能接入到 OAuth 2.0 的体系里面呢?

    现在,就让我们来看看,作为第三方软件的小兔和京东的受保护资源服务,具体需要着重处理哪些工作吧。

    注:另外说明一点,为了脱敏的需要,在下面的讲述中,我只是把京东商家开放平台作为一个角色使用,以便有场景感,来帮助你理解。

    构建第三方软件应用

    我们先来思考一下:如果要基于京东商家开放平台构建一个小兔打单软件的应用,小兔软件的研发人员应该做哪些工作?

    是不是要到京东商家开放平台申请注册为开发者,在成为开发者以后再创建一个应用,之后我们就开始开发了,对吧?没错,一定是这样的流程。那么,开发第三方软件应用的过程中,我们需要重点关注哪些内容呢?

    我先来和你总结下,这些内容包括 4 部分,分别是:注册信息、引导授权、使用访问令牌、使用刷新令牌。

    图1 开发第三方软件应用,应该关注的内容

    第一点,注册信息。

    首先,小兔软件只有先有了身份,才可以参与到 OAuth 2.0 的流程中去。也就是说,小兔软件需要先拥有自己的 app_id 和 app_serect 等信息,同时还要填写自己的回调地址 redirect_uri、申请权限等信息。这种方式的注册呢,我们有时候也称它为静态注册,也就是小兔软件的研发人员提前登录到京东商家开放平台进行手动注册,以便后续使用这些注册的相关信息来请求访问令牌。

    第二点,引导授权。

    当用户需要使用第三方软件,来操作其在受保护资源上的数据,就需要第三方软件来引导授权。比如,小明要使用小兔打单软件来对店铺里面的订单发货打印,那小明首先访问的一定是小兔软件(原则上是直接访问第三方软件,不过我们在后面讲到服务市场这种场景的时候,会有稍微不同),不会是授权服务,更不会是受保护资源服务。

    但是呢,小兔软件需要小明的授权,只有授权服务才能允许小明这样做。所以呢,小兔软件需要 “配合” 小明做的第一件事儿,就是将小明引导至授权服务,如下面代码所示。那去做什么呢?其实就是让用户为第三方软件授权,得到了授权之后,第三方软件才可以代表用户去访问数据。也就是说,小兔打单软件获得授权之后,才能够代表小明处理其在京东店铺上的订单数据。

    
    @@ -83,7 +83,7 @@
     //不同的用户对应不同的数据
     String user = OauthServlet.tokenMap.get(accessToken);
     queryOrders(user);
    -

    在上面讲三种权限的时候,我举的例子实际上都属于一个系统提供了查询、添加、删除这样的所有服务。此时你可能会想到,现在的系统不已经是分布式系统环境了么,如果有很多个受保护资源服务,比如提供用户信息查询的用户资源服务、提供商品查询的商品资源服务、提供订单查询的订单资源服务,那么每个受保护资源服务岂不是都要把上述的权限范围校验执行一遍吗,这样不就会有大量的重复工作产生么?

    在这里,我特别高兴你能想到这一点。为了应对这种情况,我们应该有一个统一的网关层来处理这样的校验,所有的请求都会经过 API GATEWAY 跳转到不同的受保护资源服务。这样呢,我们就不需要在每一个受保护资源服务上都做一遍权限校验的工作了,而只需要在 API GATEWAY 这一层做权限校验就可以了。系统结构如下图所示。

    图4 由统一的网关层处理权限校验

    总结

    截止到这一讲呢,我们已经把 OAuth 2.0 中授权码相关的流程所涉及到的内容都讲完了。通过 02 到 05 这 4 讲,你可以很清晰地理解授权码流程的核心原理了,也可以弄清楚如何使用以及如何接入这一授权流程了。

    我在本讲开始的时候,提到 OAuth 2.0 的复杂性实际上都给了授权服务来承担,接着我从第三方软件和受保护资源的角度,分别介绍了这两部分系统在接入 OAuth 2.0 的时候应该注意哪些方面。总结下来,我其实希望你能够记住以下两点。

    1. 对于第三方软件,比如小兔打单软件来讲,它的主要目的就是获取访问令牌,使用访问令牌,这当然也是整个 OAuth 2.0 的目的,就是让第三方软件来做这两件事。在这个过程中需要强调的是,第三方软件在使用访问令牌的时候有三种方式,我们建议在平台和第三方软件约定好的前提下,优先采用 Post 表单提交的方式。
    2. 受保护资源系统,比如小兔软件要访问开放平台的订单数据服务,它需要注意的是权限的问题,这个权限范围主要包括,不同的权限会有不同的操作,不同的权限也会对应不同的数据,不同的用户也会对应不同的数据。

    思考题

    如果使用刷新令牌 refresh_token 请求回来一个新的访问令牌 access_token,按照一般规则授权服务上旧的访问令牌应该要立即失效,但是如果在这之前已经有使用旧的访问令牌发出去的请求,不就受到影响了吗,这种情况下应该如何处理呢?

    - +

    在上面讲三种权限的时候,我举的例子实际上都属于一个系统提供了查询、添加、删除这样的所有服务。此时你可能会想到,现在的系统不已经是分布式系统环境了么,如果有很多个受保护资源服务,比如提供用户信息查询的用户资源服务、提供商品查询的商品资源服务、提供订单查询的订单资源服务,那么每个受保护资源服务岂不是都要把上述的权限范围校验执行一遍吗,这样不就会有大量的重复工作产生么?

    在这里,我特别高兴你能想到这一点。为了应对这种情况,我们应该有一个统一的网关层来处理这样的校验,所有的请求都会经过 API GATEWAY 跳转到不同的受保护资源服务。这样呢,我们就不需要在每一个受保护资源服务上都做一遍权限校验的工作了,而只需要在 API GATEWAY 这一层做权限校验就可以了。系统结构如下图所示。

    图4 由统一的网关层处理权限校验

    总结

    截止到这一讲呢,我们已经把 OAuth 2.0 中授权码相关的流程所涉及到的内容都讲完了。通过 02 到 05 这 4 讲,你可以很清晰地理解授权码流程的核心原理了,也可以弄清楚如何使用以及如何接入这一授权流程了。

    我在本讲开始的时候,提到 OAuth 2.0 的复杂性实际上都给了授权服务来承担,接着我从第三方软件和受保护资源的角度,分别介绍了这两部分系统在接入 OAuth 2.0 的时候应该注意哪些方面。总结下来,我其实希望你能够记住以下两点。

    1. 对于第三方软件,比如小兔打单软件来讲,它的主要目的就是获取访问令牌,使用访问令牌,这当然也是整个 OAuth 2.0 的目的,就是让第三方软件来做这两件事。在这个过程中需要强调的是,第三方软件在使用访问令牌的时候有三种方式,我们建议在平台和第三方软件约定好的前提下,优先采用 Post 表单提交的方式。
    2. 受保护资源系统,比如小兔软件要访问开放平台的订单数据服务,它需要注意的是权限的问题,这个权限范围主要包括,不同的权限会有不同的操作,不同的权限也会对应不同的数据,不同的用户也会对应不同的数据。

    思考题

    如果使用刷新令牌 refresh_token 请求回来一个新的访问令牌 access_token,按照一般规则授权服务上旧的访问令牌应该要立即失效,但是如果在这之前已经有使用旧的访问令牌发出去的请求,不就受到影响了吗,这种情况下应该如何处理呢?

    + diff --git a/other/oauth2/06.html b/other/oauth2/06.html index e1b8cf4e8c..4b7102fc02 100644 --- a/other/oauth2/06.html +++ b/other/oauth2/06.html @@ -30,8 +30,8 @@ 06 | 除了授权码许可类型,OAuth 2.0还支持什么授权流程? | ChenSino - - + +
    跳至主要內容

    06 | 除了授权码许可类型,OAuth 2.0还支持什么授权流程?

    ChenSino大约 11 分钟

    在前面几讲学习授权码许可类型的原理与工作流程时,不知道你是不是一直有这样一个疑问:授权码许可的流程最完备、最安全没错儿,但它适合所有的授权场景吗?在有些场景下使用授权码许可授权,是不是过于复杂了,是不是根本就没必要这样?

    比如,小兔打单软件是京东官方开发的一款软件,那么小明在使用小兔的时候,还需要小兔再走一遍授权码许可类型的流程吗?估计你也猜到答案了,肯定是不需要了。

    你还记得授权码许可流程的特点么?它通过授权码这种临时的中间值,让小明这样的用户参与进来,从而让小兔软件和京东之间建立联系,进而让小兔代表小明去访问他在京东店铺的订单数据。

    现在小兔被“招安”了,是京东自家的了,是被京东充分信任的,没有“第三方软件”的概念了。同时,小明也是京东店铺的商家,也就是说软件和用户都是京东的资产。这时,显然没有必要再使用授权码许可类型进行授权了。但是呢,小兔依然要通过互联网访问订单数据的 Web API,来提供为小明打单的功能。

    于是,为了保护这些场景下的 Web API,又为了让 OAuth 2.0 更好地适应现实世界的更多场景,来解决比如上述小兔软件这样的案例,OAuth 2.0 体系中还提供了资源拥有者凭据许可类型。

    资源拥有者凭据许可

    从“资源拥有者凭据许可”这个命名上,你可能就已经理解它的含义了。没错,资源拥有者的凭据,就是用户的凭据,就是用户名和密码。可见,这是最糟糕的一种方式。那为什么 OAuth 2.0 还支持这种许可类型,而且编入了 OAuth 2.0 的规范呢?

    我们先来思考一下。正如上面我提到的,小兔此时就是京东官方出品的一款软件,小明也是京东的用户,那么小明其实是可以使用用户名和密码来直接使用小兔这款软件的。原因很简单,那就是这里不再有“第三方”的概念了。

    但是呢,如果每次小兔都是拿着小明的用户名和密码来通过调用 Web API 的方式,来访问小明店铺的订单数据,甚至还有商品信息等,在调用这么多 API 的情况下,无疑增加了用户名和密码等敏感信息的攻击面。

    如果是使用了 token 来代替这些“满天飞”的敏感信息,不就能很大程度上保护敏感信息数据了吗?这样,小兔软件只需要使用一次用户名和密码数据来换回一个 token,进而通过 token 来访问小明店铺的数据,以后就不会再使用用户名和密码了。

    接下来,我们一起看下这种许可类型的流程,如下图所示:

    图1 资源拥有者凭据许可类型的流程

    步骤 1:当用户访问第三方软件小兔时,会提示输入用户名和密码。索要用户名和密码,就是资源拥有者凭据许可类型的特点。

    步骤 2:这里的 grant_type 的值为 password,告诉授权服务使用资源拥有者凭据许可凭据的方式去请求访问。

    
    @@ -116,7 +116,7 @@
         String toAppUrl = URLParamsUtil.appendParams(redirectUri,params);//构造第三方软件的回调地址,并重定向到该地址
         response.sendRedirect(toAppUrl);//使用sendRedirect方式模拟前端通信
     }
    -

    如何选择?

    现在,我们已经理解了 OAuth 2.0 的 4 种授权许可类型的原理与流程。那么,我们应该如何选择到底使用哪种授权许可类型呢?

    这里,我给你的建议是,在对接 OAuth 2.0 的时候先考虑授权码许可类型,其次再结合现实生产环境来选择:

    • 如果小兔软件是官方出品,那么可以直接使用资源拥有者凭据许可;
    • 如果小兔软件就是只嵌入到浏览器端的应用且没有服务端,那就只能选择隐式许可;
    • 如果小兔软件获取的信息不属于任何一个第三方用户,那可以直接使用客户端凭据许可类型。

    总结

    好了,我们马上要结束这篇文章了,在这之前呢,我们一直讲的是授权码许可类型,你已经知道了这是一种流程最完备、安全性最高的授权许可流程。不过呢,现实世界总是有各种各样的变化,OAuth 2.0 也要适应这样的变化,所以才有了我们今天讲的另外这三种许可类型。同时,关于如何来选择使用这些许可类型,我前面也给了大家一个建议。

    加上前面我们讲的授权码许可类型,我们一共讲了 4 种授权许可类型,它们最显著的区别就是获取访问令牌 access_token 的方式不同。最后,我通过一张表格来对比下:

    图4 OAuth 2.0的4种授权许可类型对比

    除了上面这张表格所展现的 4 种授权许可类型的区别之外,我希望你还能记住以下两点。

    1. 所有的授权许可类型中,授权码许可类型的安全性是最高的。因此,只要具备使用授权码许可类型的条件,我们一定要首先授权码许可类型。
    2. 所有的授权许可类型都是为了解决现实中的实际问题,因此我们还要结合实际的生产环境,在保障安全性的前提下选择最合适的授权许可类型,比如使用客户端凭据许可类型的小兔软件就是一个案例。

    我把今天用到的代码放到了 GitHub 上,你可以点击这个链接open in new window查看。

    思考题

    如果受限于应用特性所在的环境,比如在没有浏览器参与的情况下,我们应该如何选择授权许可类型呢,还可以使用授权码许可流程吗?

    - +

    如何选择?

    现在,我们已经理解了 OAuth 2.0 的 4 种授权许可类型的原理与流程。那么,我们应该如何选择到底使用哪种授权许可类型呢?

    这里,我给你的建议是,在对接 OAuth 2.0 的时候先考虑授权码许可类型,其次再结合现实生产环境来选择:

    总结

    好了,我们马上要结束这篇文章了,在这之前呢,我们一直讲的是授权码许可类型,你已经知道了这是一种流程最完备、安全性最高的授权许可流程。不过呢,现实世界总是有各种各样的变化,OAuth 2.0 也要适应这样的变化,所以才有了我们今天讲的另外这三种许可类型。同时,关于如何来选择使用这些许可类型,我前面也给了大家一个建议。

    加上前面我们讲的授权码许可类型,我们一共讲了 4 种授权许可类型,它们最显著的区别就是获取访问令牌 access_token 的方式不同。最后,我通过一张表格来对比下:

    图4 OAuth 2.0的4种授权许可类型对比

    除了上面这张表格所展现的 4 种授权许可类型的区别之外,我希望你还能记住以下两点。

    1. 所有的授权许可类型中,授权码许可类型的安全性是最高的。因此,只要具备使用授权码许可类型的条件,我们一定要首先授权码许可类型。
    2. 所有的授权许可类型都是为了解决现实中的实际问题,因此我们还要结合实际的生产环境,在保障安全性的前提下选择最合适的授权许可类型,比如使用客户端凭据许可类型的小兔软件就是一个案例。

    我把今天用到的代码放到了 GitHub 上,你可以点击这个链接open in new window查看。

    思考题

    如果受限于应用特性所在的环境,比如在没有浏览器参与的情况下,我们应该如何选择授权许可类型呢,还可以使用授权码许可流程吗?

    + diff --git a/other/oauth2/07.html b/other/oauth2/07.html index 0f53c15792..84c4b39482 100644 --- a/other/oauth2/07.html +++ b/other/oauth2/07.html @@ -30,8 +30,8 @@ 07 | 如何在移动App中使用OAuth 2.0? | ChenSino - - + +
    跳至主要內容

    07 | 如何在移动App中使用OAuth 2.0?

    王新栋大约 9 分钟

    在前面几讲中,我都是基于 Web 应用的场景来讲解的 OAuth 2.0。除了 Web 应用外,现实环境中还有非常多的移动 App。那么,在移动 App 中,能不能使用 OAuth 2.0 ,又该如何使用 OAuth 2.0 呢?

    没错,OAuth 2.0 最初的应用场景确实是 Web 应用,但是它的伟大之处就在于,它把自己的核心协议定位成了一个框架而不是单个的协议。这样做的好处是,我们可以基于这个基本的框架协议,在一些特定的领域进行扩展。

    因此,到了桌面或者移动的场景下,OAuth 2.0 的协议一样适用。考虑到授权码许可是最完备、最安全的许可类型,所以我在讲移动 App 如何使用 OAuth 2.0 的时候,依然会用授权码许可来讲解,毕竟“要用就用最好的”。

    当我们开发一款移动 App 的时候,可以选择没有 Server 端的 “纯 App” 架构,比如这款 App 不需要跟自己的 Server 端通信,或者可以调用其它开放的 HTTP 接口;当然也可以选择有服务端的架构,比如这款 App 还想把用户的操作日志记录下来并保存到 Server 端的数据库中。

    那总结下来呢,移动 App 可以分为两类,一类是没有 Server 端的 App 应用,一类是有 Server 端的 App 应用。

    图1 两类移动App

    这两类 App 在使用 OAuth 2.0 时的最大区别,在于获取访问令牌的方式:

    • 如果有 Server 端,就建议通过 Server 端和授权服务做交互来换取访问令牌;
    • 如果没有 Server 端,那么只能通过前端通信来跟授权服务做交互,比如在上一讲中提到的隐式许可授权类型。当然,这种方式的安全性就降低了很多。

    有些时候,我们可能觉得自己开发一个 App 不需要一个 Server 端。那好,就让我们先来看看没有 Server 端的 App 应用如何使用授权码许可类型。

    没有 Server 端的 App

    在一个没有 Server 端支持的纯 App 应用中,我们首先想到的是,如何可以像 Web 服务那样,让请求和响应“来去自如”呢。

    你可能会想,我是不是可以将一个“迷你”的 Web 服务器嵌入到 App 里面去,这样不就可以

    这样的 App 通过监听运行在 localhost 上的 Web 服务器 URI,就可以做到跟普通的 Web 应用一样的通信机制。但这种方式不是我们这次要讲的重点,如果你想深入了解可以去查些资料。因为当使用这种方式的时候,请求访问令牌时需要的 app_secret 就只能保存在用户本地设备上,而这并不是我们所建议的。

    到这里,你应该猜到了,问题的关键在于如何保存 app_secret,因为 App 会被安装在成千上万个终端设备上,app_secret 一旦被破解,就将会造成灾难性的后果。这时,有的同学突发奇想,如果不用 app_secret,也能在授权码流程里换回访问令牌 access_token,不就可以了吗?

    确实可以,但新的问题也来了。在授权码许可类型的流程中,如果没有了 app_secret 这一层的保护,那么通过授权码 code 换取访问令牌的时候,就只有授权码 code 在“冲锋陷阵”了。这时,授权码 code 一旦失窃,就会带来严重的安全问题。那么,我既不使用 app_secret,还要防止授权码 code 失窃,有什么好的方法吗?

    有,OAuth 2.0 里面就有这样的指导方法。这个方法就是我们将要介绍的 PKCE 协议,全称是 Proof Key for Code Exchange by OAuth Public Clients。

    在下面的流程图中,为了突出第三方软件使用 PKCE 协议时与授权服务之间的通信过程,我省略了受保护资源服务和资源拥有者的角色:

    图2 使用PKCE协议的流程图

    我来和你分析下这个流程中的重点。

    首先,App 自己要生成一个随机的、长度在 43~128 字符之间的、参数为 code_verifier 的字符串验证码;接着,我们再利用这个 code_verifier,来生成一个被称为“挑战码”的参数code_challenge。

    那怎么生成这个 code_challenge 的值呢?OAuth 2.0 规范里面给出了两种方法,就是看 code_challenge_method 这个参数的值:

    • 一种 code_challenge_method=plain,此时 code_verifier 的值就是 code_challenge 的值;
    • 另外一种 code_challenge_method=S256,就是将 code_verifier 值进行 ASCII 编码之后再进行哈希,然后再将哈希之后的值进行 BASE64-URL 编码,如下代码所示。
    code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
    @@ -49,7 +49,7 @@
       redirect_uri=REDIRECT_URI&
       app_id=APP_ID&
       code_verifier=CODE_VERIFIER
    -

    现在,你就知道了我们是如何使用 code_verifier 和 code_challenge 这两个参数的了吧。总结一下就是,换取授权码 code 的时候,我们使用 code_challenge 参数值;换取访问令牌的时候,我们使用 code_verifier 参数值。那么,有的同学会继续问了,我们为什么要这样做呢。

    现在,就让我来和你分析一下。

    我们的愿望是,没有 Server 端的手机 App,也可以使用授权码许可流程,对吧?app_secret 不能用,因为它只能被存在用户的设备上,我们担心被泄露。

    那么,在没有了 app_secret 这层保护的前提下,即使我们的授权码 code 被截获,再加上 code_challenge 也同时被截获了,那也没有办法由 code_challenge 逆推出 code_verifier 的值。而恰恰在第二步换取访问令牌的时候,授权服务需要的就是 code_verifier 的值。因此,这也就避免了访问令牌被恶意换取的安全问题。

    现在,我们可以通过 PKCE 协议的帮助,让没有 Server 端的 App 也能够安全地使用授权码许可类型进行授权了。但是,按照 OAuth 2.0 的规范建议,通过后端通信来换取访问令牌是较为安全的方式。所以呢,在这里,我想跟你探讨的是,我们真的不需要一个 Server 端吗?在做移动应用开发的时候,我们真的从设计上就决定废弃 Server 端了吗?

    有 Server 端的 App

    如果你开发接入过微信登录,就会在微信的官方文档上看到下面这句话:

    微信 OAuth 2.0 授权登录目前支持 authorization_code 模式,适用于拥有 Server 端的应用授权。

    没错,微信的 OAuth 2.0 授权登录,就是建议我们需要一个 Server 端来支持这样的授权接入。

    那么,有 Server 端支持的 App 又是如何使用 OAuth 2.0 的授权码许可流程的呢?其实,在前面几讲的基础上,我们现在理解这样的场景并不是什么难事儿。

    我们仍以微信登录为例,看一下官方的流程图open in new window

    img

    看到这个图,你是不是觉得特别熟悉,跟普通的授权码流程没有区别,仍是两步走的策略:第一步换取授权码 code,第二步通过授权码 code 换取访问令牌 access_token。

    这里的第三方应用,就是我们作为开发者来开发的应用,包含了移动 App 和 Server 端。我们将其“放大”得到下面这张图:

    img

    我们从这张“放大”的图中,就会发现有 Server 端的 App 在使用授权码流程的时候,跟普通的 Web 应用几乎没有任何差别。

    大概流程是:当我们访问第三方 App 的时候,需要用到微信来登录;第三方 App 可以拉起微信的 App,我们会在微信的 App 里面进行登录及授权;微信 Server 端验证成功之后会返回一个授权码 code,通过微信 App 传递给了第三方 App;后面的流程就是我们熟悉的使用授权码 code 和 app_secret,换取访问令牌 access_token 的值了。

    这次使用 app_secret 的时候,我们是在第三方 App 的 Server 端来使用的,因此安全性上没有任何问题。

    总结

    今天这一讲,我重点和你讲了两块内容,没有 Server 端的 App 和有 Server 端的 App 分别是如何使用授权码许可类型的。我希望你能够记住以下两点内容。

    1. 我们使用 OAuth 2.0 协议的目的,就是要起到安全性的作用,但有些时候,因为使用不当反而会造成更大的安全问题,比如将 app_secret 放入 App 中的最基本错误。如果放弃了 app_secret,又是如何让没有 Server 端的 App 安全地使用授权码许可协议呢?针对这种情况,我和你介绍了 PKCE 协议。它是一种在失去 app_secret 保护的时候,防止授权码失窃的解决方案。
    2. 我们需要思考一下,我们的 App 真的不需要一个 Server 端吗?我建议你在开发移动 App 的时候,尽可能地都要搭建一个 Server 端,因为通过后端通信来传输访问令牌比通过前端通信传输要安全得多。我也举了微信的例子,很多官方的开放平台在提供 OAuth 2.0 服务的时候,都会建议开发者要有一个相应的 Server 端。

    思考题

    在移动 App 中,你还能想到有哪些相对安全的方式来使用 OAuth 2.0 吗?

    - +

    现在,你就知道了我们是如何使用 code_verifier 和 code_challenge 这两个参数的了吧。总结一下就是,换取授权码 code 的时候,我们使用 code_challenge 参数值;换取访问令牌的时候,我们使用 code_verifier 参数值。那么,有的同学会继续问了,我们为什么要这样做呢。

    现在,就让我来和你分析一下。

    我们的愿望是,没有 Server 端的手机 App,也可以使用授权码许可流程,对吧?app_secret 不能用,因为它只能被存在用户的设备上,我们担心被泄露。

    那么,在没有了 app_secret 这层保护的前提下,即使我们的授权码 code 被截获,再加上 code_challenge 也同时被截获了,那也没有办法由 code_challenge 逆推出 code_verifier 的值。而恰恰在第二步换取访问令牌的时候,授权服务需要的就是 code_verifier 的值。因此,这也就避免了访问令牌被恶意换取的安全问题。

    现在,我们可以通过 PKCE 协议的帮助,让没有 Server 端的 App 也能够安全地使用授权码许可类型进行授权了。但是,按照 OAuth 2.0 的规范建议,通过后端通信来换取访问令牌是较为安全的方式。所以呢,在这里,我想跟你探讨的是,我们真的不需要一个 Server 端吗?在做移动应用开发的时候,我们真的从设计上就决定废弃 Server 端了吗?

    有 Server 端的 App

    如果你开发接入过微信登录,就会在微信的官方文档上看到下面这句话:

    微信 OAuth 2.0 授权登录目前支持 authorization_code 模式,适用于拥有 Server 端的应用授权。

    没错,微信的 OAuth 2.0 授权登录,就是建议我们需要一个 Server 端来支持这样的授权接入。

    那么,有 Server 端支持的 App 又是如何使用 OAuth 2.0 的授权码许可流程的呢?其实,在前面几讲的基础上,我们现在理解这样的场景并不是什么难事儿。

    我们仍以微信登录为例,看一下官方的流程图open in new window

    img

    看到这个图,你是不是觉得特别熟悉,跟普通的授权码流程没有区别,仍是两步走的策略:第一步换取授权码 code,第二步通过授权码 code 换取访问令牌 access_token。

    这里的第三方应用,就是我们作为开发者来开发的应用,包含了移动 App 和 Server 端。我们将其“放大”得到下面这张图:

    img

    我们从这张“放大”的图中,就会发现有 Server 端的 App 在使用授权码流程的时候,跟普通的 Web 应用几乎没有任何差别。

    大概流程是:当我们访问第三方 App 的时候,需要用到微信来登录;第三方 App 可以拉起微信的 App,我们会在微信的 App 里面进行登录及授权;微信 Server 端验证成功之后会返回一个授权码 code,通过微信 App 传递给了第三方 App;后面的流程就是我们熟悉的使用授权码 code 和 app_secret,换取访问令牌 access_token 的值了。

    这次使用 app_secret 的时候,我们是在第三方 App 的 Server 端来使用的,因此安全性上没有任何问题。

    总结

    今天这一讲,我重点和你讲了两块内容,没有 Server 端的 App 和有 Server 端的 App 分别是如何使用授权码许可类型的。我希望你能够记住以下两点内容。

    1. 我们使用 OAuth 2.0 协议的目的,就是要起到安全性的作用,但有些时候,因为使用不当反而会造成更大的安全问题,比如将 app_secret 放入 App 中的最基本错误。如果放弃了 app_secret,又是如何让没有 Server 端的 App 安全地使用授权码许可协议呢?针对这种情况,我和你介绍了 PKCE 协议。它是一种在失去 app_secret 保护的时候,防止授权码失窃的解决方案。
    2. 我们需要思考一下,我们的 App 真的不需要一个 Server 端吗?我建议你在开发移动 App 的时候,尽可能地都要搭建一个 Server 端,因为通过后端通信来传输访问令牌比通过前端通信传输要安全得多。我也举了微信的例子,很多官方的开放平台在提供 OAuth 2.0 服务的时候,都会建议开发者要有一个相应的 Server 端。

    思考题

    在移动 App 中,你还能想到有哪些相对安全的方式来使用 OAuth 2.0 吗?

    + diff --git a/other/oauth2/08.html b/other/oauth2/08.html index 72e9a47891..61cd230402 100644 --- a/other/oauth2/08.html +++ b/other/oauth2/08.html @@ -30,8 +30,8 @@ 08 | 实践OAuth 2.0时,使用不当可能会导致哪些安全漏洞? | ChenSino - - + +
    跳至主要內容

    08 | 实践OAuth 2.0时,使用不当可能会导致哪些安全漏洞?

    王新栋大约 16 分钟

    当知道这一讲的主题是 OAuth 2.0 的安全漏洞时,你可能要问了:“OAuth 2.0 不是一种安全协议吗,不是保护 Web API 的吗?为啥 OAuth 2.0 自己还有安全的问题了呢?”

    首先,OAuth 2.0 的确是一种安全协议。这没啥问题,但是它有很多使用规范,比如授权码是一个临时凭据只能被使用一次,要对重定向 URI 做校验等。那么,如果使用的时候你没有按照这样的规范来实施,就会有安全漏洞了。

    其次,OAuth 2.0 既然是“生长”在互联网这个大环境中,就一样会面对互联网上常见安全风险的攻击,比如跨站请求伪造(Cross-site request forgery,CSRF)、跨站脚本攻击(Cross Site Scripting,XSS)。最后,除了这些常见攻击类型外,OAuth 2.0 自身也有可被利用的安全漏洞,比如授权码失窃、重定向 URI 伪造。

    所以,我们在实践 OAuth 2.0 的过程中,安全问题一定是重中之重。接下来,我挑选了 5 个典型的安全问题,其中 CSRF、XSS、水平越权这三种是互联网环境下常见的安全风险,授权码失窃和重定向 URI 被篡改属于 OAuth2.0“专属”的安全风险。接下来,我就和你一起看看这些安全风险的由来,以及如何应对吧。

    CSRF 攻击

    对于 CSRF 的定义,《OAuth 2 in Action》这本书里的解释,是我目前看到的最为贴切的解释:恶意软件让浏览器向已完成用户身份认证的网站发起请求,并执行有害的操作,就是跨站请求伪造攻击。

    它是互联网上最为常见的攻击之一。我们在实践 OAuth2.0 的过程,其实就是在构建一次互联网的应用。因此,OAuth 2.0 同样也会面临这个攻击。接下来,我通过一个案例和你说明这个攻击类型。

    有一个软件 A,我们让它来扮演攻击者,让它的开发者按照正常的流程使用极客时间。当该攻击者授权后,拿到授权码的值 codeA 之后,“立即按下了暂停键”,不继续往下走了。那它想干啥呢,我们继续往下看。

    这时,有一个第三方软件 B,比如咱们的 Web 版极客时间,来扮演受害者吧。当然最终的受害者是用户,这里是用 Web 版极客时间来作为被软件 A 攻击的对象。

    极客时间用于接收授权码的回调地址为 https://time.geekbang.org/callback。有一个用户 G 已经在极客时间的平台登录,且对极客时间进行了授权,也就是用户 G 已经在极客时间平台上有登录态了。如果此时攻击者软件 A,在自己的网站上构造了一个恶意页面:

    
    @@ -42,7 +42,7 @@
     <html>
     <img src ="https://clientA.com/catch">
     </html>
    -

    好了,我们继续看下接下来的攻击流程:

    img

    首先,黑客将构造的攻击页面放到对应的 hacker.html 上,也就是https://time.geekbang.org/page/hacker.html上 ,同时构造出了一个新的重定向 URI,即https://time.geekbang.org/page/welcome/back.html../hacker.html。然后,黑客利用一些钓鱼手段诱导用户,去点击下面的这个地址:https://oauth-server.com/auth?respons_type=code&client_id=CLIENTID&redirect_uri=https://time.geekbang.org/page/welcome/back.html../hacker.html

    这样当授权服务做出响应进行重定向请求的时候,授权码 code 就返回到了 hacker.html 这个页面上。

    最后,黑客在https://clientA.com/catch页面上,解析 Referrer 头部就会得到用户的授权码,继而就可以像授权码失窃的场景中那样去换取访问令牌了。

    看到这里我们就知道了,如果授权服务要求的回调 URI 是https://time.geekbang.org/callback,并做了回调 URI 的完整性校验,那么被篡改之后的回调地址https://time.geekbang.org/page/welcome/back.html../hacker.html就不会被授权服务去发起重定向请求。

    严格来讲,要发生这样的漏洞问题,条件还是比较苛刻的。从图 6 的重定向 URI 被篡改的流程中,也可以看到,只要我们在授权服务验证第三方软件的请求时做了签名校验,那么攻击者在只拿到授权码 code 的情况下,仍然无法获取访问令牌,因为第三方软件只有通过访问令牌才能够访问用户的数据。

    但是,如果这些防范安全风险的规范建议你通通都没有遵守,那就是在给攻击者“大显身手”的机会,让你的应用软件以及用户遭受损失。

    总结

    好了,以上就是今天的主要内容了。我们一起学习了 OAuth 2.0 相关的常见又比较隐蔽的 5 种安全问题,包括 CSRF 攻击、XSS 攻击、水平越权、授权码失窃、重定向 URI 被篡改。更多关于 OAuth 2.0 安全方面的内容,你也可以去翻阅《OAuth 2 in Action》这本书。

    通过这一讲的学习,你需要记住以下三个知识点:

    1. 互联网场景的安全攻击类型比如 CSRF、XSS 等,在 OAuth 2.0 中一样要做防范,因为 OAuth 2.0 本身就是应用在互联网场景中。
    2. 除了常见的互联网安全攻击,OAuth 2.0 也有自身的安全风险问题,比如我们讲到的授权码失窃、重定向 URI 被篡改。
    3. 这些安全问题,本身从攻击的“技术含量”上并不高,但导致这些安全风险的因素,往往就是开发人员的安全意识不够。比如,没有意识到水平越权中的数据归属逻辑判断,需要加入到代码逻辑中。

    其实,OAuth 2.0 的规范里面对这些安全问题都有对应的规避方式,但都要求我们使用的时候一定要非常严谨。比如,重定向 URI 的校验方式,规范里面是允许模糊校验的,但在结合实际环境的时候,我们又必须做到精确匹配校验才可以保障 OAuth 2.0 流转的安全性。最后,我还整理了一张知识脑图,总结了这 5 种攻击方式的内容,来帮助你理解与记忆。

    img

    - +

    好了,我们继续看下接下来的攻击流程:

    img

    首先,黑客将构造的攻击页面放到对应的 hacker.html 上,也就是https://time.geekbang.org/page/hacker.html上 ,同时构造出了一个新的重定向 URI,即https://time.geekbang.org/page/welcome/back.html../hacker.html。然后,黑客利用一些钓鱼手段诱导用户,去点击下面的这个地址:https://oauth-server.com/auth?respons_type=code&client_id=CLIENTID&redirect_uri=https://time.geekbang.org/page/welcome/back.html../hacker.html

    这样当授权服务做出响应进行重定向请求的时候,授权码 code 就返回到了 hacker.html 这个页面上。

    最后,黑客在https://clientA.com/catch页面上,解析 Referrer 头部就会得到用户的授权码,继而就可以像授权码失窃的场景中那样去换取访问令牌了。

    看到这里我们就知道了,如果授权服务要求的回调 URI 是https://time.geekbang.org/callback,并做了回调 URI 的完整性校验,那么被篡改之后的回调地址https://time.geekbang.org/page/welcome/back.html../hacker.html就不会被授权服务去发起重定向请求。

    严格来讲,要发生这样的漏洞问题,条件还是比较苛刻的。从图 6 的重定向 URI 被篡改的流程中,也可以看到,只要我们在授权服务验证第三方软件的请求时做了签名校验,那么攻击者在只拿到授权码 code 的情况下,仍然无法获取访问令牌,因为第三方软件只有通过访问令牌才能够访问用户的数据。

    但是,如果这些防范安全风险的规范建议你通通都没有遵守,那就是在给攻击者“大显身手”的机会,让你的应用软件以及用户遭受损失。

    总结

    好了,以上就是今天的主要内容了。我们一起学习了 OAuth 2.0 相关的常见又比较隐蔽的 5 种安全问题,包括 CSRF 攻击、XSS 攻击、水平越权、授权码失窃、重定向 URI 被篡改。更多关于 OAuth 2.0 安全方面的内容,你也可以去翻阅《OAuth 2 in Action》这本书。

    通过这一讲的学习,你需要记住以下三个知识点:

    1. 互联网场景的安全攻击类型比如 CSRF、XSS 等,在 OAuth 2.0 中一样要做防范,因为 OAuth 2.0 本身就是应用在互联网场景中。
    2. 除了常见的互联网安全攻击,OAuth 2.0 也有自身的安全风险问题,比如我们讲到的授权码失窃、重定向 URI 被篡改。
    3. 这些安全问题,本身从攻击的“技术含量”上并不高,但导致这些安全风险的因素,往往就是开发人员的安全意识不够。比如,没有意识到水平越权中的数据归属逻辑判断,需要加入到代码逻辑中。

    其实,OAuth 2.0 的规范里面对这些安全问题都有对应的规避方式,但都要求我们使用的时候一定要非常严谨。比如,重定向 URI 的校验方式,规范里面是允许模糊校验的,但在结合实际环境的时候,我们又必须做到精确匹配校验才可以保障 OAuth 2.0 流转的安全性。最后,我还整理了一张知识脑图,总结了这 5 种攻击方式的内容,来帮助你理解与记忆。

    img

    + diff --git a/other/oauth2/09.html b/other/oauth2/09.html index 472c654fdb..1493794980 100644 --- a/other/oauth2/09.html +++ b/other/oauth2/09.html @@ -30,8 +30,8 @@ 09 | 实战:利用OAuth 2.0实现一个OpenID Connect用户身份认证协议 | ChenSino - - + +
    跳至主要內容

    09 | 实战:利用OAuth 2.0实现一个OpenID Connect用户身份认证协议

    王新栋大约 12 分钟

    如果你是一个第三方软件开发者,在实现用户登录的逻辑时,除了可以让用户新注册一个账号再登录外,还可以接入微信、微博等平台,让用户使用自己的微信、微博账号去登录。同时,如果你的应用下面又有多个子应用,还可以让用户只登录一次就能访问所有的子应用,来提升用户体验。这就是联合登录和单点登录了。再继续深究,它们其实都是 OpenID Connect(简称 OIDC)的应用场景的实现。那 OIDC 又是什么呢?今天,我们就来学习下 OIDC 和 OAuth 2.0 的关系,以及如何用 OAuth 2.0 来实现一个 OIDC 用户身份认证协议。

    OIDC 是什么?

    OIDC 其实就是一种用户身份认证的开放标准。使用微信账号登录极客时间的场景,就是这种开放标准的实践。

    说到这里,你可能要发问了:“不对呀,使用微信登录第三方 App 用的不是 OAuth 2.0 开放协议吗,怎么又扯上 OIDC 了呢?”

    没错,用微信登录某第三方软件,确实使用的是 OAuth 2.0。但 OAuth2.0 是一种授权协议,而不是身份认证协议。OIDC 才是身份认证协议,而且是基于 OAuth 2.0 来执行用户身份认证的互通协议。更概括地说,OIDC 就是直接基于 OAuth 2.0 构建的身份认证框架协议。

    换种表述方式,OIDC= 授权协议 + 身份认证,是 OAuth 2.0 的超集。为方便理解,我们可以把 OAuth 2.0 理解为面粉,把 OIDC 理解为面包。这下,你是不是就理解它们的关系了?因此,我们说“第三方 App 使用微信登录用到了 OAuth 2.0”没有错,说“使用到了 OIDC”更没有错。

    考虑到单点登录、联合登录,都遵循的是 OIDC 的标准流程,因此今天我们就讲讲如何利用 OAuth2.0 来实现一个 OIDC,“高屋建瓴” 地去看问题。掌握了这一点,我们再去做单点登录、联合登录的场景,以及其他更多关于身份认证的场景,就都不再是问题了。

    OIDC 和 OAuth 2.0 的角色对应关系

    说到“如何利用 OAuth 2.0 来构建 OIDC 这样的认证协议”,我们可以想到一个切入点,这个切入点就是 OAuth 2.0 的四种角色。

    OAuth 2.0 的授权码许可流程的运转,需要资源拥有者、第三方软件、授权服务、受保护资源这 4 个角色间的顺畅通信、配合才能够完成。如果我们要想在 OAuth 2.0 的授权码许可类型的基础上,来构建 OIDC 的话,这 4 个角色仍然要继续发挥 “它们的价值”。那么,这 4 个角色又是怎么对应到 OIDC 中的参与方的呢?

    那么,我们就先想想一个关于身份认证的协议框架,应该有什么角色。你可能已经想出来了,它需要一个登录第三方软件的最终用户、一个第三方软件,以及一个认证服务来为这个用户提供身份证明的验证判断。

    没错,这就是 OIDC 的三个主要角色了。在 OIDC 的官方标准框架中,这三个角色的名字是:

    • EU(End User),代表最终用户。
    • RP(Relying Party),代表认证服务的依赖方,就是上面我提到的第三方软件。
    • OP(OpenID Provider),代表提供身份认证服务方。

    EU、RP 和 OP 这三个角色对于 OIDC 非常重要,我后面也会时常使用简称来描述,希望你能先记住。

    现在很多 App 都接入了微信登录,那么微信登录就是一个大的身份认证服务(OP)。一旦我们有了微信账号,就可以登录所有接入了微信登录体系的 App(RP),这就是我们常说的联合登录。

    现在,我们就借助极客时间的例子,来看一下 OAuth 2.0 的 4 个角色和 OIDC 的 3 个角色之间的对应关系:

    图1 OAuth 2.0和OIDC的角色对应关系

    OIDC 和 OAuth 2.0 的关键区别

    看到这张角色对应关系图,你是不是有点 “恍然大悟” 的感觉:要实现一个 OIDC 协议,不就是直接实现一个 OAuth 2.0 协议吗。没错,我在这一讲的开始也说了,OIDC 就是基于 OAuth 2.0 来实现的一个身份认证协议框架。

    我再继续给你画一张 OIDC 的通信流程图,你就更清楚 OIDC 和 OAuth 2.0 的关系了:

    图2 基于授权码流程的OIDC通信流程

    可以发现,一个基于授权码流程的 OIDC 协议流程,跟 OAuth 2.0 中的授权码许可的流程几乎完全一致,唯一的区别就是多返回了一个 ID_TOKEN,我们称之为 ID 令牌。这个令牌是身份认证的关键。所以,接下来我就着重和你讲一下这个令牌,而不再细讲 OIDC 的整个流程。

    OIDC 中的 ID 令牌生成和解析方法

    在图 2 的 OIDC 通信流程的第 6 步,我们可以看到 ID 令牌(ID_TOKEN)和访问令牌(ACCESS_TOKEN)是一起返回的。关于为什么要同时返回两个令牌,我后面再和你分析。我们先把焦点放在 ID 令牌上。

    我们知道,访问令牌不需要被第三方软件解析,因为它对第三方软件来说是不透明的。但 ID 令牌需要能够被第三方软件解析出来,因为第三方软件需要获取 ID 令牌里面的内容,来处理用户的登录

    那 ID 令牌的内容是什么呢?

    首先,ID 令牌是一个 JWT 格式的令牌。你可以到第 4 讲中复习下 JWT 的相关内容。这里需要强调的是,虽然 JWT 令牌是一种自包含信息体的令牌,为将其作为 ID 令牌带来了方便性,但是因为 ID 令牌需要能够标识出用户、失效时间等属性来达到身份认证的目的,所以要将其作为 OIDC 的 ID 令牌时,下面这 5 个 JWT 声明参数也是必须要有的。

    • iss,令牌的颁发者,其值就是身份认证服务(OP)的 URL。
    • sub,令牌的主题,其值是一个能够代表最终用户(EU)的全局唯一标识符。
    • aud,令牌的目标受众,其值是三方软件(RP)的 app_id。
    • exp,令牌的到期时间戳,所有的 ID 令牌都会有一个过期时间。
    • iat,颁发令牌的时间戳。

    生成 ID 令牌这部分的示例代码如下:

    
    @@ -74,7 +74,7 @@
             
             return map;
         }
    -

    需要特别指出的是,第三方软件解析并验证 ID 令牌的合法性之后,不需要将整个 JWT 信息保存下来,只需保留 JWT 中的 PAYLOAD(数据体)部分就可以了。因为正是这部分内容,包含了身份认证所需要的用户唯一标识等信息。

    另外,在验证 JWT 合法性的时候,因为 ID 令牌本身已经被身份认证服务(OP)的密钥签名过,所以关键的一点是合法性校验时需要做签名校验。具体的加密方法和校验方法,你可以回顾下第 4 讲

    这样当第三方软件(RP)拿到 ID 令牌之后,就已经获得了处理身份认证标识动作的信息,也就是拿到了那个能够唯一标识最终用户(EU)的 ID 值,比如 3521。

    用访问令牌获取 ID 令牌之外的信息

    但是,为了提升第三方软件对用户的友好性,在页面上显示 “您好,3521” 肯定不如显示 “您好,小明同学”的体验好。这里的 “小明同学”,恰恰就是用户的昵称。

    那如何来获取“小明同学”这个昵称呢。这也很简单,就是通过返回的访问令牌 access_token 来重新发送一次请求。当然,这个流程我们现在也已经很熟悉了,它属于 OAuth 2.0 标准流程中的请求受保护资源服务的流程。

    这也就是为什么在 OIDC 协议里面,既给我们返回 ID 令牌又返回访问令牌的原因了。在保证用户身份认证功能的前提下,如果想获取更多的用户信息,就再通过访问令牌获取。在 OIDC 框架里,这部分内容叫做创建 UserInfo 端点和获取 UserInfo 信息。和 UserInfo 端点,以保障

    这样看下来,细粒度地去看 OIDC 的流程就是:生成 ID 令牌 -> 创建 UserInfo 端点 -> 解析 ID 令牌 -> 记录登录状态 -> 获取 UserInfo。

    好了,利用 OAuth 2.0 实现一个 OIDC 框架的工作,我们就做完了。你可以到GitHub上查看这些流程的完整代码。现在,我再来和你小结下。

    用 OAuth 2.0 实现 OIDC 的最关键的方法是:在原有 OAuth 2.0 流程的基础上增加 ID 令牌和 UserInfo 端点,以保障 OIDC 中的第三方软件能够记录用户状态和获取用户详情的功能。

    因为第三方软件可以通过解析 ID 令牌的关键用户标识信息来记录用户状态,同时可以通过 Userinfo 端点来获取更详细的用户信息。有了用户态和用户信息,也就理所当然地实现了一个身份认证。

    接下来,我们就具体看看如何实现单点登录(Single Sign On,SSO)。

    单点登录

    一个用户 G 要登录第三方软件 A,A 有三个子应用,域名分别是 a1.com、a2.com、a3.com。如果 A 想要为用户提供更流畅的登录体验,让用户 G 登录了 a1.com 之后也能顺利登录其他两个域名,就可以创建一个身份认证服务,来支持 a1.com、a2.com 和 a3.com 的登录。

    这就是我们说的单点登录,“一次登录,畅通所有”。

    那么,可以使用 OIDC 协议标准来实现这样的单点登录吗?我只能说 “太可以了”。如下图所示,只需要让第三方软件(RP)重复我们 OIDC 的通信流程就可以了。

    图3 单点登录的通信流程

    你看,单点登录就是 OIDC 的一种具体应用方式,只要掌握了 OIDC 框架的原理,实现单点登录就不在话下了。关于单点登录的具体实现,在 GitHub 上搜索“通过 OIDC 来实现单点登录”,你就可以看到很多相关的开源内容。

    总结

    在一些较大的、已经具备身份认证服务的平台上,你可能并没有发现 OIDC 的描述,但大可不必纠结。有时候,我们可能会困惑于,到底是先有 OIDC 这样的标准,还是先有类似微信登录这样的身份认证实现方式呢?

    其实,要理解这层先后关系,我们可以拿设计模式来举例。当你想设计一个较为松耦合、可扩展的系统时,即使没有接触过设计模式,通过不断地尝试修改后,也会得出一个逐渐符合了设计模式那样“味道”的代码架构思路。理解 OIDC 解决身份认证问题的思路,也是同样的道理。

    今天,我们在 OAuth2.0 的基础上实现了一个 OIDC 的流程,我希望你能记住以下两点。

    1. OAuth 2.0 不是一个身份认证协议,请一定要记住这点。身份认证强调的是“谁的问题”,而 OAuth2.0 强调的是授权,是“可不可以”的问题。但是,我们可以在 OAuth2.0 的基础上,通过增加 ID 令牌来获取用户的唯一标识,从而就能够去实现一个身份认证协议。
    2. 有些 App 不想非常麻烦地自己设计一套注册和登录认证流程,就会寻求统一的解决方案,然后势必会出现一个平台来收揽所有类似的认证登录场景。我们再反过来理解也是成立的。如果有个拥有海量用户的、大流量的访问平台,来提供一套统一的登录认证服务,让其他第三方应用来对接,不就可以解决一个用户使用同一个账号来登录众多第三方 App 的问题了吗?而 OIDC,就是这样的登录认证场景的开放解决方案。
    - +

    需要特别指出的是,第三方软件解析并验证 ID 令牌的合法性之后,不需要将整个 JWT 信息保存下来,只需保留 JWT 中的 PAYLOAD(数据体)部分就可以了。因为正是这部分内容,包含了身份认证所需要的用户唯一标识等信息。

    另外,在验证 JWT 合法性的时候,因为 ID 令牌本身已经被身份认证服务(OP)的密钥签名过,所以关键的一点是合法性校验时需要做签名校验。具体的加密方法和校验方法,你可以回顾下第 4 讲

    这样当第三方软件(RP)拿到 ID 令牌之后,就已经获得了处理身份认证标识动作的信息,也就是拿到了那个能够唯一标识最终用户(EU)的 ID 值,比如 3521。

    用访问令牌获取 ID 令牌之外的信息

    但是,为了提升第三方软件对用户的友好性,在页面上显示 “您好,3521” 肯定不如显示 “您好,小明同学”的体验好。这里的 “小明同学”,恰恰就是用户的昵称。

    那如何来获取“小明同学”这个昵称呢。这也很简单,就是通过返回的访问令牌 access_token 来重新发送一次请求。当然,这个流程我们现在也已经很熟悉了,它属于 OAuth 2.0 标准流程中的请求受保护资源服务的流程。

    这也就是为什么在 OIDC 协议里面,既给我们返回 ID 令牌又返回访问令牌的原因了。在保证用户身份认证功能的前提下,如果想获取更多的用户信息,就再通过访问令牌获取。在 OIDC 框架里,这部分内容叫做创建 UserInfo 端点和获取 UserInfo 信息。和 UserInfo 端点,以保障

    这样看下来,细粒度地去看 OIDC 的流程就是:生成 ID 令牌 -> 创建 UserInfo 端点 -> 解析 ID 令牌 -> 记录登录状态 -> 获取 UserInfo。

    好了,利用 OAuth 2.0 实现一个 OIDC 框架的工作,我们就做完了。你可以到GitHub上查看这些流程的完整代码。现在,我再来和你小结下。

    用 OAuth 2.0 实现 OIDC 的最关键的方法是:在原有 OAuth 2.0 流程的基础上增加 ID 令牌和 UserInfo 端点,以保障 OIDC 中的第三方软件能够记录用户状态和获取用户详情的功能。

    因为第三方软件可以通过解析 ID 令牌的关键用户标识信息来记录用户状态,同时可以通过 Userinfo 端点来获取更详细的用户信息。有了用户态和用户信息,也就理所当然地实现了一个身份认证。

    接下来,我们就具体看看如何实现单点登录(Single Sign On,SSO)。

    单点登录

    一个用户 G 要登录第三方软件 A,A 有三个子应用,域名分别是 a1.com、a2.com、a3.com。如果 A 想要为用户提供更流畅的登录体验,让用户 G 登录了 a1.com 之后也能顺利登录其他两个域名,就可以创建一个身份认证服务,来支持 a1.com、a2.com 和 a3.com 的登录。

    这就是我们说的单点登录,“一次登录,畅通所有”。

    那么,可以使用 OIDC 协议标准来实现这样的单点登录吗?我只能说 “太可以了”。如下图所示,只需要让第三方软件(RP)重复我们 OIDC 的通信流程就可以了。

    图3 单点登录的通信流程

    你看,单点登录就是 OIDC 的一种具体应用方式,只要掌握了 OIDC 框架的原理,实现单点登录就不在话下了。关于单点登录的具体实现,在 GitHub 上搜索“通过 OIDC 来实现单点登录”,你就可以看到很多相关的开源内容。

    总结

    在一些较大的、已经具备身份认证服务的平台上,你可能并没有发现 OIDC 的描述,但大可不必纠结。有时候,我们可能会困惑于,到底是先有 OIDC 这样的标准,还是先有类似微信登录这样的身份认证实现方式呢?

    其实,要理解这层先后关系,我们可以拿设计模式来举例。当你想设计一个较为松耦合、可扩展的系统时,即使没有接触过设计模式,通过不断地尝试修改后,也会得出一个逐渐符合了设计模式那样“味道”的代码架构思路。理解 OIDC 解决身份认证问题的思路,也是同样的道理。

    今天,我们在 OAuth2.0 的基础上实现了一个 OIDC 的流程,我希望你能记住以下两点。

    1. OAuth 2.0 不是一个身份认证协议,请一定要记住这点。身份认证强调的是“谁的问题”,而 OAuth2.0 强调的是授权,是“可不可以”的问题。但是,我们可以在 OAuth2.0 的基础上,通过增加 ID 令牌来获取用户的唯一标识,从而就能够去实现一个身份认证协议。
    2. 有些 App 不想非常麻烦地自己设计一套注册和登录认证流程,就会寻求统一的解决方案,然后势必会出现一个平台来收揽所有类似的认证登录场景。我们再反过来理解也是成立的。如果有个拥有海量用户的、大流量的访问平台,来提供一套统一的登录认证服务,让其他第三方应用来对接,不就可以解决一个用户使用同一个账号来登录众多第三方 App 的问题了吗?而 OIDC,就是这样的登录认证场景的开放解决方案。
    + diff --git a/other/oauth2/10.html b/other/oauth2/10.html index 6b27f03267..f9f72c347c 100644 --- a/other/oauth2/10.html +++ b/other/oauth2/10.html @@ -30,11 +30,11 @@ 10 | 串讲:OAuth 2.0的工作流程与安全问题 | ChenSino - - + + -
    跳至主要內容

    10 | 串讲:OAuth 2.0的工作流程与安全问题

    ChenSino大约 10 分钟

    今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。

    当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。

    好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。

    OAuth 2.0 工作流程串讲

    img

    我们一直在讲 OAuth 2.0 是一种授权协议,这种协议可以让第三方软件代表用户去执行被允许的操作。那么,第三方软件就需要向用户索取授权来获得那个令牌。

    我们回想下第 1 讲拜访百度王总的例子。只有拿到前台小姐姐给你的门禁卡,你才能够进入百度大楼。这个过程就相当于前台小姐姐给你做了一次授权,而这个授权的凭证就是门禁卡。对应到我们的系统中,门禁卡便相当于访问令牌。

    通过“代表”“授权”这样的关键词,我们可以认识到,OAuth 2.0 是一个授权协议,也是一个安全协议。那么,如果我说它也是一种委托协议,你也不要吃惊。

    试想一下,用户在微信平台上有修改昵称、修改头像、修改个人兴趣的权限,当第三方软件请求让自己代表用户来操作这些权限的时候,就是第三方软件请求用户把这些权限委托给自己,用户在批准了委托请求之后,才可以代表用户去执行这些操作。

    这时,我们细想一下,委托才是 OAuth 2.0 授权概念的根基,因为没有“委托”之意就不会有“代表”行为的发生。

    在整个课程讲述授权的过程中,我频繁举例和强调的就是授权码许可流程。在学习授权码流程的时候,你最困惑的一点恐怕莫过于 “为什么要多此一举,非得通过一个授权码 code 来换取访问令牌 access_token”了吧。这个问题我在讲授权码许可的整体流程时也做过分析了,你现在回想起来应该不会再为此“痛苦不堪” 了吧。

    我们再来分析下,第三方软件要获取访问令牌,只能通过两个渠道:

    • 一个渠道是第三方软件的前端页面。但是,如果直接返回到前端页面上,访问令牌是很容易被通过浏览器截获的,所以显然不可取。
    • 另外一个渠道是通过后端传输。第三方软件的后端和授权服务的后端之间通信,这样就可以避免令牌被直接暴露的问题。

    再往深了想,第三方软件的后端总不能向授权服务的后端 “硬要” 吧,总要告诉授权服务是要哪个用户的 access_token 吧,所以还需要用户的参与。

    用户一旦参与进来,访问的第一个页面是第三方软件,用户要授权,第三方软件就需要把用户引导到授权服务页面。但这个时候,用户就跟第三方软件之间没有任何“通信连接”了。如果授权服务通过后端通信直接将令牌给了第三方软件的后端,那第三方软件该如何通知用户呢,恐怕就不太好实现了。

    这种情况下就很巧妙地引入了授权码 code:先把 code 通过重定向返回到第三方软件的页面;第三方软件通过浏览器获取到 code 后,再通过后端通信换取 access_token;待拿到 token 之后,由于此时用户已经在第三方软件的服务上,所以可以很容易地通知到用户。

    以上,就是授权码许可的整体工作流程了。我们说,这是 OAuth 2.0 授权体系中最完备的流程,其他的授权许可类型,比如资源拥有者凭据许可、客户端凭据许可、隐式许可,都是以此为基础。因此,只要你能理解授权码许可的流程,也就掌握了整个 OAuth 2.0 中所有许可类型的运转机制,在实际工作场景中用上 OAuth 2.0 将不再是问题。

    OAuth 2.0 安全问题串讲

    但是,到这里并没有万事大吉,我们只是解决了 OAuth 2.0 的基础使用的问题。要想用好、用对这个协议,成长为这个协议的应用专家,我们还必须关注 OAu

    我们在实践 OAuth 2.0 的过程中,还必须按照规范建议来执行,否则便会引发一系列的安全问题。这也往往导致有的同学会发出这样的疑问,OAuth 2.0 不是安全的吗?它不是一直在保护着互联网上成千上万个 Web API 吗,我们不也说它是一种安全协议吗?

    首先我们说 OAuth 2.0 是安全协议没问题,但如果使用不当也会引起安全上的问题。比如,我们在第 8 讲中提到了一个很广泛的跨站请求伪造问题。之所以出现这样的安全问题,就是因为我们没有遵循 OAuth 2.0 的使用建议,比如没有使用 state 这样的参数来做请求的校验,或者是没有遵循授权码 code 值只能使用一次,并且还要清除使用过的 code 值跟 token 值之间的绑定关系的建议。

    在安全问题上,其实我们一直都没有特别说明一点,那就是在使用 OAuth 2.0 的流程中,我们的 HTTP 通信要使用 HTTPS 协议来保护数据传输的安全性。这是因为 OAuth 2.0 支持的 bearer 令牌类型,也就是任意字符串格式的令牌,并没有提供且没有要求使用信息签名的机制。

    你可能会说,JWT 令牌有这样的加密机制啊。但其实,这也正说明了 OAuth 2.0 是一个没有约束普通令牌的规则,所以才有了 JWT 这样对 OAuth 2.0 的额外补充。

    实际上,JWT 跟 OAuth 2.0 并没有直接关系,它只是一种结构化的信息存储,可以被用在除了 OAuth 2.0 以外的任何地方。比如,重置密码的时候,会给你的邮箱发送一个链接,这个链接就需要能够标识出用户是谁、不能篡改、有效期 5 分钟,这些特征都跟 JWT 相符合。也就是说,JWT 并不是 OAuth 2.0 协议规范所涵盖的内容。

    OAuth 2.0 似乎没有自己的规则约束机制,或者说只有比较弱的约束,但其实不是不约束,而是它就致力于做好授权框架这一件事儿。通过我们前面的学习,也可以验证出它的确很好地胜任了这项工作。

    也许正是因为 OAuth 2.0 可以支持类似 OIDC 这样的身份认证协议,导致我们总是“坚持”认为 OAuth 2.0 是一种身份认证协议。当然了,OAuth 2.0 并不是身份认证协议,我在第 9 讲中用“面粉”和“面包”来类比 OAuth 2.0 和 OIDC 的关系。

    这里我再解释一下。究竟是什么原因导致了我们对 OAuth 2.0 有这样的 “误解” 呢?我想大概原因是,OAuth 2.0 中确实包含了身份认证的内容,即授权服务需要让用户登录以后才可以进行用户确认授权的操作。

    但这样的流程,仅仅是 OAuth 2.0 涉及到了身份认证的行为,还不足以让 OAuth 2.0 成为一个真正的用户身份认证协议。因为 OAuth 2.0 关心的只有两点,颁发令牌和使用令牌,并且令牌对第三方软件是不透明的;同时,受保护资源服务也不关心是哪个用户来请求,只要有合法的令牌 “递” 过来,就会给出正确的响应,把数据返回给第三方软件。

    以上,就是与 OAuth 2.0 安全问题息息相关的内容了。讲到这里,希望你可以按照自己的理解,融会贯通 OAuth 2.0 的这些核心知识了。接下来,我再和你分享一个我在实践 OAuth 2.0 过程中感触最深的一个问题吧。

    再强调都不为过的安全意识

    根据我在开放平台上这些年的工作经验,安全意识是实践 OAuth 2.0 过程中,再怎么强调都不为过的问题。

    因为总结起来,要说使用 OAuth 2.0 的过程中如果能有哪个机会让你“栽个大跟头”的话,那这个机会一定是在安全上:OAuth 2.0 本就是致力于保护开放的 Web API,保护用户在平台上的资源,如果因为 OAuth 2.0 使用不当而造成安全问题,确实是一件非常 “丢人” 的事情。

    而 OAuth2.0 的流程里面能够为安全做贡献的只有两方,一方是第三方软件,一方是平台方。在安全性这个问题上,第三方软件开发者的安全意识参差不齐。那针对这一点,就需要平台方在其官方文档上重笔描述,并给出常见安全漏洞相应的解决方案。同时,作为平台方的内部开发人员,对安全的问题同样不能忽视,而且要有更高的安全意识和认知。

    只有第三方软件开发者和平台方的研发人员共同保有较高的安全意识,才能让“安全的墙”垒得越来越高,让攻击者的成本越来越高。因为安全的本质就是成本问题。

    你看,我花了这么大的篇幅来和你讲解 OAuth 2.0 的安全问题,并单独分析了安全意识,是不是足以凸显安全性这个问题的重要程度了。没错儿,这也是你能用好 OAuth 2.0 的一个关键标志。

    总结

    好了,以上就是我们今天的主要内容了。我希望你能记住以下三点:

    1. OAuth 2.0 是一个授权协议,它通过访问令牌来表示这种授权。第三软件拿到访问令牌之后,就可以使用访问令牌来代表用户去访问用户的数据了。所以,我们说授权的核心就是获取访问令牌和使用访问令牌。

    2. OAuth 2.0 是一个安全协议,但是如果你使用不当,它并不能保证一定是安全的。如果你不按照 OAuth 2.0 规范中的建议来实施,就会有安全风险。比如,你没有遵循授权服务中的授权码只能使用一次、第三方软件的重定向 URL 要精确匹配等建议。

    3. 安全防护的过程一直都是“魔高一尺道高一丈”,相互攀升的过程。因此,在使用 OAuth 2.0 的过程中,第三方软件和平台方都要有足够的安全意识,来把“安全的墙”筑得更高。

      最后我想说的是,无论你使用 OAuth 2.0 目的是保护 API,还是作为用户身份认证的基础,OAuth 2.0 都只是解决这些问题的一种工具。而掌握 OAuth 2.0 这种工具的原理及其使用场景,将会帮助你更高效、更优雅地解决这些问题。

    思考题

    如果你是一名第三方软件的开发人员,你觉得应该如何提高自己的安全意识呢?

    - +
    跳至主要內容

    10 | 串讲:OAuth 2.0的工作流程与安全问题

    ChenSino大约 10 分钟

    今天这一讲,我并不打算带你去解决新的什么问题,而是把我们已经讲过的内容再串一遍,就像学生时代每个学期即将结束时的一次串讲,来 “回味”下 OAuth 2.0 的整个知识体系。

    当然了,我也会在这个过程中,与你分享我在实践 OAuth 2.0 的过程中,积累的最值得分享的经验。

    好,接下来就让我们先串一串 OAuth 2.0 的工作流程吧。

    OAuth 2.0 工作流程串讲

    img

    我们一直在讲 OAuth 2.0 是一种授权协议,这种协议可以让第三方软件代表用户去执行被允许的操作。那么,第三方软件就需要向用户索取授权来获得那个令牌。

    我们回想下第 1 讲拜访百度王总的例子。只有拿到前台小姐姐给你的门禁卡,你才能够进入百度大楼。这个过程就相当于前台小姐姐给你做了一次授权,而这个授权的凭证就是门禁卡。对应到我们的系统中,门禁卡便相当于访问令牌。

    通过“代表”“授权”这样的关键词,我们可以认识到,OAuth 2.0 是一个授权协议,也是一个安全协议。那么,如果我说它也是一种委托协议,你也不要吃惊。

    试想一下,用户在微信平台上有修改昵称、修改头像、修改个人兴趣的权限,当第三方软件请求让自己代表用户来操作这些权限的时候,就是第三方软件请求用户把这些权限委托给自己,用户在批准了委托请求之后,才可以代表用户去执行这些操作。

    这时,我们细想一下,委托才是 OAuth 2.0 授权概念的根基,因为没有“委托”之意就不会有“代表”行为的发生。

    在整个课程讲述授权的过程中,我频繁举例和强调的就是授权码许可流程。在学习授权码流程的时候,你最困惑的一点恐怕莫过于 “为什么要多此一举,非得通过一个授权码 code 来换取访问令牌 access_token”了吧。这个问题我在讲授权码许可的整体流程时也做过分析了,你现在回想起来应该不会再为此“痛苦不堪” 了吧。

    我们再来分析下,第三方软件要获取访问令牌,只能通过两个渠道:

    • 一个渠道是第三方软件的前端页面。但是,如果直接返回到前端页面上,访问令牌是很容易被通过浏览器截获的,所以显然不可取。
    • 另外一个渠道是通过后端传输。第三方软件的后端和授权服务的后端之间通信,这样就可以避免令牌被直接暴露的问题。

    再往深了想,第三方软件的后端总不能向授权服务的后端 “硬要” 吧,总要告诉授权服务是要哪个用户的 access_token 吧,所以还需要用户的参与。

    用户一旦参与进来,访问的第一个页面是第三方软件,用户要授权,第三方软件就需要把用户引导到授权服务页面。但这个时候,用户就跟第三方软件之间没有任何“通信连接”了。如果授权服务通过后端通信直接将令牌给了第三方软件的后端,那第三方软件该如何通知用户呢,恐怕就不太好实现了。

    这种情况下就很巧妙地引入了授权码 code:先把 code 通过重定向返回到第三方软件的页面;第三方软件通过浏览器获取到 code 后,再通过后端通信换取 access_token;待拿到 token 之后,由于此时用户已经在第三方软件的服务上,所以可以很容易地通知到用户。

    以上,就是授权码许可的整体工作流程了。我们说,这是 OAuth 2.0 授权体系中最完备的流程,其他的授权许可类型,比如资源拥有者凭据许可、客户端凭据许可、隐式许可,都是以此为基础。因此,只要你能理解授权码许可的流程,也就掌握了整个 OAuth 2.0 中所有许可类型的运转机制,在实际工作场景中用上 OAuth 2.0 将不再是问题。

    OAuth 2.0 安全问题串讲

    但是,到这里并没有万事大吉,我们只是解决了 OAuth 2.0 的基础使用的问题。要想用好、用对这个协议,成长为这个协议的应用专家,我们还必须关注 OAu

    我们在实践 OAuth 2.0 的过程中,还必须按照规范建议来执行,否则便会引发一系列的安全问题。这也往往导致有的同学会发出这样的疑问,OAuth 2.0 不是安全的吗?它不是一直在保护着互联网上成千上万个 Web API 吗,我们不也说它是一种安全协议吗?

    首先我们说 OAuth 2.0 是安全协议没问题,但如果使用不当也会引起安全上的问题。比如,我们在第 8 讲中提到了一个很广泛的跨站请求伪造问题。之所以出现这样的安全问题,就是因为我们没有遵循 OAuth 2.0 的使用建议,比如没有使用 state 这样的参数来做请求的校验,或者是没有遵循授权码 code 值只能使用一次,并且还要清除使用过的 code 值跟 token 值之间的绑定关系的建议。

    在安全问题上,其实我们一直都没有特别说明一点,那就是在使用 OAuth 2.0 的流程中,我们的 HTTP 通信要使用 HTTPS 协议来保护数据传输的安全性。这是因为 OAuth 2.0 支持的 bearer 令牌类型,也就是任意字符串格式的令牌,并没有提供且没有要求使用信息签名的机制。

    你可能会说,JWT 令牌有这样的加密机制啊。但其实,这也正说明了 OAuth 2.0 是一个没有约束普通令牌的规则,所以才有了 JWT 这样对 OAuth 2.0 的额外补充。

    实际上,JWT 跟 OAuth 2.0 并没有直接关系,它只是一种结构化的信息存储,可以被用在除了 OAuth 2.0 以外的任何地方。比如,重置密码的时候,会给你的邮箱发送一个链接,这个链接就需要能够标识出用户是谁、不能篡改、有效期 5 分钟,这些特征都跟 JWT 相符合。也就是说,JWT 并不是 OAuth 2.0 协议规范所涵盖的内容。

    OAuth 2.0 似乎没有自己的规则约束机制,或者说只有比较弱的约束,但其实不是不约束,而是它就致力于做好授权框架这一件事儿。通过我们前面的学习,也可以验证出它的确很好地胜任了这项工作。

    也许正是因为 OAuth 2.0 可以支持类似 OIDC 这样的身份认证协议,导致我们总是“坚持”认为 OAuth 2.0 是一种身份认证协议。当然了,OAuth 2.0 并不是身份认证协议,我在第 9 讲中用“面粉”和“面包”来类比 OAuth 2.0 和 OIDC 的关系。

    这里我再解释一下。究竟是什么原因导致了我们对 OAuth 2.0 有这样的 “误解” 呢?我想大概原因是,OAuth 2.0 中确实包含了身份认证的内容,即授权服务需要让用户登录以后才可以进行用户确认授权的操作。

    但这样的流程,仅仅是 OAuth 2.0 涉及到了身份认证的行为,还不足以让 OAuth 2.0 成为一个真正的用户身份认证协议。因为 OAuth 2.0 关心的只有两点,颁发令牌和使用令牌,并且令牌对第三方软件是不透明的;同时,受保护资源服务也不关心是哪个用户来请求,只要有合法的令牌 “递” 过来,就会给出正确的响应,把数据返回给第三方软件。

    以上,就是与 OAuth 2.0 安全问题息息相关的内容了。讲到这里,希望你可以按照自己的理解,融会贯通 OAuth 2.0 的这些核心知识了。接下来,我再和你分享一个我在实践 OAuth 2.0 过程中感触最深的一个问题吧。

    再强调都不为过的安全意识

    根据我在开放平台上这些年的工作经验,安全意识是实践 OAuth 2.0 过程中,再怎么强调都不为过的问题。

    因为总结起来,要说使用 OAuth 2.0 的过程中如果能有哪个机会让你“栽个大跟头”的话,那这个机会一定是在安全上:OAuth 2.0 本就是致力于保护开放的 Web API,保护用户在平台上的资源,如果因为 OAuth 2.0 使用不当而造成安全问题,确实是一件非常 “丢人” 的事情。

    而 OAuth2.0 的流程里面能够为安全做贡献的只有两方,一方是第三方软件,一方是平台方。在安全性这个问题上,第三方软件开发者的安全意识参差不齐。那针对这一点,就需要平台方在其官方文档上重笔描述,并给出常见安全漏洞相应的解决方案。同时,作为平台方的内部开发人员,对安全的问题同样不能忽视,而且要有更高的安全意识和认知。

    只有第三方软件开发者和平台方的研发人员共同保有较高的安全意识,才能让“安全的墙”垒得越来越高,让攻击者的成本越来越高。因为安全的本质就是成本问题。

    你看,我花了这么大的篇幅来和你讲解 OAuth 2.0 的安全问题,并单独分析了安全意识,是不是足以凸显安全性这个问题的重要程度了。没错儿,这也是你能用好 OAuth 2.0 的一个关键标志。

    总结

    好了,以上就是我们今天的主要内容了。我希望你能记住以下三点:

    1. OAuth 2.0 是一个授权协议,它通过访问令牌来表示这种授权。第三软件拿到访问令牌之后,就可以使用访问令牌来代表用户去访问用户的数据了。所以,我们说授权的核心就是获取访问令牌和使用访问令牌。

    2. OAuth 2.0 是一个安全协议,但是如果你使用不当,它并不能保证一定是安全的。如果你不按照 OAuth 2.0 规范中的建议来实施,就会有安全风险。比如,你没有遵循授权服务中的授权码只能使用一次、第三方软件的重定向 URL 要精确匹配等建议。

    3. 安全防护的过程一直都是“魔高一尺道高一丈”,相互攀升的过程。因此,在使用 OAuth 2.0 的过程中,第三方软件和平台方都要有足够的安全意识,来把“安全的墙”筑得更高。

      最后我想说的是,无论你使用 OAuth 2.0 目的是保护 API,还是作为用户身份认证的基础,OAuth 2.0 都只是解决这些问题的一种工具。而掌握 OAuth 2.0 这种工具的原理及其使用场景,将会帮助你更高效、更优雅地解决这些问题。

    思考题

    如果你是一名第三方软件的开发人员,你觉得应该如何提高自己的安全意识呢?

    + diff --git a/other/oauth2/11.html b/other/oauth2/11.html index fec29ae628..f384c8e931 100644 --- a/other/oauth2/11.html +++ b/other/oauth2/11.html @@ -30,8 +30,8 @@ 11 | 实战案例:使用Spring Security搭建一套基于JWT的OAuth 2.0架构 | ChenSino - - + +
    跳至主要內容

    11 | 实战案例:使用Spring Security搭建一套基于JWT的OAuth 2.0架构

    ChenSino大约 26 分钟

    你好,我朱晔,是《Java 业务开发常见错误 100 例open in new window》专栏课程的作者。

    《OAuth 2.0 实战课》上线之后,我也第一时间关注了这门课。在开篇词中,我看到有一些同学留言问道:“如何使用 Spring Security 来实现 OAuth 2.0?”这时,我想到之前自己写过一篇相关的文章,于是就直接在开篇词下留了言。后面我很快收到了不少用户的点赞和肯定,紧接着极客时间编辑也邀请我从自己的角度为专栏写篇加餐。好吧,功不唐捐,于是我就将之前我写的那篇老文章再次迭代、整理为今天的这一讲内容,希望可以帮助你掌握 OAuth 2.0。

    如果你熟悉 Spring Security 的话,肯定知道它因为功能多、组件抽象程度高、配置方式多样,导致了强大且复杂的特性。也因此,Spring Security 的学习成本几乎是 Spring 家族中最高的。但不仅于此,在结合实际的复杂业务场景使用 Spring Security 时,我们还要去理解一些组件的工作原理和流程,不然需要自定义和扩展框架的时候就会手足无措。这就让使用 Spring Security 的门槛更高了。

    因此,在决定使用 Spring Security 搭建整套安全体系(授权、认证、权限、审计)之前,我们还需要考虑的是:将来我们的业务会多复杂,徒手写一套安全体系来得划算,还是使用 Spring Security 更好?我相信,这也是王老师给出课程配套代码中,并没有使用 Spring Security 来演示 OAuth 2.0 流程的原因之一。

    反过来说,如果你的应用已经使用了 Spring Security 来做鉴权、认证和权限管理的话,那么仍然使用 Spring Security 来实现 OAuth 的成本是很低的。而且,在学习了 OAuth 2.0 的流程打下扎实的基础之后,我们再使用 Spring Security 来配置 OAuth 2.0 就不会那么迷茫了。这也是我在工作中使用 Spring Security 来实现 OAuth 2.0 的直观感受。

    所以,我就结合自己的实践和积累,带你使用 Spring Security 来一步一步地搭建一套基于 JWT 的 OAuth 2.0 授权体系。这些内容会涉及 OAuth 2.0 的三角色(客户端、授权服务、受保护资源),以及资源拥有者凭据许可、客户端凭据许可和授权码许可这三种常用的授权许可类型(隐式许可类型,不太安全也不太常用)。同时,我还会演示 OAuth 2.0 的权限控制,以及使用 OAuth 2.0 实现 SSO 单点登录体系。

    这样一来,今天这一讲涉及到的流程就会比较多,内容也会很长。不过不用担心,我会手把手带你从零开始,完成整个程序的搭建,并给出所有流程的演示。

    项目准备工作

    实战之前,我们先来搭建项目父依赖和初始化数据库结构,为后面具体的编码做准备。

    首先,我们来创建一个父 POM,内含三个模块:

    • springsecurity101-cloud-oauth2-client,用来扮演客户端角色;

    • springsecurity101-cloud-oauth2-server,用来扮演授权服务器角色;

    • springsecurity101-cloud-oauth2-userservice,是用户服务,用来扮演资源提供者角色。

      
      @@ -735,7 +735,7 @@
       public String name(OAuth2Authentication authentication) {
           return authentication.getName();
       }
      -

      换一个 writer 用户登录试试,也能得到正确的输出:

      img

      总结

      今天这一讲,我们完整演示了如何使用 Spring Cloud 的 OAuth 2.0 组件基于三个程序角色(授权服务器、受保护资源服务器和客户端)实现三种 OAuth 2.0 的授权许可类型(资源拥有者凭据许可、客户端凭据许可和授权码许可)。

      我们先演示了三种授权许可类型的手动流程,然后也演示了如何实现权限控制和单点登录,以及如何使用客户端程序来实现自动的 OAuth 2.0 流程。

      我把今天用到的所有代码都放到了 GitHub 上,你可以点击这个链接open in new window查看。

      最后,我再提一下,将来 Spring 对于 OAuth 2.0 的支持可能会转移到由社区推进的 Spring Authorization Serveropen in new window 项目上来继续运作。如果你感兴趣的话,可以及时关注这个项目的进展。

    - +

    换一个 writer 用户登录试试,也能得到正确的输出:

    img

    总结

    今天这一讲,我们完整演示了如何使用 Spring Cloud 的 OAuth 2.0 组件基于三个程序角色(授权服务器、受保护资源服务器和客户端)实现三种 OAuth 2.0 的授权许可类型(资源拥有者凭据许可、客户端凭据许可和授权码许可)。

    我们先演示了三种授权许可类型的手动流程,然后也演示了如何实现权限控制和单点登录,以及如何使用客户端程序来实现自动的 OAuth 2.0 流程。

    我把今天用到的所有代码都放到了 GitHub 上,你可以点击这个链接open in new window查看。

    最后,我再提一下,将来 Spring 对于 OAuth 2.0 的支持可能会转移到由社区推进的 Spring Authorization Serveropen in new window 项目上来继续运作。如果你感兴趣的话,可以及时关注这个项目的进展。

    + diff --git a/other/oauth2/12.html b/other/oauth2/12.html index c62146c0ca..ea079f74f4 100644 --- a/other/oauth2/12.html +++ b/other/oauth2/12.html @@ -30,11 +30,11 @@ 12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构 | ChenSino - - + + -
    跳至主要內容

    12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构

    ChenSino大约 19 分钟

    在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。

    因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于 OAuth 2.0/JWT 的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监,在微服务和 OAuth 2.0 有非常丰富的实践经验。

    其中,在携程工作期间,他负责过携程的 API 网关产品的研发工作,包括它和携程的令牌服务的集成;在拍拍贷工作期间,他负责过拍拍贷的令牌服务的研发和运维工作。这两家公司的令牌服务和 OAuth 2.0 类似,但要更简单些。

    接下来,我们就开始学习杨波老师给我们带来的内容吧。

    你好,我是杨波。

    从单体到微服务架构的演进,是当前企业数字化转型的一大趋势。OAuth 2.0open in new window是当前业界标准的授权协议,它的核心是若干个针对不同场景的令牌颁发和管理流程;而JWTopen in new window是一种轻量级、自包含的令牌,可用于在微服务间安全地传递用户信息。

    据我目前了解到的情况,虽然有不少企业已经部分或全部转型到微服务架构,但是在授权认证机制方面,它们一般都是定制自研的,比方说携程和拍拍贷的令牌服务。之所以定制自研,主要原因在于标准的 OAuth 2.0 协议相对比较复杂,门槛也比较高。定制自研固然可以暂时解决企业的问题,但是不具备通用性,也可能有很多潜在的安全风险。

    那么,到底应该如何将行业标准的 OAuth 2.0/JWT 和微服务集成起来呢,又有没有可落地的参考架构呢?

    针对这个问题,今天我就和你分享一种可落地的参考架构。不过,我要提前说明的是,这个架构的思想源于MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT – PART 1 – OVERVIEWopen in new window这篇文章。

    根据原作者 Thijs 的描述,他提出的架构已经在企业落地架构了。如果你还想获得关于原架构的更多细节,建议进一步参考“What is PKCEopen in new window?”这篇文章。我认为,Thijs 给出的架构确实具有可落地性和参考价值,但是他的架构里面对某些微服务层次的命名,例如 BFF 和 Facade 层,和目前主流的微服务架构不符,还有他的架构应该是手绘,不够清晰,也不容易理解。为此,我专门用今天这一讲,来改进 Thijs 给出的架构,并补充针对不同场景的流程。

    为了方便理解,在接下来的讲述中,我会假定有这样一家叫 ACME 的新零售公司,它已经实现了数字化转型,微服务电商平台是支持业务运作的核心基础设施。

    在业务架构方面,ACME 有近千家线下门店,这些门店通过 POS 系统和电商平台对接。公司还有一些物流发货中心,拣选(Order Picking)系统也要和电商平台对接。另外,公司还有很多送货司机,通过 App 和电商平台对接。当然,ACME 还有一些电商网站,做线上营销和销售,这些网站是电商平台的主要流量源。

    虽然支持 ACME 公司业务运作的技术平台很复杂,但是它的核心可以用一个简化的微服务架构图来描述:

    img

    可以看出,这个微服务架构是运行在 Kubernetes 集群中的。当然了,这个架构实际上并不一定需要 Kubernetes 环境,用传统数据中心也可以。另外,它的整体认证授权架构是基于 OAuth 2.0/JWT 实现的。

    接下来,我按这个微服务架构的分层方式,依次和你分析下它的每一层,以及应用认证 / 授权和服务调用的相关流程。这样,你不仅可以理解一个典型的微服务架构该如何分层,还可以弄清楚 OAuth 2.0/JWT 该如何与微服务进行集成。

    微服务分层架构

    ACME 公司的微服务架构,大致可以分为 Nginx 反向代理层、Web 应用层、Gateway 网关层、BEF 层和领域服务层,还包括一个 IDP 服务。总体上讲,这是一种目前主流的微服务架构分层方式,每一层职责单一、清晰。

    接下来,我们具体看看每一层的主要功能。

    Nginx 反向代理层

    首先,Nginx 集群是整个平台的流量入口。Nginx 是 7 层 HTTP 反向代理,主要功能是实现反向路由,也就是将外部流量根据 HOST 主机头或者 PATH,路由到不同的后端,比方说路由到 Web 应用,或者直接到网关 Gateway。在 Kubernetes 体系中,Nginx 是和 Ingress Controller(入口控制器)配合工作的(总称为 Nginx Ingress),Ingress Controller 支持通过 Ingress Rules,配置 Nginx 的路由规则。

    Web 应用层

    这一层主要是一些 Web 应用,html/css/js 等资源就住在这一层。Web 服务层通常采用传统的 Web MVC + 模版引擎方式处理,可以实现服务器端渲染,也可以采用单页 SPA 方式。这一层主要由公司的前端团队负责,通常会使用 Node.js 技术栈来实现,也可以采用 Spring MVC 技术栈实现。具体怎么实现,要看公司的前端团队更擅长哪种技术。当这一层需要后台数据时,可以通过网关调用后台服务获取数据。

    Gateway 网关层

    这一层是微服务调用流量的入口。网关的主要职责是反向路由,也就是将前端请求根据 HOST 主机头、或者 PATH、或者查询参数,路由到后端目标微服务(比如,图中的 IDP/BFF 或者直接到领域服务)。

    另外,网关还承担两个重要的安全职责:

    • 一个是令牌的校验和转换,将前端传递过来的 OAuth 2.0 访问令牌,通过调用 IDP 进行校验,并转换为包含用户和权限信息的 JWT 令牌,再将 JWT 令牌向后台微服务传递。
    • 另外一个是权限校验,网关的路由表可以和 OAuth 2.0 的 Scope 进行关联。这样,网关根据请求令牌中的权限范围 Scope,就可以判断请求是否具有调用后台服务的权限。

    关于安全相关的场景和流程,我会在下一章节做进一步解释。另外,网关还需承担集中式限流、日志监控,以及支持 CORS 等功能。对于网关层的技术选型,当前主流的 API 网关产品,像 Netflix 开源的 Zuul、Spring Cloud Gateway 等,都可以考虑。

    IDP 服务

    IDP 是 Identity Provider 的简称,主要负责 OAuth 2.0 授权协议处理,OAuth 2.0 和 JWT 令牌颁发和管理,以及用户认证等功能。IDP 使用后台的 Login-Service 进行用户认证。对于 IDP 的技术选型,当前主流的 Spring Security OAuth,或者 RedHat 开源的 KeyCloak,都可以考虑。其中,Spring Security OAuth 是一个 OAuth 2.0 的开发框架,适合企业定制。KeyCloak 则是一个开箱即用的 OAuth 2.0/OIDC 产品。

    BFF 层

    BFF 是 Backend for Frontend 的简称,主要实现对后台领域服务的聚合(Aggregation,有点类似数据库的 Join)功能,同时为不同的前端体验(PC/Mobile/ 开放平台等)提供更友好的 API 和数据格式。

    BFF 中可以包含一些业务逻辑,甚至还可以有自己的数据库存储。通常,BFF 要调用两个或两个以上的领域服务,甚至还可能调用其它的 BFF(当然一般并不建议这样调用,因为这样会让调用关系变得错综复杂,无法理解)。

    如果 BFF 需要获取调用用户或者 OAuth 2.0 Scope 相关信息,它可以从传递过来的 JWT 令牌中直接获取。

    BFF 服务可以用 Node.js 开发,也可以用 Java/Spring 等框架开发。

    领域服务层

    领域服务层在整个微服务架构的底层。这些服务包含业务逻辑,通常有自己独立的数据库存储,还可以根据需要调用外部的服务。根据微服务分层原则,领域服务禁止调用其它的领域服务,更不允许反向调用 BFF 服务。这样做是为了保持微服务职责单一(Single Responsibility)和有界上下文(Bounded Context),避免复杂的领域依赖。领域服务是独立的开发、测试和发布单位。在电商领域,常见的领域服务有用户服务、商品服务、订单服务和支付服务等。和 BFF 一样,如果领域服务需要获取调用用户或者 OAuth 2.0 Scope 相关信息,它可以从传递过来的 JWT 令牌中直接获取。可以看到,领域服务和 BFF 服务都是无状态的,它们本身并不存储用户状态,而是通过传递过来的 JWT 数据获取用户信息。所以在整个架构中,微服务都是无状态、可以按需水平扩展的,状态要么存在用户端(浏览器或者手机 App 中),要么存在集中的数据库中。

    OAuth 2.0/JWT 如何与微服务进行集成?

    以上,就是 ACME 公司的整个微服务架构的层次了。这个分层架构,对于大部分的互联网业务系统场景都适用。因此,如果你是一家企业的架构师,需要设计一套微服务架构,完全可以参考它来设计。接下来,我再演示几个典型的应用认证场景,以及相应的服务调用流程,来帮助你理解 OAuth 2.0/JWT 是如何和微服务进行集成的。

    场景 1:第一方 Web 应用 + 资源拥有者凭据模式

    这个场景是用户访问 ACME 公司自己的电商网站,假设这个电商网站是用 Spring MVC 开发的。考虑到这是一个第一方场景(也就是公司自己开发的网站应用),我们可以选 OAuth 2.0 的资源拥有者凭据许可(Resource Owner Password Credentials Grant),也可以选更安全的授权码许可(Authorization Code Grant)。因为这里没有第三方的概念,所以我们就选相对简单的资源拥有者凭据许可。下面是一个认证授权流程样例。注意,这个只是突出了关键步骤,实际生产的话,还有很多需要完善和优化的地方。另外,为描述简单,这里假定一个成功流程。

    img

    在上面的图中,用户对应 OAuth 2.0 中的资源拥有者,ACME IDP 对应 OAuth 2.0 中的授权服务。另外,前面架构图中的后台微服务(包括 BFF 和基础领域服务),对应 OAuth 2.0 中的受保护资源。下面是流程说明:

    1. 用户通过浏览器访问 ACME 公司的电商网站,点击登录链接。
    2. Web 应用返回登录界面(这个登录页可以是网站自己定制开发)。
    3. 用户输入用户名、密码进行认证。
    4. Web 应用将用户名、密码,通过网关转发到 IDP 的令牌获取端点(POST /oauth2/token,grant_type=password)。
    5. IDP 通过 Login Service 对用户进行认证。
    6. IDP 认证通过,返回有效访问令牌(根据需要也可以返回刷新令牌)。
    7. Web 应用接收到访问令牌,创建用户 Session,并将 OAuth 2.0 令牌保存其中,然后返回登录成功到用户端。
    8. 用户浏览器中记录 Session Cookie,登录成功。

    那接下来,我们再来看看认证授权之后的服务调用流程。同样,这里也只是突出了关键步骤,并假定是一个成功流程。

    img

    1. 用户登录后,在网站上点击查看自己的购物历史记录。
    2. Web 应用通过网关调用后台 API(查询用户的购物历史记录),请求 HTTP header 中带上 OAuth 2.0 令牌(来自用户 Session)。
    3. 网关截取 OAuth 2.0 令牌,去 IDP 进行校验。
    4. IDP 校验令牌通过,再通过令牌查询用户和 Scope 信息,构建 JWT 令牌,返回。
    5. 网关获得 JWT 令牌,校验 Scope 是否有权限调用 API,如果有就转发到后台 API 进行调用。
    6. 后台 BFF(或者领域服务)通过传递过来的 JWT 获取用户信息,根据用户 ID 查询购物历史记录,返回。
    7. Web 应用获得用户的购物历史数据,可以根据需要缓存在 Session 中,再返回用户端。
    8. 购物历史数据返回到用户浏览器端。

    注意,这个服务调用流程,也可以应用在其他场景中,比如我们接下来要学习的“第一方移动应用 + 授权码许可模式”和“第三方 Web 应用 + 授权码许可模式”。基本上只要你理解了这个流程原理,就可以根据实际场景灵活套用。

    场景 2:第一方移动应用 + 授权码许可模式

    第二个场景是用户通过手机访问 ACME 公司自己的电商 App。这是第一方的原生应用(Native App)场景,通常考虑选用 OAuth 2.0 的用户名密码模式,但是并不安全(参考MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT – PART 3 – IDP的 Security Considerationopen in new window 部分),所以业界建议采用授权码模式,而且是要支持PKCE扩展的授权码模式。那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假定是一个成功流程。

    img

    1. 用户访问电商 App,点击登录。
    2. App 生成 PKCE 相关的 code verifier + challenge。
    3. App 以内嵌方式启动手机浏览器,访问 IDP 的统一认证页 (GET /authorize),请求带上 PKCE 的 code challenge 相关参数。
    4. IDP 返回统一认证页。
    5. 用户认证和授权。
    6. IDP 通过 Login Service 对用户进行认证。
    7. IDP 返回授权码到 App 浏览器。
    8. App 截取浏览器带回的授权码,将授权码 +PKCE code verifer,通过网关转发到 IDP 的令牌获取端点(POST /oauth2/token, grant_type=authorization-code)。
    9. IDP 校验 PKCE 和授权码,校验通过则返回有效访问令牌。
    10. App 获取令牌,本地存储,登录成功。

    之后,App 如果需要和后台交互,可直接通过网关调用后台微服务,请求 HTTP header 中带上 OAuth 2.0 访问令牌即可。后续的服务调用流程,和“第一方应用 + 资源拥有者凭据模式”类似。

    场景 3:第三方 Web 应用 + 授权码模式

    第三个场景是某第三方合作厂商开发了一个 Web 网站,要访问 ACME 公司的电商开放平台 API。这是一个第三方 Web 应用场景,通常选用 OAuth 2.0 的授权码许可模式。那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假设是一个成功流程。

    img

    1. 用户访问这个第三方 Web 应用,点击登录链接。
    2. Web 应用后台向 ACME 公司的 IDP 服务发送申请授权码请求(GET /authorize)。
    3. 用户被重定向到 ACME 公司的 IDP 统一登录页面。
    4. 用户进行认证和授权。
    5. IDP 通过 Login Service 对用户进行认证。
    6. 认证和授权通过,IDP 返回授权码。
    7. Web 应用获得授权码,再向 IDP 服务的令牌获取端点发起请求(POST /oauth2/token, grant_type=authorization-code)。
    8. IDP 校验授权码,校验通过则返回有效 OAuth 2.0 令牌(根据需要也可以返回刷新令牌)。
    9. Web 应用创建用户 Session,将 OAuth 2.0 令牌保存在 Session 中,然后返回登录成功到用户端。
    10. 用户浏览器中记录 Session Cookie,登录成功。

    之后,第三方 Web 应用如果需要和 ACME 电商平台交互,可直接通过网关调用微服务,请求 HTTP header 中带上 OAuth 2.0 访问令牌即可。后续的服务调用流程,和前面的“第一方应用 + 资源拥有者凭据模式”类似。

    ==额外说明==

    除了上面的三个主要场景和流程,我还要和你分享 6 点。这 6 点是对上面基本流程的补充,也是企业级的 OAuth 2.0 应用要额外考虑的。

    ==第一点是,IDP 的 API 要支持从 OAuth 2.0 访问令牌到 JWT 令牌的互转。== 今天我们提到的集成架构采用 OAuth 2.0 访问令牌 + JWT 令牌的混合模式,中间需要实现 OAuth 2.0 访问令牌到 JWT 令牌的互转。这个互转 API 并非 OAuth 2.0 的标准,有些 IDP 产品(比方 Spring Security OAuth)可能并不支持,因此需要用户定制扩展。

    ==第二点是,关于单页 SPA 应用场景。== 关于单页 SPA 应用场景,简单做法是采用隐式许可,但是这个模式是 OAuth 2.0 中比较不安全的,所以一般不建议采用。对于纯单页 SPA 应用,业界推荐的做法是:

    • 如果浏览器支持 Web Crypto for PKCE,则可以考虑使用类似“第一方移动应用”场景下的授权码许可 +PKCE 扩展流程;
    • 否则,考虑 SPA+ 传统 Web 混合(hybrid)模式,前端页面可以住在客户浏览器端中,但登录认证还是由后台 Web 站点配合实现,走类似“第一方 Web 应用”场景的资源拥有者凭据模式,或者“第三方 Web 应用”场景下的授权码许可模式。

    ==第三点是,关于 SSO 单点登录场景。== 为了简化描述,上面的流程没有考虑 SSO 单点登录场景。如果要支持 Web SSO,那么各种应用场景都必须通过浏览器 +IDP 登录页集中登录,并且 IDP 要支持 Session,用于维护登录态。如果 IDP 以集群方式部署的话,还要考虑粘性 Sticky Session 或者集中式 Session。

    这样,当用户通过一个 Web 应用登录后,后续如果再用其它 Web 应用登录的话,只要 IDP 上的 Session 还存在,那么这个登录就可以自动完成,相当于单点登录。

    当然,如果要支持 SSO,IDP 的 Session Cookie 要种在 Web 应用的根域上,也就是说不同 Web 应用的根域必须相同,否则会有跨域问题。

    ==第四点是关于 IDP 和网关的部署方式。== 前面的几张架构图中,IDP 虽然躲在网关后面,但实际上 IDP 可以直接通过 Nginx 对外暴露,不经过网关。或者,IDP 的登录授权页面,可以通过 Nginx 直接暴露,API 接口则走网关。

    ==第五点是关于刷新令牌。== 为了简化描述,上面的流程没有详细说明刷新令牌的集成方式。企业根据场景需要,可以启用刷新令牌,来延长用户的登录时间,具体的集成方式需要考虑安全性的需求。

    ==第六点是关于 Web Session。== 为了简化描述,在上面的流程中,Web 应用登录成功后假设启用 Web Session,也就是服务器端 Session。在实际场景中,Web Session 并非唯一选择,也可以采用简单的客户端 Session 方式,也称无状态 Session,也就是在客户端浏览器 Cookie 中保存 OAuth 2.0 访问令牌。

    小结

    好了,以上就是今天的主要内容了。今天,我和你分享了如何将行业标准的 OAuth 2.0/JWT 和微服务集成起来,你需要记住以下四点。

    第一,目前主流的微服务架构大致可以分为 5 层,分别是:Nginx 流量接入层 ->Web 应用层 ->API 网关层 ->BFF 聚合层 -> 领域服务层。这个架构可以住在云原生的 Kubernetes 环境中,也可以住在传统数据中心里头。

    第二,API 网关是微服务调用的入口,承担重要的安全认证和鉴权功能。主要的安全操作包括:一,通过 IDP 校验 OAuth 2.0 访问令牌,并获取带用户和权限信息的 JWT 令牌;二,基于 OAuth 2.0 的 Scope 对 API 调用进行鉴权。

    第三,在微服务架构体系下,通常需要一个集中的 IDP 服务,它相当于一个 Authentication & Authorization as a Service 角色,负责令牌颁发 / 校验 / 管理,还有用户认证。

    第四,在今天这一讲提出的架构中,Web 应用层(网关之前)的安全机制主要基于 OAuth 2.0 访问令牌实现(它是一种透明令牌或者称引用令牌),微服务层(网关之后)的安全机制主要基于 JWT 令牌实现(它是一种不透明的自包含令牌)。网关层在中间实现两种令牌的转换。这是一种 OAuth 2.0 访问令牌 +JWT 令牌的混合模式。

    之所以这样设计,是因为 Web 层靠近用户端,如果采用 JWT 令牌,会暴露用户信息,有一定的安全风险,所以采用 OAuth 2.0 访问令牌,它是一个无意义随机字符串。而在网关之后,安全风险相对低,同时很多服务需要用户信息,所以采用自包含用户信息的 JWT 令牌更合适。当然,如果企业内网没有特别的安全考量,也可以直接传递完全透明的用户信息(例如使用 JSON 格式)。

    思考题

    1. 除了今天我们讲到的 OAuth 2.0 访问令牌 +JWT 令牌的混合模式,实践中也可以全程采用 OAuth 2.0 访问令牌,或者全程采用 JWT 令牌。对比混合模式,如果全程采用 OAuth 2.0 访问令牌,或者全程采用 JWT 令牌,你觉得有哪些利弊呢?
    2. 你可以说说自己对基于传统 Web 应用的认证授权机制的理解吗?并对比今天讲到的现代微服务的认证授权机制,你可以说说它们之间的本质差异和相似点吗?
    - +
    跳至主要內容

    12 | 架构案例:基于OAuth 2.0/JWT的微服务参考架构

    ChenSino大约 19 分钟

    在前面几讲,我们一起学习了 OAuth 2.0 在开放环境中的使用过程。那么 OAuth 2.0 不仅仅可以用在开放的场景中,它可以应用到我们任何需要授权 / 鉴权的地方,包括微服务。

    因此今天,我特别邀请了我的朋友杨波,来和你分享一个基于 OAuth 2.0/JWT 的微服务参考架构。杨波,曾先后担任过携程框架部的研发总监和拍拍贷基础架构部的研发总监,在微服务和 OAuth 2.0 有非常丰富的实践经验。

    其中,在携程工作期间,他负责过携程的 API 网关产品的研发工作,包括它和携程的令牌服务的集成;在拍拍贷工作期间,他负责过拍拍贷的令牌服务的研发和运维工作。这两家公司的令牌服务和 OAuth 2.0 类似,但要更简单些。

    接下来,我们就开始学习杨波老师给我们带来的内容吧。

    你好,我是杨波。

    从单体到微服务架构的演进,是当前企业数字化转型的一大趋势。OAuth 2.0open in new window是当前业界标准的授权协议,它的核心是若干个针对不同场景的令牌颁发和管理流程;而JWTopen in new window是一种轻量级、自包含的令牌,可用于在微服务间安全地传递用户信息。

    据我目前了解到的情况,虽然有不少企业已经部分或全部转型到微服务架构,但是在授权认证机制方面,它们一般都是定制自研的,比方说携程和拍拍贷的令牌服务。之所以定制自研,主要原因在于标准的 OAuth 2.0 协议相对比较复杂,门槛也比较高。定制自研固然可以暂时解决企业的问题,但是不具备通用性,也可能有很多潜在的安全风险。

    那么,到底应该如何将行业标准的 OAuth 2.0/JWT 和微服务集成起来呢,又有没有可落地的参考架构呢?

    针对这个问题,今天我就和你分享一种可落地的参考架构。不过,我要提前说明的是,这个架构的思想源于MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT – PART 1 – OVERVIEWopen in new window这篇文章。

    根据原作者 Thijs 的描述,他提出的架构已经在企业落地架构了。如果你还想获得关于原架构的更多细节,建议进一步参考“What is PKCEopen in new window?”这篇文章。我认为,Thijs 给出的架构确实具有可落地性和参考价值,但是他的架构里面对某些微服务层次的命名,例如 BFF 和 Facade 层,和目前主流的微服务架构不符,还有他的架构应该是手绘,不够清晰,也不容易理解。为此,我专门用今天这一讲,来改进 Thijs 给出的架构,并补充针对不同场景的流程。

    为了方便理解,在接下来的讲述中,我会假定有这样一家叫 ACME 的新零售公司,它已经实现了数字化转型,微服务电商平台是支持业务运作的核心基础设施。

    在业务架构方面,ACME 有近千家线下门店,这些门店通过 POS 系统和电商平台对接。公司还有一些物流发货中心,拣选(Order Picking)系统也要和电商平台对接。另外,公司还有很多送货司机,通过 App 和电商平台对接。当然,ACME 还有一些电商网站,做线上营销和销售,这些网站是电商平台的主要流量源。

    虽然支持 ACME 公司业务运作的技术平台很复杂,但是它的核心可以用一个简化的微服务架构图来描述:

    img

    可以看出,这个微服务架构是运行在 Kubernetes 集群中的。当然了,这个架构实际上并不一定需要 Kubernetes 环境,用传统数据中心也可以。另外,它的整体认证授权架构是基于 OAuth 2.0/JWT 实现的。

    接下来,我按这个微服务架构的分层方式,依次和你分析下它的每一层,以及应用认证 / 授权和服务调用的相关流程。这样,你不仅可以理解一个典型的微服务架构该如何分层,还可以弄清楚 OAuth 2.0/JWT 该如何与微服务进行集成。

    微服务分层架构

    ACME 公司的微服务架构,大致可以分为 Nginx 反向代理层、Web 应用层、Gateway 网关层、BEF 层和领域服务层,还包括一个 IDP 服务。总体上讲,这是一种目前主流的微服务架构分层方式,每一层职责单一、清晰。

    接下来,我们具体看看每一层的主要功能。

    Nginx 反向代理层

    首先,Nginx 集群是整个平台的流量入口。Nginx 是 7 层 HTTP 反向代理,主要功能是实现反向路由,也就是将外部流量根据 HOST 主机头或者 PATH,路由到不同的后端,比方说路由到 Web 应用,或者直接到网关 Gateway。在 Kubernetes 体系中,Nginx 是和 Ingress Controller(入口控制器)配合工作的(总称为 Nginx Ingress),Ingress Controller 支持通过 Ingress Rules,配置 Nginx 的路由规则。

    Web 应用层

    这一层主要是一些 Web 应用,html/css/js 等资源就住在这一层。Web 服务层通常采用传统的 Web MVC + 模版引擎方式处理,可以实现服务器端渲染,也可以采用单页 SPA 方式。这一层主要由公司的前端团队负责,通常会使用 Node.js 技术栈来实现,也可以采用 Spring MVC 技术栈实现。具体怎么实现,要看公司的前端团队更擅长哪种技术。当这一层需要后台数据时,可以通过网关调用后台服务获取数据。

    Gateway 网关层

    这一层是微服务调用流量的入口。网关的主要职责是反向路由,也就是将前端请求根据 HOST 主机头、或者 PATH、或者查询参数,路由到后端目标微服务(比如,图中的 IDP/BFF 或者直接到领域服务)。

    另外,网关还承担两个重要的安全职责:

    • 一个是令牌的校验和转换,将前端传递过来的 OAuth 2.0 访问令牌,通过调用 IDP 进行校验,并转换为包含用户和权限信息的 JWT 令牌,再将 JWT 令牌向后台微服务传递。
    • 另外一个是权限校验,网关的路由表可以和 OAuth 2.0 的 Scope 进行关联。这样,网关根据请求令牌中的权限范围 Scope,就可以判断请求是否具有调用后台服务的权限。

    关于安全相关的场景和流程,我会在下一章节做进一步解释。另外,网关还需承担集中式限流、日志监控,以及支持 CORS 等功能。对于网关层的技术选型,当前主流的 API 网关产品,像 Netflix 开源的 Zuul、Spring Cloud Gateway 等,都可以考虑。

    IDP 服务

    IDP 是 Identity Provider 的简称,主要负责 OAuth 2.0 授权协议处理,OAuth 2.0 和 JWT 令牌颁发和管理,以及用户认证等功能。IDP 使用后台的 Login-Service 进行用户认证。对于 IDP 的技术选型,当前主流的 Spring Security OAuth,或者 RedHat 开源的 KeyCloak,都可以考虑。其中,Spring Security OAuth 是一个 OAuth 2.0 的开发框架,适合企业定制。KeyCloak 则是一个开箱即用的 OAuth 2.0/OIDC 产品。

    BFF 层

    BFF 是 Backend for Frontend 的简称,主要实现对后台领域服务的聚合(Aggregation,有点类似数据库的 Join)功能,同时为不同的前端体验(PC/Mobile/ 开放平台等)提供更友好的 API 和数据格式。

    BFF 中可以包含一些业务逻辑,甚至还可以有自己的数据库存储。通常,BFF 要调用两个或两个以上的领域服务,甚至还可能调用其它的 BFF(当然一般并不建议这样调用,因为这样会让调用关系变得错综复杂,无法理解)。

    如果 BFF 需要获取调用用户或者 OAuth 2.0 Scope 相关信息,它可以从传递过来的 JWT 令牌中直接获取。

    BFF 服务可以用 Node.js 开发,也可以用 Java/Spring 等框架开发。

    领域服务层

    领域服务层在整个微服务架构的底层。这些服务包含业务逻辑,通常有自己独立的数据库存储,还可以根据需要调用外部的服务。根据微服务分层原则,领域服务禁止调用其它的领域服务,更不允许反向调用 BFF 服务。这样做是为了保持微服务职责单一(Single Responsibility)和有界上下文(Bounded Context),避免复杂的领域依赖。领域服务是独立的开发、测试和发布单位。在电商领域,常见的领域服务有用户服务、商品服务、订单服务和支付服务等。和 BFF 一样,如果领域服务需要获取调用用户或者 OAuth 2.0 Scope 相关信息,它可以从传递过来的 JWT 令牌中直接获取。可以看到,领域服务和 BFF 服务都是无状态的,它们本身并不存储用户状态,而是通过传递过来的 JWT 数据获取用户信息。所以在整个架构中,微服务都是无状态、可以按需水平扩展的,状态要么存在用户端(浏览器或者手机 App 中),要么存在集中的数据库中。

    OAuth 2.0/JWT 如何与微服务进行集成?

    以上,就是 ACME 公司的整个微服务架构的层次了。这个分层架构,对于大部分的互联网业务系统场景都适用。因此,如果你是一家企业的架构师,需要设计一套微服务架构,完全可以参考它来设计。接下来,我再演示几个典型的应用认证场景,以及相应的服务调用流程,来帮助你理解 OAuth 2.0/JWT 是如何和微服务进行集成的。

    场景 1:第一方 Web 应用 + 资源拥有者凭据模式

    这个场景是用户访问 ACME 公司自己的电商网站,假设这个电商网站是用 Spring MVC 开发的。考虑到这是一个第一方场景(也就是公司自己开发的网站应用),我们可以选 OAuth 2.0 的资源拥有者凭据许可(Resource Owner Password Credentials Grant),也可以选更安全的授权码许可(Authorization Code Grant)。因为这里没有第三方的概念,所以我们就选相对简单的资源拥有者凭据许可。下面是一个认证授权流程样例。注意,这个只是突出了关键步骤,实际生产的话,还有很多需要完善和优化的地方。另外,为描述简单,这里假定一个成功流程。

    img

    在上面的图中,用户对应 OAuth 2.0 中的资源拥有者,ACME IDP 对应 OAuth 2.0 中的授权服务。另外,前面架构图中的后台微服务(包括 BFF 和基础领域服务),对应 OAuth 2.0 中的受保护资源。下面是流程说明:

    1. 用户通过浏览器访问 ACME 公司的电商网站,点击登录链接。
    2. Web 应用返回登录界面(这个登录页可以是网站自己定制开发)。
    3. 用户输入用户名、密码进行认证。
    4. Web 应用将用户名、密码,通过网关转发到 IDP 的令牌获取端点(POST /oauth2/token,grant_type=password)。
    5. IDP 通过 Login Service 对用户进行认证。
    6. IDP 认证通过,返回有效访问令牌(根据需要也可以返回刷新令牌)。
    7. Web 应用接收到访问令牌,创建用户 Session,并将 OAuth 2.0 令牌保存其中,然后返回登录成功到用户端。
    8. 用户浏览器中记录 Session Cookie,登录成功。

    那接下来,我们再来看看认证授权之后的服务调用流程。同样,这里也只是突出了关键步骤,并假定是一个成功流程。

    img

    1. 用户登录后,在网站上点击查看自己的购物历史记录。
    2. Web 应用通过网关调用后台 API(查询用户的购物历史记录),请求 HTTP header 中带上 OAuth 2.0 令牌(来自用户 Session)。
    3. 网关截取 OAuth 2.0 令牌,去 IDP 进行校验。
    4. IDP 校验令牌通过,再通过令牌查询用户和 Scope 信息,构建 JWT 令牌,返回。
    5. 网关获得 JWT 令牌,校验 Scope 是否有权限调用 API,如果有就转发到后台 API 进行调用。
    6. 后台 BFF(或者领域服务)通过传递过来的 JWT 获取用户信息,根据用户 ID 查询购物历史记录,返回。
    7. Web 应用获得用户的购物历史数据,可以根据需要缓存在 Session 中,再返回用户端。
    8. 购物历史数据返回到用户浏览器端。

    注意,这个服务调用流程,也可以应用在其他场景中,比如我们接下来要学习的“第一方移动应用 + 授权码许可模式”和“第三方 Web 应用 + 授权码许可模式”。基本上只要你理解了这个流程原理,就可以根据实际场景灵活套用。

    场景 2:第一方移动应用 + 授权码许可模式

    第二个场景是用户通过手机访问 ACME 公司自己的电商 App。这是第一方的原生应用(Native App)场景,通常考虑选用 OAuth 2.0 的用户名密码模式,但是并不安全(参考MICRO-SERVICES ARCHITECTURE WITH OAUTH2 AND JWT – PART 3 – IDP的 Security Considerationopen in new window 部分),所以业界建议采用授权码模式,而且是要支持PKCE扩展的授权码模式。那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假定是一个成功流程。

    img

    1. 用户访问电商 App,点击登录。
    2. App 生成 PKCE 相关的 code verifier + challenge。
    3. App 以内嵌方式启动手机浏览器,访问 IDP 的统一认证页 (GET /authorize),请求带上 PKCE 的 code challenge 相关参数。
    4. IDP 返回统一认证页。
    5. 用户认证和授权。
    6. IDP 通过 Login Service 对用户进行认证。
    7. IDP 返回授权码到 App 浏览器。
    8. App 截取浏览器带回的授权码,将授权码 +PKCE code verifer,通过网关转发到 IDP 的令牌获取端点(POST /oauth2/token, grant_type=authorization-code)。
    9. IDP 校验 PKCE 和授权码,校验通过则返回有效访问令牌。
    10. App 获取令牌,本地存储,登录成功。

    之后,App 如果需要和后台交互,可直接通过网关调用后台微服务,请求 HTTP header 中带上 OAuth 2.0 访问令牌即可。后续的服务调用流程,和“第一方应用 + 资源拥有者凭据模式”类似。

    场景 3:第三方 Web 应用 + 授权码模式

    第三个场景是某第三方合作厂商开发了一个 Web 网站,要访问 ACME 公司的电商开放平台 API。这是一个第三方 Web 应用场景,通常选用 OAuth 2.0 的授权码许可模式。那接下来,我们来看看这个认证授权的流程。同样,这里只是突出了关键步骤,并假设是一个成功流程。

    img

    1. 用户访问这个第三方 Web 应用,点击登录链接。
    2. Web 应用后台向 ACME 公司的 IDP 服务发送申请授权码请求(GET /authorize)。
    3. 用户被重定向到 ACME 公司的 IDP 统一登录页面。
    4. 用户进行认证和授权。
    5. IDP 通过 Login Service 对用户进行认证。
    6. 认证和授权通过,IDP 返回授权码。
    7. Web 应用获得授权码,再向 IDP 服务的令牌获取端点发起请求(POST /oauth2/token, grant_type=authorization-code)。
    8. IDP 校验授权码,校验通过则返回有效 OAuth 2.0 令牌(根据需要也可以返回刷新令牌)。
    9. Web 应用创建用户 Session,将 OAuth 2.0 令牌保存在 Session 中,然后返回登录成功到用户端。
    10. 用户浏览器中记录 Session Cookie,登录成功。

    之后,第三方 Web 应用如果需要和 ACME 电商平台交互,可直接通过网关调用微服务,请求 HTTP header 中带上 OAuth 2.0 访问令牌即可。后续的服务调用流程,和前面的“第一方应用 + 资源拥有者凭据模式”类似。

    ==额外说明==

    除了上面的三个主要场景和流程,我还要和你分享 6 点。这 6 点是对上面基本流程的补充,也是企业级的 OAuth 2.0 应用要额外考虑的。

    ==第一点是,IDP 的 API 要支持从 OAuth 2.0 访问令牌到 JWT 令牌的互转。== 今天我们提到的集成架构采用 OAuth 2.0 访问令牌 + JWT 令牌的混合模式,中间需要实现 OAuth 2.0 访问令牌到 JWT 令牌的互转。这个互转 API 并非 OAuth 2.0 的标准,有些 IDP 产品(比方 Spring Security OAuth)可能并不支持,因此需要用户定制扩展。

    ==第二点是,关于单页 SPA 应用场景。== 关于单页 SPA 应用场景,简单做法是采用隐式许可,但是这个模式是 OAuth 2.0 中比较不安全的,所以一般不建议采用。对于纯单页 SPA 应用,业界推荐的做法是:

    • 如果浏览器支持 Web Crypto for PKCE,则可以考虑使用类似“第一方移动应用”场景下的授权码许可 +PKCE 扩展流程;
    • 否则,考虑 SPA+ 传统 Web 混合(hybrid)模式,前端页面可以住在客户浏览器端中,但登录认证还是由后台 Web 站点配合实现,走类似“第一方 Web 应用”场景的资源拥有者凭据模式,或者“第三方 Web 应用”场景下的授权码许可模式。

    ==第三点是,关于 SSO 单点登录场景。== 为了简化描述,上面的流程没有考虑 SSO 单点登录场景。如果要支持 Web SSO,那么各种应用场景都必须通过浏览器 +IDP 登录页集中登录,并且 IDP 要支持 Session,用于维护登录态。如果 IDP 以集群方式部署的话,还要考虑粘性 Sticky Session 或者集中式 Session。

    这样,当用户通过一个 Web 应用登录后,后续如果再用其它 Web 应用登录的话,只要 IDP 上的 Session 还存在,那么这个登录就可以自动完成,相当于单点登录。

    当然,如果要支持 SSO,IDP 的 Session Cookie 要种在 Web 应用的根域上,也就是说不同 Web 应用的根域必须相同,否则会有跨域问题。

    ==第四点是关于 IDP 和网关的部署方式。== 前面的几张架构图中,IDP 虽然躲在网关后面,但实际上 IDP 可以直接通过 Nginx 对外暴露,不经过网关。或者,IDP 的登录授权页面,可以通过 Nginx 直接暴露,API 接口则走网关。

    ==第五点是关于刷新令牌。== 为了简化描述,上面的流程没有详细说明刷新令牌的集成方式。企业根据场景需要,可以启用刷新令牌,来延长用户的登录时间,具体的集成方式需要考虑安全性的需求。

    ==第六点是关于 Web Session。== 为了简化描述,在上面的流程中,Web 应用登录成功后假设启用 Web Session,也就是服务器端 Session。在实际场景中,Web Session 并非唯一选择,也可以采用简单的客户端 Session 方式,也称无状态 Session,也就是在客户端浏览器 Cookie 中保存 OAuth 2.0 访问令牌。

    小结

    好了,以上就是今天的主要内容了。今天,我和你分享了如何将行业标准的 OAuth 2.0/JWT 和微服务集成起来,你需要记住以下四点。

    第一,目前主流的微服务架构大致可以分为 5 层,分别是:Nginx 流量接入层 ->Web 应用层 ->API 网关层 ->BFF 聚合层 -> 领域服务层。这个架构可以住在云原生的 Kubernetes 环境中,也可以住在传统数据中心里头。

    第二,API 网关是微服务调用的入口,承担重要的安全认证和鉴权功能。主要的安全操作包括:一,通过 IDP 校验 OAuth 2.0 访问令牌,并获取带用户和权限信息的 JWT 令牌;二,基于 OAuth 2.0 的 Scope 对 API 调用进行鉴权。

    第三,在微服务架构体系下,通常需要一个集中的 IDP 服务,它相当于一个 Authentication & Authorization as a Service 角色,负责令牌颁发 / 校验 / 管理,还有用户认证。

    第四,在今天这一讲提出的架构中,Web 应用层(网关之前)的安全机制主要基于 OAuth 2.0 访问令牌实现(它是一种透明令牌或者称引用令牌),微服务层(网关之后)的安全机制主要基于 JWT 令牌实现(它是一种不透明的自包含令牌)。网关层在中间实现两种令牌的转换。这是一种 OAuth 2.0 访问令牌 +JWT 令牌的混合模式。

    之所以这样设计,是因为 Web 层靠近用户端,如果采用 JWT 令牌,会暴露用户信息,有一定的安全风险,所以采用 OAuth 2.0 访问令牌,它是一个无意义随机字符串。而在网关之后,安全风险相对低,同时很多服务需要用户信息,所以采用自包含用户信息的 JWT 令牌更合适。当然,如果企业内网没有特别的安全考量,也可以直接传递完全透明的用户信息(例如使用 JSON 格式)。

    思考题

    1. 除了今天我们讲到的 OAuth 2.0 访问令牌 +JWT 令牌的混合模式,实践中也可以全程采用 OAuth 2.0 访问令牌,或者全程采用 JWT 令牌。对比混合模式,如果全程采用 OAuth 2.0 访问令牌,或者全程采用 JWT 令牌,你觉得有哪些利弊呢?
    2. 你可以说说自己对基于传统 Web 应用的认证授权机制的理解吗?并对比今天讲到的现代微服务的认证授权机制,你可以说说它们之间的本质差异和相似点吗?
    + diff --git a/other/oauth2/13.html b/other/oauth2/13.html index 53f147e125..6736ebf075 100644 --- a/other/oauth2/13.html +++ b/other/oauth2/13.html @@ -30,11 +30,11 @@ 13 | 各大开放平台是如何使用OAuth 2.0的? | ChenSino - - + + -
    跳至主要內容

    13 | 各大开放平台是如何使用OAuth 2.0的?

    ChenSino大约 12 分钟

    在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就

    到这里,你会发现“开放平台的官方文档”会是一个关键点。不过呢,当你去各大开放平台上面看这些文

    其中的原因也很简单,那就是开放平台为了让已经具备 OAuth 2.0 知识的研发人员去快速地对接平台上面的业务,把各类对接流程做了分类归档。比如,你会发现微信开放平台上有使用授权码获取授权信息的文档,也有获取令牌的文档,但并没有一份整体的、能够串起来的文档说明。从我的角度来看,这其实也就间接提高了使用门槛,因为如果你不懂 OAuth 2.0,基本是

    那么,今天我就借着这个点,和你说说以京东、微信、支付宝、美团为代表的各大开放平台是如何使用 OAuth 2.0 的。理解了这个问题,你以后再对接一个开放平台、再阅读一份官方对接文档时,就更能明白它们的底层逻辑了。

    在正式介绍各大开放平台的使用细节之前,我们先来看看大厂的开放平台全局体系。据我观察,各个开放平台基本的系统结构和授权系统在中间的交互流程,大同小异,都是通过授权服务来授权,通过网关来鉴权。所以接下来,我就以京东商家开放平台为例,来和你说说开放平台的体系到底是什么样子的。

    开放平台体系是什么样子的?

    我们首先来看一下京东商家开放平台全局体系的结构,如下图所示。

    图1 京东商家开放平台体系结构示意图

    我们可以把这个架构体系分为三部分来看:

    1. 第三方软件,一般是指第三方开发者或者 ISV 通过对接开放平台来实现的应用软件,比如小兔打单软件。
    2. 京东商家开放平台,包含 API 网关服务、OAuth 2.0 授权服务和第三方软件开发者中心服务。其中,API 网关服务和 OAuth 2.0 授权服务,是开放平台的“两条腿”;第三方软件开发者中心服务,是为开发者提供管理第三方软件应用基本信息的服务,比如 app_id、app_secret 等信息。
    3. 京东内部的各个微服务,比如订单服务、商品服务等。这些微服务,就是我们之前提到的受保护资源服务。

    从图中我们还可以看到这个体系整体的调用关系是:第三方软件通过 HTTP 协议请求到开放平台,更具体地说是开放平台的 API 网关服务,然后由 API 网关通过内部的 RPC 调用到各个微服务。接下来,我们再以用户小明使用小兔打单软件为例,来看看这些系统角色之间具体又是怎样交互的?

    图2 开放平台体系交互示意图

    到这里,我们可以发现,在开放平台体系中各个系统角色间的交互可以归结为:

    1. 当用户小明访问小兔软件的时候,小兔会首先向开放平台的 OAuth 2.0 授权服务去请求访问令牌,接着小兔拿着访问令牌去请求 API 网关服务;
    2. 在 API 网关服务中,会做最基本的两种校验,一种是访问令牌的合法性校验,比如访问令牌是否过期的校验,另一种是小兔打单软件的基本信息的合法性校验,比如 app_id 和 app_secret 的校验;
    3. 都校验成功之后,API 网关服务会发起最终的数据请求。

    这里需要说明的是,在第 5 讲中我们提到,验证访问令牌或者第三方软件应用信息的时候,都是在受保护资源服务中去做的。当有了 API 网关这一层的时候,这些校验工作就会都落到了 API 网关的身上,因为我们不能让很多个受保护资源服务做同样的事情。

    我们理解了京东商家开放平台的体系结构后,可以小结下了。依靠开放平台提供的能力,可以说开放平台、用户和开发者实现了三赢:小明因为使用小兔提高了打单效率;小兔的开发者因为小明的订购服务获得了收益;而通过开放出去的 API 让小兔帮助小明能够极快地处理 C 端用户的订单,京东提高了用户的使用体验。

    但同时呢,开放也是一把双刃剑。理想状态下,平台、开发者、用户可以实现三赢,但正如我们在第 8 讲第 10 讲中提到的,安全的问题绝不容忽视,而用户的信息安全又是重中之重。接下来,我就和你分享一个,开放平台体系是如何解决访问令牌安全问题的案例。

    我们已经知道,用户给第三方软件授权之后,授权服务就会生成一个访问令牌,而且这个访问令牌是跟用户关联的。比如,小明给小兔打单软件进行了授权,那么此时访问令牌的粒度就是:小兔打单软件 + 小明。

    我们还知道了,小兔打单软件可以拿着这个访问令牌去代表小明访问小明的数据;如果访问令牌过期了,小兔打单软件还可以继续使用刷新令牌来访问,直到刷新令牌也过期了。

    现在问题来了,如果小明注销了账号,或者修改了自己的密码,那他之前为其它第三方软件进行授权的访问令牌就应该立即失效。否则,在刷新令牌过期之前,第三方软件可以一直拿着之前的访问令牌去请求数据。这显然不合理。

    所以在这种情况下,授权服务就要通过 MQ(消息队列)接收用户的注销和修改密码这两类消息,然后对访问令牌进行清理。

    图3 访问令牌的清理

    其实,这个案例中解决访问令牌安全问题的方式,不仅仅适用于开放平台,还可以为你在企业内构建自己的 OAuth 2.0 授权体系结构时提供借鉴。

    以上就是开放平台整体的结构,以及其中需要重点关注的用户访问令牌的安全性问题了。我们作为第三方软件开发者,在对接到这些开放平台或者浏览它们的网站时,几乎都能看到类似这样的一句话:“所有接口都需要接入 OAuth 授权,经过用户确认授权后才可以调用”,这正是 OAuth 2.0 的根本性作用。

    理解了开放平台的脉络之后,接下来,就让我们通过一组图看一看开放平台是如何使用 OAuth 2.0 授权流程的吧。

    各大开放平台授权流程

    我们以微信open in new window支付宝open in new window美团open in new window为例,看看它们在开放授权上是如何使用 OAuth 2.0 的。我们首先看一下官方的授权流程图:

    图4 微信开放平台授权流程图

    图5 支付宝开放平台授权流程图

    图6 美团开放平台授权流程图

    我们可以在这三张授权流程图中看到,都有和授权码 code 相关的文字。这就说明,它们都建议开发者首选授权码流程。所以,你现在更能明白我为啥在这门课里要花这么多篇幅,来和你讲授权码许可相关的内容了吧。

    在这一讲最开始我也提到了,我们作为开发者在对接开放平台的时候,最关心的就是它们提供的官方对接文档了。而这些文档里面,最让人头疼就是那些通信过程中需要传递的参数了。下面我会带着你从我的角度,以京东商家开放平台为例,给你串下这些参数背后的含义,以及关键点。这样你在做具体接入操作的时候,就可以举重若轻了。

    授权码流程中的参数说明

    概括来讲,在京东商家开放平台的授权服务这一侧,提供服务的就是两个端点:负责生成授权码的授权端点以及负责颁发访问令牌的令牌端点。整个授权过程中,虽然看着有很多参数,但你可以围绕这两条线,来对它们做归类。

    接下来,我们继续以小兔打单软件为例,来看一下它在对接京东商家开放平台的时候都用到了哪些参数。

    小明在使用小兔打单软件的时候,首先被小兔通过重定向的方式引导到京东商家开放平台的授权服务上,其实就是引导到了授权服务的授权端点上。这个重定向的过程中用到的参数如下:

    图7 重定向过程用到的参数

    这里需要强调的是,对于 state 参数,现在官方都是“推荐”使用。我们在第 8 讲中说过,OAuth 2.0 官方建议的避免 CSRF 攻击的方式,就是使用 state 参数。所以安全起见,你还是应该使用。

    接着,京东商家开放平台授权服务的授权端点,会向小兔软件做出响应。这个响应的过程用到的基本参数,如下:

    图8 授权端点响应小兔软件用到的参数

    对于授权码 code 的值,一般建议的最长生命周期是 10 分钟。另外,小兔打单软件只能被允许使用一次该授权码的值,如果使用一次之后还用同样的授权码值来请求,授权服务必须拒绝。

    对于这次的 state 值,授权服务每次都是必须要返回给小兔打单软件的。无论小兔打单软件在起初的时候有没有发送该值,都必须返回回去,如果没有就返回空。这样当小兔打单软件日后升级增加该值的时候,京东商家开放平台就不需要改动任何代码逻辑了。

    在拿到授权码 code 的值之后,接下来就是小兔打单软件向京东商家开放平台的授权服务的令牌端点发起请求,申请访问令牌。这个过程中需要传递的基本参数,如下:

    图9 申请访问令牌需要传递的基本参数

    在授权服务接收到小兔打单软件申请访问令牌的请求后,像授权端点一样,令牌端点也需要向小兔打单软件做出响应。这个过程涉及到的基本参数,如下:

    图10 令牌端点响应小兔软件涉及的参数

    对于这里返回的 scope 值,我要强调下,其实就是小兔软件被允许的实际的权限范围,因为小明有可能给小兔软件授予了小于它在开放平台注册时申请的权限范围。比如,小兔打单软件申请了查询历史订单、查询当天订单两个 API 的权限,但小明可能只给小兔授权了查询当天订单 API 的权限。

    总结

    好了,这一讲就要结束了。我们一起学习了开放平台体系的整体结构和授权流程,以及第三方软件开发者关心的对接开放平台的通信流程中需要传递的参数。现在,我希望你能记住以下三点内容。

    1. 当有多个受保护资源服务的时候,基本的鉴权工作,包括访问令牌的验证、第三方软件应用信息的验证都应该抽出一个 API 网关层,并把这些基本的工作放到这个 API 网关层。
    2. 各大开放平台都是推荐使用授权码许可流程,无论是网页版的 Web 应用程序,还是移动应用程序。
    3. 对于第三方软件开发者重点关注的参数,可以从授权服务的授权端点和令牌端点来区分,授权端点重点是授权码请求和响应的处理,令牌端点重点是访问令牌请求和响应的处理。
    - +
    跳至主要內容

    13 | 各大开放平台是如何使用OAuth 2.0的?

    ChenSino大约 12 分钟

    在咱们这门课中,我提到了很多次“开放平台”,不难理解,它的作用就是企业把自己的业务能力主要以开放 API 的形式,赋能给外部开发者。而作为第三方开发者或者 ISV(独立软件供应商)在接入这些开放平台的时候,我们最应该关心的就是它们的官方文档,关注接入的流程是怎样的、对应的 API 是什么、每个 API 都传递哪些参数,也就

    到这里,你会发现“开放平台的官方文档”会是一个关键点。不过呢,当你去各大开放平台上面看这些文

    其中的原因也很简单,那就是开放平台为了让已经具备 OAuth 2.0 知识的研发人员去快速地对接平台上面的业务,把各类对接流程做了分类归档。比如,你会发现微信开放平台上有使用授权码获取授权信息的文档,也有获取令牌的文档,但并没有一份整体的、能够串起来的文档说明。从我的角度来看,这其实也就间接提高了使用门槛,因为如果你不懂 OAuth 2.0,基本是

    那么,今天我就借着这个点,和你说说以京东、微信、支付宝、美团为代表的各大开放平台是如何使用 OAuth 2.0 的。理解了这个问题,你以后再对接一个开放平台、再阅读一份官方对接文档时,就更能明白它们的底层逻辑了。

    在正式介绍各大开放平台的使用细节之前,我们先来看看大厂的开放平台全局体系。据我观察,各个开放平台基本的系统结构和授权系统在中间的交互流程,大同小异,都是通过授权服务来授权,通过网关来鉴权。所以接下来,我就以京东商家开放平台为例,来和你说说开放平台的体系到底是什么样子的。

    开放平台体系是什么样子的?

    我们首先来看一下京东商家开放平台全局体系的结构,如下图所示。

    图1 京东商家开放平台体系结构示意图

    我们可以把这个架构体系分为三部分来看:

    1. 第三方软件,一般是指第三方开发者或者 ISV 通过对接开放平台来实现的应用软件,比如小兔打单软件。
    2. 京东商家开放平台,包含 API 网关服务、OAuth 2.0 授权服务和第三方软件开发者中心服务。其中,API 网关服务和 OAuth 2.0 授权服务,是开放平台的“两条腿”;第三方软件开发者中心服务,是为开发者提供管理第三方软件应用基本信息的服务,比如 app_id、app_secret 等信息。
    3. 京东内部的各个微服务,比如订单服务、商品服务等。这些微服务,就是我们之前提到的受保护资源服务。

    从图中我们还可以看到这个体系整体的调用关系是:第三方软件通过 HTTP 协议请求到开放平台,更具体地说是开放平台的 API 网关服务,然后由 API 网关通过内部的 RPC 调用到各个微服务。接下来,我们再以用户小明使用小兔打单软件为例,来看看这些系统角色之间具体又是怎样交互的?

    图2 开放平台体系交互示意图

    到这里,我们可以发现,在开放平台体系中各个系统角色间的交互可以归结为:

    1. 当用户小明访问小兔软件的时候,小兔会首先向开放平台的 OAuth 2.0 授权服务去请求访问令牌,接着小兔拿着访问令牌去请求 API 网关服务;
    2. 在 API 网关服务中,会做最基本的两种校验,一种是访问令牌的合法性校验,比如访问令牌是否过期的校验,另一种是小兔打单软件的基本信息的合法性校验,比如 app_id 和 app_secret 的校验;
    3. 都校验成功之后,API 网关服务会发起最终的数据请求。

    这里需要说明的是,在第 5 讲中我们提到,验证访问令牌或者第三方软件应用信息的时候,都是在受保护资源服务中去做的。当有了 API 网关这一层的时候,这些校验工作就会都落到了 API 网关的身上,因为我们不能让很多个受保护资源服务做同样的事情。

    我们理解了京东商家开放平台的体系结构后,可以小结下了。依靠开放平台提供的能力,可以说开放平台、用户和开发者实现了三赢:小明因为使用小兔提高了打单效率;小兔的开发者因为小明的订购服务获得了收益;而通过开放出去的 API 让小兔帮助小明能够极快地处理 C 端用户的订单,京东提高了用户的使用体验。

    但同时呢,开放也是一把双刃剑。理想状态下,平台、开发者、用户可以实现三赢,但正如我们在第 8 讲第 10 讲中提到的,安全的问题绝不容忽视,而用户的信息安全又是重中之重。接下来,我就和你分享一个,开放平台体系是如何解决访问令牌安全问题的案例。

    我们已经知道,用户给第三方软件授权之后,授权服务就会生成一个访问令牌,而且这个访问令牌是跟用户关联的。比如,小明给小兔打单软件进行了授权,那么此时访问令牌的粒度就是:小兔打单软件 + 小明。

    我们还知道了,小兔打单软件可以拿着这个访问令牌去代表小明访问小明的数据;如果访问令牌过期了,小兔打单软件还可以继续使用刷新令牌来访问,直到刷新令牌也过期了。

    现在问题来了,如果小明注销了账号,或者修改了自己的密码,那他之前为其它第三方软件进行授权的访问令牌就应该立即失效。否则,在刷新令牌过期之前,第三方软件可以一直拿着之前的访问令牌去请求数据。这显然不合理。

    所以在这种情况下,授权服务就要通过 MQ(消息队列)接收用户的注销和修改密码这两类消息,然后对访问令牌进行清理。

    图3 访问令牌的清理

    其实,这个案例中解决访问令牌安全问题的方式,不仅仅适用于开放平台,还可以为你在企业内构建自己的 OAuth 2.0 授权体系结构时提供借鉴。

    以上就是开放平台整体的结构,以及其中需要重点关注的用户访问令牌的安全性问题了。我们作为第三方软件开发者,在对接到这些开放平台或者浏览它们的网站时,几乎都能看到类似这样的一句话:“所有接口都需要接入 OAuth 授权,经过用户确认授权后才可以调用”,这正是 OAuth 2.0 的根本性作用。

    理解了开放平台的脉络之后,接下来,就让我们通过一组图看一看开放平台是如何使用 OAuth 2.0 授权流程的吧。

    各大开放平台授权流程

    我们以微信open in new window支付宝open in new window美团open in new window为例,看看它们在开放授权上是如何使用 OAuth 2.0 的。我们首先看一下官方的授权流程图:

    图4 微信开放平台授权流程图

    图5 支付宝开放平台授权流程图

    图6 美团开放平台授权流程图

    我们可以在这三张授权流程图中看到,都有和授权码 code 相关的文字。这就说明,它们都建议开发者首选授权码流程。所以,你现在更能明白我为啥在这门课里要花这么多篇幅,来和你讲授权码许可相关的内容了吧。

    在这一讲最开始我也提到了,我们作为开发者在对接开放平台的时候,最关心的就是它们提供的官方对接文档了。而这些文档里面,最让人头疼就是那些通信过程中需要传递的参数了。下面我会带着你从我的角度,以京东商家开放平台为例,给你串下这些参数背后的含义,以及关键点。这样你在做具体接入操作的时候,就可以举重若轻了。

    授权码流程中的参数说明

    概括来讲,在京东商家开放平台的授权服务这一侧,提供服务的就是两个端点:负责生成授权码的授权端点以及负责颁发访问令牌的令牌端点。整个授权过程中,虽然看着有很多参数,但你可以围绕这两条线,来对它们做归类。

    接下来,我们继续以小兔打单软件为例,来看一下它在对接京东商家开放平台的时候都用到了哪些参数。

    小明在使用小兔打单软件的时候,首先被小兔通过重定向的方式引导到京东商家开放平台的授权服务上,其实就是引导到了授权服务的授权端点上。这个重定向的过程中用到的参数如下:

    图7 重定向过程用到的参数

    这里需要强调的是,对于 state 参数,现在官方都是“推荐”使用。我们在第 8 讲中说过,OAuth 2.0 官方建议的避免 CSRF 攻击的方式,就是使用 state 参数。所以安全起见,你还是应该使用。

    接着,京东商家开放平台授权服务的授权端点,会向小兔软件做出响应。这个响应的过程用到的基本参数,如下:

    图8 授权端点响应小兔软件用到的参数

    对于授权码 code 的值,一般建议的最长生命周期是 10 分钟。另外,小兔打单软件只能被允许使用一次该授权码的值,如果使用一次之后还用同样的授权码值来请求,授权服务必须拒绝。

    对于这次的 state 值,授权服务每次都是必须要返回给小兔打单软件的。无论小兔打单软件在起初的时候有没有发送该值,都必须返回回去,如果没有就返回空。这样当小兔打单软件日后升级增加该值的时候,京东商家开放平台就不需要改动任何代码逻辑了。

    在拿到授权码 code 的值之后,接下来就是小兔打单软件向京东商家开放平台的授权服务的令牌端点发起请求,申请访问令牌。这个过程中需要传递的基本参数,如下:

    图9 申请访问令牌需要传递的基本参数

    在授权服务接收到小兔打单软件申请访问令牌的请求后,像授权端点一样,令牌端点也需要向小兔打单软件做出响应。这个过程涉及到的基本参数,如下:

    图10 令牌端点响应小兔软件涉及的参数

    对于这里返回的 scope 值,我要强调下,其实就是小兔软件被允许的实际的权限范围,因为小明有可能给小兔软件授予了小于它在开放平台注册时申请的权限范围。比如,小兔打单软件申请了查询历史订单、查询当天订单两个 API 的权限,但小明可能只给小兔授权了查询当天订单 API 的权限。

    总结

    好了,这一讲就要结束了。我们一起学习了开放平台体系的整体结构和授权流程,以及第三方软件开发者关心的对接开放平台的通信流程中需要传递的参数。现在,我希望你能记住以下三点内容。

    1. 当有多个受保护资源服务的时候,基本的鉴权工作,包括访问令牌的验证、第三方软件应用信息的验证都应该抽出一个 API 网关层,并把这些基本的工作放到这个 API 网关层。
    2. 各大开放平台都是推荐使用授权码许可流程,无论是网页版的 Web 应用程序,还是移动应用程序。
    3. 对于第三方软件开发者重点关注的参数,可以从授权服务的授权端点和令牌端点来区分,授权端点重点是授权码请求和响应的处理,令牌端点重点是访问令牌请求和响应的处理。
    + diff --git a/other/oauth2/14.html b/other/oauth2/14.html index f75a887261..337b4e506f 100644 --- a/other/oauth2/14.html +++ b/other/oauth2/14.html @@ -30,11 +30,11 @@ 14 | 查漏补缺:OAuth 2.0 常见问题答疑 | ChenSino - - + + -
    跳至主要內容

    14 | 查漏补缺:OAuth 2.0 常见问题答疑

    ChenSino大约 10 分钟

    从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题:

    1. 发明 OAuth 的目的到底是什么?

    2. OAuth 2.0 是身份认证协议吗?

    3. 有了刷新令牌,是不是就可以让访问令牌一直有效了?

    4. 使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?

    5. ID 令牌和访问令牌之间有联系吗?

    6. PKCE 协议到底解决的是什么问题?

    接下来,我们就一一看看这些问题吧。

    发明 OAuth 的目的到底是什么?

    OAuth 协议的设计初衷,就是让最终用户也就是资源拥有者(小明),将他们在受保护资源服务器(京东商家开放平台)上的部分权限(查询当天订单)委托给第三方应用(小兔打单软件),使得第三方应用(小兔)能够代表最终用户(小明)执行操作(查询当天订单)。这便是 OAuth 协议设计的目的。在 OAuth 协议中,通过为每个第三方软件和每个用户的组合分别生成对受保护资源具有受限的访问权限的凭据,也就是访问令牌,来代替之前的用户名和密码。而生成访问令牌之前的登录操作,又是在用户跟平台之间进行的,第三方软件根本无从得知用户的任何信息。这样第三方软件的逻辑处理就大大简化了,它今后的动作就变成了请求访问令牌、使用访问令牌、访问受保护资源,同时在第三方软件调用大量 API 的时候,不再传输用户名和密码,从而减少了网络安全的攻击面。从安全的角度来讲,为每个第三方软件和每个用户的组合来生成一个访问令牌的方式,可以减少对平台更多用户造成的危害。因为这样一来,单个第三方软件被攻破而带来的危害,仅仅会让这一个第三方软件的用户受到影响。那么有的同学就要会问了,这样攻击的对象就会转移到授权服务身上。这个想法没错,但保护一个授权服务肯定要比保护成千上万个、由不同研发人员开发的第三方软件容易得多。

    OAuth 2.0 是身份认证协议吗?

    在这门课中,我其实一直在强调,OAuth 2.0 是一种授权协议,“它一心只专注于干好授权这件事儿”,OAuth 2.0 不是身份认证协议。但实际上,我在刚开始学习 OAuth 2.0 的时候,也曾错误地认为它是身份认证协议。因为我当时觉得,有用户参与其中,比如小明在使用小兔打单软件之前,要向授权服务进行登录操作从而进行身份认证 ,那 OAuth 2.0 就应该是一个身份认证协议啊。但是,小明必须登录之后才能进行授权,是一个额外的需求,登录跟授权体系是独立的。虽然登录操作看似“内嵌”在了 OAuth 2.0 的流程中,但生产环境中登录和授权还是两套独立存在的系统。所以说,像这种“内嵌”的身份认证行为,并不是说 OAuth 2.0 自身承担起了身份认证协议的责任。同时,身份认证会告诉第三方软件当前的用户是谁,但实际上 OAuth 2.0 自始至终都没有向第三方软件透露过关于用户的任何信息。这一点,我们在讲发明 OAuth 协议的目的时也提到过。我们可以再想想小兔打单软件的例子,看是不是这样:小兔打单软件永远也不会知道小明的任何信息,它仅仅是请求访问令牌,使用访问令牌并最终调用查询订单的 API。

    有了刷新令牌,是不是就可以让访问令牌一直有效了?

    要回答这个问题,我们先复习下访问令牌和刷新令牌相关的几个知识点吧。

    第一,OAuth 2.0 的核心是授权,授权的核心是令牌,也就是我们说的访问令牌。

    第二,在第 3 讲中我们提到,为了提高用户的体验,OAuth 2.0 提供了刷新令牌的机制,使得访问令牌过期后,第三方软件在无需用户再次授权的情况下,可以重新请求一个访问令牌。

    第三,在使用上,刷新令牌只能用在授权服务上,而访问令牌只能用在受保护资源服务上。

    有了这些知识做基础,我们可以继续分析“有了刷新令牌,是不是就可以让访问令牌一直有效”这个问题了。

    当访问令牌被 “递给” 受保护资源服务的时候,受保护资源服务需要对访问令牌进行验证,还要对访问令牌关联的权限和第三方软件的请求进行权限匹配校验。当访问令牌过期的时候,我们使用刷新令牌请求到的访问令牌,是授权服务重新生成的,而不是延长了原访问令牌的有效期。

    当前的这个刷新令牌被使用之后,授权服务可以自行决定是颁发一个新的刷新令牌,还是仍然给第三方软件返回上一个刷新令牌。安全起见,我们的建议是返回一个新的刷新令牌。这时,你可能就有一个疑问了:第三方软件已经换了一个访问令牌了,刷新令牌又一直存在,那是不是就可以一直使用刷新令牌来获取访问令牌了呢?

    要解决这个疑问,我们要知道的是,刷新令牌也有有效期。尽管生成了新的刷新令牌,但它的有效期不会改变,有效期的时间戳仍然是上一个刷新令牌的。刷新令牌的有效期到了,就不能再继续用它来申请新的访问令牌了。

    使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?

    OAuth 2.0 的使用从来都不应该脱离 HTTPS。因为访问令牌、应用密钥敏感信息要在网络上传输,都离不开 HTTPS 的保护。但是,HTTPS 也只是保证了访问令牌等重要信息在网络传输上的安全。

    在 OAuth 2.0 的规范中,访问令牌对第三方软件是不透明的,从来都不应该被任何第三方软件解析到。由于 JWT 格式的令牌自包含了用户相关的信息,比如用户标识,因此仅仅对它进行签名还不够。要避免第三方软件有机会获取访问令牌所包含的信息,那我们在与第三方软件交互的环境下使用 JWT 格式的令牌时,还要对它进行加密来保障令牌的安全,而不是仅仅依靠 HTTPS。

    ID 令牌和访问令牌之间有联系吗?

    第 9 讲中,我们在用 OAuth 2.0 实现一个 OpenID Connect 身份认证协议的时候,讲到了 ID 令牌。在这一讲的后面,有同学还是不太清楚 ID 令牌和访问令牌是啥关系,当时我就在留言区做了回复。现在,我重新整理了思路再和你解释一下,因为认识到 ID 令牌和访问令牌的联系与区别,对我们利用 OAuth 2.0 搭建一个身份认证协议来说太重要了。

    我们先来总结下 ID 令牌和访问令牌的作用:

    • ID 令牌,也就是 ID_TOKEN,代表的是用户身份令牌,可以说是一个单独的身份认证结果,永远不会像访问令牌那样作为一个参数,去传递给其它外部服务;
    • 访问令牌,也就是 ACCESS_TOKEN,就是一个令牌,是要被第三方软件用来作为凭证,从而代表用户去请求受保护资源服务的。

    你看,这两种令牌是截然不同的。接下来,我们就分析下,它们的区别都体现在哪些方面吧。

    第一,ID 令牌是对访问令牌的补充,而不是要替换访问令牌。之所以采用这样双令牌的方式,就是想让早先存在的访问令牌,可以在 OAuth 2.0 中继续保持对第三方软件的不透明性,而让后来新增的 ID 令牌要能够被解析,目的就是方便应用到身份认证协议中。第

    二,ID 令牌和访问令牌有不同的生命周期,ID 令牌的生命周期相对来说更短些。因为 ID 令牌的作用就是代表一个单独的身份认证结果,它的使命就是用来标识用户的。而这个标识并不是用户名,用户登录的时候用的是用户名而不是这个 ID 令牌,所以如果用户注销或者退出了登录,ID 令牌的生命周期就随之结束了。

    访问令牌可以在用户离开后的很长时间内,继续被第三方软件用来请求受保护资源服务。比如,小明使用了小兔打单软件的批量导出订单功能,如果耗时相对比较长,小明不必一直在场。

    PKCE 协议到底解决的是什么问题?

    我们在第 7 讲中学习 PKCE 协议时,我看到了大家对这个协议的很多留言,有的是自己的思考,有的是问题的进一步讨论。我们要理解 PKCE 协议到底解决了什么问题,就要先看一

    2012 年 10 月 OAuth 2.0 的正式授权协议框架,也就是官方的 RFC 6749 被正式发布,2015 年 9 月增补了 PKCE 协议,也就是官方的 RFC 7636。从时间上来看,从正式发布 OAuth 2.0 授权协议到增补发布了 PKCE 协议,整整间隔了三年,而这三年恰恰是移动应用蓬勃发展的时期。

    同时,在原生的移动客户端应用保存秘钥又存在特殊的安全问题,使用 OAuth 2.0 授权码许可类型的客户端又容易受到授权码窃听的攻击。

    所以,PKCE 被增补发布的背景是,移动应用大力发展,同时原生客户端使用 OAuth 2.0 面临着安全风险。这样我们就能理解了,发布 PKCE 协议的目的,主要就是缓解针对公开客户端的攻击,提高授权码使用的安全性。

    总结

    1. OAuth 协议被发明的目的,就是用令牌代替用户名和密码。
    2. OAuth 2.0 不能被直接用来“从事”身份认证协议的“工作”。虽然 OAuth2.0 的使用要求是在 HTTPS 的环境下,但这并不能解决 JWT 令牌对第三方软件“不透明”的问题,还需要进行加密。
    3. 有了刷新令牌也不能让访问令牌一直有效下去,因为刷新令牌也有有效期。
    4. ID 令牌是对访问令牌的补充,而不是要替代访问令牌。
    5. PKCE 是 OAuth 2.0 的一个增补协议,主要用来缓解授权码被窃听的安全风险。
    - +
    跳至主要內容

    14 | 查漏补缺:OAuth 2.0 常见问题答疑

    ChenSino大约 10 分钟

    从 6 月 29 日这门课上线,到现在已经过去一个多月了。我看到了很多同学的留言,有思考,也有提出的问题。那我首先,在这里要感谢你对咱们这门课的支持、鼓励和反馈。在回复你们的留言时,我也把你们提出的问题记了下来。在梳理今天这期答疑的时候,我又从头到尾看了一遍这些问题,也进一步思考了每个问题背后的元认知,最后我归纳出了 6 个问题:

    1. 发明 OAuth 的目的到底是什么?

    2. OAuth 2.0 是身份认证协议吗?

    3. 有了刷新令牌,是不是就可以让访问令牌一直有效了?

    4. 使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?

    5. ID 令牌和访问令牌之间有联系吗?

    6. PKCE 协议到底解决的是什么问题?

    接下来,我们就一一看看这些问题吧。

    发明 OAuth 的目的到底是什么?

    OAuth 协议的设计初衷,就是让最终用户也就是资源拥有者(小明),将他们在受保护资源服务器(京东商家开放平台)上的部分权限(查询当天订单)委托给第三方应用(小兔打单软件),使得第三方应用(小兔)能够代表最终用户(小明)执行操作(查询当天订单)。这便是 OAuth 协议设计的目的。在 OAuth 协议中,通过为每个第三方软件和每个用户的组合分别生成对受保护资源具有受限的访问权限的凭据,也就是访问令牌,来代替之前的用户名和密码。而生成访问令牌之前的登录操作,又是在用户跟平台之间进行的,第三方软件根本无从得知用户的任何信息。这样第三方软件的逻辑处理就大大简化了,它今后的动作就变成了请求访问令牌、使用访问令牌、访问受保护资源,同时在第三方软件调用大量 API 的时候,不再传输用户名和密码,从而减少了网络安全的攻击面。从安全的角度来讲,为每个第三方软件和每个用户的组合来生成一个访问令牌的方式,可以减少对平台更多用户造成的危害。因为这样一来,单个第三方软件被攻破而带来的危害,仅仅会让这一个第三方软件的用户受到影响。那么有的同学就要会问了,这样攻击的对象就会转移到授权服务身上。这个想法没错,但保护一个授权服务肯定要比保护成千上万个、由不同研发人员开发的第三方软件容易得多。

    OAuth 2.0 是身份认证协议吗?

    在这门课中,我其实一直在强调,OAuth 2.0 是一种授权协议,“它一心只专注于干好授权这件事儿”,OAuth 2.0 不是身份认证协议。但实际上,我在刚开始学习 OAuth 2.0 的时候,也曾错误地认为它是身份认证协议。因为我当时觉得,有用户参与其中,比如小明在使用小兔打单软件之前,要向授权服务进行登录操作从而进行身份认证 ,那 OAuth 2.0 就应该是一个身份认证协议啊。但是,小明必须登录之后才能进行授权,是一个额外的需求,登录跟授权体系是独立的。虽然登录操作看似“内嵌”在了 OAuth 2.0 的流程中,但生产环境中登录和授权还是两套独立存在的系统。所以说,像这种“内嵌”的身份认证行为,并不是说 OAuth 2.0 自身承担起了身份认证协议的责任。同时,身份认证会告诉第三方软件当前的用户是谁,但实际上 OAuth 2.0 自始至终都没有向第三方软件透露过关于用户的任何信息。这一点,我们在讲发明 OAuth 协议的目的时也提到过。我们可以再想想小兔打单软件的例子,看是不是这样:小兔打单软件永远也不会知道小明的任何信息,它仅仅是请求访问令牌,使用访问令牌并最终调用查询订单的 API。

    有了刷新令牌,是不是就可以让访问令牌一直有效了?

    要回答这个问题,我们先复习下访问令牌和刷新令牌相关的几个知识点吧。

    第一,OAuth 2.0 的核心是授权,授权的核心是令牌,也就是我们说的访问令牌。

    第二,在第 3 讲中我们提到,为了提高用户的体验,OAuth 2.0 提供了刷新令牌的机制,使得访问令牌过期后,第三方软件在无需用户再次授权的情况下,可以重新请求一个访问令牌。

    第三,在使用上,刷新令牌只能用在授权服务上,而访问令牌只能用在受保护资源服务上。

    有了这些知识做基础,我们可以继续分析“有了刷新令牌,是不是就可以让访问令牌一直有效”这个问题了。

    当访问令牌被 “递给” 受保护资源服务的时候,受保护资源服务需要对访问令牌进行验证,还要对访问令牌关联的权限和第三方软件的请求进行权限匹配校验。当访问令牌过期的时候,我们使用刷新令牌请求到的访问令牌,是授权服务重新生成的,而不是延长了原访问令牌的有效期。

    当前的这个刷新令牌被使用之后,授权服务可以自行决定是颁发一个新的刷新令牌,还是仍然给第三方软件返回上一个刷新令牌。安全起见,我们的建议是返回一个新的刷新令牌。这时,你可能就有一个疑问了:第三方软件已经换了一个访问令牌了,刷新令牌又一直存在,那是不是就可以一直使用刷新令牌来获取访问令牌了呢?

    要解决这个疑问,我们要知道的是,刷新令牌也有有效期。尽管生成了新的刷新令牌,但它的有效期不会改变,有效期的时间戳仍然是上一个刷新令牌的。刷新令牌的有效期到了,就不能再继续用它来申请新的访问令牌了。

    使用了 HTTPS,是不是就能确保 JWT 格式令牌的数据安全?

    OAuth 2.0 的使用从来都不应该脱离 HTTPS。因为访问令牌、应用密钥敏感信息要在网络上传输,都离不开 HTTPS 的保护。但是,HTTPS 也只是保证了访问令牌等重要信息在网络传输上的安全。

    在 OAuth 2.0 的规范中,访问令牌对第三方软件是不透明的,从来都不应该被任何第三方软件解析到。由于 JWT 格式的令牌自包含了用户相关的信息,比如用户标识,因此仅仅对它进行签名还不够。要避免第三方软件有机会获取访问令牌所包含的信息,那我们在与第三方软件交互的环境下使用 JWT 格式的令牌时,还要对它进行加密来保障令牌的安全,而不是仅仅依靠 HTTPS。

    ID 令牌和访问令牌之间有联系吗?

    第 9 讲中,我们在用 OAuth 2.0 实现一个 OpenID Connect 身份认证协议的时候,讲到了 ID 令牌。在这一讲的后面,有同学还是不太清楚 ID 令牌和访问令牌是啥关系,当时我就在留言区做了回复。现在,我重新整理了思路再和你解释一下,因为认识到 ID 令牌和访问令牌的联系与区别,对我们利用 OAuth 2.0 搭建一个身份认证协议来说太重要了。

    我们先来总结下 ID 令牌和访问令牌的作用:

    • ID 令牌,也就是 ID_TOKEN,代表的是用户身份令牌,可以说是一个单独的身份认证结果,永远不会像访问令牌那样作为一个参数,去传递给其它外部服务;
    • 访问令牌,也就是 ACCESS_TOKEN,就是一个令牌,是要被第三方软件用来作为凭证,从而代表用户去请求受保护资源服务的。

    你看,这两种令牌是截然不同的。接下来,我们就分析下,它们的区别都体现在哪些方面吧。

    第一,ID 令牌是对访问令牌的补充,而不是要替换访问令牌。之所以采用这样双令牌的方式,就是想让早先存在的访问令牌,可以在 OAuth 2.0 中继续保持对第三方软件的不透明性,而让后来新增的 ID 令牌要能够被解析,目的就是方便应用到身份认证协议中。第

    二,ID 令牌和访问令牌有不同的生命周期,ID 令牌的生命周期相对来说更短些。因为 ID 令牌的作用就是代表一个单独的身份认证结果,它的使命就是用来标识用户的。而这个标识并不是用户名,用户登录的时候用的是用户名而不是这个 ID 令牌,所以如果用户注销或者退出了登录,ID 令牌的生命周期就随之结束了。

    访问令牌可以在用户离开后的很长时间内,继续被第三方软件用来请求受保护资源服务。比如,小明使用了小兔打单软件的批量导出订单功能,如果耗时相对比较长,小明不必一直在场。

    PKCE 协议到底解决的是什么问题?

    我们在第 7 讲中学习 PKCE 协议时,我看到了大家对这个协议的很多留言,有的是自己的思考,有的是问题的进一步讨论。我们要理解 PKCE 协议到底解决了什么问题,就要先看一

    2012 年 10 月 OAuth 2.0 的正式授权协议框架,也就是官方的 RFC 6749 被正式发布,2015 年 9 月增补了 PKCE 协议,也就是官方的 RFC 7636。从时间上来看,从正式发布 OAuth 2.0 授权协议到增补发布了 PKCE 协议,整整间隔了三年,而这三年恰恰是移动应用蓬勃发展的时期。

    同时,在原生的移动客户端应用保存秘钥又存在特殊的安全问题,使用 OAuth 2.0 授权码许可类型的客户端又容易受到授权码窃听的攻击。

    所以,PKCE 被增补发布的背景是,移动应用大力发展,同时原生客户端使用 OAuth 2.0 面临着安全风险。这样我们就能理解了,发布 PKCE 协议的目的,主要就是缓解针对公开客户端的攻击,提高授权码使用的安全性。

    总结

    1. OAuth 协议被发明的目的,就是用令牌代替用户名和密码。
    2. OAuth 2.0 不能被直接用来“从事”身份认证协议的“工作”。虽然 OAuth2.0 的使用要求是在 HTTPS 的环境下,但这并不能解决 JWT 令牌对第三方软件“不透明”的问题,还需要进行加密。
    3. 有了刷新令牌也不能让访问令牌一直有效下去,因为刷新令牌也有有效期。
    4. ID 令牌是对访问令牌的补充,而不是要替代访问令牌。
    5. PKCE 是 OAuth 2.0 的一个增补协议,主要用来缓解授权码被窃听的安全风险。
    + diff --git a/other/oauth2/index.html b/other/oauth2/index.html index 6ccd517fce..5c98badf73 100644 --- a/other/oauth2/index.html +++ b/other/oauth2/index.html @@ -30,11 +30,11 @@ 开篇词 | 为什么要学OAuth 2.0? | ChenSino - - + + -
    跳至主要內容

    开篇词 | 为什么要学OAuth 2.0?

    ChenSino大约 8 分钟

    申明

    oauth2相关博客转载于极客时间open in new window

    你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。

    我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,毕竟网关要承载整个开放平台的调用量,同时还要有足够的系统容错能力。

    但随着对开放平台理解的不断深入,我们要想在开放平台支持更多样的业务场景,我才发现网关和授权同样重要,相当于开放平台的 “两条腿”。

    而对于授权 “这条腿”,它不仅要像网关一样要承载访问量,还要同时兼顾业务场景的发展。什么样的业务场景呢?类似的微信登录就是其中之一,越来越多的第三方应用都在向用户提供使用微信登录的解决方案,来减少用户注册的繁琐操作。而这个解决方案的背后原理,也是我们这门课要讲到的OAuth 2.0技术。

    OAuth 2.0是什么?

    那,OAuth 2.0到底是什么呢?我们先从字面上来分析下。OAuth 2.0一词中的 “Auth” 表示 “授权”,字母 “O” 是Open的简称,表示 “开放” ,连在一起就表示 “开放授权”。这也是为什么我们使用OAuth的场景,通常发生在开放平台的环境下。

    看到这里,你可能会说应该还有OAuth 1.0吧。没错,OAuth 2.0之前就是OAuth 1.0。现在,我就来和你说说这两个版本的OAuth有什么区别吧。

    在OAuth 1.0的时候,它有个 “很大的愿望” 就是想用一套授权机制来应对现实中的所有场景,比如Web应用场景、移动App应用场景、官方应用场景等等,但是这些场景并不是完全相同的。比如官方应用场景,你说还需要让用户来授权吗?如果需要,始终使用一套授权机制给用户带来的体验,是好还是坏呢?

    到了OAuth 2.0的时候,就解决了OAuth 1.0 面临的这种“尴尬”。OAuth 2.0 不再局限于一种授权机制,它扩充了授权许可机制类型,有了授权码许可机制、客户端凭据机制、资源拥有者凭据机制和隐式许可机制。这样的OAuth机制就能够很灵活地适应现实中的各种场景,比如移动应用的场景、官方应用的场景,等等。

    此外,OAuth 1.0的弊端还包括安全上的固化攻击等问题,因此OAuth 1.0现在已经是废弃状态了。对于我们来讲,直接使用OAuth 2.0就可以了。

    为什么会有这门课?

    但其实呢,OAuth 2.0并不是一门新的技术,从2007年OAuth 1.0面世,到2011年发布OAuth 2.0草案,互联网上已经有很多关于OAuth的资料了。所以,在我初次接触OAuth 2.0去查阅这些零散的资料时,觉得OAuth 2.0很简单啊,不就是授权吗,看两篇文章就够了啊。

    但是,看似简单的OAuth 2.0却又让我望而却步,在如何使用授权码流程上踌躇不前。比如,在Web应用中到底应该怎么使用授权码流程,移动App中还能使用授权码流程吗?当我带着这些问题尝试到网上搜索资料时,那些不成体系的资料着实也让我走了不少弯路。不知道你是不是也被下面问题困扰着:

    我要开发一个Web应用,当使用OAuth 2.0的时候,担心授权码被拦截,却因为没有较好的解决方法而一筹莫展。 我要开发一款移动App,当使用OAuth 2.0的时候,在确定是否需要Server端上,花费了大把的时间。 后来我看到《OAuth 2 in Action》这本书,如获至宝。它非常系统地讲解了OAuth2.0,让我对这个协议框架有了更全面、深刻的认识。也正是这本书给了我足够的勇气,让我能够把自己这些年在开放平台的工作中,所掌握的OAuth知识体系梳理一遍。也是在这一刻,我才意识到只要有了方向,就有了厚度。

    当我开始试着整理出自己这些年掌握的OAuth 2.0相关技术、实践,并计划输出的时候,我真真切切地发现,OAuth 2.0是讲授权没错,但要用对、用好这个协议,绝不是短短两篇文章就能讲清楚的。这也是我做这门课的初衷。

    我要结合自己在开放平台上的工作经验以及对OAuth 2.0的理解,一次性地给你说透授权这件事儿。同时,我还查阅了诸多资料,包括OAuth 2协议规范open in new windowOpenID Connect explainedopen in new window等,力求给你带来最贴近本质的OAuth 2.0知识的讲解。

    这门课是怎么设计的?

    在这门课程里,我会分为基础篇和进阶篇两大模块,每个模块都会安排一些实践内容,和你讲清楚OAuth 2.0。接下来,我就和你解释下为什么要这么安排。

    ==第一部分是基础篇,就是你必须要掌握的OAuth2.0的基础知识。== 在这一模块中,我会和你细致地讲解授权码许可(Authorization Code)类型的流程,包括OAuth 2.0内部组件之间的通信方式,以及授权服务、客户端(第三方软件)、受保护资源服务这三个组件的原理。

    在此基础上,我还会为你讲解其他三种常见许可类型,分别是资源拥有者凭据许可(Resource Owner Password Credentials)、隐式许可(Implicit)、客户端凭据许可(Client Credentials)的原理,以及如何选择适合自己实际场景的授权类型。这样一来,你就能掌握整个OAuth 2.0中所有许可类型的运转机制了,并且能够从容地在实际工作环境中使用它们。

    为了能够把你带入到OAuth 2.0的场景中,方便你理解这些概念、流程,我在讲述这些基础内容的时候,会用一个小明使用第三方“小兔打单软件”来打印自己在京东店铺的订单数据的例子,来贯穿始终。

    我可以告诉你的是,学完基础篇的内容,你就可以把OAuth 2.0用到实际的工作场景了。

    ==第二部分进阶篇的内容,我会侧重讲一些OAuth 2.0 “更高级” 的用法,== 可以让你知道如何更安全地用、扩展地用OAuth 2.0。

    所以,这部分内容会包括如何在移动App中使用OAuth 2.0,因使用不当而导致的OAuth 2.0安全漏洞有哪些,以及如何利用OAuth 2.0实现一个OpenID Connect用户身份认证协议。此外,我还邀请了微服务技术领域的专家杨波老师,给我们分享了一个架构案例,基于OAuth 2.0/JWT的微服务参考架构。

    最后,为了配合课程的学习,不让理论过于枯燥,也为了学以致用, 我GitHubopen in new window上为你准备了一份非常简单、可落地的 通过Java语言来实现的代码。

    简单的地方在于,代码中除了基本的Servlet技术外,我没有引入任何其它的第三方内容。所以,你只要能够理解Request和Response,就能够理解这份代码。

    可落地的地方在于,虽然它是一份简单的代码,但它不仅把整个OAuth 2.0的组件都跑通了,还包含了实践一个OIDC协议的具体实现。当然,我在代码里面还预留了一些TODO的地方,你可以结合上下文来自行实践处理。这是一项开源的工程。

    在这里,我总结了OAuth 2.0的知识体系图,你也可以先了解下整个课程的知识结构。

    体系图

    这样一来,你学完这门课后,便能在互联网的授权领域练就一双“火眼金睛”,可以发现所有使用过OAuth 2.0的痕迹,诸如微信登录的场景。这样,即使你不用抓包分析,也能够洞悉它背后的原理,为今后快速熟知互联网的类似场景打下基础。

    最后,我还想正式认识一下你。你可以在留言区里做个自我介绍,和我聊聊,你目前学习、使用OAuth 2.0的难点、痛点是什么?或者,你也可以聊聊你对OAuth 2.0、对授权还有哪些独特的思考和体验,欢迎在留言区和我交流讨论。

    好了,现在就开启我们的OAuth 2.0之旅吧。

    - +
    跳至主要內容

    开篇词 | 为什么要学OAuth 2.0?

    ChenSino大约 8 分钟

    申明

    oauth2相关博客转载于极客时间open in new window

    你好,我是王新栋,是京东的资深架构师,主要负责京东商家开放平台的架构工作。在接下来的时间里,我将带你一起学习OAuth 2.0这个授权协议。

    我从2014年加入京东,便开始接触开放平台相关的技术,主要包括网关、授权两块的内容。在刚开始的几年时间里面,我一直都认为网关是开放平台的核心,起到 “中流砥柱” 的作用,毕竟网关要承载整个开放平台的调用量,同时还要有足够的系统容错能力。

    但随着对开放平台理解的不断深入,我们要想在开放平台支持更多样的业务场景,我才发现网关和授权同样重要,相当于开放平台的 “两条腿”。

    而对于授权 “这条腿”,它不仅要像网关一样要承载访问量,还要同时兼顾业务场景的发展。什么样的业务场景呢?类似的微信登录就是其中之一,越来越多的第三方应用都在向用户提供使用微信登录的解决方案,来减少用户注册的繁琐操作。而这个解决方案的背后原理,也是我们这门课要讲到的OAuth 2.0技术。

    OAuth 2.0是什么?

    那,OAuth 2.0到底是什么呢?我们先从字面上来分析下。OAuth 2.0一词中的 “Auth” 表示 “授权”,字母 “O” 是Open的简称,表示 “开放” ,连在一起就表示 “开放授权”。这也是为什么我们使用OAuth的场景,通常发生在开放平台的环境下。

    看到这里,你可能会说应该还有OAuth 1.0吧。没错,OAuth 2.0之前就是OAuth 1.0。现在,我就来和你说说这两个版本的OAuth有什么区别吧。

    在OAuth 1.0的时候,它有个 “很大的愿望” 就是想用一套授权机制来应对现实中的所有场景,比如Web应用场景、移动App应用场景、官方应用场景等等,但是这些场景并不是完全相同的。比如官方应用场景,你说还需要让用户来授权吗?如果需要,始终使用一套授权机制给用户带来的体验,是好还是坏呢?

    到了OAuth 2.0的时候,就解决了OAuth 1.0 面临的这种“尴尬”。OAuth 2.0 不再局限于一种授权机制,它扩充了授权许可机制类型,有了授权码许可机制、客户端凭据机制、资源拥有者凭据机制和隐式许可机制。这样的OAuth机制就能够很灵活地适应现实中的各种场景,比如移动应用的场景、官方应用的场景,等等。

    此外,OAuth 1.0的弊端还包括安全上的固化攻击等问题,因此OAuth 1.0现在已经是废弃状态了。对于我们来讲,直接使用OAuth 2.0就可以了。

    为什么会有这门课?

    但其实呢,OAuth 2.0并不是一门新的技术,从2007年OAuth 1.0面世,到2011年发布OAuth 2.0草案,互联网上已经有很多关于OAuth的资料了。所以,在我初次接触OAuth 2.0去查阅这些零散的资料时,觉得OAuth 2.0很简单啊,不就是授权吗,看两篇文章就够了啊。

    但是,看似简单的OAuth 2.0却又让我望而却步,在如何使用授权码流程上踌躇不前。比如,在Web应用中到底应该怎么使用授权码流程,移动App中还能使用授权码流程吗?当我带着这些问题尝试到网上搜索资料时,那些不成体系的资料着实也让我走了不少弯路。不知道你是不是也被下面问题困扰着:

    我要开发一个Web应用,当使用OAuth 2.0的时候,担心授权码被拦截,却因为没有较好的解决方法而一筹莫展。 我要开发一款移动App,当使用OAuth 2.0的时候,在确定是否需要Server端上,花费了大把的时间。 后来我看到《OAuth 2 in Action》这本书,如获至宝。它非常系统地讲解了OAuth2.0,让我对这个协议框架有了更全面、深刻的认识。也正是这本书给了我足够的勇气,让我能够把自己这些年在开放平台的工作中,所掌握的OAuth知识体系梳理一遍。也是在这一刻,我才意识到只要有了方向,就有了厚度。

    当我开始试着整理出自己这些年掌握的OAuth 2.0相关技术、实践,并计划输出的时候,我真真切切地发现,OAuth 2.0是讲授权没错,但要用对、用好这个协议,绝不是短短两篇文章就能讲清楚的。这也是我做这门课的初衷。

    我要结合自己在开放平台上的工作经验以及对OAuth 2.0的理解,一次性地给你说透授权这件事儿。同时,我还查阅了诸多资料,包括OAuth 2协议规范open in new windowOpenID Connect explainedopen in new window等,力求给你带来最贴近本质的OAuth 2.0知识的讲解。

    这门课是怎么设计的?

    在这门课程里,我会分为基础篇和进阶篇两大模块,每个模块都会安排一些实践内容,和你讲清楚OAuth 2.0。接下来,我就和你解释下为什么要这么安排。

    ==第一部分是基础篇,就是你必须要掌握的OAuth2.0的基础知识。== 在这一模块中,我会和你细致地讲解授权码许可(Authorization Code)类型的流程,包括OAuth 2.0内部组件之间的通信方式,以及授权服务、客户端(第三方软件)、受保护资源服务这三个组件的原理。

    在此基础上,我还会为你讲解其他三种常见许可类型,分别是资源拥有者凭据许可(Resource Owner Password Credentials)、隐式许可(Implicit)、客户端凭据许可(Client Credentials)的原理,以及如何选择适合自己实际场景的授权类型。这样一来,你就能掌握整个OAuth 2.0中所有许可类型的运转机制了,并且能够从容地在实际工作环境中使用它们。

    为了能够把你带入到OAuth 2.0的场景中,方便你理解这些概念、流程,我在讲述这些基础内容的时候,会用一个小明使用第三方“小兔打单软件”来打印自己在京东店铺的订单数据的例子,来贯穿始终。

    我可以告诉你的是,学完基础篇的内容,你就可以把OAuth 2.0用到实际的工作场景了。

    ==第二部分进阶篇的内容,我会侧重讲一些OAuth 2.0 “更高级” 的用法,== 可以让你知道如何更安全地用、扩展地用OAuth 2.0。

    所以,这部分内容会包括如何在移动App中使用OAuth 2.0,因使用不当而导致的OAuth 2.0安全漏洞有哪些,以及如何利用OAuth 2.0实现一个OpenID Connect用户身份认证协议。此外,我还邀请了微服务技术领域的专家杨波老师,给我们分享了一个架构案例,基于OAuth 2.0/JWT的微服务参考架构。

    最后,为了配合课程的学习,不让理论过于枯燥,也为了学以致用, 我GitHubopen in new window上为你准备了一份非常简单、可落地的 通过Java语言来实现的代码。

    简单的地方在于,代码中除了基本的Servlet技术外,我没有引入任何其它的第三方内容。所以,你只要能够理解Request和Response,就能够理解这份代码。

    可落地的地方在于,虽然它是一份简单的代码,但它不仅把整个OAuth 2.0的组件都跑通了,还包含了实践一个OIDC协议的具体实现。当然,我在代码里面还预留了一些TODO的地方,你可以结合上下文来自行实践处理。这是一项开源的工程。

    在这里,我总结了OAuth 2.0的知识体系图,你也可以先了解下整个课程的知识结构。

    体系图

    这样一来,你学完这门课后,便能在互联网的授权领域练就一双“火眼金睛”,可以发现所有使用过OAuth 2.0的痕迹,诸如微信登录的场景。这样,即使你不用抓包分析,也能够洞悉它背后的原理,为今后快速熟知互联网的类似场景打下基础。

    最后,我还想正式认识一下你。你可以在留言区里做个自我介绍,和我聊聊,你目前学习、使用OAuth 2.0的难点、痛点是什么?或者,你也可以聊聊你对OAuth 2.0、对授权还有哪些独特的思考和体验,欢迎在留言区和我交流讨论。

    好了,现在就开启我们的OAuth 2.0之旅吧。

    + diff --git "a/other/pve/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html" "b/other/pve/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html" index dd4a8111eb..2f42143c54 100644 --- "a/other/pve/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html" +++ "b/other/pve/ddns\347\224\263\350\257\267\350\257\201\344\271\246.html" @@ -30,8 +30,8 @@ ddns证书 | ChenSino - - + +
    跳至主要內容

    ddns证书

    chensino原创大约 2 分钟

    acme官方文档:https://github.com/acmesh-official/acme.shopen in new window pve官方文档:https://pve-doc-cn.readthedocs.io/zh-cn/latest/chapter_system_administration/certmgr.htmlopen in new window

    1. 申请动态域名(省略)

    2. 使用acme申请免费的

    2.1 使用acme申请证书

    1 安装很简单: 国内用户参考https://github.com/acmesh-official/acme.sh/wiki/Install-in-Chinaopen in new window

    git clone https://gitee.com/neilpang/acme.sh.git
    @@ -58,7 +58,7 @@
     mv chensino.dynv6.net.pem pve-ssl.pem
     mv chensino.dynv6.net.key pve-ssl.key
     
    1. 重启pveproxy
     systemctl restart pveproxy
    -

    4. 自动续签

    这种免费的证书时间很短,acme.sh会帮我们自动创建一个crontab定时任务更新证书

    pve的webui界面自带的acme插件

    https://gist.github.com/tavinus/15ea64c50ac5fb7cea918e7786c94a95open in new window

    1. 数据中心——ACME,添加账户chensino 20241009155933

    2. 数据中心——ACME,添加质询插件,名字还是chenisno,API数据是dynv6中生成的token,格式如下

    20241009160021

    1. 到对应的pve节点(我的节点叫chensino)——凭证,添加凭证,质询类型DNS,插件选择之前创建的 20241009160140

    2. pve节点——凭证,选择域名,点击立即预定凭证

    注意

    若报错找不到了对应的key什么的,需要事先创建好,名字要对应上,比如报错叫dynv6的key没有,那么就要到~/.ssh下生成对应的名字的key,生成方法请参考之前的

    20241009160816

    - +

    4. 自动续签

    这种免费的证书时间很短,acme.sh会帮我们自动创建一个crontab定时任务更新证书

    pve的webui界面自带的acme插件

    https://gist.github.com/tavinus/15ea64c50ac5fb7cea918e7786c94a95open in new window

    1. 数据中心——ACME,添加账户chensino 20241009155933

    2. 数据中心——ACME,添加质询插件,名字还是chenisno,API数据是dynv6中生成的token,格式如下

    20241009160021

    1. 到对应的pve节点(我的节点叫chensino)——凭证,添加凭证,质询类型DNS,插件选择之前创建的 20241009160140

    2. pve节点——凭证,选择域名,点击立即预定凭证

    注意

    若报错找不到了对应的key什么的,需要事先创建好,名字要对应上,比如报错叫dynv6的key没有,那么就要到~/.ssh下生成对应的名字的key,生成方法请参考之前的

    20241009160816

    + diff --git a/other/pve/firewall.html b/other/pve/firewall.html index 9ffa1a86eb..3596998150 100644 --- a/other/pve/firewall.html +++ b/other/pve/firewall.html @@ -30,8 +30,8 @@ pve防火墙 | ChenSino - - + +
    跳至主要內容

    pve防火墙

    chensino原创大约 2 分钟

    参考:https://www.xh86.me/?p=11324open in new window

    一、防火墙简介

    pve有3个层级防火墙:

    1. 数据中心的防火墙
    2. 节点防火墙(主机防火墙)
    3. 虚拟机防火墙

    20240929173211

    1.1 数据中心防火墙

    数据中心,是由节点组成的一个集群。 可以说,数据中心防火墙,是专门处理集群流量的防火墙。

    ==只有数据中心防火墙开启,才能开启集群内的防火墙,否则单独开启vm的防火墙是无效的。== 它是一个总开关

    1.2 主机防火墙

    主机防火墙,只负责处理虚拟化服务器上的流量,比如一台pve服务器ip为192.168.1.2。

    那么这个防火墙,只会关注192.168.1.2的流量。下面的虚拟机流量是独立开的。

    这个防火墙的开关,不会影响vm的防火墙,所以要开启vm的防火墙,不必须开启这个防火墙,不同于数据中心防火墙。

    说的直白一点就是这个防火墙控制的就是pve那个debian系统本身,比如pve的web管理页面(8006端口)、ssh等

    1.3 虚拟机防火墙

    这个就是系统级别的防火墙了,和我们平时用的物理机防火墙就是一样的了

    二: 防火墙配置文件

    pve的防火墙配置文件在下面路径

    #数据中心防火墙
    @@ -45,7 +45,7 @@
     #vm和lxc的防火墙
     
     /etc/pve/firewall/<VMID>.fw
    -

    三、实际配置

    3.1 配置主机防火墙

    目的是实现pve系统收到保护,我不想让别人的ip随便登录我的pve管理界面,以及远程我的sshd服务

    1、 开启数据中心防火墙(必须开,否则所有防火墙就不生效)

    20240929174029

    2、设置别名

    在数据中心设置一个别名,方便在其他地方引用,当然也可以在虚拟机防火墙设置也行,这里我犯了一个严重错误导致浪费很长时间。我的目的是想让所有192.168内网都放行,结果我写成192.168.0.0/24,这个放行的其实是192.168.0.1到192.168.0.255,正确写法应该是192.168.1.0/24

    20240929174255

    3、在主机防火墙引入上一步设置的别名

    20240929174733

    4、开启主机防火墙

    20240929174802

    3.2 开启虚拟机级别防火墙

    1、2步同上,3、4步也差不多,不过要点到对应的虚拟机再设置。

    20240929174925

    20240929174938

    - +

    三、实际配置

    3.1 配置主机防火墙

    目的是实现pve系统收到保护,我不想让别人的ip随便登录我的pve管理界面,以及远程我的sshd服务

    1、 开启数据中心防火墙(必须开,否则所有防火墙就不生效)

    20240929174029

    2、设置别名

    在数据中心设置一个别名,方便在其他地方引用,当然也可以在虚拟机防火墙设置也行,这里我犯了一个严重错误导致浪费很长时间。我的目的是想让所有192.168内网都放行,结果我写成192.168.0.0/24,这个放行的其实是192.168.0.1到192.168.0.255,正确写法应该是192.168.1.0/24

    20240929174255

    3、在主机防火墙引入上一步设置的别名

    20240929174733

    4、开启主机防火墙

    20240929174802

    3.2 开启虚拟机级别防火墙

    1、2步同上,3、4步也差不多,不过要点到对应的虚拟机再设置。

    20240929174925

    20240929174938

    + diff --git a/other/pve/index.html b/other/pve/index.html index 733ccf172d..afcdc6ac03 100644 --- a/other/pve/index.html +++ b/other/pve/index.html @@ -30,11 +30,11 @@ 玩转PVE虚拟机 | ChenSino - - + + -
    跳至主要內容

    玩转PVE虚拟机

    chensino原创小于 1 分钟

    pve

    这是一个好玩的工具

    - + + diff --git a/other/rabbitmq/index.html b/other/rabbitmq/index.html index ba2055bc90..57d137daaa 100644 --- a/other/rabbitmq/index.html +++ b/other/rabbitmq/index.html @@ -30,11 +30,11 @@ rabbitmq | ChenSino - - + + - - + + diff --git a/other/software/index.html b/other/software/index.html index 414a481831..0e6b9d5afb 100644 --- a/other/software/index.html +++ b/other/software/index.html @@ -30,11 +30,11 @@ Software | ChenSino - - + + - - + + diff --git "a/other/software/\347\240\264\350\247\243\350\275\257\344\273\266.html" "b/other/software/\347\240\264\350\247\243\350\275\257\344\273\266.html" index 2f98a5fc7b..4aa2a08b22 100644 --- "a/other/software/\347\240\264\350\247\243\350\275\257\344\273\266.html" +++ "b/other/software/\347\240\264\350\247\243\350\275\257\344\273\266.html" @@ -30,8 +30,8 @@ 软件激活 | ChenSino - - + +
    跳至主要內容

    软件激活

    chensino小于 1 分钟

    smartsvn

    安装过程略......

    创建smartsvn.license,把以下内容复制进去,激活时选择smartsvn.license就可以了

    Name=csdn  
    @@ -43,7 +43,7 @@
     Addon-API=true  
     Enterprise=true  
     Key=4kl-<Zqcm-iUF7I-IVmYG-XAyvv-KYRoC-xlgsv-sSBds-VAnP6
    -
    - +
    + diff --git a/other/sono/SUC.html b/other/sono/SUC.html index eacea121a5..3823fdac92 100644 --- a/other/sono/SUC.html +++ b/other/sono/SUC.html @@ -30,8 +30,8 @@ 客户端对接用户中心 | ChenSino - - + +
    跳至主要內容

    客户端对接用户中心

    ChenSino公司业务大约 1 分钟

    1、基础环境搭建

    1.1 引入依赖

           <dependency>
    @@ -113,7 +113,7 @@
     4. 拿到token,重定向到最初访问的接口,并且此次请求会携带token
     5. 客户端过滤器拿到token后,先把token转化为本系统的用户信息
     5. 通过过滤器验证,最终访问接口,返回接口数据
    -
    - +
    + diff --git a/other/sono/index.html b/other/sono/index.html index cfce3bd95a..46c188b218 100644 --- a/other/sono/index.html +++ b/other/sono/index.html @@ -30,11 +30,11 @@ Sono | ChenSino - - + + - - + + diff --git a/other/tools/Idea.html b/other/tools/Idea.html index 58f158c394..ad0ea33877 100644 --- a/other/tools/Idea.html +++ b/other/tools/Idea.html @@ -30,13 +30,13 @@ Idea | ChenSino - - + +
    跳至主要內容

    Idea

    chenkun大约 3 分钟

    1、多线程debug遇到的问题

    多线程调试需要把Thread选上,至于Thread和All的区别请查看官方文档open in new window

    • All: all threads are suspended when any of the threads hits the breakpoint.

    • Thread: only the thread which hits the breakpoint is suspended.

    1

    今天用idea调试mybatis-plus多数据源切换时,遇到一个有趣的问题,我有两个线程,分别进入了两个方法,因为多数据源切换使用的ThreadLocal,我想调试为何数据源切换会失败的问题。在调试过程中,我在两个方法分别获取ThreadLocalMap,调试过程如下:

    1. 我先执行了线程1,然后断点停留,查看第一个方法中threadLocalMap的结果,符合预期
    2. 然后执行线程2,查看第二个方法中threadLocalMap的结果,也符合预期
    3. 此时我又用Ctrl+鼠标左键去查看线程1中的threadLocalMap,发现变成和线程2中一样了 2

    经过第三步测试,我蒙蔽了,ThreadLocal不是线程之间不会互相干扰吗?怎么线程2修改了线程1的结果?
    这里说一下,debug中,Ctrl+鼠标左键点击字段确实能快捷查看字段值,但是其实它的本质还是查看当前线程的xxx字段,只要你没切换线程,直接用鼠标点到另一个线程中的字段,它其实只是用了你用快捷键点的那个字段名,实际还是差的当前线程的这个字段 3

    如果想看另一个线程的同名字段,需要先切换线程
    4

    另外如果你把两个线程中的字段名改成不一样,你会发现在线程2中用快捷键点线程1的字段,是取不到值的

    2、Idea指定jdk启动

    Idea升级后无法启动,查看日志报错明显的是jdk版本不兼容,需要用最新的jdk17,解决方法有多个。

    方法1:设置全局jdk为17,这个方法可以启动idea,但是不太好,毕竟其他软件可能不能使用最新的jdk

    方法2:单独给idea配置jdk,我记得以前使用eclipse有个虚拟机参数-vm可以指定,但是在idea中并不好使, 所以就去看了一下idea的启动脚本idea.sh,看一下启动脚本,就知idea启动时到底使用的是那个jdk,根据idea读取 的配置文件去配置就好了,可以设置JRE环境变量、可以指定JDK_HOME环境变量,我这里使用的是配置文件的方式,配置 idea.jdk

    20221202112129

     cat ~/.config/JetBrains/IntelliJIdea2022.3/idea.jdk                                      
     /usr/lib/jvm/java-17-openjdk
    -

    3、idea中git操作

    3.1 对比任意非连续的两次提交文件差异

    选中两次提交,右键Compare Versions,

    image-20221229215718576

    会出现一个change log的视图,这就是两次提价的差异文件

    image-20221229215820791

    4、debug无法进入jdk源码

    idea2022版本默认,调试时跳过底层源码,具体参考下图,把需要加入debug的源码去掉勾选 20230201173830

    - +

    3、idea中git操作

    3.1 对比任意非连续的两次提交文件差异

    选中两次提交,右键Compare Versions,

    image-20221229215718576

    会出现一个change log的视图,这就是两次提价的差异文件

    image-20221229215820791

    4、debug无法进入jdk源码

    idea2022版本默认,调试时跳过底层源码,具体参考下图,把需要加入debug的源码去掉勾选 20230201173830

    + diff --git a/other/tools/SoftWare.html b/other/tools/SoftWare.html index ee09f4a4e9..b6478f270a 100644 --- a/other/tools/SoftWare.html +++ b/other/tools/SoftWare.html @@ -30,15 +30,15 @@ 软件分享 | ChenSino - - + +
    跳至主要內容

    软件分享

    chenkun小于 1 分钟

    1、Beyond Compare3

    1.1、下载

    资源地址open in new window

    1.2 、集成到git 对比

    参考此处open in new window配置,主要有两步,

    第一步,设置:

     git config --global merge.tool bc3
     $ git config --global mergetool.bc3.path "c:/program files/beyond compare 3/bcomp.exe"
     

    第二部,使用:

    #注意对比使用git difftool不要用git diff
     git difftool [xxx]
    -
    - +
    + diff --git a/other/tools/VsCode.html b/other/tools/VsCode.html index e001ff7262..7aed4d4b00 100644 --- a/other/tools/VsCode.html +++ b/other/tools/VsCode.html @@ -30,8 +30,8 @@ vscode配置 | ChenSino - - + +
    跳至主要內容

    vscode配置

    chenkun大约 1 分钟

    版本: 1.69.1 (user setup) 提交: b06ae3b2d2dbfe28bca3134cc6be65935cdfea6a 日期: 2022-07-12T08:21:24.514Z Electron: 18.3.5 Chromium: 100.0.4896.160 Node.js: 16.13.2 V8: 10.0.139.17-electron.0 OS: Windows_NT x64 6.1.7601

    1. vscode终端无法打开

    现象,使用vscod打开终端一直卡着。

    解决方法是禁用GPU加速,猜测是因为公司电脑没有独显的原因

    1、取消win7的兼容模式 2、启动方式后加 --disable-gpuopen in new window,似乎是禁用 GPU 硬件加速 找其他各种方法都无效,管理员、兼容、改setting.json都没用。

    1

    2、在vscode中使用git-bash作为默认终端

    1. 给vscode设置以管理员方式运行,
    2. 在配置文件加入以下配置,把git的path改成自己的
      "terminal.integrated.defaultProfile.windows": "GitBash",
    @@ -73,7 +73,7 @@
                 "default": false
             }
         ]
    -
    - +
    + diff --git a/other/tools/index.html b/other/tools/index.html index 3a484b7ac4..1a767f7b0d 100644 --- a/other/tools/index.html +++ b/other/tools/index.html @@ -30,11 +30,11 @@ 软件工具 | ChenSino - - + + - - + + diff --git a/other/training/CloudServiceTraining.html b/other/training/CloudServiceTraining.html index 0d18cce7e6..ad1f342e02 100644 --- a/other/training/CloudServiceTraining.html +++ b/other/training/CloudServiceTraining.html @@ -30,8 +30,8 @@ 小组分享-云服务 | ChenSino - - + +
    跳至主要內容

    小组分享-云服务

    chenkun小组分享大约 10 分钟

    [TOC]

    问题:

    1. 一个域名能解析出多少个IP?
    2. 请求一个地址,加www和不加是否有区别?

    一、DNS

    本章节参考DNS 原理入门——阮一峰open in new window

    1.1 什么是DNS

    DNS (Domain Name System 的缩写)的作用非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。 举例来说,如果你要访问域名math.stackexchange.com,首先要通过DNS查出它的IP地址是151.101.129.69。

    1.1 A(Address)记录

    A (Address) 记录是用来指定主机名(或域名)对应的IP地址记录。用户可以将该域名下的网站服务器指向到自己的web server上。 同时也可以设置您域名的二级域名。一般情况下我们收的域名都指的是A记录,比如sonoscape.com,dccm.sonoscape.com,meet.sonoscape.com 都是A记录

    1.2 CNAME(Canonical Name)

    1.2.1 定义

    CName记录是Canonical Name的简称,通常称别名指向,CNAME记录可用于将一个域名别名为另一个规范名称的域名系统(DNS)资源记录。

    以下摘自国内某dns服务商文档

    如果需要将域名指向另一个域名,再由另一个域名提供 IP 地址,就需要添加 CNAME 记录,最常用到 CNAME 的场景包括做 CDN、做企业邮箱。

    1.2.2 参考博客

    Cname存在的意义open in new windowcname解析过程open in new window

    注意
    CNAME只是一个相对的叫法,CNAME相对A记录叫CNAME,但是它本身也是一条A记录

    1.2.2 场景

    • CDN加速
    • 为特定网络服务(例如电子邮件或 FTP)提供单独的主机名,并将该主机名指向根域
    • 许多托管服务在服务提供商的域(例如 company.hostname.com)上为每个客户提供一个子域,并使用 CNAME 指向客户的域(www.company.com)。
    • 在多个国家注册同一个域并将国家版本指向主“.com”域
    • 从同一组织拥有的多个网站指向一个主网站 -
    • 用于 SSL 证书申请时的域名验证,例如 _dnsauth.yryz.net CNAME mnwwgx3uijnhsvkyjezf6nlpkn4xotzrkjpto6tfgbbuu22g.dcv.httpsauto.com.

    1.3 NS(Name Server)记录

    NS(Name Server)记录是域名服务器记录,用来指定该域名由哪个DNS服务器来进行解析。比如指定sonoscape.com的子域名具体由哪个服务器进行解析(参考后续dig指令)

    ns记录查询使用指令:

    $ dig ns com
    @@ -182,7 +182,7 @@
     ;; WHEN: Tue Jul 12 14:20:30 CST 2022
     ;; MSG SIZE  rcvd: 62
     
    -

    分别使用dig命令查看,得到结果显示这两个域名都是A记录,并且指向同一IP,说明我司并未采用CNAME,直接使用的是把两个域名绑定到 47.242.63.134

    一般情况下加www是用来做cname绑定到原来的A记录,所以访问时加不加都一样,若是把www.sonoscape.com和sonoscape.com分别绑定到不同ip,则他俩其实只是有个子域名关联关系。

    五、云服务使用遇到的问题案例

    云服务问题分析open in new window

    - +

    分别使用dig命令查看,得到结果显示这两个域名都是A记录,并且指向同一IP,说明我司并未采用CNAME,直接使用的是把两个域名绑定到 47.242.63.134

    一般情况下加www是用来做cname绑定到原来的A记录,所以访问时加不加都一样,若是把www.sonoscape.com和sonoscape.com分别绑定到不同ip,则他俩其实只是有个子域名关联关系。

    五、云服务使用遇到的问题案例

    云服务问题分析open in new window

    + diff --git a/other/training/SSO.html b/other/training/SSO.html index adf5b22291..40b82a857d 100644 --- a/other/training/SSO.html +++ b/other/training/SSO.html @@ -30,11 +30,11 @@ OAuth2分享 | ChenSino - - + + -
    跳至主要內容

    OAuth2分享

    chenkun小组分享大约 7 分钟

    1、SSO

    1.1 SSO介绍

    单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    1.2 SSO使用场景(解决了什么问题)

    很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都要进行注册登录,退出的时候又要一个个退出。用户体验很不好!你可以想象一下,上豆瓣 要登录豆瓣FM、豆瓣读书、豆瓣电影豆瓣日记......真的会让人崩溃的。我们想要另一种登录体验:一家企业下的服务只要一次注册,登录的时候只要一次登录,退出的时候只要一次退出。怎么做?

    一次注册不难,想一下是不是只要Server之间同步用户信息就行了?可以,但这样描述不太完整,后续讲用户注册的时候详细说。实际上用户信息的管理才是SSO真正的难点,只是作为初学者,我们的难点在于实现SSO的技术!我们先讨论实现手段。

    一次登录与一次退出。 回头看看普通商场的故事,什么东西才是保持登录状态关键的东西?记录器(session)?那种叫做cookie的纸张?写在纸张上的ID? 是session里面记录的信息跟那个ID,cookie不只是记录ID的工具而已。客户端持有ID,服务端持有session,两者一起用来保持登录状态。客户端需要用ID来作为凭证,而服务端需要用session来验证ID的有效性(ID可能过期、可能根本就是伪造的找不到对应的信息、ID下对应的客户端还没有进行登录验证等)。但是session这东西一开始是每个server自己独有的,豆瓣FM有自己的session、豆瓣读书有自己的session,而记录ID的cookie又是不能跨域的。所以,我们要实现一次登录一次退出,只需要想办法让各个server的共用一个session的信息,让客户端在各个域名下都能持有这个ID就好了。再进一步讲,只要各个server拿到同一个ID,都能有办法检验出ID的有效性、并且能得到ID对应的用户信息就行了,也就是能检验ID 。

    SSO就是解决以上问题,当使用浏览器访问公司任何一个服务时,只要其中一个服务登陆了,那么再打开一个新页面访问另一个服务,是无需再次输入用户名和密码。比如:登陆了OA后,再访问考勤系统是无需登陆。

    1.3 使用SSO的好处

    • 方便用户,用户使用应用系统时,能够一次登录,多次使用。用户不再需要每次输入用户名称和用户密码,也不需要牢记多套用户名称和用户密码。单点登录平台能够改善用户使用应用系统的体验。
    • 方便管理员,系统管理员只需要维护一套统一的用户账号,方便、简单。相比之下,系统管理员以前需要管理很多套的用户账号。每一个应用系统就有一套用户账号,不仅给管理上带来不方便,而且,也容易出现管理漏洞。
    • 简化应用系统开发,开发新的应用系统时,可以直接使用单点登录平台的用户认证服务,简化开发流程。单点登录平台通过提供统一的认证平台,实现单点登录。因此,应用系统并不需要开发用户认证程序。

    2、SSO常见实现方案

    注意

    SSO只是一个规范,具体的实现有多种途径,类似Jdbc定义了java数据库连接的规范,具体的实现方案由各数据库厂商自行实现。

    单点登录的实现方案一般包含:Cookies、Session 同步、分布式 Session 方式、统一认证授权方式等。目前的大型网站通常采用分布式 Session 及第三方认证授权的方式。

    基于 Cookie 的单点登录是最简单的单点登录实现方式,它使用 Cookie 作为媒介,存放用户凭证。 用户登录父应用之后,应用返回一个加密的 Cookie,用户访问子应用的时候,携带上这个 Cookie,授权应用解密 Cookie 并进行校验,校验通过则登录当前用户。 这种方式虽然实现简单,但 Cookie 不够安全,容易泄漏,且不能跨域实现免登。

    2.2 分布式 Session 实现单点登录

    分布式 Session 实现单点登录原理是将用户认证信息保存于 Session 中,即以 Session 内存储的值为用户凭证,一般采用 Cache 中间件实现(如 Redis)。用户再次登录时,应用服务端获取分布式 Session 来校验用户信息。如图所示: 20221011175808 一般情况下都是基于 Redis 实现 Session 共享,将 Session 存储于 Redis 上,然后将整个系统的全局 Cookie Domain 设置于顶级域名上,这样 SessionID 就能在各个子系统间共享。 这种方式也有一个问题,共享 Session 无法处理跨顶级域名。

    2.3 统一认证授权方式实现单点登录

    20221011175916

    由图可知,通过统一认证授权方式实现单点登录,需要有一个独立的认证系统。

    用户第一次访问应用系统时,由于还未登录,被引导到认证系统中进行登录,认证系统接受用户名密码等安全信息,生成访问令牌(ticket)。用户通过 ticket 访问应用系统,应用系统接受到请求之后会访问认证系统检查 ticket 的合法性,如果检查通过,用户就可以在不用再次登录的情况下访问应用系统资源。

    以上介绍了单点登录常见的 3 种实现方案,在实际应用中还需要根据具体场景来实现。

    接下来通过几个实际生产场景,来阐述单点登录的详细实现方案。

    3、实际场景中SSO实现

    3.1 JWT(完全跨域方案)

    JWT (JSON Web Token)是一个开放标准(RFC7519),它是一个含签名并携带用户相关信息的加密串。页面请求校验登录接口时,请求头中携带 JWT 串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改,校验通过则认为是可靠的请求,将正常返回数据。

    DataSimba(奇点云数据中台产品)结合 JWT 与分布式 session,实现多域多空间单点登录。通过 JWT 生成和校验令牌,将刷新令牌存储在 redis 中,网关统一校验令牌,校验通过后将用户信息设置在请求头中,应用在拦截器中获取到用户信息后即可验证通过。

    不同域中的 DataSimba 共用一套密钥并且实时同步用户信息,通过 JWT 生成和校验令牌,用户登录其中一个域后,前端获取 JWT 加密串并存储在 Local Storage 中,当用户切换到其他域时前端传入加密串,后端网关校验,由此实现免登录访问其他域资源,如下图所示: 20221011180112

    3.2 使用 OAuth2.0 实现单点登录

    OAuth2.0是可以实现SSO的功能,但是OAuth 诞生之初就是为了解决 Web 浏览器场景下的授权问题

    4、参考资料

    OAuth2.0授权码模式中为什么一定要有codeopen in new window授权码的补充open in new window授权码模式中code的意义open in new window授权码模式是让token在服务器和服务器之间传递,不会经过资源拥有者,资源拥有者无需看到tokenopen in new window

    - +
    跳至主要內容

    OAuth2分享

    chenkun小组分享大约 7 分钟

    1、SSO

    1.1 SSO介绍

    单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    1.2 SSO使用场景(解决了什么问题)

    很早期的公司,一家公司可能只有一个Server,慢慢的Server开始变多了。每个Server都要进行注册登录,退出的时候又要一个个退出。用户体验很不好!你可以想象一下,上豆瓣 要登录豆瓣FM、豆瓣读书、豆瓣电影豆瓣日记......真的会让人崩溃的。我们想要另一种登录体验:一家企业下的服务只要一次注册,登录的时候只要一次登录,退出的时候只要一次退出。怎么做?

    一次注册不难,想一下是不是只要Server之间同步用户信息就行了?可以,但这样描述不太完整,后续讲用户注册的时候详细说。实际上用户信息的管理才是SSO真正的难点,只是作为初学者,我们的难点在于实现SSO的技术!我们先讨论实现手段。

    一次登录与一次退出。 回头看看普通商场的故事,什么东西才是保持登录状态关键的东西?记录器(session)?那种叫做cookie的纸张?写在纸张上的ID? 是session里面记录的信息跟那个ID,cookie不只是记录ID的工具而已。客户端持有ID,服务端持有session,两者一起用来保持登录状态。客户端需要用ID来作为凭证,而服务端需要用session来验证ID的有效性(ID可能过期、可能根本就是伪造的找不到对应的信息、ID下对应的客户端还没有进行登录验证等)。但是session这东西一开始是每个server自己独有的,豆瓣FM有自己的session、豆瓣读书有自己的session,而记录ID的cookie又是不能跨域的。所以,我们要实现一次登录一次退出,只需要想办法让各个server的共用一个session的信息,让客户端在各个域名下都能持有这个ID就好了。再进一步讲,只要各个server拿到同一个ID,都能有办法检验出ID的有效性、并且能得到ID对应的用户信息就行了,也就是能检验ID 。

    SSO就是解决以上问题,当使用浏览器访问公司任何一个服务时,只要其中一个服务登陆了,那么再打开一个新页面访问另一个服务,是无需再次输入用户名和密码。比如:登陆了OA后,再访问考勤系统是无需登陆。

    1.3 使用SSO的好处

    • 方便用户,用户使用应用系统时,能够一次登录,多次使用。用户不再需要每次输入用户名称和用户密码,也不需要牢记多套用户名称和用户密码。单点登录平台能够改善用户使用应用系统的体验。
    • 方便管理员,系统管理员只需要维护一套统一的用户账号,方便、简单。相比之下,系统管理员以前需要管理很多套的用户账号。每一个应用系统就有一套用户账号,不仅给管理上带来不方便,而且,也容易出现管理漏洞。
    • 简化应用系统开发,开发新的应用系统时,可以直接使用单点登录平台的用户认证服务,简化开发流程。单点登录平台通过提供统一的认证平台,实现单点登录。因此,应用系统并不需要开发用户认证程序。

    2、SSO常见实现方案

    注意

    SSO只是一个规范,具体的实现有多种途径,类似Jdbc定义了java数据库连接的规范,具体的实现方案由各数据库厂商自行实现。

    单点登录的实现方案一般包含:Cookies、Session 同步、分布式 Session 方式、统一认证授权方式等。目前的大型网站通常采用分布式 Session 及第三方认证授权的方式。

    基于 Cookie 的单点登录是最简单的单点登录实现方式,它使用 Cookie 作为媒介,存放用户凭证。 用户登录父应用之后,应用返回一个加密的 Cookie,用户访问子应用的时候,携带上这个 Cookie,授权应用解密 Cookie 并进行校验,校验通过则登录当前用户。 这种方式虽然实现简单,但 Cookie 不够安全,容易泄漏,且不能跨域实现免登。

    2.2 分布式 Session 实现单点登录

    分布式 Session 实现单点登录原理是将用户认证信息保存于 Session 中,即以 Session 内存储的值为用户凭证,一般采用 Cache 中间件实现(如 Redis)。用户再次登录时,应用服务端获取分布式 Session 来校验用户信息。如图所示: 20221011175808 一般情况下都是基于 Redis 实现 Session 共享,将 Session 存储于 Redis 上,然后将整个系统的全局 Cookie Domain 设置于顶级域名上,这样 SessionID 就能在各个子系统间共享。 这种方式也有一个问题,共享 Session 无法处理跨顶级域名。

    2.3 统一认证授权方式实现单点登录

    20221011175916

    由图可知,通过统一认证授权方式实现单点登录,需要有一个独立的认证系统。

    用户第一次访问应用系统时,由于还未登录,被引导到认证系统中进行登录,认证系统接受用户名密码等安全信息,生成访问令牌(ticket)。用户通过 ticket 访问应用系统,应用系统接受到请求之后会访问认证系统检查 ticket 的合法性,如果检查通过,用户就可以在不用再次登录的情况下访问应用系统资源。

    以上介绍了单点登录常见的 3 种实现方案,在实际应用中还需要根据具体场景来实现。

    接下来通过几个实际生产场景,来阐述单点登录的详细实现方案。

    3、实际场景中SSO实现

    3.1 JWT(完全跨域方案)

    JWT (JSON Web Token)是一个开放标准(RFC7519),它是一个含签名并携带用户相关信息的加密串。页面请求校验登录接口时,请求头中携带 JWT 串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改,校验通过则认为是可靠的请求,将正常返回数据。

    DataSimba(奇点云数据中台产品)结合 JWT 与分布式 session,实现多域多空间单点登录。通过 JWT 生成和校验令牌,将刷新令牌存储在 redis 中,网关统一校验令牌,校验通过后将用户信息设置在请求头中,应用在拦截器中获取到用户信息后即可验证通过。

    不同域中的 DataSimba 共用一套密钥并且实时同步用户信息,通过 JWT 生成和校验令牌,用户登录其中一个域后,前端获取 JWT 加密串并存储在 Local Storage 中,当用户切换到其他域时前端传入加密串,后端网关校验,由此实现免登录访问其他域资源,如下图所示: 20221011180112

    3.2 使用 OAuth2.0 实现单点登录

    OAuth2.0是可以实现SSO的功能,但是OAuth 诞生之初就是为了解决 Web 浏览器场景下的授权问题

    4、参考资料

    OAuth2.0授权码模式中为什么一定要有codeopen in new window授权码的补充open in new window授权码模式中code的意义open in new window授权码模式是让token在服务器和服务器之间传递,不会经过资源拥有者,资源拥有者无需看到tokenopen in new window

    + diff --git a/other/training/draw.html b/other/training/draw.html index bac66aca02..c46c35bd13 100644 --- a/other/training/draw.html +++ b/other/training/draw.html @@ -30,8 +30,8 @@ 画图工具 | ChenSino - - + +
    跳至主要內容

    画图工具

    chenkun小组分享大约 1 分钟

    1 示例

    https://paper.pigx.vip/

    2 制图工具

    名称地址特性推荐指数
    visio可视化制图,单独安装,收费★★
    draw.iohttps://www.draw.io/提供在线网页制图工具、单独安装包、各种IDE集成插件,软件本身也有提供额外的插件支持,可安插件来增强drawio功能。制图采用可视化拖拉拽,简单直观,容易上手。★★★★★
    processonhttps://www.processon.com/网页在线制图,通过拖拉拽制图,免费版有很多限制,不推荐★★
    plantUMLhttp://www.plantuml.com/Java开发的,提供网页在线制图,各种IDE插件,vscode和JetBrains家族都有对应插件。采用标记语言进行制图,图片自动根据标记语言生成★★★★
    mermaidhttps://mermaid-js.github.io/mermaid/#/和PlantUML类似,也提供在线制图,支持markdown渲染,和markdown工具有关,大多md工具都会提供mermaid支持★★★

    3 工具选择

    流程图、架构图推荐使用drawio

    时序图、思维导图推荐使用PlantUML标记语言,自动生成的图比较规整好看

    如果使用markdown,推荐使用mermaid,mermaid语法简单,和PlantUML差不多

    graph TD;
    @@ -50,7 +50,7 @@
         John-->>Alice: Great!
         John->>Bob: How about you?
         Bob-->>John: Jolly good!
    -
    - +
    + diff --git a/other/training/index.html b/other/training/index.html index adfe0d04ca..85fd7fc8df 100644 --- a/other/training/index.html +++ b/other/training/index.html @@ -30,11 +30,11 @@ 小组分享 | ChenSino - - + + - - + + diff --git a/other/web/BuildWebProject.html b/other/web/BuildWebProject.html index 3297429767..cb7e5558ae 100644 --- a/other/web/BuildWebProject.html +++ b/other/web/BuildWebProject.html @@ -30,8 +30,8 @@ 前后分离项目搭建 | ChenSino - - + +
    跳至主要內容

    前后分离项目搭建

    chenkunwebwebspringboot大约 11 分钟

    温馨提示

    项目地址open in new window,每个节点的代码使用commitId作为区分,想看某个节点代码,直接还原到对应commitid即可,执行git reset --hard commitId

    1、后端篇

    1.1 初始化springboot项目

    git reset --hard 20e22c237e51fb9c7f01bdfd589a90f47fa73c34

    1.1.1 使用maven聚合模块以及parent依赖的方式初始化好了项目

    问题1
    分模块后,如何读取到其他模块中的bean,比如全局异常处理放在了common模块,在业务模块依赖了common,如何让common中的全局异常拦截生效?
    首先要明白无法common模块的component在core-biz不生效的原因是在biz模块默认扫描的component的包范围是启动类所在的包,也就是com.chensino.core,而全局异常类所在的包是com.chensino.common.security.exception,根本没有被扫描到。

    1

    解决方法有三种
    参考此处文档open in new window

    1. 把扫描范围搞大一点
    package com.chensino.core;
    @@ -456,7 +456,7 @@
             filterChain.doFilter(request, response);
         }
     }
    -

    1.9.5 过滤器配置

    20221206161730

    1.9.6 思考

    虽然以上完成了自定义用uuid当token,但是token的设计并不严谨,token的key到底该如何设计,以下两个方法是否合理?

    • 方法1:使用前缀加用户名当成key,value存入token
    • 方法2:前缀加token当成key,用户信息当成value

    方法1,如果使用用户名做key,那么每个请求都需要客户端携带一个用户名和token,然后后端在过滤器根据用户名从redis查询token, 对比两个token来做权限验证,这个貌似不太合理,至少我在实际项目开发中没看到过要求前端每次都要传递用户名的。使用这个方法有 一个优点是很容易做重复登录的限制,只需要根据用户名限制登录就好了,每次登录前检查redis中是否已经有了这个用户的登陆记录,如果 有,可以提示用户已经登陆,或者挤掉已登录用户,这个根据自己业务来做。

    方法2中使用的token当key,用户请求时只需要带上token在头部,但是用token当key,不太方便限制重复登录,需要额外维护一个已经登录用户列表, 或许有其他更好的方法。

    next 菜单权限

    2、前端篇

    2.1 初始化vue2

    2.2 axios统一处理

    - +

    1.9.5 过滤器配置

    20221206161730

    1.9.6 思考

    虽然以上完成了自定义用uuid当token,但是token的设计并不严谨,token的key到底该如何设计,以下两个方法是否合理?

    方法1,如果使用用户名做key,那么每个请求都需要客户端携带一个用户名和token,然后后端在过滤器根据用户名从redis查询token, 对比两个token来做权限验证,这个貌似不太合理,至少我在实际项目开发中没看到过要求前端每次都要传递用户名的。使用这个方法有 一个优点是很容易做重复登录的限制,只需要根据用户名限制登录就好了,每次登录前检查redis中是否已经有了这个用户的登陆记录,如果 有,可以提示用户已经登陆,或者挤掉已登录用户,这个根据自己业务来做。

    方法2中使用的token当key,用户请求时只需要带上token在头部,但是用token当key,不太方便限制重复登录,需要额外维护一个已经登录用户列表, 或许有其他更好的方法。

    next 菜单权限

    2、前端篇

    2.1 初始化vue2

    2.2 axios统一处理

    + diff --git a/other/web/Cookie.html b/other/web/Cookie.html index 2b395a4c0e..6c07295ef1 100644 --- a/other/web/Cookie.html +++ b/other/web/Cookie.html @@ -30,11 +30,11 @@ Cookie | ChenSino - - + + -
    跳至主要內容

    Cookie

    qianxun必会大约 2 分钟

    Cookie的作用域domain

    一级域名:aaa.com 二级域名:bbb.aaa.com 三级域名:ccc.bbb.aaa.com

    如上例子: aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.bbb.aaa.com 的父域名; 反过来bbb.aaa.com 和 ccc.bbb.aaa.com 是 aaa.com 的子域名;ccc.bbb.aaa.com 是 bbb.aaa.com 的子域名

    划重点

    在当前域名下,只能设置当前域以及父域的cookie,不能设置子域下的cookie。例如我在浏览器访问后端服务的域名为bbb.aaa.com时,我在后端就只能把cookie的 域设置为当前域(缺省状态就是当前)或者设置为其父域名aaa.com,而不能设置为其子域名ccc.bbb.aaa.com,设置子域名前端SetCookie或有警告

    重点

    cookie挂载在某个域下,只有在此域名下或者此域名的子域下才能获取cookie。也就是说例如我当前在浏览器访问的域名为bbb.aaa.com,我只能看到当前域名下的cookie以及父域名aaa.com下的 cookie,而看不到子域名ccc.bbb.aaa.com下的cookie

    Cookie的path

    path和域差不多,默认情况下的path是/也就是域下所有路径都可以看到cookie,如果设置了path为/somepth则浏览器只有访问/somepath或者/somepath/***等这些地址才能看到

    实例

    域名可以看到当前域以及父域名下的cookie,看不到其子域名下的cookie

    20221027151137

    20221027151245

    设置了path,要同时满足url中有指定path才能看到

    20221027151447

    20221027151508

    浏览器请求时会自动携带其所有能看到的cookie发送到后端

    20221027153648

    20221027153801

    20221027153905

    20221027153954

    java中Session和Cookie交互

    以上图中可以看到,每次请求在请求头都会携带一个名字为JSESSIONID的COOKIE这个cookie的值是一个sessionId,也就是当前客户端和服务器交互的一个凭证, 客户端吧sessionid给了服务端,服务端就能找到对应session,有了session后,可以从session中获取到对应信息,比如用户信息。 20221027161215

    - +
    跳至主要內容

    Cookie

    qianxun必会大约 2 分钟

    Cookie的作用域domain

    一级域名:aaa.com 二级域名:bbb.aaa.com 三级域名:ccc.bbb.aaa.com

    如上例子: aaa.com 是 bbb.aaa.com 和 ccc.bbb.aaa.com 的父域名;bbb.aaa.com 是 ccc.bbb.aaa.com 的父域名; 反过来bbb.aaa.com 和 ccc.bbb.aaa.com 是 aaa.com 的子域名;ccc.bbb.aaa.com 是 bbb.aaa.com 的子域名

    划重点

    在当前域名下,只能设置当前域以及父域的cookie,不能设置子域下的cookie。例如我在浏览器访问后端服务的域名为bbb.aaa.com时,我在后端就只能把cookie的 域设置为当前域(缺省状态就是当前)或者设置为其父域名aaa.com,而不能设置为其子域名ccc.bbb.aaa.com,设置子域名前端SetCookie或有警告

    重点

    cookie挂载在某个域下,只有在此域名下或者此域名的子域下才能获取cookie。也就是说例如我当前在浏览器访问的域名为bbb.aaa.com,我只能看到当前域名下的cookie以及父域名aaa.com下的 cookie,而看不到子域名ccc.bbb.aaa.com下的cookie

    Cookie的path

    path和域差不多,默认情况下的path是/也就是域下所有路径都可以看到cookie,如果设置了path为/somepth则浏览器只有访问/somepath或者/somepath/***等这些地址才能看到

    实例

    域名可以看到当前域以及父域名下的cookie,看不到其子域名下的cookie

    20221027151137

    20221027151245

    设置了path,要同时满足url中有指定path才能看到

    20221027151447

    20221027151508

    浏览器请求时会自动携带其所有能看到的cookie发送到后端

    20221027153648

    20221027153801

    20221027153905

    20221027153954

    java中Session和Cookie交互

    以上图中可以看到,每次请求在请求头都会携带一个名字为JSESSIONID的COOKIE这个cookie的值是一个sessionId,也就是当前客户端和服务器交互的一个凭证, 客户端吧sessionid给了服务端,服务端就能找到对应session,有了session后,可以从session中获取到对应信息,比如用户信息。 20221027161215

    + diff --git a/other/web/Http.html b/other/web/Http.html index 818728e371..55f8d60243 100644 --- a/other/web/Http.html +++ b/other/web/Http.html @@ -30,8 +30,8 @@ Http | ChenSino - - + +
    跳至主要內容

    Http

    chensino必会大约 2 分钟

    必看手册

    https://developer.mozilla.org/zh-CN/docs/Web/HTTP

    一、状态码

    1.1 3xx

    1.1.1 304

    HTTP 304 Not Modified 说明无需再次传输请求的内容,也就是说可以使用缓存的内容。这通常是在一些安全的方法(safe),例如GET 或HEAD 或在请求中附带了头部信息: If-None-Match 或If-Modified-Since。

    如果是 200 OK ,响应会带有头部 Cache-Control, Content-Location, Date, ETag, Expires,和 Vary.

    温馨提示

    很多浏览器的 开发者工具 会发出额外的请求,以达到 304 的目的,这样可以把资源以本地缓存的形式展现给开发者。一般缓存静态文件,如果用户在服务器上修改了静态文件,则请求时服务器会读取其修改时间, 这样就知道此文件是已修改过的,需要重新响应给浏览器修改后的内容。

    比如以下请求,浏览器会自动携带If-Modified-Since请求头,然后拿这个时间和服务器上文件修改时间对比,如果服务器上的时间比这个新,就会返回200,否则返回304。 20221101150553

    另外浏览器有个Disable cache选择项,勾选此项代表不允许浏览器自动携带If-Modified-Sinc请求头,也就无法使用本地缓存了。当使用Ctrl+F5刷新页面时也是同样的道理,不携带If-Modified-Sinc请求头。 20221101150938

    二、http请求

    2.1 302重定向

    请求被重定向后,是无法给客户端响应ResbonseBody,但是可以有Response Header,测试代码如下,请求http://localhost:8888/hello/cookie,在请求处理逻辑里面 添加一个cookie,然后重定向到百度。可以看到如下代码我有设置返回值,但是其实毫无意义,在return之前就被redirect到了百度。同时,在Response Headers中可以看到有 Cookie: test=aaaaaa,并且打开localhost能看到在下面有对应的cookie

    @Controller
    @@ -51,7 +51,7 @@
      URLEncoder.encode("中文", "UTF-8")
     
     
    let chineseStr = decodeURI(header)
    -
    - +
    + diff --git a/other/web/Jwt.html b/other/web/Jwt.html index ae12cc1709..caabcdd276 100644 --- a/other/web/Jwt.html +++ b/other/web/Jwt.html @@ -30,8 +30,8 @@ jwt | ChenSino - - + +
    跳至主要內容

    jwt

    chenkunwebweb大约 3 分钟

    1、jwt在服务端如何校验的?

    之前一直用jwt但是仅仅了解他的基本原理没有去思考一个问题——服务端是如何校验jwt的?

    了解过jwt原理的同学都知道jwt是可以自校验的,token里面有header,payload,我有个想法就是如果用户随便生成一个token,那后端是如何知道这个token不能用?或者我把开发环境的token拿到生产环境使用 是否可行?

    jwt用户认证流程如下,因jwt的token是无状态的,所以每次请求都要经过过滤器进行校验,第一次登陆后把生成的token缓存到redis,当校验时如果找到对应token则继续,解析head中userid信息,根据userid查用户角色权限等信息,然后再设置到security的context中,具体代码如下

    Jwt认证

    @Slf4j
    @@ -78,7 +78,7 @@
     }
     

    2、为什么相同用户每次登陆得到的jwt token不一样?

    一般登录后生成token都会在里面设置过期时间,后端生成token时会在payload添加这个字段,不同时间下,经过签名后得到的token肯定不一样

    3、Jwt VS 普通token

    我一直认为Jwt能做的,普通token都能做,比如过期时间,普通token只需要设置redis过期时间 就可以,好像jwt并没有太大的优势,但是今天做Oauth客户端登录,我发先jwt还是有它自己的独到之处的。

    优势一

    在Oauth服务中,jwt可以自校验,这样当授权成功后,授权服务器返回jwt重定向到请求的接口时,客户端在过滤器中就可以很方便的校验token合法性。假如使用普通token,我们在客户端校验token势必要去授权服务器的redis中查询token是否存在,以此来确认合法性,这里就存在一个不合理的地方,客户端必须连接服务端的redis,这是一种耦合,如果项目不大,可能客户端和服务端都是一个redis还好,如果是不同redis就比较麻烦。
     

    优势二

    jwt中包含用户信息,当授权服务器返回jwt时,我们在客户端过滤器中根据jwt很方便的解析出里面的用户信息,比如username,拿到username我在客户端就可以很方便的把username转换为本地用户信息(当然前提jwt校验成功我们才给他做转换)和权限信息。如果使用普通token我们就无法根据token获取用户信息,和上面的问题差不多。
    -
    - +
    + diff --git a/other/web/OAUTH_LOGIN.html b/other/web/OAUTH_LOGIN.html index 0d45ed3bdf..261cbca501 100644 --- a/other/web/OAUTH_LOGIN.html +++ b/other/web/OAUTH_LOGIN.html @@ -30,8 +30,8 @@ oauth第三方登录 | ChenSino - - + +
    跳至主要內容

    oauth第三方登录

    ChenSino原创oauthoauth大约 6 分钟

    本博客介绍前后端分离项目的完整接入oauth的流程,本博客使用github来示范,因为github注册oauth应用无须审核,微信审核特别麻烦,并且个人用户无法注册

    1 oauth2

    1.1 典型应用场景

        1. 社交帐号登录应用,比如使用微信、微博登录其它应用
    @@ -206,7 +206,7 @@
     </script>
     </body>
     </html>
    -

    5.3 前后交互数据问题

    这里前后交互数据其实是通过一个中转页面github.ftl实现的,这里中转时遇到了数据格式问题,后端通过freemarker模板回应的数据,被前端监听后解析总有双引号问题,所以这通过base64中转一下,后端base64编码,前端base64解码。

    20230427182157

    20230427182214

    - +

    5.3 前后交互数据问题

    这里前后交互数据其实是通过一个中转页面github.ftl实现的,这里中转时遇到了数据格式问题,后端通过freemarker模板回应的数据,被前端监听后解析总有双引号问题,所以这通过base64中转一下,后端base64编码,前端base64解码。

    20230427182157

    20230427182214

    + diff --git a/other/web/RefreshToken.html b/other/web/RefreshToken.html index 10796d6310..c9e0e69941 100644 --- a/other/web/RefreshToken.html +++ b/other/web/RefreshToken.html @@ -30,11 +30,11 @@ refresh_token | ChenSino - - + + -
    跳至主要內容

    refresh_token

    ChenSino原创大约 2 分钟

    1、 refresh_token介绍

    ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_token和refresh_token,并在access_token过期时使用refresh_token申请新的access_token。refresh_token的安全性非常重要,授权服务器会根据设定的有效期对refresh_token进行管理,以确保对用户的信息和资源进行保护。同时,在使用refresh_token时,也需要进行国家或行业标准的数据保护和安全性认证,以避免出现不必要的风险和隐私泄漏。

    2、token过期处理

    2.1 方式一,重新登录

    让用户重新登录,获得新的Token,但是这种方式体验很差,通常Token过期时间都比较短,每次都要重新登录操作。

    2.2 方式二,续签(刷新)token

    续签token,避免用户在操作的过程中被强制下线,续签也有多种方式

    1. 在每个请求响应后进行拦截,如果发现请求失败(Token过期导致的)时,刷新Token再刷新请求接口。这种方式的优点是无需Token过期时间字段且无需判断时间,缺点在于多消耗一次请求。
    2. 在每个请求发起前进行拦截,根据expires_in判断token是否过期,如果过期则会刷新后再继续请求接口。这个方法的优点是请求前拦截处理,能节省请求次数,缺点是后端需要提供Token过期时间字段,并且需要结合计算机本地时间判断,如果计算机时间被篡改,拦截就会失败。
    3. 添加一个过滤器,每个请求进来都重置token的过期时间,这种方式对服务器资源消耗增加了
    4. 在OAuth2.0协议中,可以使用refresh_token实现自动续期token。这个方案需要你的应用程序使用OAuth2.0的认证机制,并且获取到了refresh_token。
    5. 使用JSON Web Token:对于使用JSON Web Token (JWT)的应用程序,可以在JWT中设置token的过期时间,预先考虑过期时间,合理设置有效期,通常情况下,token的有效期建议短一些,这样更安全。在即将到期时,在后端进行更新或延长有效期。
    - +
    跳至主要內容

    refresh_token

    ChenSino原创大约 2 分钟

    1、 refresh_token介绍

    ==refresh_token是OAuth2协议中的一个概念==,用于在access_token过期时获取新的access_token,说道token续签大多博客上来就refresh_token巴拉巴拉。在OAuth2协议中,通过客户端以授权服务器的名义请求access_token,客户端会收到access_token和refresh_token,并在access_token过期时使用refresh_token申请新的access_token。refresh_token的安全性非常重要,授权服务器会根据设定的有效期对refresh_token进行管理,以确保对用户的信息和资源进行保护。同时,在使用refresh_token时,也需要进行国家或行业标准的数据保护和安全性认证,以避免出现不必要的风险和隐私泄漏。

    2、token过期处理

    2.1 方式一,重新登录

    让用户重新登录,获得新的Token,但是这种方式体验很差,通常Token过期时间都比较短,每次都要重新登录操作。

    2.2 方式二,续签(刷新)token

    续签token,避免用户在操作的过程中被强制下线,续签也有多种方式

    1. 在每个请求响应后进行拦截,如果发现请求失败(Token过期导致的)时,刷新Token再刷新请求接口。这种方式的优点是无需Token过期时间字段且无需判断时间,缺点在于多消耗一次请求。
    2. 在每个请求发起前进行拦截,根据expires_in判断token是否过期,如果过期则会刷新后再继续请求接口。这个方法的优点是请求前拦截处理,能节省请求次数,缺点是后端需要提供Token过期时间字段,并且需要结合计算机本地时间判断,如果计算机时间被篡改,拦截就会失败。
    3. 添加一个过滤器,每个请求进来都重置token的过期时间,这种方式对服务器资源消耗增加了
    4. 在OAuth2.0协议中,可以使用refresh_token实现自动续期token。这个方案需要你的应用程序使用OAuth2.0的认证机制,并且获取到了refresh_token。
    5. 使用JSON Web Token:对于使用JSON Web Token (JWT)的应用程序,可以在JWT中设置token的过期时间,预先考虑过期时间,合理设置有效期,通常情况下,token的有效期建议短一些,这样更安全。在即将到期时,在后端进行更新或延长有效期。
    + diff --git a/other/web/Restful.html b/other/web/Restful.html index 64afb7b063..3d8e534dbf 100644 --- a/other/web/Restful.html +++ b/other/web/Restful.html @@ -30,11 +30,11 @@ HTTP Restful | ChenSino - - + + - - + + diff --git a/other/web/index.html b/other/web/index.html index 60d847b03a..05f63ff313 100644 --- a/other/web/index.html +++ b/other/web/index.html @@ -30,11 +30,11 @@ web开发通用知识 | ChenSino - - + + - - + + diff --git a/other/windows/WSL.html b/other/windows/WSL.html index 43440cff80..0f87ae7cec 100644 --- a/other/windows/WSL.html +++ b/other/windows/WSL.html @@ -30,8 +30,8 @@ wsl问题 | ChenSino - - + +
    跳至主要內容

    wsl问题

    chensino原创大约 2 分钟

    1. wsl系统设置桥接网络

    参考:https://www.cnblogs.com/cheyunhua/p/17577895.htmlopen in new window

    1.1 开启hyper-v

    桥接功能需要windows的hyper-v组件支持,但是win10/11家庭版是不包含hyper-v的,专业版才包含。网上也有文章提到家庭版安装hyper-v的方法,但是我没有测试,以下内容都是在win11专业版上进行的测试,win10专业版应该也是一样的。 首先,进入控制面板—程序—启用或关闭windows功能,勾选hyper-v,确认后重启电脑。

    1.2 桥接网络

    WSL2 默认采用了一个 NAT 网络,这对于大多数情况而言都是没有问题的,但是如果想要把 WSL 中的服务直接暴露出来,就不得不考虑做端口转发等问题。以及如果要使用 IPv6,自带的 NAT 方案也不能满足。

    因此,这种时候如果能让 WSL2 使用直接接在 NIC 上自然是最好的,可惜 Windows 中没有直接提供这样的配置选项,如果在 Hyper-V 管理器中配置 WSL 网卡为外部网络则会直接报错。

    万幸的是,可以使用 PowerShell 直接进行配置,本文则记录使用 PowerShell 让 WSL2 用上桥接网络的方法。

    以下内容需要以管理员身份在PowerShell内执行。

    一切开始之前首先需要启动 WSL,直接运行 wsl 即可,这样 WSL 的网卡才会被自动创建出来。

    重启后首先运行wsl2(这样才能出现WSL的虚拟网卡),以管理员方式打开powershell,执行Get-NetAdapter,可以列出系统所有的网卡,记住想要桥接的网卡名称,比如我想桥接到有线网络其名称为“以太网”。

    桥接网卡输入以下代码:

    Set-VMSwitch WSL -NetAdapterName <你的网卡名字>
    @@ -39,7 +39,7 @@
     ip addr add 192.168.1.64/24 broadcast 192.168.1.255 dev eth0
     ip route add 0.0.0.0/0 via 192.168.1.1 dev eth0
     

    接下来更新名称解析服务器地址,执行 nano /etc/resolv.conf,修改其中内容为 nameserver 192.168.1.1,

    1.4 取消桥接

    在windows中管理员方式打开powershell,执行以下指令:

    Set-VMSwitch WSL -SwitchType Internal
    -

    然后执行 wsl --shutdown 重启wsl,即可恢复原有的虚拟内部网络。

    - +

    然后执行 wsl --shutdown 重启wsl,即可恢复原有的虚拟内部网络。

    + diff --git a/other/windows/index.html b/other/windows/index.html index 185ff20e83..a5230815f9 100644 --- a/other/windows/index.html +++ b/other/windows/index.html @@ -30,11 +30,11 @@ Windows | ChenSino - - + + - - + + diff --git a/sitemap.xml b/sitemap.xml index 5260940476..c636fbc6a6 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -1,3 +1,3 @@ -https://chensino.github.io/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/home.html2023-01-29T08:23:34.000Zdailyhttps://chensino.github.io/slide.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/designpattern/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/designpattern/%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/designpattern/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/designpattern/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F.html2023-05-17T07:05:43.000Zdailyhttps://chensino.github.io/designpattern/%E8%A3%85%E9%A5%B0%E6%A8%A1%E5%BC%8F.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/designpattern/%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/frontweb/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/guide/2022-07-29T09:43:43.000Zdailyhttps://chensino.github.io/guide/disable.html2022-07-29T09:43:43.000Zdailyhttps://chensino.github.io/guide/encrypt.html2022-07-29T09:43:43.000Zdailyhttps://chensino.github.io/guide/markdown.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/guide/page.html2022-07-29T09:43:43.000Zdailyhttps://chensino.github.io/java/2022-11-01T07:45:02.000Zdailyhttps://chensino.github.io/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html2024-03-22T06:08:50.000Zdailyhttps://chensino.github.io/myserver/2024-03-22T06:08:50.000Zdailyhttps://chensino.github.io/myserver/x86_openwrt.html2024-03-29T09:24:34.000Zdailyhttps://chensino.github.io/myserver/%E6%97%81%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/myserver/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/myserver/%E8%87%AA%E5%BB%BAnas.html2024-03-22T06:08:50.000Zdailyhttps://chensino.github.io/other/2022-08-02T06:00:31.000Zdailyhttps://chensino.github.io/cpp/other/1.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/cpp/other/2.html2023-01-29T02:35:09.000Zdailyhttps://chensino.github.io/cpp/study/2023-01-13T05:57:56.000Zdailyhttps://chensino.github.io/frontweb/es5/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/frontweb/es5/aboutAsync.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/aboutEvent.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/aboutThis.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/asyncError.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es5/crossDomain.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/crossDomain2.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/lazyLoad.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/throttle.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/frontweb/es6/js%E4%B8%AD%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/promise.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/useModule.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/useNpm.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/usePnpm.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/useYarn.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/nodejs/2024-08-14T06:14:44.000Zdailyhttps://chensino.github.io/frontweb/nodejs/%E5%AE%89%E8%A3%85%E9%97%AE%E9%A2%98.html2024-08-14T06:14:44.000Zdailyhttps://chensino.github.io/frontweb/typeScript/2022-11-03T08:05:16.000Zdailyhttps://chensino.github.io/frontweb/typeScript/action-usage.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/typeScript/axios.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/typeScript/basic-usage.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/typeScript/fanType.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/typeScript/tsAndvue3.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vite/2022-08-02T04:20:07.000Zdailyhttps://chensino.github.io/frontweb/vue/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/frontweb/vue/elementui%E8%A1%A8%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/eventBus.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-Direactive.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-authority.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-in-action.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-nextTick.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-pic.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-router1.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-router2.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue3Emit.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/frontweb/vue/vue3LifeTime.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/vue/vueExtend.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/vue/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4%E7%9A%84%E5%AE%9E%E8%B7%B5.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/advance/Arthas.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/Collection.html2024-09-02T00:39:12.000Zdailyhttps://chensino.github.io/java/advance/CompileJdk11.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/Concurrent.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/advance/Future.html2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/advance/IO-Model.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/IO-model1.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/ImplementSameInterface.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/MysqlMasterSlave.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/advance/NativeMethod.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/ParentDelegationClassLoader.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/ProxyInJava.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/advance/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/advance/Synchronized.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/advance/ThreadLocal.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/advance/ThreadPool.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/io.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/base/ConstantPool.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/base/CustomLRU.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/base/IntegerConstantPool.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/base/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/base/Serialization.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/base/String.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/jvm/ClassLoader.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/jvm/MemoryModel.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/jvm/NewObject.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/jvm/ObjectReference.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/jvm/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/jvm/SetObjectNull.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/jvm/StringAdd.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/jvm/volatile.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html2024-08-29T00:28:26.000Zdailyhttps://chensino.github.io/java/other/JdkVersion.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/books/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/books/ebooks.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/computerprinciple/TCP-IP.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/database/CPUOverLoad.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/database/MysqlCollate.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/database/MysqlNote.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/database/MysqlRemoteConnect.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/database/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/database/Recurse.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/database/SQLOptimization.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/distributeservice/DistributeLock.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/distributeservice/Nacos.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/distributeservice/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/docker/Docker.html2022-11-28T06:54:04.000Zdailyhttps://chensino.github.io/other/docker/ServiceInstall.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/essay/2022-04-12.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/BTree.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/CDN.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/ChromeDevTools.html2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/essay/CloudService.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/DeployGithubPage.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/essay/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/Jenkins.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/essay/TyporaPicgo.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/elasticSearch%E6%93%8D%E4%BD%9C.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/elk%E9%83%A8%E7%BD%B2.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html2024-09-02T00:39:12.000Zdailyhttps://chensino.github.io/other/essay/windows%E4%B8%8B%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/essay/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%BC%95%E5%AF%BC.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/git/BatchDeleteGitHubRepo.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/git/GitCommands.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/git/branch01.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/git/branch02.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/git/fatal.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/gitConflict.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/gitRebase.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/gitwork.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/mergeBranch.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/proxy.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/git/rebaseAndMerge.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/reset.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/git/stash.html2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/hardware/CPU.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/CentOS.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/linux/CommonUsedCMD.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/linux/Curl.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/InstallMysqlWithDocker.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/linux/Manjaro.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/MountDisk.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/linux/MultiNetworkCard.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/linux/Samba.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/ShareBetweenWindowsAndLinux.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/linux/TcpDump.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/linux/Wifi.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/linux/firewall.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/wsl.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/linux/%E6%88%AA%E5%9B%BE.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/markdown/2022-08-02T06:00:31.000Zdailyhttps://chensino.github.io/other/oauth2/01.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/02.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/03.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/04.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/05.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/06.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/07.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/08.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/09.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/10.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/11.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/12.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/13.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/14.html2022-11-08T08:57:50.000Zdailyhttps://chensino.github.io/other/oauth2/2022-11-08T08:17:56.000Zdailyhttps://chensino.github.io/other/pve/2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/pve/ddns%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/pve/firewall.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/rabbitmq/2022-12-07T08:10:55.000Zdailyhttps://chensino.github.io/other/software/%E7%A0%B4%E8%A7%A3%E8%BD%AF%E4%BB%B6.html2023-10-30T07:18:49.000Zdailyhttps://chensino.github.io/other/sono/SUC.html2023-01-30T05:28:26.000Zdailyhttps://chensino.github.io/other/tools/Idea.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/tools/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/tools/SoftWare.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/tools/VsCode.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/training/CloudServiceTraining.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/training/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/training/SSO.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/training/draw.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/web/BuildWebProject.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/web/Cookie.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/web/Http.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/web/Jwt.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/web/OAUTH_LOGIN.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/web/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/web/RefreshToken.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/web/Restful.html2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/windows/WSL.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/mybatis/MybatisPlusDataSource.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/mybatis/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/framework/mybatis/mybatis.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/Authorization.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/CustomAuthenticationProvider.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/CustomLoginPage.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/CustomTokenAuthentication.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/security/DelegatingFilterProxy.html2024-09-29T09:49:47.000Zdailyhttps://chensino.github.io/java/framework/security/OAuth2Authentication.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/OncePerRequestFilter.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/security/PreAuthorize.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/security/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/security/SSO.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/SecurityFilterChain.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/security/Session.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/note.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/security/spring-security-oauth2-authorization-server.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/Annotation.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/BeanPostProcessor.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/CircularDependency.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/DesignPatternInSpring.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/OncePerRequestFilter.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringAOP.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringCache.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringExtensionPoint.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringIOC.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringMVC.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringSourceAnalize.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/spring/Validator.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/springboot/AOPLog.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/springboot/CollectionInject.html2023-01-03T13:56:27.000Zdailyhttps://chensino.github.io/java/framework/springboot/Http2.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/springboot/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/framework/springboot/SpringBootAutoConfiguration.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/springboot/Swagger.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/springboot/%E9%83%A8%E7%BD%B2.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/springcloud/SpringCloudGateway.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/gradle/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/gradle/wrapper.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/locateproblem/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/locateproblem/TooManyOpenFiles.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/locateproblem/Undertow.xxxNotFount.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/locateproblem/%E5%90%84%E7%A7%8D%E9%97%AE%E9%A2%98.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/log/logback.html2023-05-22T02:50:31.000Zdailyhttps://chensino.github.io/java/other/maven/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/maven/build%E6%A0%87%E7%AD%BE.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/other/maven/import.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/maven/multiModule.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/maven/problem.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/other/maven/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html2024-11-01T09:56:25.000Zdaily \ No newline at end of file +https://chensino.github.io/2024-11-02T13:30:46.000Zdailyhttps://chensino.github.io/home.html2024-11-02T13:30:46.000Zdailyhttps://chensino.github.io/slide.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/designpattern/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/designpattern/%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/designpattern/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/designpattern/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F.html2023-05-17T07:05:43.000Zdailyhttps://chensino.github.io/designpattern/%E8%A3%85%E9%A5%B0%E6%A8%A1%E5%BC%8F.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/designpattern/%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/guide/2022-07-29T09:43:43.000Zdailyhttps://chensino.github.io/guide/disable.html2022-07-29T09:43:43.000Zdailyhttps://chensino.github.io/guide/encrypt.html2022-07-29T09:43:43.000Zdailyhttps://chensino.github.io/guide/markdown.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/guide/page.html2022-07-29T09:43:43.000Zdailyhttps://chensino.github.io/java/2022-11-01T07:45:02.000Zdailyhttps://chensino.github.io/myserver/Jellyfin%E6%90%AD%E5%BB%BA.html2024-03-22T06:08:50.000Zdailyhttps://chensino.github.io/myserver/2024-03-22T06:08:50.000Zdailyhttps://chensino.github.io/myserver/x86_openwrt.html2024-03-29T09:24:34.000Zdailyhttps://chensino.github.io/myserver/%E6%97%81%E8%B7%AF%E7%94%B1%E7%BD%91%E5%85%B3.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/myserver/%E7%BD%91%E7%BB%9C%E8%AE%BE%E7%BD%AE.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/myserver/%E8%87%AA%E5%BB%BAnas.html2024-03-22T06:08:50.000Zdailyhttps://chensino.github.io/other/2022-08-02T06:00:31.000Zdailyhttps://chensino.github.io/cpp/other/1.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/cpp/other/2.html2023-01-29T02:35:09.000Zdailyhttps://chensino.github.io/cpp/study/2023-01-13T05:57:56.000Zdailyhttps://chensino.github.io/frontweb/es5/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/frontweb/es5/aboutAsync.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/aboutEvent.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/aboutThis.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/asyncError.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es5/crossDomain.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/crossDomain2.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/lazyLoad.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/es5/throttle.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/frontweb/es6/js%E4%B8%AD%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/promise.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/useModule.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/useNpm.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/usePnpm.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/es6/useYarn.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/nodejs/2024-08-14T06:14:44.000Zdailyhttps://chensino.github.io/frontweb/nodejs/%E5%AE%89%E8%A3%85%E9%97%AE%E9%A2%98.html2024-08-14T06:14:44.000Zdailyhttps://chensino.github.io/frontweb/typeScript/2022-11-03T08:05:16.000Zdailyhttps://chensino.github.io/frontweb/typeScript/action-usage.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/typeScript/axios.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/typeScript/basic-usage.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/typeScript/fanType.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/typeScript/tsAndvue3.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vite/2022-08-02T04:20:07.000Zdailyhttps://chensino.github.io/frontweb/vue/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/frontweb/vue/elementui%E8%A1%A8%E5%8D%95%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/eventBus.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-Direactive.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-authority.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-in-action.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-nextTick.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-pic.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-router1.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue-router2.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/frontweb/vue/vue3Emit.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/frontweb/vue/vue3LifeTime.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/vue/vueExtend.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/frontweb/vue/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4%E7%9A%84%E5%AE%9E%E8%B7%B5.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/advance/Arthas.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/Collection.html2024-09-02T00:39:12.000Zdailyhttps://chensino.github.io/java/advance/CompileJdk11.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/Concurrent.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/advance/Future.html2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/advance/IO-Model.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/IO-model1.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/ImplementSameInterface.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/MysqlMasterSlave.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/advance/NativeMethod.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/ParentDelegationClassLoader.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/ProxyInJava.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/advance/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/advance/Synchronized.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/advance/ThreadLocal.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/advance/ThreadPool.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/advance/io.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/base/ConstantPool.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/base/CustomLRU.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/base/IntegerConstantPool.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/base/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/base/Serialization.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/base/String.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/jvm/ClassLoader.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/jvm/MemoryModel.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/jvm/NewObject.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/jvm/ObjectReference.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/jvm/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/jvm/SetObjectNull.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/jvm/StringAdd.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/jvm/volatile.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/jvm/%E5%90%8D%E8%AF%8D%E8%A7%A3%E9%87%8A.html2024-08-29T00:28:26.000Zdailyhttps://chensino.github.io/java/other/JdkVersion.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/books/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/books/ebooks.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/computerprinciple/TCP-IP.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/database/CPUOverLoad.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/database/MysqlCollate.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/database/MysqlNote.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/database/MysqlRemoteConnect.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/database/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/database/Recurse.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/database/SQLOptimization.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/distributeservice/DistributeLock.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/distributeservice/Nacos.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/distributeservice/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/docker/Docker.html2022-11-28T06:54:04.000Zdailyhttps://chensino.github.io/other/docker/ServiceInstall.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/essay/2022-04-12.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/BTree.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/CDN.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/ChromeDevTools.html2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/essay/CloudService.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/DeployGithubPage.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/essay/IM%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E6%8A%80%E6%9C%AF%E9%80%89%E5%9E%8B.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/Jenkins.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/essay/TyporaPicgo.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/elasticSearch%E6%93%8D%E4%BD%9C.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/elk%E9%83%A8%E7%BD%B2.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/essay/im%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1%E7%9A%84%E9%9C%80%E6%B1%82.html2024-09-02T00:39:12.000Zdailyhttps://chensino.github.io/other/essay/windows%E4%B8%8B%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/essay/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%BC%95%E5%AF%BC.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/git/BatchDeleteGitHubRepo.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/git/GitCommands.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/git/branch01.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/git/branch02.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/git/fatal.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/gitConflict.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/gitRebase.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/gitwork.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/mergeBranch.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/proxy.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/git/rebaseAndMerge.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/git/reset.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/git/stash.html2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/hardware/CPU.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/CentOS.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/linux/CommonUsedCMD.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/linux/Curl.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/InstallMysqlWithDocker.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/linux/Manjaro.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/MountDisk.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/linux/MultiNetworkCard.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/linux/Samba.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/ShareBetweenWindowsAndLinux.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/linux/TcpDump.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/linux/Wifi.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/linux/firewall.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/linux/wsl.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/linux/%E6%88%AA%E5%9B%BE.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/markdown/2022-08-02T06:00:31.000Zdailyhttps://chensino.github.io/other/oauth2/01.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/02.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/03.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/04.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/05.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/06.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/07.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/08.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/09.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/10.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/11.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/12.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/13.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/oauth2/14.html2022-11-08T08:57:50.000Zdailyhttps://chensino.github.io/other/oauth2/2022-11-08T08:17:56.000Zdailyhttps://chensino.github.io/other/pve/2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/pve/ddns%E7%94%B3%E8%AF%B7%E8%AF%81%E4%B9%A6.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/pve/firewall.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/rabbitmq/2022-12-07T08:10:55.000Zdailyhttps://chensino.github.io/other/software/%E7%A0%B4%E8%A7%A3%E8%BD%AF%E4%BB%B6.html2023-10-30T07:18:49.000Zdailyhttps://chensino.github.io/other/sono/SUC.html2023-01-30T05:28:26.000Zdailyhttps://chensino.github.io/other/tools/Idea.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/tools/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/tools/SoftWare.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/tools/VsCode.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/training/CloudServiceTraining.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/training/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/training/SSO.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/training/draw.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/web/BuildWebProject.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/web/Cookie.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/web/Http.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/web/Jwt.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/other/web/OAUTH_LOGIN.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/other/web/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/web/RefreshToken.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/other/web/Restful.html2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/other/windows/WSL.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/mybatis/MybatisPlusDataSource.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/mybatis/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/framework/mybatis/mybatis.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/Authorization.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/CustomAuthenticationProvider.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/CustomLoginPage.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/CustomTokenAuthentication.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/security/DelegatingFilterProxy.html2024-09-29T09:49:47.000Zdailyhttps://chensino.github.io/java/framework/security/OAuth2Authentication.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/OncePerRequestFilter.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/security/PreAuthorize.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/security/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/security/SSO.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/SecurityFilterChain.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/security/Session.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/security/note.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/security/spring-security-oauth2-authorization-server.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/Annotation.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/BeanPostProcessor.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/CircularDependency.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/DesignPatternInSpring.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/OncePerRequestFilter.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringAOP.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringCache.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringExtensionPoint.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringIOC.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringMVC.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/spring/SpringSourceAnalize.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/spring/Validator.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/springboot/AOPLog.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/springboot/CollectionInject.html2023-01-03T13:56:27.000Zdailyhttps://chensino.github.io/java/framework/springboot/Http2.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/springboot/2022-08-01T13:56:59.000Zdailyhttps://chensino.github.io/java/framework/springboot/SpringBootAutoConfiguration.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/framework/springboot/Swagger.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/framework/springboot/%E9%83%A8%E7%BD%B2.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/framework/springcloud/SpringCloudGateway.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/gradle/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/gradle/wrapper.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/locateproblem/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/locateproblem/TooManyOpenFiles.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/locateproblem/Undertow.xxxNotFount.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/locateproblem/%E5%90%84%E7%A7%8D%E6%A1%86%E6%9E%B6%E7%89%88%E6%9C%AC%E7%9A%84%E5%9D%91.html2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/locateproblem/%E5%90%84%E7%A7%8D%E9%97%AE%E9%A2%98.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/log/logback.html2023-05-22T02:50:31.000Zdailyhttps://chensino.github.io/java/other/maven/2024-03-22T03:45:12.000Zdailyhttps://chensino.github.io/java/other/maven/build%E6%A0%87%E7%AD%BE.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/other/maven/import.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/maven/multiModule.html2024-10-08T07:30:29.000Zdailyhttps://chensino.github.io/java/other/maven/problem.html2024-11-01T09:56:25.000Zdailyhttps://chensino.github.io/java/other/maven/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html2024-11-01T09:56:25.000Zdaily \ No newline at end of file diff --git a/slide.html b/slide.html index 09c091c3aa..38a48b04b8 100644 --- a/slide.html +++ b/slide.html @@ -30,11 +30,11 @@ 幻灯片页 | ChenSino - - + +
    - + diff --git a/star/index.html b/star/index.html index 637394b107..660d73576d 100644 --- a/star/index.html +++ b/star/index.html @@ -30,12 +30,12 @@ 星标 | ChenSino - - + + - +
    Ms.Hope大约 1 分钟使用指南页面配置使用指南
    + diff --git "a/tag/---\345\271\266\345\217\221/index.html" "b/tag/---\345\271\266\345\217\221/index.html" index fb06a28f9e..cd94aa9d51 100644 --- "a/tag/---\345\271\266\345\217\221/index.html" +++ "b/tag/---\345\271\266\345\217\221/index.html" @@ -30,8 +30,8 @@ 标签: -- 并发 | ChenSino - - + + - +
    ChenSino原创大约 2 分钟-- 并发
    + diff --git a/tag/index.html b/tag/index.html index 0f120d5ff1..5ec16e758d 100644 --- a/tag/index.html +++ b/tag/index.html @@ -30,11 +30,11 @@ 标签 | ChenSino - - + + - - + + diff --git a/tag/linux/index.html b/tag/linux/index.html index 6ef621d73f..e6277b440e 100644 --- a/tag/linux/index.html +++ b/tag/linux/index.html @@ -30,15 +30,15 @@ 标签: linux | ChenSino - - + + - +
    chenkun大约 5 分钟linuxlinux
    + diff --git a/tag/markdown/index.html b/tag/markdown/index.html index 181e404daa..57e6d9b03f 100644 --- a/tag/markdown/index.html +++ b/tag/markdown/index.html @@ -30,15 +30,15 @@ 标签: markdown | ChenSino - - + + - +
    chenkun小于 1 分钟markdownmarkdown
    + diff --git a/tag/mysql/index.html b/tag/mysql/index.html index 9a4e751c67..094b33a4a7 100644 --- a/tag/mysql/index.html +++ b/tag/mysql/index.html @@ -30,8 +30,8 @@ 标签: mysql | ChenSino - - + + - +
    ChenSino原创大约 6 分钟mysql
    + diff --git a/tag/oauth/index.html b/tag/oauth/index.html index edecb7e89e..3bbe6812b4 100644 --- a/tag/oauth/index.html +++ b/tag/oauth/index.html @@ -30,8 +30,8 @@ 标签: oauth | ChenSino - - + +
    跳至主要內容
    oauth第三方登录
    @@ -55,7 +55,7 @@

    2、探索

    也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源时,系统会校验其拥有的权限是否可以访问这个资源。

    2、SSO和OAUTH2介绍

    -

    SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。


    ChenSino大约 4 分钟oauthsso
    - +

    SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。


    ChenSino大约 4 分钟oauthsso
    + diff --git a/tag/springboot/index.html b/tag/springboot/index.html index 06e245b75d..026c2e9865 100644 --- a/tag/springboot/index.html +++ b/tag/springboot/index.html @@ -30,8 +30,8 @@ 标签: springboot | ChenSino - - + + - +

    1.1 初始化springboot项目


    chenkun大约 11 分钟webwebspringboot
    + diff --git a/tag/sso/index.html b/tag/sso/index.html index 8c452aa937..7d6d7534e7 100644 --- a/tag/sso/index.html +++ b/tag/sso/index.html @@ -30,8 +30,8 @@ 标签: sso | ChenSino - - + +
    跳至主要內容
    子类和父类(或者父接口)实现同一个接口

    1、背景

    @@ -46,7 +46,7 @@

    2、探索

    也就是说,用户登录(认证)成功后,会获得此用户拥有的权限,用户在去访问资源时,系统会校验其拥有的权限是否可以访问这个资源。

    2、SSO和OAUTH2介绍

    -

    SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。


    ChenSino大约 4 分钟oauthsso
    - +

    SSO(SingleSignOn)的出现是为了解决多系统认证的问题的,这里特别强调一下,它是解决认证问题的。就是通过用户的一次性鉴别登录,当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。


    ChenSino大约 4 分钟oauthsso
    + diff --git a/tag/ts/index.html b/tag/ts/index.html index 58a2994d8c..d8f33ace13 100644 --- a/tag/ts/index.html +++ b/tag/ts/index.html @@ -30,8 +30,8 @@ 标签: ts | ChenSino - - + + - +
    ChenSino原创小于 1 分钟tsvue3
    + diff --git a/tag/vite/index.html b/tag/vite/index.html index 428c045355..f1a0859834 100644 --- a/tag/vite/index.html +++ b/tag/vite/index.html @@ -30,11 +30,11 @@ 标签: vite | ChenSino - - + + - - + + diff --git a/tag/vue3/index.html b/tag/vue3/index.html index 733e89e28c..31e55b9ed9 100644 --- a/tag/vue3/index.html +++ b/tag/vue3/index.html @@ -30,8 +30,8 @@ 标签: vue3 | ChenSino - - + + - +
    ChenSino原创小于 1 分钟tsvue3
    + diff --git "a/tag/vue\344\270\255\347\232\204-typescript/index.html" "b/tag/vue\344\270\255\347\232\204-typescript/index.html" index 3f6aaf44b6..7f7bf6046c 100644 --- "a/tag/vue\344\270\255\347\232\204-typescript/index.html" +++ "b/tag/vue\344\270\255\347\232\204-typescript/index.html" @@ -30,8 +30,8 @@ 标签: vue中的 TypeScript | ChenSino - - + + - +
    qianxun大约 2 分钟vue知识点必会vue中的 TypeScript
    + diff --git a/tag/web/index.html b/tag/web/index.html index 9deb3a2e81..e7833a186e 100644 --- a/tag/web/index.html +++ b/tag/web/index.html @@ -30,8 +30,8 @@ 标签: web | ChenSino - - + + - +
    chenkun小于 1 分钟webweb
    + diff --git "a/tag/\344\272\214\345\217\211\346\240\221/index.html" "b/tag/\344\272\214\345\217\211\346\240\221/index.html" index 270d702e30..d10251e6ed 100644 --- "a/tag/\344\272\214\345\217\211\346\240\221/index.html" +++ "b/tag/\344\272\214\345\217\211\346\240\221/index.html" @@ -30,13 +30,13 @@ 标签: 二叉树 | ChenSino - - + + - +
    ChenSino小于 1 分钟数据结构二叉树
    + diff --git "a/tag/\344\275\240\346\211\200\344\270\215\344\272\206\350\247\243\347\232\204javascript/index.html" "b/tag/\344\275\240\346\211\200\344\270\215\344\272\206\350\247\243\347\232\204javascript/index.html" index c2837ac038..0e4dd6de45 100644 --- "a/tag/\344\275\240\346\211\200\344\270\215\344\272\206\350\247\243\347\232\204javascript/index.html" +++ "b/tag/\344\275\240\346\211\200\344\270\215\344\272\206\350\247\243\347\232\204javascript/index.html" @@ -30,8 +30,8 @@ 标签: 你所不了解的JavaScript | ChenSino - - + + - +
    Zxf小于 1 分钟你所不了解的JavaScript
    Vue

    Zxf小于 1 分钟你所不了解的JavaScript
    + diff --git "a/tag/\344\275\277\347\224\250\346\214\207\345\215\227/index.html" "b/tag/\344\275\277\347\224\250\346\214\207\345\215\227/index.html" index 9292bc895f..9ed7e2fe49 100644 --- "a/tag/\344\275\277\347\224\250\346\214\207\345\215\227/index.html" +++ "b/tag/\344\275\277\347\224\250\346\214\207\345\215\227/index.html" @@ -30,12 +30,12 @@ 标签: 使用指南 | ChenSino - - + + - +
    Ms.Hope大约 1 分钟使用指南页面配置使用指南
    + diff --git "a/tag/\345\217\215\346\261\207\347\274\226/index.html" "b/tag/\345\217\215\346\261\207\347\274\226/index.html" index a70412f137..3b69202193 100644 --- "a/tag/\345\217\215\346\261\207\347\274\226/index.html" +++ "b/tag/\345\217\215\346\261\207\347\274\226/index.html" @@ -30,8 +30,8 @@ 标签: 反汇编 | ChenSino - - + + - +
    ChenSino大约 3 分钟javajvm字节码反汇编
    + diff --git "a/tag/\345\244\232\347\272\277\347\250\213/index.html" "b/tag/\345\244\232\347\272\277\347\250\213/index.html" index 693caf03bb..cf7e4fa31c 100644 --- "a/tag/\345\244\232\347\272\277\347\250\213/index.html" +++ "b/tag/\345\244\232\347\272\277\347\250\213/index.html" @@ -30,8 +30,8 @@ 标签: 多线程 | ChenSino - - + + - +
    chenkun大约 12 分钟线程池多线程多线程线程池
    + diff --git "a/tag/\345\255\227\350\212\202\347\240\201/index.html" "b/tag/\345\255\227\350\212\202\347\240\201/index.html" index e712708c31..2565503e53 100644 --- "a/tag/\345\255\227\350\212\202\347\240\201/index.html" +++ "b/tag/\345\255\227\350\212\202\347\240\201/index.html" @@ -30,8 +30,8 @@ 标签: 字节码 | ChenSino - - + + - +
    ChenSino大约 3 分钟javajvm字节码反汇编
    + diff --git "a/tag/\345\267\245\345\205\267\344\275\277\347\224\250/index.html" "b/tag/\345\267\245\345\205\267\344\275\277\347\224\250/index.html" index f37b6476ff..973cdec18b 100644 --- "a/tag/\345\267\245\345\205\267\344\275\277\347\224\250/index.html" +++ "b/tag/\345\267\245\345\205\267\344\275\277\347\224\250/index.html" @@ -30,14 +30,14 @@ 标签: 工具使用 | ChenSino - - + + - +
    陈老师小于 1 分钟工具使用
    + diff --git "a/tag/\345\277\205\344\274\232/index.html" "b/tag/\345\277\205\344\274\232/index.html" index 8cb80715b5..dc849e46d7 100644 --- "a/tag/\345\277\205\344\274\232/index.html" +++ "b/tag/\345\277\205\344\274\232/index.html" @@ -30,8 +30,8 @@ 标签: 必会 | ChenSino - - + + - +
    qianxun大约 1 分钟vue知识点必会
    + diff --git "a/tag/\346\225\260\346\215\256\345\272\223/index.html" "b/tag/\346\225\260\346\215\256\345\272\223/index.html" index da21785424..a5a573462a 100644 --- "a/tag/\346\225\260\346\215\256\345\272\223/index.html" +++ "b/tag/\346\225\260\346\215\256\345\272\223/index.html" @@ -30,8 +30,8 @@ 标签: 数据库 | ChenSino - - + + - +
    chenkun大约 1 分钟数据库数据库
    + diff --git "a/tag/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" "b/tag/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" index 6a31bc0d22..cbfe45d201 100644 --- "a/tag/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" +++ "b/tag/\346\225\260\346\215\256\347\273\223\346\236\204/index.html" @@ -30,13 +30,13 @@ 标签: 数据结构 | ChenSino - - + + - +
    ChenSino小于 1 分钟数据结构二叉树
    + diff --git "a/tag/\346\226\207\347\253\240\345\212\240\345\257\206/index.html" "b/tag/\346\226\207\347\253\240\345\212\240\345\257\206/index.html" index cb8ebecf48..14ccb05bd8 100644 --- "a/tag/\346\226\207\347\253\240\345\212\240\345\257\206/index.html" +++ "b/tag/\346\226\207\347\253\240\345\212\240\345\257\206/index.html" @@ -30,11 +30,11 @@ 标签: 文章加密 | ChenSino - - + + - - + + diff --git "a/tag/\346\227\245\345\277\227/index.html" "b/tag/\346\227\245\345\277\227/index.html" index e656c9287e..59007934f9 100644 --- "a/tag/\346\227\245\345\277\227/index.html" +++ "b/tag/\346\227\245\345\277\227/index.html" @@ -30,8 +30,8 @@ 标签: 日志 | ChenSino - - + + - +
    ChenSino原创大约 4 分钟日志
    + diff --git "a/tag/\346\241\206\346\236\266/index.html" "b/tag/\346\241\206\346\236\266/index.html" index b98913e13b..854c656d31 100644 --- "a/tag/\346\241\206\346\236\266/index.html" +++ "b/tag/\346\241\206\346\236\266/index.html" @@ -30,15 +30,15 @@ 标签: 框架 | ChenSino - - + +
    跳至主要內容
    Mybatis使用

    mybatis缓存

    缓存介绍

    一级缓存存在的问题

    1、 一级缓存

    -

    mybatis缓存有一级缓存也叫SelSession缓存,是强制打开的,也就是说Mybatis没有提供关闭一级缓存的方式。一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其查询结果放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。一级缓存的存储位置是在JVM虚拟机内存,它放在一个Map中


    chenkun大约 12 分钟框架框架
    - +

    mybatis缓存有一级缓存也叫SelSession缓存,是强制打开的,也就是说Mybatis没有提供关闭一级缓存的方式。一级缓存只是相对于同一个SqlSession而言。所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其查询结果放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。一级缓存的存储位置是在JVM虚拟机内存,它放在一个Map中


    chenkun大约 12 分钟框架框架
    + diff --git "a/tag/\347\246\201\347\224\250/index.html" "b/tag/\347\246\201\347\224\250/index.html" index a823d09482..0e8e603fd9 100644 --- "a/tag/\347\246\201\347\224\250/index.html" +++ "b/tag/\347\246\201\347\224\250/index.html" @@ -30,12 +30,12 @@ 标签: 禁用 | ChenSino - - + + - +
    ChenSino小于 1 分钟使用指南禁用
    + diff --git "a/tag/\347\272\277\347\250\213\346\261\240/index.html" "b/tag/\347\272\277\347\250\213\346\261\240/index.html" index 7847be21ff..94c0a57751 100644 --- "a/tag/\347\272\277\347\250\213\346\261\240/index.html" +++ "b/tag/\347\272\277\347\250\213\346\261\240/index.html" @@ -30,8 +30,8 @@ 标签: 线程池 | ChenSino - - + + - +
    chenkun大约 12 分钟线程池多线程多线程线程池
    + diff --git "a/tag/\350\277\220\347\273\264/index.html" "b/tag/\350\277\220\347\273\264/index.html" index 5384641e9a..e7493a5b5a 100644 --- "a/tag/\350\277\220\347\273\264/index.html" +++ "b/tag/\350\277\220\347\273\264/index.html" @@ -30,8 +30,8 @@ 标签: 运维 | ChenSino - - + + - +

    3、问题分析


    陈老师大约 8 分钟运维
    + diff --git "a/tag/\351\203\250\347\275\262\346\220\255\345\273\272/index.html" "b/tag/\351\203\250\347\275\262\346\220\255\345\273\272/index.html" index 05f86691d7..05c4ddbb42 100644 --- "a/tag/\351\203\250\347\275\262\346\220\255\345\273\272/index.html" +++ "b/tag/\351\203\250\347\275\262\346\220\255\345\273\272/index.html" @@ -30,8 +30,8 @@ 标签: 部署搭建 | ChenSino - - + + - +
    ChenSino大约 7 分钟java部署搭建
    + diff --git "a/tag/\351\233\206\345\220\210/index.html" "b/tag/\351\233\206\345\220\210/index.html" index 2055664133..77b4602714 100644 --- "a/tag/\351\233\206\345\220\210/index.html" +++ "b/tag/\351\233\206\345\220\210/index.html" @@ -30,14 +30,14 @@ 标签: 集合 | ChenSino - - + + - +
    ChenSino小于 1 分钟集合集合
    + diff --git "a/tag/\351\241\265\351\235\242\351\205\215\347\275\256/index.html" "b/tag/\351\241\265\351\235\242\351\205\215\347\275\256/index.html" index 7d8fbd062a..be0a5d80bf 100644 --- "a/tag/\351\241\265\351\235\242\351\205\215\347\275\256/index.html" +++ "b/tag/\351\241\265\351\235\242\351\205\215\347\275\256/index.html" @@ -30,12 +30,12 @@ 标签: 页面配置 | ChenSino - - + + - +
    Ms.Hope大约 1 分钟使用指南页面配置使用指南
    + diff --git a/timeline/index.html b/timeline/index.html index b25ade82d9..f78ad5a615 100644 --- a/timeline/index.html +++ b/timeline/index.html @@ -30,11 +30,11 @@ 时间轴 | ChenSino - - + + -
    跳至主要內容
    - +
    跳至主要內容
    +